在各种中台、微服务、分布式等互联网概念盛行的今天,部门承接的项目也逐渐开始采用的微服务框架,前后端分离方式。现在的软件开发,随着前后端任务的细分,相应的工作职责也就越来越清晰。现在项目开发的前端的技术栈和相应的生态圈都已经相当成熟,前端开发人员随之走俏,地位也越来越高。
新开发系统中融合各种微应用的方式来构建生态系统,所以使用SpringBoot2对原有的产品按照微服务的模式进行拆分重构,本文主要介绍在重构过程中微服务Restful API风格,返回接口设计统一返回形式。
系统的整体架构示意图如下:
※此图是概要示意图,省略了网关、消息中间件等,重点用来辅助理解API设计。
根据目前发展趋势来看,随着前端设备种类的增多,前端和后端进行交互就越来越灵活多样。因此,最好有一种统一的方式来进行前端设备与后端进行通信。RESTful架构是对MVC架构改进后所形成的一种架构,通过使用事先定义好的接口与不同的服务联系起来。在RESTful架构中,客户端使用POST,DELETE,PUT和GET四种请求方式分别对指定的URI资源进行增删改查操作。这种对资源的管理访问方式具有强扩展性、结构清晰等特点,是目前比较成熟的一套互联网应用程序的API设计架构。本文重点介绍后端服务器接收请求后,如何实现封装业务数据返回前端。
现在开发项目中,前端和后端并不是固定的搭配来同时进行同一个项目,在频繁交替过程中,不能保证大家保持接口和调用形式的默契行,如果前端和后端,在没有统一返回数据格式,就会给接口调试带来较大的麻烦。不同的人员基于自身对RestFul的了解,按照自己的喜好来封装接口返回的数据格式:
张三:习惯返回一个编码(code=0)并返回数据;
李四:喜欢直接返回一个布尔类型变量(success=true),然后返回数据;
王五:习惯在调用失败时,返回一个状态(status=0)并返回数据。
如上三个人的做法基本都没有大问题,并没有绝对的谁对谁错,只要给前端人员正确的接口文档,前端都可以调用接口。但是,如果没有同意规则,在同一个项目中任由他们自由生长,随着功能越来越多,就会带来很大的麻烦。返回规则不一致,导致同一个前端,需要分别针对不同的接口做各种规则的适配。沟通后台,每个人都有很多开发完成的接口,修改量巨大,牵一发而动全身。所以,在项目开发初期,搭建基础框架时,就要定好标准的接口数据返回格式,定义好全局的状态码。在同一个项目,必须遵循同一套接口返回格式,以便降低开发沟通成本。
后端返回给前端返回一般用JSON体方式,定义如下:
/**
* 定义返回的基本数据格式
*
* {status=0, code=1001, msg='查询成功', data=数据}
* {status=0, code=1002, msg='查询成功无记录', data=[]}
* {status=0, code=1003, msg='自定义业务msg', data=这是自定义的数据}
* {status=1, code=5001, msg='参数为空或格式错误', data=[]}
* {status=1, code=5002, msg='查询失败', data=[]}
*/
public class BaseResult {
// 处理结果: 0 : 成功 1 : 失败 。和错误编码区分
// 参数格式错误 赋值为1 发生异常,查询出错 赋值为1 查询成功 没有错误 都设定为0
private Integer status;
// 服务器返回的业务编码
private Integer code;
// 服务器返回的提示信息(包括成功提示消息和错误消息)
private String msg;
// 服务器的返回数据(即使出错,也要返回空对象,不能直接返回null)
private T data;
}
※code返回业务编码,开发同学们根据实际的业务功能自行封装,随着项目的进行逐步添加。例如:接口要返回用户密码异常,初始业务编码可以设定为1001。接下来有一个参数检查异常,就加一个1002的状态码。这样前端人员在得到返回值后,根据业务编码就可以概要知道错误是什么,再根据msg描述能够快速定位问题,提高开发效率。
当然为了规范化,我们可以参考HTTP请求返回的状态码来定义我们的业务消息:
#1000~1099 表示参数错误类信息 #1100~1199 表示XX业务类错误 #1200~1299 表示YY业务类异常 …… #9000~9099 表示DB访问类信息
通常会在controller层处理业务请求,并将请求结果返回给前端。下面是一个检索微信公众号列表信息的返回结果。
@RestController
@RequestMapping("/api/wechat")
public class WechatController {
@Autowired
WechatDataService wechatDataService;
@GetMapping("/docs")
public BaseResult> getWechatDocs() throws NewsException {
return BaseResponse.makeOKRsp(wechatDataService.searchDocs(docType));
}
}
返回结果
{
"status": 0,
"code": 1001,
"msg": "信息查询成功",
"data": [
{
"hotsportName": "北京师范大学珠海分校教师XXX",
"urlTime": "2019-11-05 00:54:38",
"siteName": "微信",
"evaluate": "正面"
},
{
"hotsportName": "477名骑士用车轮丈量美丽金湾!多图回顾“速度与激情”",
"urlTime": "2019-11-03 20:27:43",
"siteName": "微信",
"evaluate": "正面"
},
{
"hotsportName": "科技赋能粤港澳大湾区文创产业发展",
"urlTime": "2019-11-07 09:45:22",
"siteName": "微信",
"evaluate": "正面"
}
]
}
Controller中使用封装的Result类,通过静态方法的调用使得代码比较简化。后续我们可以继续考虑代码的优化,在控制层直接返回业务对象,通过拦截器的方式进行统一的返回值的封装处理。