通过需求文档,描述清楚业务流程,定义好相关的设计规范。根据项目大小划分好前台和后台的功能需求,通过需求来得到对应的模块。
一般前后端分离项目会拥有不同的数据格式,例如 json、url等。 这里要通过文档的形式同前后端一起确定好数据阐述的格式。
接口地址:不包含接口BASE地址。
请求方式: get、post、put、delete等。
请求参数:数据格式(默认json)、数据类型、是否必填、中文描述。
响应参数:类型、中文描述。
这里的接口文档一般就是对应后台的实体,即RequestVo(调用后台接口访问的实体)和给往前端的ResponseVo(前台调用接口时前往的实体)。一般来说ResponseVo都会在后台做一个统一的处理为ResultVo。
对于ResultVo的规范需要在上一节中定义好,例如:错误码Code,错误描述msg,请求的url,以及实体泛型T。
需要注意的是,这里定义的接口文档实在彻底了解号数据流、业务流的基础之上完成的。
尽量采用自动化接口文档,可以做到在线测试,同步更新。 文档中应包含:接口BASE地址、接口版本、接口模块分类等。
有了这个接口文档之后(实质上就是定义实体的过程和对应的json),前后端的开发基本根据这个文档去开发。在实际的项目业务中,接口文档会产生版本的迭代,这个时候我们会需要将它放到版本管理器中,不论你用的是git亦或者是svn。
注:除了以上描述的,我们的项目中还有redis,mongoDB,elasticsearch等。这些都是在非常了解业务的状况和系统架构下去设计的。后台运用这些工具去完成接口功能的实现以及系统功能和性能的实现。
主要是对RESTful接口设计做一个介绍。
前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收
请求,进行业务处理,返回数据给前端。
针对URL路径的restful风格,以及传入参数的公共请求头的要求(如:app_version,api_version,device等),这里就不介绍了,小伙伴们可以自行去了解,也比较简单。
在 0.2小节 中我们提到了会把ResponseVo 处理为一个统一的ResultVo。
这个ResultVo大致由4个部分组成,分别 是接口请求地址(url)、接口请求方式(get/post)、请求数据(request)、响应数据(response)。
在前端的眼中,拿到的数据就会长这样,也就是我们所理解的返回格式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iPfV1rRu-1662523338889)(C:\Users\Hasee\AppData\Roaming\Typora\typora-user-images\image-20220906214352990.png)]
一般来说没有明确的规范要求,具体是看开发时需要什么就添加什么。
例如,我们要提示前端用户权限不足,那么我们返回的状态码就可以定义为 403 ;如果我们要告诉前端的数据参数异常,我们就可以把返回的状态码定义成 102 。具体的状态码细节可以参考http请求返回的状态码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9VfIYYH-1662523338892)(C:\Users\Hasee\AppData\Roaming\Typora\typora-user-images\image-20220906215237691.png)]
分类 | 区间 | 分类描述 |
---|---|---|
1** | 100~199 | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 200~299 | 成功,操作被成功接收并处理 |
3** | 300~399 | 重定向,需要进一步的操作以完成请求 |
4** | 400~499 | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 500~599 | 服务器错误,服务器在处理请求的过程中发生了错误 |
具体可以参考这篇文章:HTTP状态码有哪些分类?
同样我们可以这么设计:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMuiZ7de-1662523338893)(C:\Users\Hasee\AppData\Roaming\Typora\typora-user-images\image-20220906215907754.png)]
这样设计的好处是可以把错误类型归类到某个区间内,如果区间不够,可以设计成4位数。 这样前端开发人员在得到返回值后,根据状态码就可以知道,大概什么错误,再根据msg相关的信息描述,可以快速定位。
这个字段相对理解比较简单,就是发生错误时,如何友好的进行提示。一般的设计是和code状态码一起设计。
返回数据体,json格式,根据不同的业务又不同的json体。一般来说返回给前端时会结合msg、code一起返回。
我们先来看一段数据校验的代码:
public String addUser(User user) {
if (user.getId() == null || user.getAccount() == null || user.getPassword() == null ) {
return "对象或者对象字段不能为空";
}
if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword())) {
return "不能输入空字符串";
}
// 参数校验完毕后这里就写上业务逻辑
return "success";
}
是不是感觉比较熟悉,在最开始不熟悉开发的时候,都会这么写过。虽然我们阅读这段代码对参数的校验是没有问题的,而且排版也还看得过去。但是每个接口都这么写显然会看的比较繁琐。而且还没到业务处理,参数校验的代码就这么多了,显然会让人头大。
在我们引入的依赖 spring-boot-starter-web 中包含了 Validator ,它可以非常方便的定制校验规则,并自动完成校验。只需要在需要校验的字段加上对应的注解即可,message后面填校验失败后的信息。
@NotNull(message = "xxx")
@Size(min=*, max=*, message = "xxx")
//下面这个注解适用于邮箱验证
@Email(message="xxx")
校验规则和错误提示信息配置完毕后,接下来只需要在接口中需要校验的参数上加上@Valid 注解,并添加BindResult参数即可方便完成验证。
BindResult作用:如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
当然,每个接口中都添加BindResult显然会比较繁琐,我们完全可以把BindResult去掉
例如:
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("user")
public String addUser(@RequestBody @Valid User user,) {
return userService.addUser(user);
}
}
如果我们接口向上一节那么写,就会发现这样就把所有的问题都响应到前端了,解决这个问题的办法就是使用全局异常处理。
由于参数校验会自动引发异常,我们就不用再去手动捕捉异常进行处理,这个时候我们就可以用spring boot全局异常处理。
首先,我们需要新建一个ExceptionControllerAdvice类,在这个类上加上**@ControllerAdvice或@RestControllerAdvice注解**,这个类就配置成全局处理类了。(这个根据你的Controller层用的是@Controller还是@RestController来决定)
然后在类中新建方法,在方法上加上**@ExceptionHandler注解**并指定你想处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理。
例如:
@RestControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}
我们在 1.0.1小节 中了解了返回格式的写法,我们自定义异常也可以参考这一小节来编写。
@Getter //只要getter方法,无需setter
public class APIException extends RuntimeException {
private int code;
private String msg;
public APIException() {
this(1001, "接口错误");
}
public APIException(String msg) {
this(1001, msg);
}
public APIException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
}
当然,我们也需要在全局异常类中添加上我们对自定义异常的处理。
@ExceptionHandler(APIException.class)
public String APIExceptionHandler(APIException e) {
return e.getMsg();
}
这样就对异常的处理就比较规范了,当然还可以添加对Exception的处理,这样无论发生什么异常我们都能屏蔽掉然后响应数据给前端,不过建议最后项目上线时这样做,能够屏蔽掉错误信息暴露给前端,在开发中为了方便调试还是不要这样做。
在上一大节中,我们对全局异常处理和自定义异常已经处理完毕,但是当我们抛出自定义异常的时候,全局异常处理只响应了异常中的错误信息msg给前端,并没有将错误代码code返回。这里就要说到数据统一响应了。
我们尽管对参数校验方式和异常处理方式做好了规范,但是对响应数据没有做好规范。
统一数据响应第一步肯定要做的就是我们自己自定义一个响应体类,无论后台是运行正常还是发生异常,响应给前端的数据格式是不变的。
可以参考我们自定义异常类,也来一个响应信息代码code和响应信息说明msg。
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultVo<T> {
/**
* 状态码
*/
private Integer code;
/**
* 提示信息,如果有错误时,前端可以获取该字段进行提示
*/
private String msg;
/**
* 查询到的结果数据,
*/
private T data;
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg =msg;
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg =msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public ResultVo(Integer code, String msg, T data) {
this.code = code;
this.msg =msg;
this.data = data;
}
}
然后我们修改全局异常处理的返回值:
@ExceptionHandler(APIException.class)
public ResultVO<String> APIExceptionHandler(APIException e) {
// 注意哦,这里返回类型是自定义响应体
return new ResultVO<>(e.getCode(), "响应失败", e.getMsg());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 注意哦,这里返回类型是自定义响应体
return new ResultVO<>(1001, "参数校验失败", objectError.getDefaultMessage());
}
通过上面的操作,我们将状态码和响应说明还有错误提示数据都返给了前端,并且是所有异常都会返回相同的格式。
这样无论是正确响应还是发生异常,响应数据的格式都是统一的。
数据格式规范好了,不过响应码code和响应信息msg还没有规范。所以,必须要将响应码和响应信息给规范起来。
要规范响应体中的响应码和响应信息我们使用枚举是最好的。Java中自带了枚举类型,我们可以依据枚举类型创建一个响应码枚举类。
@Getter
public enum ResultCode {
private int code;
private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
//这里的状态码依据项目实际情况自行定义,不必照搬
SUCCESS(1000, "操作成功"),
FAILED(1001, "响应失败"),
VALIDATE_FAILED(1002, "参数校验失败"),
ERROR(5000, "未知错误");
}
我们还要把状态码和信息就会一一对应,这样比较好维护。
这里要修改响应体的构造方法,让其只准接受响应码枚举来设置响应码和响应信息。
public ResultVO(T data) {
//这里的SUCCESS依据自己定义的ResultCode来替换
this(ResultCode.SUCCESS, data);
}
public ResultVO(ResultCode resultCode, T data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}
同时我们还应当对全局异常处理的响应码设置方式进行修改:
@ExceptionHandler(APIException.class)
public ResultVO APIExceptionHandler(APIException e) {
// 注意哦,这里传递的响应码枚举
return new ResultVO<>(ResultCode.FAILED, e.getMsg());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 注意哦,这里传递的响应码枚举
return new ResultVO<>(ResultCode.VALIDATE_FAILED, objectError.getDefaultMessage());
}
既然我们对响应数据做了处理,也对异常做了统一返回响应体,我们可以用一个全局处理来把省略掉用响应体包装接口返回数据。
我们同样是像全局异常处理一样,定义一个全局处理类 ResponseControllerAdvice 继承 ResponseBodyAdvice 接口并重写其中的方法。
// 注意,这里要加上需要扫描的包,即项目的controller包
@RestControllerAdvice(basePackages = {"xxx"})
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
// 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
return !returnType.getGenericParameterType().equals(ResultVO.class);
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装,所以要进行些特别的处理
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVO里后,再转换为json字符串响应给前端
return objectMapper.writeValueAsString(new ResultVO<>(data));
} catch (JsonProcessingException e) {
throw new APIException("返回String类型错误");
}
}
// 将原本的数据包装在ResultVO里
return new ResultVO<>(data);
}
}
重写的这两个方法是用来在controller将数据进行返回前进行增强操作,supports方法要返回为true才会执行beforeBodyWrite方法,所以如果有些情况不需要进行增强操作可以在supports方法里进行判断。
对返回数据进行真正的操作还是在beforeBodyWrite方法中,我们可以直接在该方法里包装数据,这样就不需要每个接口都进行数据包装了,省去了很多麻烦。
这样尽管我们没有在接口中包装数据,但返回给前端的依然是经过包装后的数据。
在上一节中,我们得到了一个统一响应体ResultVo,里面具有code、msg、data。
如果返回给前端的数据,要求给出分页信息,那么ResponsePages类中,还需要有Page类,其中至少包括当前页数,每页显示条数和总条数信息。分页使用github的pagehelper工具类来完成。
http返回对象示例:
{
code: code,
msg: msg,
pageInfo: { // 分页信息
curPage: 1,
pageLimit: 10,
page: 1,
total: 10
}
data: {data}
}
很多时候我们往往需要多种返回数据,例如添加返回分页信息,我们可以灵活的根据业务需求修改ResultVo,但是切记不要在项目进行到中段时再反复修改,一定要在项目开始时就确立,这就是接口开发文档存在的意义。
参考面向对象原则,接口设计也有六大原则,分别是单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则、开闭原则。
接下来介绍一些其他的设计原则。
常规性校验:包括必填字段,长度检测。格式校验等。
鉴权校验:当前请求必须符合权限等。
业务校验:根据实际业务而定,比如订单金额不能小于0等等。
作用:在接口层做用户鉴权校验和参数校验,比如做ID基础校验,id<0 直接拦截这样就很有效的能够解决恶意请求从而导致缓存击穿的问题。这里有一个小细节,就是我们开发的时候要保持一个不信任的心,就是不要相信任何的调用方,比如你提供了一个API接口出去,有几个参数。我觉得作为被调用方,任何可能的参数都应该被考虑到去做校验。因为你不相信任何调用你的人,你不知道她会传什么参数给你。举个简单的例子,如果你的接口是分页查询的,若是你没有对分页参数的大小做限制,调用的人万一一口气查询十万条数据,一次请求就会要好几秒。多几个并发不就挂掉了。
幂等的概念: 多次调用对系统的产生的影响是一样的,即对资源的作用是一样的,但是返回值允许不同。
HTTP的幂等性:
(1)GET : 只是获取资源,对资源本身并没有副作用。天然的幂等性。
(2)HEAD: 本质上和GET一样,获取请求头的信息,主要是探活的作用,具有幂等性。
(3)OPTIONS: 获取当前URL所支持的方法,因此也是具有幂等性的。
(4)DELETE: 用于资源的删除,具有副作用,但是他应满足幂等性,如根据ID删除某一个资源。调用方法可以调用N次而不用担心出错。
(5)PUT: 用于资源更新,有副作用,但是他应该满足幂等性,如根据ID更新数据,调用多次和N次的作用是相同的。
(6)POST: 用于资源添加,多次提交可能产生副作用,比如订单的提交等等。
如何实现幂等(后端的方法):
解决方法为:使用Token。前端向服务端请求一个唯一Token,然后再将Token随表单一起提交。后端拿到请求的时候需要先将请求的Token去除去做校验,只有当Token存在的时候才会获取表单数据进行DB的操作;若是Token压根就不存在就说明已经有了其他线程的请求已经在处理了这个数据,所以这次请求是多余的。
数据在传输的过程中我们知道资源数据都是直接爆露出口来的,用抓包工具就可以很轻松的修改。所以一般我们会对传输的数据进行加密。常规的做法是对敏感数据(比如用户的身份证号等)做MD5加密。现在主流的做法是使用HTTPS协议,再HTTP和TCP之间添加一层数据安全层(SSL层),这一层负责数据的加密和解密。
尽管数据经过了加密处理,但还是会有人一直进行恶意请求,这个时候的简单做法就是添加时间戳机制,在每次的请求中加入当前时间,服务端对每次请求中的时间(解密之后)与当前系统的时间作对比,看是否在一个固定的时间范围内,比如5s。恶意伪造的数据在没有弄清楚这个混淆时间戳的字段之前,是没办法频繁的更改数据中心的时间的,超过10s就可以当做非法请求了。
首先我们要知道,所有的设计考量都应当基于 减少重复工作,增加可读性可维护性 出发的。
由于是在设计API接口,我们就应该符合API的一般模式:
ResultType ApiName(ParamType )
1. 接口参数,即形参。可以是 string,int,以及其他任意可以称之为参数的东西
2. 接口返回值。可以是 string,int,以及其他任意可以称之为返回值的东西
3. 接口名(签名)
那么在RESTful中我们要对应上这个模式:
HttpResponse URL(HttpRequest)
1. HttpRequest:包括请求头,URL参数,请求body参数
2. HttpResponse: 包括响应头,响应的body
我们可以总结出一个大概的标准:
1.接口命名必须要做到清晰。
2.接口数量,越少越好。
3.有明确的输入输出总是有返回值,告诉调用端,我到底做了什么,做得怎么样,即:反馈。
应该尽量将API部署在专用域名之下: https://api.example.com
①具有统一的前缀或者将其版本化。
版本号分为整型和浮点型 。整型是大功能版本,如v1、v2。浮点型是补充功能版本, 如v1.1、v2.1。
https://api.example.com/v{n}
/SERVICE_NAME/v1/users
或者
/APP_NAME/v2/users
②url应该尽可能的短小,同一份资源可以由不同的路径。
③接口的数量越少越好,能合并的接口就尽量合并。
获取用户列表信息:GET /users
获取单个用户信息:GET /users/{id}
坦白说,获取一个与获取一批,似乎并没有什么语义上的差别,
但是后端的同学就不一样了,他可能需要写两个 View Class。
所以只保留批量的接口,查询一个时,用 URL 参数传递就行了。
参考 1.2 小节
Response Headers
可以做少量文章,如自定义一个Header
Status Code
按照基本规范来,该404的404,该200的200
Response body
基本都是围绕这个做文章
{
是否成功
boolean "is_success":
错误码是多少
number|null "err_code":
错误信息
string|null "err_msg":
错误详情(可选)
string|null "err_detail":
出错的时哪个服务
string|null "provider":
正常返回时的数据
"response_data": {
}
}
实际业务开展过程中,可能会出现各种的api不是简单的restful 规范能实现的。需要有一些api突破restful规范原则。特别是移动互联网的api设计,更需要有一些特定的api来优化数据请求的交互。
删除单个or批量删除: DELETE /v1/product body参数{ids:[]}
页面级API:把当前页面中需要用到的所有数据通过一个接口一次性返回全部数据
实际上没有很明确的规范,但是尽量跟随数据库的风格,即:下划线风格。 这样,在 序列化整个 Model 时,也许会很方便。参考阿里巴巴开发手册。
在项目开发中,web项目的前后端分离开发,APP开发,需要由前后端工程师共同定义接口,编写接口文档,之后大家都根据这个接口文档进行开发,到项目结束前都要一直维护。
接口文档在项目开发过程中,使前后端工程师有一个统一的文件进行沟通交流开发;项目维护中或者项目人员更迭,方便后期人员查看、维护。
简单的核心就是怎么简单怎么来,怎么省时间怎么来。
如果不知道怎么写,就把案例写的越详细越好。
开发时间是非常宝贵的,而接口对接通常都是一些工期紧张的情况下去快速编写,而且面对一些碎片化的时间工作者,一份简单直观的文档可能更受欢迎。
另外,接口文档最终形式最好是pdf。
• 接口说明
• 请求示例
• 请求参数说明
• 响应示例
• 响应参数说明
接口功能:
本接口用于获取用户的token信息。
接口请求地址:
https: xxx/xxx/xxxx
复制代码
请求头 :
请求头 | 请求内容 | 说明 |
---|---|---|
Authorization | Basic secretKey | 访问token |
Content-Type | application/json | 请求方式 |
请求方式:POST
参数类型:JSON
绝大多数为json,自定义格式
[
{"id":"20201219",
"name":"21.59",
"age":"ftp_1002"
...
},
{"id":"20201219",
"name":"21.59",
"age":"ftp_1002"
...
},
]
复制代码
字段名 | 字段说明 | 字段类型 | 是否必填 |
---|---|---|---|
字段1 | 说明字段1的作用 | varchar(50) | 是 |
字段2 | 说明字段2的作用 | int | 是 |
字段3 | 说明字段3的作用 | decimal | 是 |
成功响应编码:
{
"code: "200",
"message": "请求成功",
"data": 返回数据,格式自定
}
复制代码
失败响应编码:
{
"code: "200",
"message": "请求成功",
"data": 返回数据,格式自定
}
复制代码
接口返回码 | 接口返回描述 |
---|---|
200 | 成功 |
400 | 请求参数异常 |
401 | 授权失败 |
500 | 系统异常 |
由于不同的公司有不同的文档格式要求,此处只列举一个仅供参考的案例。
+ 封面
+ 接口文档名称
+ 接口版本号
+ 版权说明
+ 文档信息
+ 标题 | 创建时间 | 打印时间 | 文件名 | 存放目录 | 所有者 | 作用
+ 小题:版权声明
+ 版本历史(重点1)
+ \| 版本号 \| 日期 \| 修改者 \| 描述 \|
+ \| v1.0.0 \| xxx \| xxx \| xxx |
+ 目录
+ 结构清晰
+ 有条理
+ 能快速定位需要的信息(后文会介绍)
+ 文档具体内容部分
+ 编写目的
+ 对接准备事项
+ 测试联调
+ 上线
+ 使用协议 + 规范
+ 报文规范
+ 请求报文规范
+ 响应报文规范
+ 接口描述
+ 报文规范
+ 请求报文
+ 响应报文
+ 公共报文头
+ 接口码说明
+ 业务接口
+ 查询接口
+ 加解密规范
+ 原则
+ 令牌信息
+ 加密规范
+ 解密规范
+ 业务接口
+ 具体接口1:
+ 说明
+ 规范码(查表)
+ 使用方式
+ 请求字段
+ 响应字段
+ 案例
+ 具体接口2....
........
+ 附录
+ 参考资料1
+ 参考资料2
+ 其他.....
复制代码
封面还是比较重要的,毕竟是打开文档的第一眼内容,下面用阿里的文档作为参考,可以看到封面一般是如下内容:
公司名称
文档名称
版本号
文档信息主要记录这份文件的产生日期以及具体的创建打印日期等。
文档名 | 内容 |
---|---|
标题 | xxx文档 |
创建日期 | 20xx-xx-xx |
打印日期 | 20xx-xx-xx |
文件名 | 文档的全名 |
存放目录 | 文件位置 |
所有者 | 某某公司 |
作者 | xxx |
版权声明:(现在这个时代版权是极其重要的)
xxxx所有,不得三方借阅、出让、出版
版本历史是很重要的,每次改动都需要有详细的记录,这样才能保证文档的干净和有效,同时可以方便review的时候,对于文档的修订者进行文档审查。
版本号 | 日期 | 概述 | 修订者 |
---|---|---|---|
1.0.0 | 20xx-xx-xx | 创建 | xxx |
1.0.1 | 20xx-xx-xx | 修改文档第一小节内容 | xxx |
1.0.2 | 20xx-xx-xx | 修订文档第四小节的错误描述,更新文档说明 | xxx |
好的文档一定有好的目录,只要按照一定的规范和格式写出来的文档,一般看上去都是十分舒服的。还是用阿里的开发手册做参考
这一部分发挥的自由空间就比较大了,不同的业务不同的公司不同的需求不同的人都能写出万千种格式的文档,所以这里也是给一个样例做参考使用。是否有实用价值因人而异。
为了不让整个目录树太长,这里没有做标题说明=-=
需要解决什么问题,为什么要这份文档,这份文档有什么参考价值?
接口方可以提供什么内容,接口方需要对接方的那些内容,以及提供的其他信息,比如需要对接方提供 系统应用id,系统唯一标识。向对接方提供密钥等等
1. 测试联调:分配测试的密钥,测试环境的账户和密码以及其他信息
2. 上线:上线之后需要做什么事情,如:替换生产url,替换生产环境账户密码,替换密钥为生产密钥等等
可以是本次对接使用的算法,通信协议,可以是术语说明或者和业务相关的其他说明,以及对接的要求都可以,发挥空间很大,自由设计。
报文规范是接口对接的核心部分,因为对接大部分的时间基本都是花在接口参数调试和请求调试等。所以报文规范算是非常重要的内容。具体内容可以参考简单版本的接口描述,也可以使用目录格式进行对应的描述
+ 请求报文:主要为请求的Body,以及请求的header内容,一般都是Json的格式,并且要求UTF8编码
+ 响应报文:返回的格式和内容,也是需要协商的部分
+ 公共报文头:一般需要重复使用的参数可以作为公共报文头,但是不是所有的公共报文头都是必选,存在可选的参数
+ 接口码说明:描述接口的注意事项,以及那些字段参数需要重点关注,主要为提示信息
+ 业务接口:一般表示业务的返回结果,比如统一2000作为报文的成功响应码,其他所有码都是存在对应的接口码表进行设计。
+ 查询接口:如何才算是表示查询成功,比如一个还钱的接口当中可能是受理中,拒绝或者处理完成,等查询接口的信息描述
复制代码
也是比较重要的部分,也是比较花时间的地方,需要大量调试来打通接口的地方,存在以下的几个要点。
原则:接口存在一些简单的原则,比如`非对称加密`,`数字签名`,`时间戳判断有效性`,具体按照接口的原则自由设置
令牌信息:描述令牌是如何生成的,是比较重要的部分,一般由对接双方沟通完成,最好多以案例和代码辅助解释
加密规范:描述接口数据的加密过程,比较重要的内容信息,最好多以案例和代码辅助解释
解密规范:就是解释接口要如何解密,比如需要拿到服务端给过来的配对公钥才能解密,再比如使用签名+参数进行对照加密验证签名是否正确等。
加解密规范参考:
一般的加密方式,一般情况下做到下面这种形式基本可以屏蔽大部分的攻击:
1. 按照map的key进行字典排序,同时加入`timetamp`值校验核对时间
2. 把参数按照一些特殊形式拼接为`key=value&key=value`的形式,末尾带入时间戳或者其他的一些信息,比如应用Id等核实身份的内容
3. 把这一串按照**AES加密**,然后按照**BASE64编码**,生成一个编码串
4. 把BASE64编码进行**MD5加密**,加密完成之后,得到固定长度的MD5字符串
5. 按照md5串+上面的string在进行一次md5加密,生成签名,那么这个签名基本上就唯一的
这里基本可以照抄简单接口模板,因为接口描述每个人的描述不同,下面给出一些基本上涉及的点,另外,到了这一步就尽量用案例辅助,因为案例可以帮助接口阅读者更快速的上手和理解,注意这一部分的内容:实用性大于理论性。
具体接口:
1. 说明
2. 规范码(查表)
3. 使用方式
4. 请求字段
5. 响应字段
6. 案例
可能这部分和说明书一样基本没人看,所以不做过多的解释,附录不必写的很详细的,这里可以随意施展。
1.CSDN_如何设计和编写标准的后端接口
2.开源博客_API 接口规范
3.CSDN_什么是接口文档,如何写接口,有什么规范?