介绍
对于后端接口调用,接口文档十分重要,而写好一份接口文档却是一件难事。在大部分情况下,文档并不是一成不变的,定义好的接口也不是一成不变的,可能在开发中,接口文档的修改不止一次变化,这对于调用者以及编写者是很难受的一件事情。我们相信没人会真心愿意去写这份东西,在调用中,一个字母的错误,则会造成很大的麻烦。但是通过knife4j,我们可以每次修改接口都去文档中进行编写,而出错率近乎为零。只要在你写代码的过程中,加上几个注解,文档就会自动生成。
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案。
具体相关内容请阅读:Knife4j文档
相关接口注解API的使用
@Api
在控制层Controller中添加注解来描述接口信息
@Api(tags = "企业信息表 控制层")
@RestController
@RequestMapping("enterprise")
public class EnterpriseController {
效果图如下:
@ApiOperation
在控制层Controller方法中配置接口的标题信息
@ApiOperation("获取单条记录")
@RequestMapping(value = "/query", method = RequestMethod.GET)
public Reply<EnterpriseVO> query(String id) {
return Reply.success(this.enterpriseService.selectById(id));
}
效果图如下:
@ApiModel
定义在返回对象类上,用于描述返回对象的的意义
@ApiModel("企业信息表")
public class EnterpriseVO implements Serializable {
效果图如下:
@ApiModelProperty
定义在出入参数对象的字段上,用来描述对象属性。
@ApiModelProperty("申请时间")
private Date applytime;
效果图如下:
关于文档中”是否是必须”说明
文档中的”是否是必须”主要通过注解的required的属性进行设置。有这个属性的注解常见的主要有@ApiModelProperty,@RequestParam和@RequestBody
对于@ApiModelProperty在接口文档”是否是必须”的,我们看到默认是false的。我们来看看这个注解里面是怎么定义的。
//截取一部分代码,并没有完整展示
public @interface ApiModelProperty {
String value() default "";
String name() default "";
boolean required() default false;
}
我们看到required属性默认是false的。
我们再来看看@RequestParam,@RequestBody注解中required默认值是怎么样的:
//截取一部分代码,并没有完整展示
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
}
public @interface RequestBody {
boolean required() default true;
}
可以看到@RequestParam和@RequestBody它们两个的required属性默认都是true,而对于@ApiModelProperty注解的required属性默认是false的。也就是说他们是冲突的,在实际开发中,我们应该注意一下这种情况的出现。
相关注解,具体参考下表:
作用范围 | API | 使用位置 |
---|---|---|
协议集描述 | @Api | 用于controller类上 |
协议描述 | @ApiOperation | 用在controller的方法上 |
对象类 | @ApiModel | 用在对象返回类上 |
对象属性 | @ApiModelProperty | 用在出入参数对象的字段上 |
Response集 | @ApiResponses | 用在controller的方法上 |
Response | @ApiResponse | 用在 @ApiResponses里边 |
非对象参数集 | @ApiImplicitParams | 用在controller的方法上 |
非对象参数描述 | @ApiImplicitParam | 用在@ApiImplicitParams的方法里边 |
接口的调用问题
接口的调用主要分两种情况,一种是内部服务之间的相互调用,一种是外部调用服务。它们之间的区别在我们这里主要是:
- 是否需要携带token去访问接口
1.内部服务之间访问不需要携带token,只需要提供一个标识。
2.外部调用服务需要携带token,否则访问失败。
内部服务之间调用
下面我举一个例子,我想要访问另外一个内部服务的接口 通过id获取员工数据
。
首先在 cn.flyrise.pai.example.feign包下声明一个service接口。
启动类中的@EnablePaiFeignClients("cn.flyrise.*")
在扫描@FeignClient
注解的时候:默认扫描cn.flyrise.**.feign
这个包下的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnablePaiFeignClients {
String[] value() default {};
String[] basePackages() default {"cn.flyrise.**.feign"};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
因为String[] basePackages() default {“cn.flyrise.**.feign”},这里已经声明了默认值。
操作示例
1. 首先通过@FeignClient来实现
name属性用于指定要访问的内部服务名称
@PostMapping指定访问该服务方法的URL地址。
@FeignClient(
name = "pai-console"
)
@ApiOperation("通过id获取员工数据")
@PostMapping({"/info/test/getStaffByIds"})
Reply<List<MessageStaffVO>> getStaffByIds(@RequestBody Collection<String> staffIds);
2. 看一下反编译后的方法是怎么样的。
@ApiOperation("通过id获取员工数据")
@PostMapping({"/info/test/getStaffByIds"})
Reply<List<MessageStaffVO>> getStaffByIds(@RequestBody Collection<String> var1, @RequestHeader("from") String var2);
到这里你会发现多了一个 @RequestHeader(“from”) String var2。它就是我所说的一个标识。这个from标识,其实是一个常量,它让内部服务之间相互调用不需要提供token。
外部调用服务
路由问题
在说外部调用服务之前,我们先来讨论一个关于路由的问题。在一个本地工程部署成为应用服务后,它的路由会发送变化。
接口文档地址变化
- 在成为应用服务之前,也就是还是一个本地项目时:
http://localhost:8080/doc.html - 在成为应用服务之后,它的接口文档地址会变成:
http://pai.flyrise.cn/example-api/doc.html
访问接口的URL地址发送变化
/console-demo/getEntByIds
在成为应用服务之后:
/example-api/console-demo/getEntByIds
原因分析
Ingress的自动配置
目前项目分两种Ingress配置: 后端 API 服务、前端 UI 服务。
为了配合Pai-cli
在执行部署(deploy命令)项目时,能自动生成Ingress
路由配置,增加了新标签:
metadata.labels.ingress: front|service
, 其中front
表示前端 UI 服务项目,service
表后端 API 服务项目,技术中台在部署后会根据这个标签对 Ingress 进行更新操作metadata.labels.path: demo-api
, 其中demo-api
的值仅用于指定路由规则的中path
值的名称,如/demo-api/(.*)$
中的demo-api
,不含其他表达式。特别注意:这个标签是一个可选项,不写则会用下述默认约定规则自动生成。
# 后端 API 服务
apiVersion: v1
kind: Service
metadata:
name: pai-demo
namespace: tt
labels:
app: pai-demo
ingress: service
path: demo-api
spec:
type: NodePort
ports:
- port: 8080
targetPort: 8080
selector:
app: pai-demo
后端 API 服务项目名称默认约定为pai-demo
,部署到 Ingress 路由后会变成/demo-api
, 端口统一为8080
, 示例如下:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/service-weight: ''
# 绑定ingress-class
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: "/$1"
name: pai-ingress
namespace: tt
spec:
rules:
- host: pai.xxx.cn
http:
paths:
- path: /demo-api/(.*)$
backend:
serviceName: pai-demo
servicePort: 8080
token问题
我们可以看到,在作为外部去调用服务时,它需要填写一个请求头,该内容必须携带token值。
否则:
{
"msg": "401 UNAUTHORIZED 令牌不合法,禁止访问 /findAppPermsByEnt",
"code": "401",
"time": 1601137636447
}