本节目标
前后端分离、契约开发模式
API 接口管理、工具
RESTful 接口规范
TOKEN 安全通讯
自动生成 entity 接口实体类
dio 封装
localstorage 本地存储
密码加密
视频
代码 https://github.com/ducafecat/flutter_learn_news/releases/tag/v1.0.4
1. 接口管理 1.1 前后端分离、契约模式
1.2 常见接口管理工具
1.3 yapi 接口管理工具(猫哥推荐) http://yapi.demo.qunar.com/
1.4 mock 模拟数据
1.5 单元测试
1.6 swagger 导入
2. restful 接口风格
2.1 http 操作方式
GET 取数据
POST 新建数据
PUT 更新全部数据
PATCH 更新部分数据
DELETE 删除数据
例子: 1 2 3 4 5 6 7 8 GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
2.2 state 状态控制
200 OK
400 错误的请求,比如数据结构不对
401 需要登录认证
403 已登录,但是当前资源没有授权
404 找不到,地址错误
500 服务程序错误
502 服务网关错误
503 服务挂了
504 服务网关超时
2.3 优秀实践
3. token 安全通讯 3.1 基于令牌的安全机制
3.2 Bearer Type Access Token 在通讯 HTTP HEADER 头中加入
1 2 3 GET /resource HTTP/1.1 Host: server.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3.3 JWT
4. 自动生成 entity 4.1 json_serializable (官方)
4.2 json to code (猫哥推荐)
https://marketplace.visualstudio.com/items?itemName=quicktype.quicktype
5. dio 封装 5.1 单例模式
https://pub.dev/packages/dio
lib/common/utils/http.dart
单例常见封装方式
1 2 3 4 5 6 7 8 9 class HttpUtil { static HttpUtil _instance = HttpUtil._internal(); factory HttpUtil() => _instance; Dio dio; CancelToken cancelToken = new CancelToken(); HttpUtil._internal() { ...
5.2 维护 token 从本地 storage 中读取
https://pub.flutter-io.cn/packages/localstorage
1 2 3 4 5 6 7 8 9 10 Options getLocalOptions() { Options options; String token = StorageUtil().getItem(STORAGE_USER_TOKEN_KEY); if (token != null ) { options = Options(headers: { 'Authorization' : 'Bearer $token ' , }); } return options; }
5.3 处理异常 格式化,错误信息,进行差别对待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 ErrorEntity createErrorEntity(DioError error) { switch (error.type) { case DioErrorType.CANCEL: { return ErrorEntity(code: -1 , message: "请求取消" ); } break ; case DioErrorType.CONNECT_TIMEOUT: { return ErrorEntity(code: -1 , message: "连接超时" ); } break ; case DioErrorType.SEND_TIMEOUT: { return ErrorEntity(code: -1 , message: "请求超时" ); } break ; case DioErrorType.RECEIVE_TIMEOUT: { return ErrorEntity(code: -1 , message: "响应超时" ); } break ; case DioErrorType.RESPONSE: { try { int errCode = error.response.statusCode; switch (errCode) { case 400 : { return ErrorEntity(code: errCode, message: "请求语法错误" ); } break ; case 401 : { return ErrorEntity(code: errCode, message: "没有权限" ); } break ; case 403 : { return ErrorEntity(code: errCode, message: "服务器拒绝执行" ); } break ; case 404 : { return ErrorEntity(code: errCode, message: "无法连接服务器" ); } break ; case 405 : { return ErrorEntity(code: errCode, message: "请求方法被禁止" ); } break ; case 500 : { return ErrorEntity(code: errCode, message: "服务器内部错误" ); } break ; case 502 : { return ErrorEntity(code: errCode, message: "无效的请求" ); } break ; case 503 : { return ErrorEntity(code: errCode, message: "服务器挂了" ); } break ; case 505 : { return ErrorEntity(code: errCode, message: "不支持HTTP协议请求" ); } break ; default : { return ErrorEntity( code: errCode, message: error.response.statusMessage); } } } on Exception catch (_) { return ErrorEntity(code: -1 , message: "未知错误" ); } } break ; default : { return ErrorEntity(code: -1 , message: error.message); } } }
6. 登录调用 6.1 编写 api 接口
lib/common/apis/user.dart
1 2 3 4 5 6 7 8 9 10 11 import 'package:flutter_ducafecat_news/common/entitys/entitys.dart' ;import 'package:flutter_ducafecat_news/common/utils/utils.dart' ;class UserAPI { static Future<UserResponseEntity> login({UserRequestEntity params}) async { var response = await HttpUtil().post('/user/login' , params: params); return UserResponseEntity.fromJson(response); } }
6.2 密码加密
https://pub.dev/packages/crypto
lib/common/utils/security.dart
1 2 3 4 5 6 7 8 9 10 11 import 'dart:convert' ;import 'package:crypto/crypto.dart' ;String duSHA256(String input) { String salt = 'EIpWsyfiy@R@X#qn17!StJNdZK1fFF8iV6ffN!goZkqt#JxO' ; var bytes = utf8.encode(input + salt); var digest = sha256.convert(bytes); return digest.toString(); }
6.3 调用接口
lib/pages/sign_in/sign_in.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 _handleSignIn() async { if (!duIsEmail(_emailController.value.text)) { toastInfo(msg: '请正确输入邮件' ); return ; } if (!duCheckStringLength(_passController.value.text, 6 )) { toastInfo(msg: '密码不能小于6位' ); return ; } UserRequestEntity params = UserRequestEntity( email: _emailController.value.text, password: duSHA256(_passController.value.text), ); UserResponseEntity res = await UserAPI.login(params: params); }
YAPI 接口管理 http://yapi.demo.qunar.com/
蓝湖设计稿 https://lanhuapp.com/url/lYuz1 密码: gSKl
蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿 商业设计稿文件不好直接分享, 可以加微信联系 ducafecat
参考
RESTful
Flutter packages
VSCode 插件
视频
© 猫哥
https://ducafecat.tech
邮箱 ducafecat@gmail.com / 微信 ducafecat / 留言板 disqus