背景

1、生态目前没有成型的接口文档,接口定义都散落在每个需求的技术方案里面。前端搜集整理过到mocks里,但后端不愿意编写维护文档,效果较差
2、社区对于使用注解生成接口文档和描述数据生成TypeScript代码有着成熟的实践

具体做法

1、前端自己拉分支,部署在日常、预发环境,不发布到线上

2、Swagger生成文档和描述数据

依赖引入:

  • Swagger建议使用高版本,2.9.2。低版本如2.2.2等会解析不了引入自二方包的参数
  • 高本版依赖高版本 guava ,低版本会使应用启动不了

    1. <dependency>
    2. <groupId>io.springfox</groupId>
    3. <artifactId>springfox-swagger2</artifactId>
    4. <version>2.9.2</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>io.springfox</groupId>
    8. <artifactId>springfox-swagger-ui</artifactId>
    9. <version>2.9.2</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>com.google.guava</groupId>
    13. <artifactId>guava</artifactId>
    14. <version>20.0</version>
    15. <exclusions>
    16. <exclusion>
    17. <groupId>com.google.guava</groupId>
    18. <artifactId>guava</artifactId>
    19. </exclusion>
    20. </exclusions>
    21. </dependency>

3、swagger配置:

  1. /**
  2. * swagger2配置类
  3. */
  4. @Configuration
  5. @EnableSwagger2
  6. public class SwaggerConfig {
  7. @Bean
  8. public Docket createRestApi() {
  9. return new Docket(DocumentationType.SWAGGER_2)
  10. .apiInfo(apiInfo())
  11. .select()
  12. .apis(RequestHandlerSelectors.basePackage("com.aliyun"))
  13. .paths(PathSelectors.any())
  14. .build();
  15. }
  16. private ApiInfo apiInfo() {
  17. return new ApiInfoBuilder()
  18. .title("基于Swagger构建的Rest API文档")
  19. .description("更多请咨询开发")
  20. .termsOfServiceUrl("http://prm.aliyun-inc.test/prm/#/epp/manage")
  21. .version("1.0")
  22. .build();
  23. }
  24. }

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存在前端不存在的类型需要处理

个人模板

  1. export default class MyGenerator extends CodeGenerator {
  2. getInterfaceContentInDeclaration(inter: Interface) {
  3. let requestParams = inter.getRequestParams();
  4. const paramsCode = inter.getParamsCode('Params');
  5. requestParams = this.formatParamsType(requestParams)
  6. let result = `
  7. export ${paramsCode}
  8. export type Response = ${inter.responseType}
  9. export const init: Response;
  10. export function request(${requestParams}): Promise<Response>;
  11. `;
  12. return this.resultFormat(result)
  13. }
  14. getBaseClassInDeclaration(base: BaseClass) {
  15. const originProps = base.properties;
  16. base.properties = base.properties.map(prop => {
  17. return new Property({
  18. ...prop,
  19. required: false
  20. });
  21. });
  22. const result = super.getBaseClassInDeclaration(base);
  23. base.properties = originProps;
  24. return result;
  25. }
  26. getInterfaceContent(inter: Interface) {
  27. const method = inter.method.toUpperCase();
  28. let requestParams = inter.getRequestParams(Surrounding.typeScript);
  29. const bodyParams = inter.getBodyParamsCode();
  30. const paramsCode = inter.getParamsCode('Params', this.surrounding);
  31. requestParams = this.formatParamsType(requestParams)
  32. let result = `
  33. /**
  34. * @desc ${inter.description}
  35. */
  36. import axios from 'axios';
  37. export ${paramsCode}
  38. export type Response = ${inter.responseType}
  39. export const init = ${inter.response.getInitialValue()};
  40. export
  41. export async function request(${requestParams}): Promise<Response> {
  42. let res = await axios.request({
  43. url: "${inter.path}",
  44. ${paramsCode ? 'params,' : ''}
  45. ${bodyParams ? 'data: body,' : ''}
  46. method: "${method}",
  47. baseUrl:'',
  48. ...options
  49. });
  50. return res?.data
  51. }
  52. `;
  53. return this.resultFormat(result)
  54. }
  55. formatParamsType(params) {
  56. params = params.replace('int', 'Number')
  57. params = params.replace('long', 'Number')
  58. params = params.replace('ModelAndView', 'any')
  59. params = params.replace('Model', 'any')
  60. params = params.replace('defs.ObjectMap', 'ObjectMap')
  61. params = params.replace('ref', 'any')
  62. return params
  63. }
  64. resultFormat(result) {
  65. result = result.replace('defs.ObjectMap', 'ObjectMap')
  66. result = result.replace('ref', 'any')
  67. return result
  68. }
  69. }

image.png

遗留问题

1、GET 请求的参数为复杂类型时:

原始Java:

  1. Class QueryProgramInfoParam implements Serializable {
  2. private String parentCode;
  3. private ProgramConfigs configs;
  4. }

生成的描述文件:

  1. "parameters": [
  2. {
  3. "name": "configs.name",
  4. "in": "query",
  5. "required": false,
  6. "type": "string"
  7. },
  8. {
  9. "name": "configs.programConfigs[0].order",
  10. "in": "query",
  11. "required": false,
  12. "type": "integer",
  13. "format": "int32"
  14. },
  15. {
  16. "name": "configs.programConfigs[0].programCode",
  17. "in": "query",
  18. "required": false,
  19. "type": "string"
  20. },
  21. {
  22. "name": "configs.programConfigs[0].status",
  23. "in": "query",
  24. "required": false,
  25. "type": "string",
  26. "enum": [
  27. "ENABLE",
  28. "INVISIBLE"
  29. ]
  30. },
  31. {
  32. "name": "parentCode",
  33. "in": "query",
  34. "required": false,
  35. "type": "string"
  36. }
  37. ]

生成的TS:

  1. export class Params {
  2. /** name */
  3. name?: string;
  4. /** order */
  5. order?: number;
  6. /** programCode */
  7. programCode?: string;
  8. /** status */
  9. status?: 'ENABLE' | 'INVISIBLE';
  10. /** parentCode */
  11. parentCode?: string;
  12. }

临时做法:
要求后端GET参数不要用复杂类型

2、Webx框架