阅读更多

3顶
5踩

非技术

转载新闻 API返回结果设计经验与总结

2016-05-05 11:27 by 副主编 mengyidan1988 评论(2) 有7837人浏览
前言

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

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。

总结

嗯,规范非常重要。

文章来自:土土哥的技术Blog
3
5
评论 共 2 条 请登录后发表评论
2 楼 hantsy 2016-05-09 12:51
这个 API 设计能跟 REST 扯上关系,我真是服了你。


从 API 设计的角度看,这是我见过最垃圾的设计,没有之一。


返回状态应该优先使用 HTTP Status。。。

进行 API 设计之前,最好先阅读一下:

1. HTTP 协议(特别是关于 Entity,Method,Status 定义)
2. Fielding 博士关于 REST 风格架构的论文
3. Richardson Mature Model

1 楼 nzp12345 2016-05-06 10:34
[size=medium][/size]

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • java sql查询空内容,sql查询空指针异常

    在提交的servlet里出现异常ACCESS数据库id自动获取password文本类型测试id,password值能传递过来不明白为什么String query= ... //这样是空指针异常String query= "select * from customer where id= ' "+id+ " ' a...

  • SQLQuery 空指针

    报了空指针。 debug发现query非空。将sql简化到最简单的形式,依然。 突然发现query.addEntity(XXX);的XXX里的字段有几个是通过formula查询出来方便显示的。去掉query.addEntity();,错误消除。 SQLQuery在...

  • SQLQuery空指针

    做查询时需要用到一个比较复杂的sql,采用SQLQuery,query.list();报了空指针。 debug发现query非空。将sql简化到最简单的形式,依然。 突然发现query.addEntity(XXX);的XXX里的字段有几个是通过formula查询出来...

  • hibernate SQLQuery之坑二UNION查询报java.lang.NullPointerException空指针

    hibernate SQLQuery之坑二UNION查询报java.lang.NullPointerException空指针 1、union 是对多个select查询的结果进行组合 以union 连接两个select语句为例 sql语句 (SELECT CAST('现汇' AS varchar2(10)) AS ...

  • 浅谈HIVE执行SQL报空指针异常

    最近在HIVE上执行SQL遇到一个奇怪的问题,一段上百行简单查询语句,在执行时报空指针异常 在排除了语法错误后,第一直觉认为是集群出了错误,查阅了相关资料,大多数解释如下: 1. 分区表未指定分区 2. Union all...

  • oracle11 jdbc空指针,QueryRunner 查询Oracle 空指针

    这是public List getForList(String sql, Object... args) {Connection conn = null;try {conn = JdbcUtils.get...return qr.query(conn, sql, new BeanListHandler(clazz), args);} catch (Exception e) {/...

  • NULL空指针常见情况(修复和定位)

    总结了业务代码中 5 种最容易出现空指针异常的写法,以及相应的修复方式。针对判空,通过 Optional 配合 Stream 可以避免大多数冗长的 if-else 判空逻辑,实现一行代码优雅判空。

  • 带query参数报表api运算空指针问题解决方案

    润乾报表动态参数表达式涉及query时,api计算报空指针

  • mybatis-plus查询会报空指针(数据字典DictionaryInterceptor与空指针)

    当我们配置好数据字典的时候 @Configuration public class MybatisPlusConfig { @Bean ... 也就是说如果配置了DictionaryInterceptor的bean,查询出的数据有null值,就会报错(空指针)。 关键不太懂,记录一下。

  • mysql空指针异常处理_为什么MYSQL连接给我一个空指针异常[duplicate]

    但是,当我尝试执行它们时,它会给我一个空指针异常。我确实读过一些与此类似的问题,但问题是我使用的play框架有一个角度的前端。使添加连接器有点混乱。我确实加了很多遍,但它一定认不出来。我创建了一个新的java文件...

  • JPA使用HQL语句查询为空时空指针异常

    今天在写项目的JPA查询遇到的一个坑,分享一下。...@Query("select Users from Users as u , Students as s " + "where u.password = ?2 and s.studentId = ?1 and s.userId = u.userId") Users findByStude...

  • mybatis 执行mapper的方法时报空指针的问题

    mybatis报空指针 service层调用其他service层方法时报空指针 根据指示定位到指定行 找了很久找不到原因。配置文件,注解,依赖都是对的。 最后发现被调用层的引用没有加@Autowired 或者加上构造方法。 ...

  • Hive 空指针(NPE)异常

    空指针NullPointerException 1 Hive之前的一些BUG [HIVE-9430] - NullPointerException on ALTER TABLE ADD PARTITION if no value given 意思是当为分区表增加新的分区的时候必须指定分区的值 Wrong : ...

  • 带query函数的润乾报表API后台导出excel空指针问题

    带query函数的润乾报表API后台导出excel空指针问题

  • spark sql 调用save方法时的空指针

    最近在研究spark sql ,但是遇到一个无力着手的问题,不多说,直接上代码: public static void main(String[] args){ SparkConf conf=new SparkConf() .setMaster("local") .setAppName(...

  • mybatis查询报空指针

    前端时间,mybatis查询时,报错了,报了一个空指针。查询别的表的时候不报错,就这个表报错 ### Cause: java.lang.NullPointerException at org.apache.ibatis.exceptions.ExceptionFactory.wrapException...

  • 在Java中查询不到数据时报空指针

    当向数据库进行查询操作时 经过以上操作运行结果为 当从数据库查询数据为空时,在对其进行操作则会报空指针异常

  • Service报空指针,方法写成private了,我哭了

    今天不知道改了啥,下面的代码一直报空指针错误。 我以为是QueryWrapper的问题,又重写了SQL语句,但是还是相同的错误。 如何用System.out.println一点点调试,直到我尝试了一下Service的list方法还是报错,我就...

  • hql 使用query.list()为空指针异常 ,但是数据库可以查出结果

    hql 使用query.list 为空指针异常 ,但是数据库可以查出结果 解决方法: hibernate配置中检查数据库的方言是否配置正确 org.hibernate.dialect.OracleDialect <property name="hibernateProperties"> <...

  • Java程序中空指针异常的最佳实践

    1、空指针问题NullPointerException 是 Java 代码中最常见的异常,将其最可能出现的场景归为以下 5 种:参数值是 Integer 等包装类型,使用时因为自动拆箱出现了空指针异常; 字符串比较出现空指针异常;诸如 ...

Global site tag (gtag.js) - Google Analytics