背景
1、生态目前没有成型的接口文档,接口定义都散落在每个需求的技术方案里面。前端搜集整理过到mocks里,但后端不愿意编写维护文档,效果较差
2、社区对于使用注解生成接口文档和描述数据生成TypeScript代码有着成熟的实践
具体做法
1、前端自己拉分支,部署在日常、预发环境,不发布到线上
2、Swagger生成文档和描述数据
依赖引入:
- Swagger建议使用高版本,2.9.2。低版本如2.2.2等会解析不了引入自二方包的参数
高本版依赖高版本 guava ,低版本会使应用启动不了
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
3、swagger配置:
/**
* swagger2配置类
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.aliyun"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("基于Swagger构建的Rest API文档")
.description("更多请咨询开发")
.termsOfServiceUrl("http://prm.aliyun-inc.test/prm/#/epp/manage")
.version("1.0")
.build();
}
}
4、生成内容查看
在线文档地址:
/swagger-ui.html#/
描述数据:
/v2/api-docs (API SPEC的内容,可以使用postman获取。目前数据为本地部署后获取,日常环境需要处理Buc登录、CSRF token 问题,较麻烦)
5、生成TS Service
pont文档:
https://github.com/alibaba/pont
核心点:
1、pont封装了Swagger JSON的解析和TS文件创建,比提供了vscode快捷插件,降低了生成TS的成本
2、但核心的如何生成TS时候文件需要自己完成
3、Integer、Long等Java存在前端不存在的类型需要处理
个人模板
export default class MyGenerator extends CodeGenerator {
getInterfaceContentInDeclaration(inter: Interface) {
let requestParams = inter.getRequestParams();
const paramsCode = inter.getParamsCode('Params');
requestParams = this.formatParamsType(requestParams)
let result = `
export ${paramsCode}
export type Response = ${inter.responseType}
export const init: Response;
export function request(${requestParams}): Promise<Response>;
`;
return this.resultFormat(result)
}
getBaseClassInDeclaration(base: BaseClass) {
const originProps = base.properties;
base.properties = base.properties.map(prop => {
return new Property({
...prop,
required: false
});
});
const result = super.getBaseClassInDeclaration(base);
base.properties = originProps;
return result;
}
getInterfaceContent(inter: Interface) {
const method = inter.method.toUpperCase();
let requestParams = inter.getRequestParams(Surrounding.typeScript);
const bodyParams = inter.getBodyParamsCode();
const paramsCode = inter.getParamsCode('Params', this.surrounding);
requestParams = this.formatParamsType(requestParams)
let result = `
/**
* @desc ${inter.description}
*/
import axios from 'axios';
export ${paramsCode}
export type Response = ${inter.responseType}
export const init = ${inter.response.getInitialValue()};
export
export async function request(${requestParams}): Promise<Response> {
let res = await axios.request({
url: "${inter.path}",
${paramsCode ? 'params,' : ''}
${bodyParams ? 'data: body,' : ''}
method: "${method}",
baseUrl:'',
...options
});
return res?.data
}
`;
return this.resultFormat(result)
}
formatParamsType(params) {
params = params.replace('int', 'Number')
params = params.replace('long', 'Number')
params = params.replace('ModelAndView', 'any')
params = params.replace('Model', 'any')
params = params.replace('defs.ObjectMap', 'ObjectMap')
params = params.replace('ref', 'any')
return params
}
resultFormat(result) {
result = result.replace('defs.ObjectMap', 'ObjectMap')
result = result.replace('ref', 'any')
return result
}
}
遗留问题
1、GET 请求的参数为复杂类型时:
原始Java:
Class QueryProgramInfoParam implements Serializable {
private String parentCode;
private ProgramConfigs configs;
}
生成的描述文件:
"parameters": [
{
"name": "configs.name",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "configs.programConfigs[0].order",
"in": "query",
"required": false,
"type": "integer",
"format": "int32"
},
{
"name": "configs.programConfigs[0].programCode",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "configs.programConfigs[0].status",
"in": "query",
"required": false,
"type": "string",
"enum": [
"ENABLE",
"INVISIBLE"
]
},
{
"name": "parentCode",
"in": "query",
"required": false,
"type": "string"
}
]
生成的TS:
export class Params {
/** name */
name?: string;
/** order */
order?: number;
/** programCode */
programCode?: string;
/** status */
status?: 'ENABLE' | 'INVISIBLE';
/** parentCode */
parentCode?: string;
}
临时做法:
要求后端GET参数不要用复杂类型