编写令人愉悦的API接口(一)

3,981 阅读6分钟

引言

API接口是服务端与客户端沟通的桥梁.较好的API设计能减少客户端与服务端的联调时间,更加关注于自己本身代码的优化与业务层的逻辑.

API设计知识点

API组成

良好的API接口应该从这下面几个方向进行优化

  1. 准确的API协议
  2. 准确的内容类型
  3. 统一的返回类型以及异常处理
  4. 良好的接口版本控制体系
  5. API接口路径尽量简短统一
  6. 性能与安全

实践

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持续关注.