RESTful API 的设计不做过多介绍,只针对 JSON 型 API 返回结果结构的设计的个人理解;

请求数据返回结构

服务器返回结果的结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
data : { // 请求数据,对象或数组均可
user_id: 123,
user_name: "zerocc",
user_avatar_url: "http://zerocc.me/avatar.jpg"
...
},
msg : "个人信息", // 请求状态描述,调试用
code: 1001, // 业务自定义状态码
extra : { // 全局附加数据,字段、内容不定
type: 1,
desc: "签到成功!"
}
}

data 字段 - 业务需求数据

请求结果返回的数据 data 字段,为页面显示的具体所有数据,其值类型可以为对象(字典)或数组,据具体业务场景而定,可以灵活组合使用。

如果请求的是用户的个人信息,就可以为字典对象,结构如下:

1
2
3
4
5
6
data : { // 请求返回数据,字典对象
user_id: 123,
user_name: "zerocc",
user_avatar_url: "http://zerocc.me/avatar.jpg"
...
},

如果请求的是列表数据,就可以是数组对象(字典型数组对象),如请求新闻列表:

1
2
3
4
5
6
7
8
9
data: [
{nickname: zerocc,
news_id: 1,
news_content: "哈哈哈嘎嘎"},
{nickname: zerocc1,
news_id: 2,
news_content: "哈哈哈嘎嘎"},
...
]

msg 字段 - 请求状态描述调试数据

msg 字段是本次请求的业务、状态描述信息,用于调试,线上问题定位 debug 以及配合埋点等作用;

1
2
3
4
5
msg: "个人信息接口"

//或者

msg: "请求缺少参数"

code 字段 - 自定义业务状态码

HTTP 请求本身已经有了完备的状态码, 为什么还要自定义一套状态码呢?如一次成功的 Http status 200 的请求,可能由于用户未登录、登录过期而有不同的返回结果和处理方式,并且根据业务分类自定义状态码更容易去定位问题等等;

状态码的定义服务端应该有一套规范,可以按照用户相关、授权相关、各种业务等等做分类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Code 业务自定义状态码定义示例

// 授权相关
1001: 无权限访问
1002: access_token过期
1003: unique_token无效
...

// 用户相关
2001: 未登录
2002: 用户信息错误
2003: 用户不存在

// 业务1
3001: 业务1XXX
3002: 业务1XXX

HTTP 的状态码参考链接

extra字段 - 全局附加数据

extra字段,用来表示全局的附加数据。业务场景用户的操作(数据请求),会导致用户的等级、经验变化,而具体什么时候产生不确定,由服务端的规则决定,并且客户端要及时向用户展示变化,所以加上了extra字段。

在设计extra字段的时候,并没有对其结构内容做限制,所以比较灵活,但是还是要有个type字段,来做约束,如:

1
2
3
4
5
6
7
// 升级
type: 1,
show_msg: "恭喜您升级到XXX"

// 完成任务
type: 2,
task_desc: "达成XXX成就"

总的来说就是自由发挥,只要服务端、客户端相互沟通好即可。当然,也要避免乱用,保证真的需要全局附加数据才使用这个字段。

请求数据返回值的规范

命名统一规范

命名格式不管是使用驼峰还是下划线命名,只要全部统一就好,个人建议还是使用 _,看着舒服一点,例如:user_iduser_name 等;

命名语意尽量做到见名知意,但是别使用拼音真的很 low 而且真的反应不过来;使用常用缩写;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 字符串
user_name, task_desc, date_str, article_title, feed_content 等

// 数字
user_id, users_count, task_num, xxx_offset 等

// 日期
login_at, create_date, logout_time 等

// 布尔
is_done, is_vip, protected, can_read 等

// URL
user_avatar_url, thumb_url 等

// 数组
users, news, thumb_imgs 等

空值的处理

空值的处理需慎重,特别容易引起 crash 等问题,服务端与客服端一定要协商好;

统一空值用 null

除了布尔类型的,其余的空值统一用 null 表示,客户端保证每种字段的 null 可以被正常处理。

统一空值不同类型设置默认空值

除了 null ,还可以对字段设置 “默认值”,如数字就是 0,字符串就是空字符串 “”,数组就是空数组[],对象就是空对象 {},这样有个好处就是可以避免很多客户端(Java、OC)处理空值(Null、nil、null)产生的异常。但是危害就是容易语义不明。还是要根据具体业务、前后端约定而定。

布尔 boolean 值的处理

布尔值各种处理见过的有如下几种:

1
2
3
4
5
6
is_login: true,
is_login: "true",
is_login: 1
is_login: "TRUE"
is_login: "YES"
// ...

由于语言本身的限制、框架的处理方式,不对布尔类型的值做限制总觉得不踏实,像C、C++、Objective-C 里面的布尔就是数字0和1,其它语言也都各自不一样,还有从数据库读写导致的布尔值类型不一致等。

个人习惯性统一为 0 和 1,然后在客户端和服务端统一设置常量、宏定义,定义布尔的类型,所有的参数、结果的布尔字段全部做强制约束。

时间日期值的处理

时间的处理也是非常容易出错的,特别是遇上时区转换的时候。

强制GMT/UTC时间戳

一种做法就是强制所有时间参数只能传 Unix 时间戳,也就是标准 GMT/UTC 时间戳,然后由各自的客户端根据自己的时区、显示要求做处理后显示。

1
2
3
4
5
// 从服务器接收的时间数据
login_at: 1462068610

// 根据时区、显示要求转换,如北京时间
显示:201651日下午1点、1天前等

这样的话,客户端、服务端存储、读取时间都相当于处理纯数字。

使用ISO 8601带时区的时间日期字符串

使用Unix时间戳有个坏处,就是:

最早只能到1970/1/1 0:0:0GMT时间,一旦需求早于这个时间,时间戳就成了负数=。=
不方便人阅读。调试API的时候,开发人员不能直观看出具体时间,很不方便
所以,可以按照ISO 8601标准,用字符串保存、传输时间。

如果以YYYY-MM-DDThh:mm:ssTZD格式为准, 时间的形式就是1997-07-16T19:20:30+01:00,保存了时区信息,也方便阅读。

type 类型的处理

API数据中免不了各种类型字段,如用户类型user_type、登录类型login_type等,类型的表示也可以分为数字、字符串两种。

数字表示类型:这个应该是最直接的方式了,客户端和服务端共同维护某个API下、某个数据类型中的type常量,靠文档约束。

字符串表示类型:数字的类型毕竟不利于直观阅读,如果可以的话,用字符串也是不错的,当然坏处就是代码里面就不能用Switch语句了(除了强大的 Swift =。=)

1
2
3
4
// 如登录类型,QQ、微信、微博等
login_type: "qq",
login_type: "wechat",
login_type: "sina_weibo",

完整的URL,协议类型

API里面的数据也会有URL类型的,一般来说如用户的头像、各种图片、音频等资源,都是以URL链接的形式返回的。

返回的URL一定要“完整”,主要指的是不要忘记URL里面的协议部分,也就是 scheme 部分。像 xxxx.me/imgs/1.jpg这种URL值,就是不完整的,没有指明网络协议,难道靠猜=。=

总结
嗯,规范非常重要。:-D

参考地址