乐趣区

Flutter-新闻客户端-04-YAPI接口管理RESTful生成代码dio封装

B 站视频

https://www.bilibili.com/vide…

本节目标

  • 前后端分离、契约开发模式
  • API 接口管理、工具
  • RESTful 接口规范
  • TOKEN 安全通讯
  • 自动生成 entity 接口实体类
  • dio 封装
  • localstorage 本地存储
  • 密码加密

1. 接口管理

1.1 前后端分离、契约模式

1.2 常见接口管理工具

  • yapi
    https://github.com/YMFE/yapi

  • easymock
    https://github.com/easy-mock/…

  • RAP2
    https://github.com/thx/RAP

  • swagger
    https://swagger.io/

1.3 yapi 接口管理工具(猫哥推荐)

http://yapi.demo.qunar.com/

  • 输入

  • 输出

1.4 mock 模拟数据

1.5 单元测试

1.6 swagger 导入

2. restful 接口风格

  • REST wiki
  • 理解 RESTful 架构 阮一峰
  • RESTful API 设计指南 阮一峰
  • RESTful API 最佳实践 阮一峰
  • RESTful 架构详解

2.1 http 操作方式

  • GET 取数据
  • POST 新建数据
  • PUT 更新全部数据
  • PATCH 更新部分数据
  • DELETE 删除数据

例子:

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 优秀实践

  • Github REST API v3

3. token 安全通讯

3.1 基于令牌的安全机制

  • 流程

  • 思路

3.2 Bearer Type Access Token

在通讯 HTTP HEADER 头中加入

GET /resource HTTP/1.1
Host: server.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

3.3 JWT

  • https://jwt.io/
  • JSON Web Token 入门教程

4. 自动生成 entity

4.1 json_serializable(官方)

  • https://pub.dev/packages/json…

4.2 json to code(猫哥推荐)

  • https://app.quicktype.io/

  • vscode 插件

https://marketplace.visualstu…

5. dio 封装

5.1 单例模式

  • dio

https://pub.dev/packages/dio

  • lib/common/utils/http.dart

单例常见封装方式

class HttpUtil {static HttpUtil _instance = HttpUtil._internal();
  factory HttpUtil() => _instance;

  Dio dio;
  CancelToken cancelToken = new CancelToken();

  HttpUtil._internal() {...

5.2 维护 token

从本地 storage 中读取

  • localstorage

https://pub.flutter-io.cn/pac…

  • getLocalOptions()
  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 处理异常

格式化,错误信息,进行差别对待

  • createErrorEntity()
  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;
            // String errMsg = error.response.statusMessage;
            // return ErrorEntity(code: errCode, message: errMsg);
            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: "未知错误");
                  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
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 密码加密

  • crypto

https://pub.dev/packages/crypto

  • lib/common/utils/security.dart
import 'dart:convert';
import 'package:crypto/crypto.dart';

/// SHA256
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
  // 执行登录操作
  _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);

    // 写本地 access_token , 不写全局,业务:离线登录
    // 全局数据 gUser
  }

YAPI 接口管理

http://yapi.demo.qunar.com/

git 代码

https://github.com/ducafecat/…

蓝湖设计稿

https://lanhuapp.com/url/lYuz1
密码: gSKl

蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿
商业设计稿文件不好直接分享, 可以加微信联系 ducafecat

参考

  • RESTful

    • REST wiki
    • 理解 RESTful 架构 阮一峰
    • RESTful API 设计指南 阮一峰
    • RESTful API 最佳实践 阮一峰
    • RESTful 架构详解
  • Flutter packages

    • localstorage
    • json_serializable
    • dio
    • crypto
  • VSCode 插件

    • Awesome Flutter Snippets
    • Paste JSON as Code

视频

  • b 站
  • 油管镜像
退出移动版