编写令人愉悦的API接口(一)
引言 API接口是服务端与客户端沟通的桥梁.较好的API设计能减少客户端与服务端的联调时间,更加关注于自己本身代码的优化与业务层的逻辑. API设计知识点 API组成 良好的API接口应该从这下面几个方向进行优化 准确的API协议 准确的内容类型 统一的返回类型以及异常处理 良好的接口版本控制体系 API接口路径尽量简短统一 性能与安全 实践 API协议类型划分 GET : 从服务器上获取一个具体的资源或者一个资源列表。 POST : 在服务器上创建一个新的资源。 PUT : 以整体的方式更新服务器上的一个资源。 PATCH : 只更新服务器上一个资源的一个属性。 DELETE : 删除服务器上的一个资源。 HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。 OPTIONS : 获取客户端能对资源操作的信息。 其中GET,POST,PUT,PATCH,DELETE这五种协议在日常CRUD开发中最为常用 以用户模块的业务场景分析 //获取用户列表(分页) @GetMapping(value = "user") public R selectList(UserSearch userSearch) { //userSearch 是一个搜索实体,里面有页码以及筛选条件属性 return ResultUtil.data(); } //获取单个用户信息 @GetMapping(value = "user/{id}") public R selectOne(@PathVariable("id") String id) { //获取用户信息,与分页接口相同采用GET协议,用path传值id,区别与分页的接口 return ResultUtil.data(); } //新增用户 @PostMapping public R add(@RequestBody User user) { //新增用户为新资源写入,采用POST接口,入参为用户的实体 return ResultUtil.data(); } //修改用户 @PutMapping("{id}") public R upp(@PathVariable("id") String id,@RequestBody User user) { //修改用户所有属性,采用PUT接口,入参为用户的实体,同时id通过path传值 return ResultUtil.data(); } //删除用户 @DeleteMapping("{id}") public R del(@PathVariable("id") String id) { //删除用户,采用DELETE协议,id通过path传值 return ResultUtil.data(); } //修改用户部分属性(这里举例修改用户姓名) @PatchMapping("user/userName/{id}") public R uppPart(@PathVariable("id") String id,@PartBody String userName) { //修改用户部分属性,采用PATCH协议,在基础路由user后面加入要修改的属性名,入参用自定义注解@PartBody,原理就是解析body里单个叫userName的值,也可用Map接收,用自定义注解只是为了后期好维护. return ResultUtil.data(); } 注:切记不要直接使用@RequestMapping()注解,不准确的接口协议定义会导致url重复,客户端也可以通过任意协议调用API接口,很不规范 内容类型(content-Type)规范 application/x-www-form-urlencoded 类型 代码实例 application/x-www-form-urlencoded form表单的默认传输格式,常会在后面跟上编码,即:application/x-www-form-urlencoded;charset=utf-8 此传输格式时,数据会以键值对的形式传输 当为GET请求时,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串append到url后面,用?分割,加载这个新的url。需要对参数进行 urlencode 编码和序列化 当为POST请求时,浏览器把form数据封装到http body中,不可用@RequestBody注解修饰接收实体. multipart/form-data 类型 代码实例 multipart/form-data form表单的扩展传输格式 此传输格式时会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。 当为GET请求时,入参对象或者单属性均可与传入的name键值对一一对应 当为POST请求时,浏览器把form数据封装到http body中,不可用@RequestBody注解修饰接收实体. application/json 类型 代码实例 application/json JSON传输,现在比较推荐的传输方式 此传输格式时,数据主体是序列化后的JSON字符串 当为GET请求时,?传参方式可传值,参数名为实体内的属性值,用@RequestBody注解修饰,可直接获取到参数名对应的入参 当为POST请求时候,入参封装在body中,用@RequestBody注解修饰接收实体 统一返回类 统一返回类是必须的.统一以后客户端就只需要一个公共解析类即可.对应的业务模型放在泛型result对象中,客户端就只需要用对应的解析器解析剩下的部分. 返回类R @Data public class R<T> implements Serializable private static final long serialVersionUID = 1L; //标识请求是否成功 private boolean success; //操作成功或者失败后,客户端的提示信息 private String message; //http状态码或者自定义的异常状态码 private Integer code; //当前请求的返回时间 private long timestamp = System.currentTimeMillis(); //返回给客户端的业务主体数据,可为列表或者单个对象 private T result; } 封装返回类工具类 ResultUtil 主要封装一些常用的成功,失败或者回参的静态方法,在控制层返回前端时,只需要返回 ResultUtil.xxx() 对应的方法即可 ResultUtil 代码实例 错误码及消息 ErrorCode 用枚举类型定义ErrorCode,在程序异常时可直接调用 error(Integer code, String msg) 返回给客户端对应的业务异常或者其他系统异常 ErrorCode 代码实例 统一异常处理 异常拦截类 GlobalExceptionHandler 定义 GlobalExceptionHandler 类,用@ControllerAdvice修饰,可实现统一的异常拦截,代码示例中拦截了常见的一些异常类型. //参照格式 //ExceptionHandler 指定需要拦截的异常类型 @ExceptionHandler(value = Exception.class) @ResponseBody //HttpServletRequest 可得到对应的请求参数,Exception 对象可得到对应的异常输出,可记录在日志中便于排查,再返回给客户端相对友好的提示 public R ExceptionHandler(HttpServletRequest req, Exception e) { log.error(String.format("Exception requestURI:%s", req.getRequestURI()), e); return ResultUtil.error(500, "服务器内部错误"); } GlobalExceptionHandler 代码实例 业务异常类 BusinessException 定义业务异常类是为了一些比较特殊的情况,此类继承RuntimeException,在复杂的业务中也可定义多个业务异常类型,业务中出现一些逻辑异常就可使用这个业务异常抛出,比如字效验密码错误或者某字段数值超出临界内等等情况.可与上面的ErrorCode类结合使用,定义code应该避免一些系统预设的 http状态码 BusinessException 代码实例 后记 本文主要介绍了API遵循Restful方式的设计方案,传输内容规范以及统一返回类和统一异常拦截.涉及到的代码已经更新到github上 easyDemo-validation 项目中,下一期的文章会给大家分享validation验证包的使用以及接口设计上统一规范的小技巧,欢迎大家start持续关注.