基本思路
- 可以利用swagger提供的/v2/api-docs接口返回的数据结构,来获取系统接口信息,包括:请求参数、请求Url、响应数据、相应码等;
- 对返回的数据去做解析,来组装符合Freemarker模板的数据;
- 通过Freemarker生成系统接口文档;
- 为了让接口swagger规范,可以提供代码模板,一键生成代码。
基本实现
- 设计Word接口文档模板的基本板式和格式,在resources/templates/word中;
- 设计满足Word接口文档模板的实体类格式,相关Bean在/document/dto中;
- 使用代码模板一键生成系统功能接口,执行ApplicationTests.codeGeneration()方法即可
- 启动系统,解析/v2/api-docs接口返回数据,组装数据使其符合步骤1设计的文档模板
- 将word接口文档模板重命名为2003版的.xml文件;
- 用notepad++打开xml文件,点击 插件 -> XMl tools-> Pretty print(libXML),格式化xml文件;
- 对数据进行变量替换,并将文件重名为.ftl文件
- 保持系统启动状态,ApiDocumentGenerateTest.handler()一键生成系统接口文档;
- 到resources/word下,查看生成的word接口文档
- 更新文档目录,补充接口描述,补充接口实例等
- github源码链接
数据解析工具类SwaggerApiDataHandlerUtil
package com.ocean.angel.tool.doucment.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ocean.angel.tool.doucment.dto.*;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class SwaggerApiDataHandlerUtil {
/**
* 将swagger /v2/api-docs接口返回的数据转化成标准数据
* @param documentData
* @param jsonString
*/
public static void dataHandler(DocumentData documentData, String jsonString) {
try {
// 将字符串转换为JSON对象
JSONObject jsonObject = JSONObject.parseObject(jsonString);
// 文档标题
if(StrUtil.isEmpty(documentData.getTitle())) {
String title = jsonObject.getJSONObject("info").getString("title");
documentData.setTitle(title);
}
// swagger definitions 数据
JSONObject definitionsJSONObject = jsonObject.getJSONObject("definitions");
// swagger paths 数据
JSONObject paths = jsonObject.getJSONObject("paths");
// 接口tags数据,用于定义文档的二级目录
List<ApiTag> tags = tagsHandler(paths, definitionsJSONObject);
documentData.setTags(tags);
} catch (Exception e) {
log.error("SwaggerApiDataHandlerUtil.dataHandler() error. {}", e.getMessage(), e);
}
}
/**
* Tags数据处理
* @param pathsJSONObject
* @return
*/
private static List<ApiTag> tagsHandler(JSONObject pathsJSONObject, JSONObject definitionsJSONObject) {
List<ApiTag> tags = new ArrayList<>();
for (String path : pathsJSONObject.keySet()) {
ApiData apiData = new ApiData();
// 请求url
apiData.setPath(path);
JSONObject apiJSONObject = pathsJSONObject.getJSONObject(path);
for(String type: apiJSONObject.keySet()) {
// 请求方式
apiData.setType(type);
JSONObject apiDataJSONObject = apiJSONObject.getJSONObject(type);
// 接口名称
apiData.setName(apiDataJSONObject.getString("description"));
// 接口tag
apiData.setTag(apiDataJSONObject.getJSONArray("tags").getString(0));
// 请求参数数据处理
JSONArray parametersJSONArray = apiDataJSONObject.getJSONArray("parameters");
requestParamHandler(parametersJSONArray, definitionsJSONObject, apiData);
// 响应数据和响应码数据处理
JSONObject responsesJSONObject = apiDataJSONObject.getJSONObject("responses");
responseHandler(responsesJSONObject, definitionsJSONObject, apiData);
// 数据组装,将api划归到tag下
tagsDataHandler(tags, apiData);
}
}
return tags;
}
/**
* 请求参数处理
* @param parametersJSONArray
* @param definitionsJSONObject
* @return
*/
private static void requestParamHandler(JSONArray parametersJSONArray, JSONObject definitionsJSONObject, ApiData apiData) {
List<ApiParam> list = new ArrayList<>();
for (Object object : parametersJSONArray) {
JSONObject jsonObject = (JSONObject) object;
JSONObject schemaJSONObject = jsonObject.getJSONObject("schema");
if(null == schemaJSONObject) {
// 普通参数
ApiParam apiParam = JSONObject.parseObject(JSON.toJSONString(object), ApiParam.class);
list.add(apiParam);
} else {
// 对象参数
String ref = schemaJSONObject.getString("$ref").replace("#/definitions/", "");
JSONObject propertiesJSONObject = definitionsJSONObject.getJSONObject(ref).getJSONObject("properties");
// 对象参数转换,对象参数不能再嵌套
for (String name : propertiesJSONObject.keySet()) {
ApiParam apiParam = new ApiParam();
apiParam.setName(name);
apiParam.setType(propertiesJSONObject.getJSONObject(name).getString("type"));
apiParam.setDescription(propertiesJSONObject.getJSONObject(name).getString("description"));
apiParam.setRequired(false);
list.add(apiParam);
}
}
}
// 更新接口参数
apiData.setParameters(list);
}
private static void responseHandler(JSONObject responsesJSONObject, JSONObject definitionsJSONObject, ApiData apiData) {
// 响应码列表
List<ApiResponseCode> codes = new ArrayList<>();
for (String code : responsesJSONObject.keySet()) {
// 响应码
ApiResponseCode apiResponseCode = new ApiResponseCode();
apiResponseCode.setCode(code);
JSONObject codeJSONObject = responsesJSONObject.getJSONObject(code);
apiResponseCode.setMsg(codeJSONObject.getString("description"));
codes.add(apiResponseCode);
// 响应数据
if("200".equals(code)) {
JSONObject schemaJSONObject = codeJSONObject.getJSONObject("schema");
if(null != schemaJSONObject) {
// 响应数据逻辑
List<ApiParam> responses = new ArrayList<>();
String ref = schemaJSONObject.getString("$ref").replace("#/definitions/", "");
// 引用对象参数处理
List<ApiParam> params = refParamHandler(ref, definitionsJSONObject);
if(CollUtil.isNotEmpty(params)) {
boolean flag = true;
while (flag) {
responses.addAll(params);
flag = false;
List<ApiParam> itemParams = new ArrayList<>();
for (ApiParam param : params) {
if(StrUtil.isNotEmpty(param.getRef())) {
itemParams.addAll(refParamHandler(param.getRef(), definitionsJSONObject));
}
}
if(itemParams.size() > 0) {
params = itemParams;
flag = true;
}
}
}
// 更新接口响应数据
apiData.setResponses(responses);
}
}
}
// 更新接口响应码
apiData.setCodes(codes);
}
/**
* 将引用对象,转化为apiParam列表
* @param ref
* @param definitionsJSONObject
* @return
*/
private static List<ApiParam> refParamHandler(String ref, JSONObject definitionsJSONObject) {
JSONObject propertiesJSONObject = definitionsJSONObject.getJSONObject(ref).getJSONObject("properties");
List<ApiParam> params = new ArrayList<>();
for (String name : propertiesJSONObject.keySet()) {
ApiParam apiParam = new ApiParam();
apiParam.setName(name);
apiParam.setType(propertiesJSONObject.getJSONObject(name).getString("type"));
String description = propertiesJSONObject.getJSONObject(name).getString("description");
apiParam.setDescription(description);
JSONObject dataJSONObject = propertiesJSONObject.getJSONObject(name);
if(null != dataJSONObject && "data".equals(name)) {
String itemRef = dataJSONObject.getString("$ref");
apiParam.setRef(itemRef);
continue;
}
JSONObject rowsJSONObject = propertiesJSONObject.getJSONObject(name);
if(null != rowsJSONObject && "rows".equals(name)) {
JSONObject itemsJSONObject = rowsJSONObject.getJSONObject("items");
String itemRef = itemsJSONObject.getString("$ref").replace("#/definitions/", "");
apiParam.setRef(itemRef);
}
params.add(apiParam);
}
return params;
}
/**
* 接口API分类处理,划归到制定tag下
* @param tags
* @param apiData
*/
private static void tagsDataHandler(List<ApiTag> tags, ApiData apiData) {
if (CollUtil.isNotEmpty(tags)) {
for (ApiTag tag : tags) {
if(apiData.getTag().equals(tag.getName())) {
tag.getApis().add(apiData);
return;
}
}
}
List<ApiData> apis = new ArrayList<>();
apis.add(apiData);
ApiTag apiTag = new ApiTag();
apiTag.setApis(apis);
apiTag.setName(apiData.getTag());
tags.add(apiTag);
}
}
注意:如果小伙们的接口返回数据结构跟本系统不一致,在解析接口响应数据时,要根据自己接口返回的数据结构去解析。
使用指南
- 去github上下载本系统的源码
- 初始化sql脚本
- 修改application.yml的数据库连接配置,并启动服务
- 保持系统启动状态,执行ApiDocumentGenerateTest.handler()方法
使用的freemarker 变量替换的语句:
- 接口文档基本信息
${data.title}
${data.date}
${data.version}
${data.notes}
- TAG基本信息
<#list data.tags as tag>
${tag.name}
</#list>
- 接口基本信息
<#list tag.apis as api>
${api.name}
${api.path}
${api.type}
</#list>
- 接口参数信息
<#list api.parameters as param>
${param.name}
${param.type}
${param.requiredStr}
${param.description}
</#list>
- 响应参数信息
<#list api.responses as param>
${param.name}
${param.type}
${param.requiredStr}
${param.description}
</#list>
- 响应码信息
<#list api.code as item>
${item.code}
${item.msg}
</#list>