smart-doc相比于swagger无代码侵入,只需要标准的java注释即可生成接口文档。
knife4j是接口文档的ui,更美观实用。
原理:通过smart-doc生成标准的openapi接口文档,使用knife4j-ui展示。
单体服务集成smart-doc
工程的pom.xml中添加smart-doc-maven-plugin插件和knife4j相关依赖
<dependencies>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.14</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.shalousun</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>2.5.3</version>
<configuration>
<!--指定生成文档的使用的配置文件,配置文件放在自己的项目中-->
<configFile>./src/main/resources/smart-doc.json</configFile>
<!--指定项目名称-->
<projectName>${artifactId}</projectName>
<!--smart-doc实现自动分析依赖树加载第三方依赖的源码,如果一些框架依赖库加载不到导致报错,这时请使用excludes排除掉-->
<excludes>
<!--格式为:groupId:artifactId;参考如下-->
<!--也可以支持正则式如:com.alibaba:.* -->
</excludes>
<!--includes配置用于配置加载外部依赖源码,配置后插件会按照配置项加载外部源代码而不是自动加载所有,因此使用时需要注意-->
<!--smart-doc能自动分析依赖树加载所有依赖源码,原则上会影响文档构建效率,因此你可以使用includes来让插件加载你配置的组件-->
<includes>
<!--格式为:groupId:artifactId;参考如下-->
<!--也可以支持正则式如:com.alibaba:.* -->
<include>com.alibaba:fastjson</include>
<!-- 如果配置了includes的情况下, 使用了jpa的分页需要include所使用的源码包 -->
<include>org.springframework.data:spring-data-commons</include>
</includes>
</configuration>
<executions>
<execution>
<!--如果不需要在执行编译时启动smart-doc,则将phase注释掉-->
<phase>generate-resources</phase>
<goals>
<!--smart-doc提供了html、openapi、markdown等goal,可按需配置-->
<goal>openapi</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
application.yaml配置文件中添加配置
springdoc:
swagger-ui:
url: /doc/openapi.json
创建smart-doc.json文件
# 更多配置可以见smart-doc官网
{
"outPath": "src/main/resources/static/doc",
"displayActualType": true
}
工程编译后会自动生成openapi.json接口文档
编写controller,一定要注意注释规范 ,否则接口无法生成
/**
* 测试接口
*
* @author ***
* @date 2023/2/2 16:36
*/
@RestController
public class TestController {
/**
* 测试方法
*
* @param param 请求query
* @return 返回值
*/
@RequestMapping(value = "/test", method = RequestMethod.POST)
public String navigation(@RequestBody JSONObject param) {
return "";
}
}
启动服务
单机的接口文档就实现了,但是如果有100个微服务,怎么把这些所有的微服务集成到一起。常见的方案一般在网关统一汇总,这里采用单独部署一个接口文档服务的方式,通过nacos读取服务列表。
统一接口文档服务
创建一个 springboot服务,添加nacos依赖和接口文档依赖
<!--nacos start-->
<!--注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--开启bootstrap-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--nacos end-->
<!--接口文档-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.14</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
<version>4.0.0</version>
</dependency>
创建ApiDocController,拦截所有doc/openapi.json请求,前缀即为nacos中服务名称,转发到对应的服务中以获取服务对应的openapi.json
@RestController
public class ApiDocController {
private static final String HEADER_STR = "{\"schema\": {\"format\": \"string\", \"type\": \"string\"}, \"in\": \"header\", \"description\": \"\", \"required\": false, \"example\": \"\", \"name\": \"Authorization\"}";
@GetMapping("/*/doc/openapi.json")
public JSONObject router(HttpServletRequest request) {
if (StringUtils.equals(request.getRequestURI(), "/api-doc/doc/openapi.json")) {
return new JSONObject();
}
String servicePath = request.getRequestURI().split("/")[1];
return modifyBody(WebClientUtil.restGet(String.format(ApiDocConfig.URL, servicePath)).toJSONString());
}
private JSONObject modifyBody(String jsonStr) {
try {
JSONObject json = JSONObject.parseObject(jsonStr);
JSONObject paths = json.getJSONObject("paths");
String basePath = json.getJSONObject("info").getString("title");
Set<String> removePath = new HashSet<>(paths.keySet());
for (String path : removePath) {
paths.getJSONObject(path).forEach((k, v) -> {
JSONObject params = (JSONObject) v;
JSONArray parameters = params.getJSONArray("parameters");
parameters.add(getHeader());
});
paths.put("/" + basePath + path, paths.get(path));
paths.remove(path);
}
return json;
} catch (Exception e) {
return null;
}
}
private JSONObject getHeader() {
return JSON.parseObject(HEADER_STR);
}
}
定时任务从nacos中获取服务列表,初始化接口文档数据到SwaggerUiConfigProperties中
@Component
public class ApiDocConfig {
@Resource
private NacosServiceDiscovery nacosServiceDiscovery;
@Resource
private SwaggerUiConfigProperties swaggerUiConfigProperties;
private final RestTemplate restTemplate;
{
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectionRequestTimeout(1000);
factory.setConnectTimeout(1000);
factory.setReadTimeout(1000);
restTemplate = new RestTemplate(factory);
}
public static final String URL = "http://%s:8080/doc/openapi.json";
@PostConstruct
@Scheduled(fixedRate = 1000 * 60 * 10)
public void init() throws NacosException {
List<String> services = nacosServiceDiscovery.getServices();
for (String service : services) {
JSONObject jsonObject = new JSONObject();
try {
//校验文件是否存在
ResponseEntity<JSONObject> response = restTemplate.getForEntity(String.format(URL, service), JSONObject.class);
jsonObject = response.getBody();
} catch (Exception ignored) {
continue;
}
if (Objects.isNull(jsonObject) || jsonObject.containsKey("msg")) {
continue;
}
AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl(service, service + "/doc/openapi.json", service);
Set<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls = swaggerUiConfigProperties.getUrls();
if (urls == null) {
urls = new LinkedHashSet<>();
swaggerUiConfigProperties.setUrls(urls);
}
urls.add(swaggerUrl);
}
}
}
因为openapi.json是静态文件,浏览器会缓存,加一下nocache的配置防止无法加载新的数据
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
WebContentInterceptor interceptor = new WebContentInterceptor();
interceptor.addCacheMapping(CacheControl.noCache().cachePrivate().noTransform(), "/**");
registry.addInterceptor(interceptor);
}
}
@EnableScheduling
@SpringBootApplication(scanBasePackages = "com.poorbusy")
public class ApiDocApplication {
public static void main(String[] args) {
SpringApplication.run(ApiDocApplication.class, args);
System.out.println("启动成功");
}
}
启动服务,访问接口文档地址就可以看到聚合了所有服务的接口文档了,下拉可以切换服务
一个统一的接口文档管理平台就大功告成了。