请求规范

[toc]

一、各种示例

1、request的HEADER公共入参(固定+可变的)示例

1
2
3
4
5
6
7
8
9
10
11
12
13
- HEADER(REQUEST):
{
"version":"1.16.30",
"buildNumber":"16301610",
"platform":"iOS",
"appType":"monkey",
"appFeatureType":"dev",
"packageChannel":"general",
"trace_id":"1683158692451101/2023-05-04 08:04:52/_sy_]onw^w^",
"Authorization":"bearer clientApp247deea3-da67-47bc-94f7-04b83ca0fb07",
"content-type":"application/json",
"content-length":"28"
}

参数详情介绍见下文《公共入参介绍》。

2、request的BODY公共入参(固定+可变的)示例

示例可详见 《埋点规范》–【埋点入参】–固定的公共入参 。附:其实改部分也可考虑放在HEADER中

3、response的返回结构示例

1
2
3
4
5
6
7
8
{
"status": 200,
"message": "请求成功,但是业务处理失败",
"errorCode": 1001,
"errorMessage": "参数name不能为空",
"data": null,
"timestamp": "2023-05-04T09:00:00Z"
}

二、设置request的HEADER公共入参(固定不变7个+可变N个)

网络库初始化所需的header参数

header形参 header形参名 描述 示例
必填 Map headerCommonFixParams header 中公共但【不变】的参数 如上
可选 block headerCommonChangeParamsGetBlock header 中公共但【会变】的参数 { ‘trace_id’: TraceUtil.traceId() }
可空 String headerAuthorization header 中公共但会变的参数
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
// 1、初始化网络库的时候定义
required Map<String, dynamic> headerCommonFixParams, // header 中公共但不变的参数
String? headerAuthorization, // header 中公共但【基本不会变】的参数--Authorization
Map<String, dynamic> Function()? headerCommonChangeParamsGetBlock, // header 中公共但会变的参数
// 示例:
headerCommonChangeParamsGetBlock: () {
return {
'trace_id': TraceUtil.traceId(),
};
},
// 1.2、初始化网络库的时候使用 headerCommonFixParams
// headers 的所有参数处理
Map<String, dynamic> headers = {};
// ① header 中公共但【固定不变】的参数
headers.addAll(headerCommonFixParams);
// ② header 中公共但【基本不会变】的参数--Authorization--策略:因为这只是一次性取值,后面的没法自动变化,需要调用update接口
if (headerAuthorization != null && headerAuthorization.isNotEmpty) {
headers.addAll({'Authorization': headerAuthorization});
}
// 将 header 参数设置到网络单例中
dio!.options = dio!.options.copyWith(
baseUrl: baseUrl,
connectTimeout: connectTimeout,
receiveTimeout: receiveTimeout,
contentType: contentType,
headers: headers,
);

// 2、具体发起网络请求里进行: headers 的所有参数处理
if (options.headers != null) {
if (optionsHeaderCommonChangeParamsGetBlock != null) {
Map<String, dynamic> customHeaders =
optionsHeaderCommonChangeParamsGetBlock();
options.headers!.addAll(customHeaders);
}
}

1、【固定的】公共请求参数 headerCommonFixParams(7个)

获取方法形如:

1
2
3
4
headerCommonFixParams: await CommonParamsHelper.commonHeaderParams(
appFeatureType: appFeatureType,
channel: channelName,
);

参数介绍如下:

序号 类型 字段 值示例 用途
1 平台系统 platform iOS/Android
2 app的唯一标识 appType 区分出同一公司的多个app
3 版本号(1.月.日) version 1.04.30
4 版本编译号(月日时分)
安卓隔年需加+12
buildNumber 04301610
5 功能类型 appFeatureType dev、inner、formal 区分出内测、正式功能
6 渠道 packageChannel appstore
7 唯一设备码 deviceId 未登录时的关闭检查更新

1.1、平台(iOS/Android)

1.2、app类型(同一公司多个app)/App的唯一标识

1.3、版本号(1.月.日)

1.4、版本编译号(月日时分)

1.5、功能类型(蒲公英dev、内测inner、正式formal)

1.6、渠道

平台 应用商店 渠道名
iOS App Store general
Android 华为应用市场 huawei
Android 小米应用商店 Xiaomi
Android Vivo应用商店 vivo
Android ColorOS应用商店 oppo
Android 应用宝 yingyongbao
Android 360手机助手 360
Android 百度手机助手 baidu
Android 酷安应用市场 kuan
Android 阿里应用市场 alibaba
Android 官网包 general

1.7、DeviceId 唯一设备码

Android 端主要取 Android ID

iOS 端先尝试获取 IDFA,如果获取不到,则取 IDFV

2、【变化的】公共请求参数 headerCommonChangeParamsGetBlock

获取方法形如:

1
2
3
4
5
6
headerCommonChangeParamsGetBlock: () {
return {
'trace_id': TraceUtil.traceId(),
'Authorization': headerAuthorization,
};
},

参数介绍如下:

序号 类型 字段 值示例 其他
1 用户token Authorization bearer clientApp247deea3-da67-47bc-94f7-04b83ca0fb07
2 跟踪id trace_id 1683158692451101/2023-05-04 08:04:52/_sy_]onw^w^

2.1、用户 Authorization

该值为登录时候,后台接口返回。且需要在用户状态变更的时候同步更新。

2.2、跟踪 trace_id

组成部分:时间戳/时间字符串/10位随机字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import 'dart:math';
import 'package:flutter/foundation.dart';

class TraceUtil {
static String traceId() {
String timeStamp = DateTime.now().microsecondsSinceEpoch.toString();
String timeString = DateTime.now().toString().substring(0, 19);
String randomString = TraceUtil.generateRandomString(10);
String traceId = "$timeStamp/$timeString/_$randomString";
return traceId;
}

// Declare a fucntion for reusable purpose
static String generateRandomString(int len) {
final _random = Random();
final result = String.fromCharCodes(
List.generate(len, (index) => _random.nextInt(33) + 89));
return result;
}
}

三、设置request的BODY公共入参(当不放header时候)

详见 《埋点规范》–【埋点入参】–固定的公共入参 其实可考虑放在HEADER中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1、初始化网络库的时候定义
required Map<String, dynamic> bodyCommonFixParams, // body 中公共但不变的参数
Map<String, dynamic> Function()? bodyCommonChangeParamsGetBlock, // body 中公共但会变的参数(一般可考虑放在header中)

// 2、具体发起网络请求里进行: body 的所有参数处理
// ① body 中公共但不变的参数
Map<String, dynamic> allParams = Map.from(_bodyCommonFixParams);
// ② body 中公共但会变的参数
if (_bodyCommonChangeParamsGetBlock != null) {
allParams.addAll(_bodyCommonChangeParamsGetBlock!());
}
// ③ body 中自定义的其他参数
if (customParams != null && customParams.isNotEmpty) {
allParams.addAll(customParams);
}

四、response返回结构介绍

为了准确判断业务是否成功,通常需要同时判断status和errorCode两个字段。

1
2
3
4
5
6
7
8
{
"status": 200,
"message": "请求成功,但是业务处理失败",
"errorCode": 1001,
"errorMessage": "参数name不能为空",
"data": null,
"timestamp": "2023-05-04T09:00:00Z"
}
  1. 状态码(status code):表示请求的处理结果,常见的状态码包括200(请求成功)、400(请求参数错误)、401(未授权)、403(禁止访问)、404(请求的资源不存在)等。
  2. 消息(message):对请求处理结果的简要描述,可以用于前端展示错误信息或者提示。
  3. 数据(data):请求处理成功后返回的数据,例如json格式的数据、图片、文件等。
  4. 时间戳(timestamp):表示服务器处理请求的时间,可以用于前端调试和日志记录。
1
2
3
4
5
6
7
8
9
10
if (response.status >= 200 && response.status < 300) {
// 请求成功,继续处理业务逻辑
if (response.errorCode) {
// 业务逻辑处理失败,根据errorCode和errorMessage进行相应的处理
} else {
// 业务逻辑处理成功,根据data字段获取返回的数据
}
} else {
// 请求失败,根据错误码和错误信息进行相应的处理
}

附1:不建议把status和errorCode用同一个字段表示

将状态码(status code)和业务错误码(error code)合并成一个字段可能会带来一些问题,建议不要采用这种方法。

首先,将状态码和业务错误码合并在一个字段中会导致客户端处理逻辑复杂化。客户端需要在处理请求结果时先判断状态码,然后再解析业务错误码,这会增加客户端代码的复杂度,降低代码的可维护性。

其次,将状态码和业务错误码合并在一个字段中还会带来一些歧义。状态码是HTTP协议定义的标准字段,具有固定的含义,例如200表示请求成功,400表示请求参数错误,500表示服务器内部错误等。而业务错误码是根据具体业务定义的,具有一定的灵活性。将这两种含义合并在一起会导致状态码的含义变得模糊,不利于代码的可读性和维护性。

因此,建议在response结构中分别使用状态码和业务错误码两个字段来表示请求的处理结果。这样可以使客户端处理逻辑更加清晰,并且可以使状态码和业务错误码的含义保持清晰和统一。

五、埋点的入参

详见埋点规范

五、请求接口的整理规范

背景:为了能够将请求接口的用途和页面关联起来。

目的:①便于省去后端频繁询问该页面该位置调用的是什么接口(虽然已有日志,但是仍有部分后端人员懒到不愿去自己排查)。

②完成前端依赖服务关系的梳理,并一直到后台各级服务-数据层。

序号 参数 必填 描述与其目的 purpose 策略
1.1 caller_page 必填 接口调用者(页面、管理器等)
①多页面调用接口:告知后台,以对业务进行逻辑区分处理
②日志使用:方便查看与统计每个页面调用的接口
可全局记录获当前页面
eg: goods_detail_page
1.2 caller_view 可空 接口调用者(视图)
同上(有时候一个page下有多个tab)
必须请求单独设置
eg: sku_choose_window
2 caller_times 必填 调用场景
①页面打开:open_page
②页面输入:input
②页面点击:click
open_page\ input\ click
3 purpose 必填 接口用途中文
①日志使用:便于快速/直接筛选出问题根源接口
eg: 商品搜索
4 byApper 必填 前台具体开发者的id eg:byQian
5 byApier 必填 后端具体开发者的id eg:byApier2

方案:

将 purpose 添加到 body 的参数中

1
_params.addPurpose(caller_page: "商城页", caller_time:open_page|click, purpose: "商品搜索").byQian.byApier2;

请求接口的整理产出效果示例如下:

请求接口整理

该图来源于脑图: 请求接口的整理规范.xmind

附:多处页面调用的公共接口提取出,描述在页面中调用的公共接口的部分添加颜色或者其他标记来标识使用的是哪个部分的公共接口。

六、网络库的加强

1、网络日志加强

目的:查看日志的时候能清晰的知道接口的中文含义,以及调用其的页面或者其他类或实例。

方法:每个接口请求时候添加对应页面。

2、网络时长统计

目的:统计加载每个页面的过程中,所消耗的网络时长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NetworkUtil.requestApi1(caller:aPage, belongLoadPage:true)

requestUrl(caller, belongLoadPage) {
if (belongLoadPage != true) {
return;
}

PageRequestManager.
map[caller]["apiStartCount"] = map[caller]["apiStartCount"] + 1
dio.request({
url: url,
completeCallback: () {
int callerApiStartCount = map[caller]["apiStartCount"];
int callerApiEndCount = map[caller]["apiEndCount"];
callerApiEndCount++;
map[caller]["apiEndCount"] = callerApiEndCount;
if (callerApiEndCount == callerApiStartCount) {
_pageLoadingEnd();
}
},
);
}

End