API返回结果设计经验与总结

前言

RESTful API的设计已经很成熟了,大家也都比较认可。本文也不再过多介绍RESTful API相关的知识,而是针对JSON型API的返回结果设计,总结下自己的经验。

结构

先来看看返回结果的结构示例:

{

data : { // 请求数据,对象或数组均可

user_id: 123,

user_name: "tutuge",

user_avatar_url: "http://tutuge.me/avatar.jpg"

...

},

msg : "done", // 请求状态描述,调试用

code: 1001, // 业务自定义状态码

extra : { // 全局附加数据,字段、内容不定

type: 1,

desc: "签到成功!"

}

}

data字段 - 请求数据

首先是本次请求结果的数据data字段,其值为对象(字典)或数组均可以,根据业务而定。

如请求的是某个用户的个人profile信息,就可以是对象,对象里面是用户profile的键值对数据,如user_id: 123、user_name: "tutuge"等。

如果请求的是列表数据,就可以是数组,如请求用户列表:

data: [

{user_id: 123, user_name: "tutuge"},

{user_id: 321, user_name: "zekunyan"},

...

]

数组、对象,相互嵌套,灵活组合即可。

对于iOS来说,解析data字段是对象还是数组也很容易,在接收到JSON数据字典后,如AFNetworking的返回结果,对data判断其类型即可:

if ([jsonDict[@"data"] isKindOfClass:[NSDictionary class]]) {

// JSON对象

} else if ([jsonDict[@"data"] isKindOfClass:[NSArray class]]) {

// JSON数组

}

msg字段 - 请求状态描述,调试用

msg字段是本次请求的业务、状态描述信息,主要用于调试、测试等。

如“done”、“请求缺少参数!”

服务端可以自由发挥,开发人员看得懂就好。 -_-|||

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

code字段,业务自定义的状态码。

其实是否要在API里面自定义业务状态码,非常有争议=。=,因为Http请求本身已经有了完备的状态码,再定义一套状态码直观上感受却是不对劲。但是实际开发中,确实发现自定义业务状态码的必要性,如一次成功的Http status 200的请求,可能由于用户未登录、登录过期而有不同的返回结果和处理方式,所以还是保留了code。

状态码的定义也最好有一套规范,如按照用户相关、授权相关、各种业务,做简单的分类:

// Code 业务自定义状态码定义示例

// 授权相关

1001: 无权限访问

1002: access_token过期

1003: unique_token无效

...

// 用户相关

2001: 未登录

2002: 用户信息错误

2003: 用户不存在

// 业务1

3001: 业务1XXX

3002: 业务1XXX

// ...

Code业务状态码最好是用常量定义的,当然有能力的动态配置更新更好,这里就不再详细说明。

Http的状态码参考:List of HTTP status codes(https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)

extra字段 - 全局附加数据

extra字段,用来表示全局的附加数据。

这个字段来源于之前做项目时,用户的操作(数据请求),会导致用户的等级、经验变化,而具体什么时候产生不确定,由服务端的规则决定,并且客户端要及时向用户展示变化,所以加上了extra字段。

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

// 升级

type: 1,

show_msg: "恭喜您升级到XXX"

// 完成任务

type: 2,

task_desc: "达成XXX成就"

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

最佳实践

总结几点最佳实践。

规范统一的命名

命名风格统一

不管是驼峰式还是下划线式,统一就好。当然,按照目前的“大众规范”,还是统一小写加下划线比较好=。=

如:user_id,user_name,user_age等。

语义清晰,遵守常用缩写

字段的名字最好能体现字段的类型,遵守一些“常用”的缩写,如:

// 字符串

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, profiles, thumb_imgs 等

空值、空字段的处理

空值、空字段的处理也是比较容易出问题。

统一空值用null

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

给不同类型设置默认空值

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

以前写过一篇用Runtime的手段填充任意NSObject对象的nil属性,其实就是为对象空值统一设置默认值的=。=,可以参考。

布尔boolean值的处理

说实话,我见过各种布尔值表示方式,如:

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时间戳,然后由各自的客户端根据自己的时区、显示要求做处理后显示。

// 从服务器接收的时间数据

login_at: 1462068610

// 根据时区、显示要求转换,如北京时间

显示:2016年5月1日下午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=。=)

// 如登录类型,QQ、微信、微博等

login_type: "qq",

login_type: "wechat",

login_type: "sina_weibo",

完整的URL

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

返回的URL一定要“完整”,主要指的是不要忘记URL里面的协议部分,也就是scheme部分。

tutuge.me/imgs/1.jpg这种URL值,就是不完整的,没有指明网络协议,难道靠猜=。=

应该是http://tutuge.me/imgs/1.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容