Skip to content

后端 API 规范

此规范为 RESTful Api 定义了一个与语言无关的标准接口规范。使开发者或程序都能更快的理解服务功能。

后端 API 规范。持续补充...

目录

  • 路径
  • HTTP Status Codes
  • 字段类型
  • 键名规范
  • 请求返回值

路径

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。路径二级域名api.xxx.com开头。路径命名使用驼峰写法。

string
GET https://api.example.com/animal 列出所有动物
GET https://api.example.com/animal/carnivorousAnimal  列出所有动物里面的食肉动物
POST https://api.example.com/animal 新建一个动物
GET https://api.example.com/animal/id 获取某个指定动物的信息
PUT https://api.example.com/animal/id 更新某个指定动物的信息
DELETE https://api.example.com/animal/id 删除某个指定动物的信息

前端系统域名前缀默认wechat

string
GET https://api.example.com/wechat/animal 列出所有动物

后台系统域名前缀默认admin

string
GET https://api.example.com/admin/animal 列出所有动物

HTTP Status Codes

100-(消息)—— 服务器接受到请求

200-(成功)——服务器按照预期完成请求处理

300-(重定向)——客户端需要执行进一步的操作来完成请求

400-(客户端出错)——客户端请求无效

500-(服务器出错)——服务器出错,请求无法正常执行

正常返回使用200状态码。如遇到客户端返错误(优惠券已领取,命名重复等)使用400状态码

已存在code语义介绍IANA Status Code Registry.

字段类型

类型对应类型例子文档
booleanboolean
integerint32
integerint64
numberfloat
numberdouble
string
stringbytebase64 encoded characters
stringbinaryany sequence of octets
integerdate1598915491000

字段空值处理

项目使用spring boot jackson序列化时如果配置了如下配置

yaml
spring:
  jackson:
    default-property-inclusion: NON_NULL

空值序列化时将会被忽略。这有利于减少接口返回的数据体大小。默认开启。 因而对于前端来说,取值要对 null 做处理。

日期处理

项目使用spring boot jackson序列化时如果配置了如下配置

yaml
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

日期默认处理成yyyy-MM-dd HH:mm:ss格式。因此无需再接口返回的模型上增加日期处理。除非日期只包含年月日,那就需要额外处理,否则不用。

其他注意事项

  • 禁止使用 0 1 表示 boolean
  • 对数值计算有特殊要求的使用 string返回。

键名规范

语义化键名,并且遵循驼峰命名法

请求返回值

  • 非导出接口统一使用JsonResult对象进行包裹
json
{
    "code": 0,
    "data": {},
    "msg": "string"
}

code 状态码如下:

java
OK(0, "OK"),
BAD_REQUEST(40000, "参数错误"),
UNAUTHORIZED(40001, "未授权或登录已过期"),
NO_POWER(40002, "权限不足"),
PASSWORD_ERROR(40003, "账号密码错误"),
INVALID_TOKEN(40004, "无效的令牌"),
DISABLE_USER(40005, "用户被禁用"),
SERVER_ERROR(50000, "服务器开了点小差,请稍后再试!"),
UN_REGISTER(50001, "未注册"),
ILLEGAL(50002, "非正常请求"),
SERVICE_RESTART(50003, "服务重启中,请稍后"),
NO_STANDARD(50004, "接口不符合开发规范"),
NULL_ERROR(50005, "NULL异常"),
INELIGIBLE_ERROR(50006, "不符合条件"),
SESSION_KEY_ERROR(50007, "sessionKey不存在或已过期");

code为0表示业务成功。仅针对httpcode200的情况。

  • 导出接口统一使用void对象进行包裹

接口例子

java
@Api(tags = "Banner")
@RestController
@RequestMapping("/mall/banner")
@RequiredArgsConstructor
public class BannerController {

    private final BannerService bannerService;

    @ApiOperation("列表")
    @GetMapping
    public JsonResult<PagedResult<BannerOutputDto>> list(BannerQuery dto) {
        return JsonResult.ok(this.bannerService.list(dto));
    }

    @ApiOperation("详情")
    @GetMapping("{id}")
    public JsonResult<BannerOutputDto> get(@PathVariable("id") Long id) {
        return JsonResult.ok(this.bannerService.get(id));
    }

    @ApiOperation("新增")
    @PostMapping
    public JsonResult<Long> insert(@RequestBody @Validated BannerCreateInputDto dto) {
        return JsonResult.ok(this.bannerService.insert(dto));
    }

    @ApiOperation("编辑")
    @PutMapping("{id}")
    public JsonResult<Void> update(@PathVariable("id") Long id, @RequestBody @Validated BannerModifyInputDto dto) {
        dto.setId(id);
        this.bannerService.update(dto);
        return JsonResult.ok();
    }

    @ApiOperation("修改状态")
    @PutMapping("{id}/status")
    public JsonResult<Void> updateStatus(@PathVariable("id") Long id, @RequestBody @Validated BannerModifyStatusInputDto dto) {
        dto.setId(id);
        this.bannerService.updateStatus(dto);
        return JsonResult.ok();
    }

    @ApiOperation("删除")
    @DeleteMapping("/delete/{id}")
    public JsonResult<Void> delete(@PathVariable("id") Long id) {
        this.bannerService.delete(id);
        return JsonResult.ok();
    }

    @ApiOperation(value = "导出列表")
    @GetMapping("/export")
    public void export(HttpServletResponse response, BannerQuery query) {
        List<BannerPoiDto> list = BeanCopierUtil.copyLists(this.bannerService.list(query), BannerPoiDto.class);
        EasyPoiUtil.exportExcel(list, "banner列表", BannerPoiDto.class, response);
    }
}

前后端沟通的桥梁

后端项目会进行swagger集成,而后导入到apifox,前后端接口沟通的桥梁便在于此。为了更好的沟通,注释的编写尤为重要。以下是一些示例:

  • body 数据结构
java
    @Data
    public class SigninTaskInputDto {

        @ApiModelProperty(value = "签到方式", notes = "com.wmeimob.mall.core.mall.user.signin.value.SigninType")
        @NotNull(message = "签到不能为空")
        @RangeExtend(value = SigninType.class, message = "签到方式有误")
        private Integer type;

        @ApiModelProperty("说明")
        private String description;
    }

    @PostMapping
    @ApiOperation("保存签到任务信息")
    public JsonResult<Void> save(@RequestBody @Validated SigninTaskInputDto dto) {
        this.signinService.saveSigninTaskInfo(dto);
        return JsonResult.ok();
    }

示例save接口定义了一个SigninTaskInputDto对象,通过编写@ApiModelProperty注解来表达属性的注释。其中,对于枚举类型有个扩展,可以定义notes属性为枚举类型的全类名,swagger扩展会去读取这个属性并解析成全新的描述文本。那这其中,这个枚举有什么格式要求吗?它支持

  • 原生枚举
  • 实现接口EnumValue的枚举类

最终效果如图 swagger_demo1

  • 非 body 数据结构
java
    @PutMapping
    @ApiOperation(value = "签到")
    public JsonResult<SigninSimpleOutputDto> signin(
        @RequestParam(name = "signinType", defaultValue = "2")
        @ApiParam(name = "type", value = "签到类型", type = "com.wmeimob.mall.core.mall.user.signin.value.SigninType") Integer signinType) {
        return JsonResult.ok(this.signinService.signin(signinType));
    }

示例signin接口定义了一个参数,通过编写@ApiParam注解来表达属性的注释。其中,对于枚举类型有个扩展,可以定义type属性为枚举类型的全类名,swagger扩展会去读取这个属性并解析成全新的描述文本。那这其中,这个枚举有什么格式要求吗?它支持

  • 原生枚举
  • 实现接口EnumValue的枚举类

最终效果如图 swagger_demo2

参考资料

OpenAPI-Specification
接口设计:从前端开发的角度谈一谈接口规范(网易)