mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
5.0
This commit is contained in:
@@ -73,6 +73,11 @@ public class ApiInfo {
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
/**
|
||||
* 是否有公共响应参数
|
||||
*/
|
||||
private Integer hasCommonResponse;
|
||||
|
||||
/**
|
||||
* 注册来源,1-系统注册,2-手动注册
|
||||
*/
|
||||
|
@@ -128,6 +128,23 @@ export const tableColumns: PlusColumn[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "公共参数",
|
||||
prop: "hasCommonResponse",
|
||||
width: 100,
|
||||
valueType: "select",
|
||||
options: [
|
||||
{
|
||||
label: "无",
|
||||
value: 0,
|
||||
color: "red"
|
||||
},
|
||||
{
|
||||
label: "有",
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "注册来源",
|
||||
prop: "regSource",
|
||||
|
@@ -69,12 +69,6 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.14</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package com.gitee.sop.payment.open.impl;
|
||||
package com.gitee.sop.payment.impl;
|
||||
|
||||
import com.gitee.sop.payment.open.OpenPayment;
|
||||
import com.gitee.sop.payment.open.req.AlipayTradeWapPayRequest;
|
@@ -3,6 +3,7 @@ package com.gitee.sop.payment.open;
|
||||
import com.gitee.sop.payment.open.req.AlipayTradeWapPayRequest;
|
||||
import com.gitee.sop.payment.open.resp.AlipayTradeWapPayResponse;
|
||||
import com.gitee.sop.support.annotation.Open;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
/**
|
||||
@@ -10,6 +11,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Api("支付接口")
|
||||
public interface OpenPayment {
|
||||
|
||||
@ApiOperation(
|
||||
|
@@ -0,0 +1,27 @@
|
||||
{
|
||||
// 开启推送
|
||||
"enable": true,
|
||||
// 扫描package,多个用;隔开
|
||||
"basePackage": "com.gitee.sop.payment.open",
|
||||
// 推送URL,IP端口对应Torna服务器
|
||||
"url": "http://localhost:7700/api",
|
||||
// 模块token
|
||||
"token": "34ff76952462413982d21219cf099d46",
|
||||
// 推送人
|
||||
"author": "Jim",
|
||||
// 打开调试:true/false
|
||||
"debug": true,
|
||||
// 是否替换文档,true:替换,false:不替换(追加)。默认:true
|
||||
"isReplace": true,
|
||||
// 第三方jar中的class配置
|
||||
"jarClass": {
|
||||
"com.baomidou.mybatisplus.extension.plugins.pagination.Page": {
|
||||
"records": { "value": "查询数据列表", "example": "" },
|
||||
"total": { "value": "总数", "example": "100" },
|
||||
"size": { "value": "页数", "example": "10" },
|
||||
"current": { "value": "当前页", "example": "1" },
|
||||
"countId": { "hidden": true },
|
||||
"orders": { "hidden": true }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1 +0,0 @@
|
||||
abc,你好~!@#
|
@@ -0,0 +1,13 @@
|
||||
package com.gitee.sop.payment;
|
||||
|
||||
import cn.torna.swaggerplugin.SwaggerPlugin;
|
||||
|
||||
/**
|
||||
* 推送swagger文档
|
||||
* @author thc
|
||||
*/
|
||||
public class DocPushTest {
|
||||
public static void main(String[] args) {
|
||||
SwaggerPlugin.pushDoc();
|
||||
}
|
||||
}
|
@@ -63,6 +63,11 @@ public class ApiInfoDTO implements Serializable {
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
/**
|
||||
* 是否有公共响应参数
|
||||
*/
|
||||
private Integer hasCommonResponse;
|
||||
|
||||
private Integer status;
|
||||
|
||||
public String buildApiNameVersion() {
|
||||
|
@@ -71,6 +71,11 @@ public class ApiInfo {
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
/**
|
||||
* 是否有公共响应参数
|
||||
*/
|
||||
private Integer hasCommonResponse;
|
||||
|
||||
/**
|
||||
* 注册来源,1-系统注册,2-手动注册
|
||||
*/
|
||||
|
@@ -0,0 +1,22 @@
|
||||
package com.gitee.sop.gateway.response;
|
||||
|
||||
/**
|
||||
* 没有公共返回结果
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public class NoCommonResponse extends BaseResponse {
|
||||
|
||||
public static NoCommonResponse success(Object data) {
|
||||
NoCommonResponse apiResponse = new NoCommonResponse();
|
||||
apiResponse.setCode(SUCCESS_CODE);
|
||||
apiResponse.setMsg(SUCCESS_MSG);
|
||||
apiResponse.setData(data);
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needWrap() {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,5 +1,8 @@
|
||||
package com.gitee.sop.gateway.response;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@@ -9,4 +12,9 @@ public interface Response {
|
||||
|
||||
Object getData();
|
||||
|
||||
@JSONField(serialize = false)
|
||||
@JsonIgnore
|
||||
default boolean needWrap() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -110,8 +110,12 @@ public class ParamExecutorImpl implements ParamExecutor<HttpServletRequest, Http
|
||||
FileData fileData = (FileData) data;
|
||||
ResponseUtil.writerFile(fileData, response);
|
||||
} else {
|
||||
// 此处还可以判断charset,返回xml格式
|
||||
ResponseUtil.writerText(apiRequestContext, apiResponse, response);
|
||||
Object responseData = apiResponse;
|
||||
// 不需要公共参数
|
||||
if (!apiResponse.needWrap()) {
|
||||
responseData = data;
|
||||
}
|
||||
ResponseUtil.writerText(apiRequestContext, responseData, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.common.enums.YesOrNoEnum;
|
||||
import com.gitee.sop.gateway.config.ApiConfig;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.response.ApiResponseLower;
|
||||
import com.gitee.sop.gateway.response.NoCommonResponse;
|
||||
import com.gitee.sop.gateway.response.Response;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -22,6 +24,11 @@ public class ResultWrapperImpl implements ResultWrapper {
|
||||
|
||||
@Override
|
||||
public Response wrap(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) {
|
||||
Integer hasCommonResponse = apiInfoDTO.getHasCommonResponse();
|
||||
// 不需要公共返回参数
|
||||
if (YesOrNoEnum.of(hasCommonResponse) == YesOrNoEnum.NO) {
|
||||
return NoCommonResponse.success(result);
|
||||
}
|
||||
if (Objects.equals(apiConfig.getFieldLowercase(), true)) {
|
||||
return ApiResponseLower.success(result);
|
||||
} else {
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package com.gitee.sop.gateway.util;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import com.gitee.sop.gateway.request.ApiRequest;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.request.RequestFormatEnum;
|
||||
@@ -17,7 +15,6 @@ import java.io.InputStream;
|
||||
* @author 六如
|
||||
*/
|
||||
public class ResponseUtil {
|
||||
private static final XmlMapper XML_MAPPER = new XmlMapper();
|
||||
|
||||
public static void writerFile(FileData fileData, HttpServletResponse response) throws IOException {
|
||||
InputStream inputStream = fileData.getInputStream();
|
||||
@@ -37,11 +34,11 @@ public class ResponseUtil {
|
||||
String format = apiRequest.getFormat();
|
||||
if (RequestFormatEnum.of(format) == RequestFormatEnum.XML) {
|
||||
response.setContentType(MediaType.APPLICATION_XML_VALUE);
|
||||
String xml = XML_MAPPER.writeValueAsString(apiResponse);
|
||||
String xml = XmlUtil.toXml(apiResponse);
|
||||
response.getWriter().write(xml);
|
||||
} else {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
String json = JSON.toJSONString(apiResponse);
|
||||
String json = JsonUtil.toJSONString(apiResponse);
|
||||
response.getWriter().write(json);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,18 @@
|
||||
package com.gitee.sop.gateway.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class XmlUtil {
|
||||
|
||||
private static final XmlMapper XML_MAPPER = new XmlMapper();
|
||||
|
||||
|
||||
public static String toXml(Object object) throws JsonProcessingException {
|
||||
return XML_MAPPER.writeValueAsString(object);
|
||||
}
|
||||
|
||||
}
|
@@ -11,5 +11,6 @@
|
||||
<modules>
|
||||
<module>sop-service-support</module>
|
||||
<module>sop-spring-boot-starter</module>
|
||||
<module>sop-doc-plugin</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
35
sop-support/sop-doc-plugin/changelog.md
Normal file
35
sop-support/sop-doc-plugin/changelog.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 更新日志
|
||||
|
||||
- 1.2.27: 新增`excludePackage`配置,用来排除对应的package,多个用;隔开
|
||||
- 1.2.26: 支持Controller泛型接口
|
||||
- 1.2.25:支持Java17
|
||||
- 1.2.24:get参数使用`@ApiParam`name属性不生效问题
|
||||
- 1.2.23:修复嵌套数组显示问题(`List<List<xx>>`)
|
||||
- 1.2.22:修复嵌套数组不显示问题(`List<List<xx>`)
|
||||
- 1.2.21:修复参数错乱问题 https://gitee.com/durcframework/torna/issues/I79TZ2
|
||||
- 1.2.20:修复参数字段排序问题
|
||||
- 1.2.19:捕获ClassNotFoundException允许正常push文档; https://gitee.com/durcframework/torna/pulls/44
|
||||
- 1.2.18:修复添加了@DecimalMax注解报NPE问题
|
||||
- 1.2.17:优化泛型参数解析
|
||||
- 1.2.16:添加插件特性,支持优先使用@Api.tags属性
|
||||
- 1.2.15:支持maxLength,需要配合JSR-303注解,`@Length(max = 55)`, `@Max(55)`
|
||||
- 1.2.14:修复泛型参数解析错误问题。`List<? extends TreeVO> children`
|
||||
- 1.2.13:优化dubbo推送
|
||||
- 1.2.12:优化枚举展示
|
||||
- 1.2.11:修复推送显示request类;修复第三方JSONOject显示对象内容问题 https://gitee.com/durcframework/torna/issues/I4JNSX
|
||||
- 1.2.10:可推送错误码
|
||||
- 1.2.9:可推送指定接口;修复required显示问题
|
||||
- 1.2.8:升级SDK
|
||||
- 1.2.7:优化第三方jar文档展示
|
||||
- 1.2.6:修复返回结果填通配符问题(`Result<?>`)
|
||||
- 1.2.5:修复basePackage无效问题
|
||||
- 1.2.4:修复泛型嵌套问题;优化Path参数显示
|
||||
- 1.2.3:修复ResponseEntity无泛型参数报NPE问题,支持枚举参数
|
||||
- 1.2.2:修复@RequestMapping注解没value属性值会报错 https://gitee.com/durcframework/torna/issues/I49DC0
|
||||
- 1.2.1:优化org.springframework.http.ResponseEntity的swagger解析逻辑及swagger中name字段优化 https://gitee.com/durcframework/torna/pulls/15
|
||||
- 1.2.0:优化推送,支持dubbo
|
||||
- 1.1.2:解决循环依赖问题
|
||||
- 1.1.1:支持多环境配置
|
||||
- 1.1.0:优化泛型参数显示,支持定义第三方类文档信息
|
||||
- 1.0.7:修复`Result<List<T>>`不显示List问题
|
||||
- 1.0.6:修复泛型参数不显示问题
|
181
sop-support/sop-doc-plugin/pom.xml
Normal file
181
sop-support/sop-doc-plugin/pom.xml
Normal file
@@ -0,0 +1,181 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-doc-plugin</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>swagger-plugin</name>
|
||||
<description>Swagger plugin for Torna</description>
|
||||
<url>https://gitee.com/durcframework/torna</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
<url>https://mit-license.org/</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<name>tanghc</name>
|
||||
<email>thc8719@163.com</email>
|
||||
<organization>gitee</organization>
|
||||
<organizationUrl>https://gitee.com/durcframework/torna</organizationUrl>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection>scm:git:https://gitee.com/durcframework/torna.git</connection>
|
||||
<developerConnection>scm:git:https://gitee.com/durcframework/torna.git</developerConnection>
|
||||
<url>https://gitee.com/durcframework/torna.git</url>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<!-- Generic properties -->
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-service-support</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.torna</groupId>
|
||||
<artifactId>torna-sdk</artifactId>
|
||||
<version>1.0.16</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>6.2.4.Final</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>5.2.9.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>5.2.9.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>5.2.9.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.5.19</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-core</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 打包时跳过测试 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.18.1</version>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>maven_central</id>
|
||||
<name>Maven Central</name>
|
||||
<url>https://repo.maven.apache.org/maven2/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
5
sop-support/sop-doc-plugin/readme.md
Normal file
5
sop-support/sop-doc-plugin/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# swagger插件
|
||||
|
||||
用于生成api文档
|
||||
|
||||
使用方式:http://torna.cn/tutorial/swagger.html
|
@@ -0,0 +1,48 @@
|
||||
package cn.torna.swaggerplugin;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 文档推送辅助类
|
||||
* @author thc
|
||||
*/
|
||||
public class SwaggerPlugin {
|
||||
|
||||
/**
|
||||
* 推送文档,前提:把<code>doc-plugin.json</code>文件复制到resources下
|
||||
*/
|
||||
public static void pushDoc() {
|
||||
pushDoc("doc-plugin.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送swagger文档
|
||||
* @param configFile 配置文件
|
||||
*/
|
||||
public static void pushDoc(String configFile) {
|
||||
ClassPathResource classPathResource = new ClassPathResource(configFile);
|
||||
if (!classPathResource.exists()) {
|
||||
throw new IllegalArgumentException("找不到文件:" + configFile + ",请确保resources下有torna.json");
|
||||
}
|
||||
System.out.println("加载Torna配置文件:" + configFile);
|
||||
try {
|
||||
InputStream inputStream = classPathResource.getInputStream();
|
||||
String json = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
|
||||
JSONObject jsonObject = JSON.parseObject(json);
|
||||
TornaConfig tornaConfig = jsonObject.toJavaObject(TornaConfig.class);
|
||||
new SwaggerPluginService(tornaConfig).pushDoc();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("推送文档出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import cn.torna.swaggerplugin.util.PluginUtil;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ApiModelPropertyWrapper {
|
||||
|
||||
private static final List<String> NOT_NULL_ANNOTATIONS = Arrays.asList(
|
||||
"NotNull",
|
||||
"NotBlank",
|
||||
"NotEmpty"
|
||||
);
|
||||
|
||||
private final Optional<ApiModelProperty> apiModelPropertyOptional;
|
||||
private final Optional<Field> fieldOptional;
|
||||
|
||||
public ApiModelPropertyWrapper(ApiModelProperty apiModelPropertyOptional, Field field) {
|
||||
this.apiModelPropertyOptional = Optional.ofNullable(apiModelPropertyOptional);
|
||||
this.fieldOptional = Optional.ofNullable(field);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
String name = apiModelPropertyOptional.map(ApiModelProperty::name).orElse(null);
|
||||
if (StringUtils.isEmpty(name)) {
|
||||
name = fieldOptional.map(Field::getName).orElse("");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return apiModelPropertyOptional.map(ApiModelProperty::value).orElse("");
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
String value = getValue();
|
||||
String note = apiModelPropertyOptional.map(ApiModelProperty::notes).orElse("");
|
||||
if (StringUtils.hasText(note)) {
|
||||
value = value + "。Note:" + note;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean getRequired() {
|
||||
if (fieldOptional.isPresent()) {
|
||||
Field field = fieldOptional.get();
|
||||
boolean hasAnyAnnotation = PluginUtil.hasAnyAnnotation(field, NOT_NULL_ANNOTATIONS);
|
||||
// 如果有不为null相关的注解,直接返回true,必填
|
||||
if (hasAnyAnnotation) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return apiModelPropertyOptional.map(ApiModelProperty::required).orElse(false);
|
||||
}
|
||||
|
||||
public String getExample() {
|
||||
return apiModelPropertyOptional.map(ApiModelProperty::example).orElse("");
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return apiModelPropertyOptional.map(ApiModelProperty::position).orElse(0);
|
||||
}
|
||||
|
||||
public String getDataType() {
|
||||
String dataType = apiModelPropertyOptional.map(ApiModelProperty::dataType).orElse(null);
|
||||
if (StringUtils.isEmpty(dataType)) {
|
||||
dataType = fieldOptional.map(Field::getType).map(Class::getSimpleName).orElse("");
|
||||
}
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public String getMaxLength() {
|
||||
if (!fieldOptional.isPresent()) {
|
||||
return "-";
|
||||
}
|
||||
Field field = fieldOptional.get();
|
||||
if (PluginUtil.hasAnyAnnotation(field, Collections.singletonList("Length"))) {
|
||||
org.hibernate.validator.constraints.Length length = field.getAnnotation(org.hibernate.validator.constraints.Length.class);
|
||||
return String.valueOf(length.max());
|
||||
}
|
||||
if (PluginUtil.hasAnyAnnotation(field, Collections.singletonList("Max"))) {
|
||||
javax.validation.constraints.DecimalMax decimalMax = field.getAnnotation(javax.validation.constraints.DecimalMax.class);
|
||||
if (decimalMax != null) {
|
||||
return decimalMax.value();
|
||||
}
|
||||
javax.validation.constraints.Max max = field.getAnnotation(javax.validation.constraints.Max.class);
|
||||
if (max != null) {
|
||||
return String.valueOf(max.value());
|
||||
}
|
||||
}
|
||||
return "-";
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ApiParamInfo {
|
||||
|
||||
String name;
|
||||
|
||||
String value;
|
||||
|
||||
String defaultValue;
|
||||
|
||||
String allowableValues;
|
||||
|
||||
boolean required;
|
||||
|
||||
String access;
|
||||
|
||||
boolean allowMultiple;
|
||||
|
||||
boolean hidden;
|
||||
|
||||
String example;
|
||||
|
||||
int position;
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import io.swagger.annotations.ApiParam;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ApiParamWrapper {
|
||||
private final Optional<ApiParam> apiParamOptional;
|
||||
|
||||
public ApiParamWrapper(ApiParam apiParam) {
|
||||
this.apiParamOptional = Optional.ofNullable(apiParam);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return apiParamOptional.map(ApiParam::name).orElse("");
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return apiParamOptional.map(ApiParam::type).orElse("");
|
||||
}
|
||||
|
||||
public String getExample() {
|
||||
return apiParamOptional.map(ApiParam::example).orElse("");
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return apiParamOptional.map(ApiParam::value).orElse("");
|
||||
}
|
||||
|
||||
public boolean getRequired() {
|
||||
return apiParamOptional.map(ApiParam::required).orElse(false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
public class Booleans {
|
||||
public static final byte TRUE = 1;
|
||||
public static final byte FALSE = 0;
|
||||
|
||||
public static boolean isTrue(Byte b) {
|
||||
return b != null && b == TRUE;
|
||||
}
|
||||
|
||||
public static byte toValue(Boolean b) {
|
||||
if (b == null) {
|
||||
return FALSE;
|
||||
}
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CodeInfo {
|
||||
|
||||
/** 枚举名称 */
|
||||
private String name;
|
||||
|
||||
/** 枚举说明 */
|
||||
private String description;
|
||||
|
||||
/** 元素类型 */
|
||||
private String itemType;
|
||||
|
||||
/** 枚举项 */
|
||||
private List<CodeItem> items;
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CodeItem {
|
||||
|
||||
/** 名称,字面值 */
|
||||
private String name;
|
||||
|
||||
/** 类型 */
|
||||
private String type;
|
||||
|
||||
/** 枚举值 */
|
||||
private String value;
|
||||
|
||||
/** 枚举描述 */
|
||||
private String description = "";
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class ControllerInfo {
|
||||
private String name;
|
||||
private String description;
|
||||
private int position;
|
||||
private Class<?> controllerClass;
|
||||
|
||||
private Map<String, Class<?>> genericParamMap = new HashMap<>();
|
||||
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class DocParamInfo {
|
||||
|
||||
/** 示例值 */
|
||||
private String example;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 数据类型 */
|
||||
private String type;
|
||||
|
||||
/** 必填 */
|
||||
private Boolean required;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author thc
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum ModeEnum {
|
||||
MVC(0),
|
||||
DUBBO(1)
|
||||
;
|
||||
private final int value;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public interface PluginConstants {
|
||||
byte FALSE = 0;
|
||||
byte TRUE = 1;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
/**
|
||||
* @author thc
|
||||
*/
|
||||
public enum PushFeature {
|
||||
/**
|
||||
* 优先使用@Api.tags属性
|
||||
*/
|
||||
USE_API_TAGS
|
||||
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
package cn.torna.swaggerplugin.bean;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Data
|
||||
public class TornaConfig {
|
||||
|
||||
private String configFile;
|
||||
|
||||
/**
|
||||
* 开启推送
|
||||
*/
|
||||
private Boolean enable = false;
|
||||
/**
|
||||
* 扫描package,多个用";"隔开。不指定扫描全部
|
||||
*/
|
||||
private String basePackage;
|
||||
|
||||
/**
|
||||
* 排除package,多个用";"隔开
|
||||
*/
|
||||
private String excludePackage;
|
||||
|
||||
/**
|
||||
* 指定扫描的API,可以是某个controller,也可以是某个接口方法
|
||||
*/
|
||||
private List<String> scanApis;
|
||||
/**
|
||||
* 推送URL
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 模块token
|
||||
*/
|
||||
private String token;
|
||||
/**
|
||||
* 调试环境,格式:环境名称,调试路径,多个用"|"隔开
|
||||
*/
|
||||
private String debugEnv;
|
||||
/**
|
||||
* 推送人
|
||||
*/
|
||||
private String author;
|
||||
/**
|
||||
* 打开调试:true/false
|
||||
*/
|
||||
private Boolean debug = true;
|
||||
/**
|
||||
* 调试输出json格式化
|
||||
*/
|
||||
private Boolean debugPrintFormat = false;
|
||||
/**
|
||||
* 接口多个method只显示
|
||||
*/
|
||||
private String methodWhenMulti = "GET";
|
||||
/**
|
||||
* 具有body体的方法
|
||||
*/
|
||||
private String hasBodyMethods = "POST,PUT,DELETE";
|
||||
/**
|
||||
* 是否替换文档,true:替换,false:不替换(追加)。默认:true
|
||||
*/
|
||||
private Boolean isReplace = true;
|
||||
|
||||
/**
|
||||
* 0:springmvc, 1:dubbo
|
||||
*/
|
||||
private int mode = 1;
|
||||
|
||||
/**
|
||||
* 全局的contentType
|
||||
*/
|
||||
private String globalContentType = "application/x-www-form-urlencoded";
|
||||
/**
|
||||
* 默认的http method
|
||||
*/
|
||||
private String defaultHttpMethod = "POST";
|
||||
|
||||
/**
|
||||
* 第三方jar
|
||||
*/
|
||||
private JSONObject jarClass;
|
||||
|
||||
/**
|
||||
* 定义错误码
|
||||
*/
|
||||
private List<CodeInfo> codes;
|
||||
|
||||
/**
|
||||
* 只能是object类型对象
|
||||
* 如:com.alibaba.fastjson.JSONObject
|
||||
*/
|
||||
private List<String> objectClassList;
|
||||
|
||||
/**
|
||||
* 特性
|
||||
* @see PushFeature
|
||||
*/
|
||||
private List<String> features = Collections.emptyList();
|
||||
|
||||
}
|
@@ -0,0 +1,434 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.ApiModelPropertyWrapper;
|
||||
import cn.torna.swaggerplugin.bean.ApiParamInfo;
|
||||
import cn.torna.swaggerplugin.bean.Booleans;
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import cn.torna.swaggerplugin.util.ClassUtil;
|
||||
import cn.torna.swaggerplugin.util.PluginUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import sun.reflect.generics.repository.ClassRepository;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 解析文档信息
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ApiDocBuilder {
|
||||
|
||||
// key: ClassA.getName() + fieldType.getName() + field.getName()
|
||||
private Map<String, AtomicInteger> cycleFieldsMap = new HashMap<>();
|
||||
private Map<String, Class<?>> genericParamMap = Collections.emptyMap();
|
||||
private Set<String> hiddenColumns = Collections.emptySet();
|
||||
private TornaConfig tornaConfig;
|
||||
|
||||
public ApiDocBuilder() {
|
||||
}
|
||||
|
||||
public ApiDocBuilder(Map<String, Class<?>> genericParamMap, TornaConfig tornaConfig) {
|
||||
this.tornaConfig = tornaConfig;
|
||||
if (genericParamMap == null) {
|
||||
genericParamMap = Collections.emptyMap();
|
||||
}
|
||||
this.genericParamMap = genericParamMap;
|
||||
this.initHiddenColumns(tornaConfig);
|
||||
}
|
||||
|
||||
private void initHiddenColumns(TornaConfig tornaConfig) {
|
||||
JSONObject jarClass = tornaConfig.getJarClass();
|
||||
if (jarClass == null) {
|
||||
return;
|
||||
}
|
||||
hiddenColumns = new HashSet<>(8);
|
||||
Set<String> classNames = jarClass.keySet();
|
||||
for (String className : classNames) {
|
||||
/*
|
||||
"com.baomidou.mybatisplus.extension.plugins.pagination.Page": {
|
||||
"countId": { "value": "xxx", "hidden": true, "required": false, "example": "" }
|
||||
}
|
||||
*/
|
||||
JSONObject classConfig = jarClass.getJSONObject(className);
|
||||
Set<String> fields = classConfig.keySet();
|
||||
for (String field : fields) {
|
||||
// { "value": "xxx", "hidden": true, "required": false, "example": "" }
|
||||
JSONObject fieldConfig = classConfig.getJSONObject(field);
|
||||
Boolean hidden = fieldConfig.getBoolean("hidden");
|
||||
if (hidden != null && hidden) {
|
||||
hiddenColumns.add(className + "." + field.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ApiParamInfo getApiParamInfo(Class<?> targetClass, String fieldName) {
|
||||
JSONObject jarClass = tornaConfig.getJarClass();
|
||||
if (jarClass == null) {
|
||||
return null;
|
||||
}
|
||||
List<Class<?>> allClasses = ClassUtil.getAllClasses(targetClass);
|
||||
for (Class<?> clazz : allClasses) {
|
||||
String className = clazz.getName();
|
||||
JSONObject classConfig = jarClass.getJSONObject(className);
|
||||
if (classConfig == null) {
|
||||
continue;
|
||||
}
|
||||
Set<String> fields = classConfig.keySet();
|
||||
for (String field : fields) {
|
||||
if (Objects.equals(field, fieldName)) {
|
||||
JSONObject fieldConfig = classConfig.getJSONObject(field);
|
||||
return fieldConfig == null ? null : fieldConfig.toJavaObject(ApiParamInfo.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文档信息
|
||||
* @param paramClass 参数类型
|
||||
* @return 返回文档内容
|
||||
*/
|
||||
public List<FieldDocInfo> buildFieldDocInfo(Class<?> paramClass) {
|
||||
return buildFieldDocInfosByType(paramClass, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从api参数中构建
|
||||
*/
|
||||
protected List<FieldDocInfo> buildFieldDocInfosByType(Class<?> clazz, boolean root, Class<?> generic) {
|
||||
Class<?> targetClassRef = PluginUtil.isCollectionOrArray(clazz) ? getCollectionElementClass(clazz) : clazz;
|
||||
|
||||
// 查找泛型
|
||||
if(targetClassRef.getName().equals("org.springframework.http.ResponseEntity")){
|
||||
Field body = ReflectionUtils.findField(targetClassRef, "body");
|
||||
String typeName = ((TypeVariable<?>) body.getGenericType()).getName();
|
||||
targetClassRef = getGenericParamClass(targetClassRef, typeName);
|
||||
// 没有指定泛型类型
|
||||
if (targetClassRef == null) {
|
||||
System.out.println("Warning: Use ResponseEntity but not specified generic parameter.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
final Class<?> targetClass = targetClassRef;
|
||||
// 如果是基本类型
|
||||
if (!PluginUtil.isPojo(targetClass) || isObjectClass(targetClass)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final List<FieldDocInfo> fieldDocInfos = new ArrayList<>();
|
||||
// 遍历参数对象中的属性
|
||||
ReflectionUtils.doWithFields(targetClass, field -> {
|
||||
ApiModelProperty apiModelProperty = AnnotationUtils.findAnnotation(field, ApiModelProperty.class);
|
||||
if (root) {
|
||||
resetCycle();
|
||||
}
|
||||
FieldDocInfo fieldDocInfo;
|
||||
Class<?> fieldType = field.getType();
|
||||
Type genericType = field.getGenericType();
|
||||
Class<?> realClass = null;
|
||||
// 如果是未知泛型,private T data
|
||||
// 获取真实的类型
|
||||
if (genericType instanceof TypeVariable) {
|
||||
String typeName = ((TypeVariable<?>) genericType).getName();
|
||||
realClass = getGenericParamClass(targetClass, typeName);
|
||||
}
|
||||
if (fieldType.isEnum()) {
|
||||
fieldDocInfo = buildFieldDocInfoByEnumClass((Class<Enum>) fieldType, apiModelProperty, field);
|
||||
} else if (realClass != null) {
|
||||
fieldDocInfo = buildFieldDocInfoByClass(apiModelProperty, realClass, field);
|
||||
} else if (PluginUtil.isPojo(fieldType)) {
|
||||
// 如果是自定义类
|
||||
fieldDocInfo = buildFieldDocInfoByClass(apiModelProperty, fieldType, field);
|
||||
} else {
|
||||
fieldDocInfo = buildFieldDocInfo(apiModelProperty, field, generic);
|
||||
}
|
||||
formatDataType(fieldDocInfo, realClass != null ? realClass : fieldType);
|
||||
fieldDocInfos.add(fieldDocInfo);
|
||||
}, field -> {
|
||||
if (PluginUtil.isTransientField(field) || Modifier.isStatic(field.getModifiers()) || isClassFieldHidden(targetClass, field)) {
|
||||
return false;
|
||||
}
|
||||
ApiModelProperty apiModelProperty = AnnotationUtils.findAnnotation(field, ApiModelProperty.class);
|
||||
return apiModelProperty == null || !apiModelProperty.hidden();
|
||||
});
|
||||
this.bindJarClassFields(targetClass, fieldDocInfos);
|
||||
return fieldDocInfos;
|
||||
}
|
||||
|
||||
protected void formatDataType(FieldDocInfo fieldDocInfo, Class<?> fieldType) {
|
||||
if (isObjectClass(fieldType)) {
|
||||
fieldDocInfo.setType(DataType.OBJECT.getValue());
|
||||
fieldDocInfo.setChildren(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isObjectClass(Class<?> targetClass) {
|
||||
List<String> objectClassList = this.tornaConfig.getObjectClassList();
|
||||
if (objectClassList == null) {
|
||||
return false;
|
||||
}
|
||||
String name = targetClass.getName();
|
||||
return objectClassList.contains(name);
|
||||
}
|
||||
|
||||
|
||||
protected void bindJarClassFields(Class<?> targetClass, List<FieldDocInfo> fieldDocInfos) {
|
||||
for (FieldDocInfo child : fieldDocInfos) {
|
||||
ApiParamInfo apiParamInfo = this.getApiParamInfo(targetClass, child.getName());
|
||||
if (apiParamInfo != null) {
|
||||
child.setRequired(Booleans.toValue(apiParamInfo.isRequired()));
|
||||
child.setExample(apiParamInfo.getExample());
|
||||
child.setDescription(apiParamInfo.getValue());
|
||||
child.setOrderIndex(apiParamInfo.getPosition());
|
||||
if(!StringUtils.isEmpty(apiParamInfo.getName())){
|
||||
child.setName(apiParamInfo.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Class<?> getCollectionElementClass(Class<?> clazz) {
|
||||
if (clazz.isArray()) {
|
||||
return clazz.getComponentType();
|
||||
}
|
||||
Class<?> targetClass = getGenericParamClass(clazz, clazz.getName());
|
||||
// Class<?> targetClass = clazz;
|
||||
// Class<? extends Class> rawTypeClass = clazz.getClass();
|
||||
// Method getGenericInfo = ReflectionUtils.findMethod(rawTypeClass, PluginUtil.METHOD_GET_GENERIC_INFO);
|
||||
// if (getGenericInfo != null) {
|
||||
// ReflectionUtils.makeAccessible(getGenericInfo);
|
||||
// ClassRepository classRepository = (ClassRepository) ReflectionUtils.invokeMethod(getGenericInfo, clazz);
|
||||
// if (classRepository != null) {
|
||||
// TypeVariable<?>[] typeParameters = classRepository.getTypeParameters();
|
||||
// for (TypeVariable<?> typeParameter : typeParameters) {
|
||||
// Class<?> realClass = getGenericParamClass(clazz, typeParameter.getName());
|
||||
// if (realClass != null) {
|
||||
// targetClass = realClass;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
return targetClass;
|
||||
}
|
||||
|
||||
protected FieldDocInfo buildFieldDocInfo(ApiModelProperty apiModelProperty, Field field, Type generic) {
|
||||
ApiModelPropertyWrapper apiModelPropertyWrapper = new ApiModelPropertyWrapper(apiModelProperty, field);
|
||||
Class<?> fieldType = field.getType();
|
||||
String type = getFieldType(field, apiModelPropertyWrapper);
|
||||
// 优先使用注解中的字段名
|
||||
String fieldName = getFieldName(field, apiModelPropertyWrapper);
|
||||
String description = getFieldDescription(apiModelProperty);
|
||||
boolean required = apiModelPropertyWrapper.getRequired();
|
||||
String example = apiModelPropertyWrapper.getExample();
|
||||
|
||||
FieldDocInfo fieldDocInfo = new FieldDocInfo();
|
||||
fieldDocInfo.setName(fieldName);
|
||||
fieldDocInfo.setType(type);
|
||||
fieldDocInfo.setRequired(getRequiredValue(required));
|
||||
fieldDocInfo.setExample(example);
|
||||
fieldDocInfo.setDescription(description);
|
||||
fieldDocInfo.setOrderIndex(apiModelPropertyWrapper.getPosition());
|
||||
fieldDocInfo.setMaxLength(apiModelPropertyWrapper.getMaxLength());
|
||||
|
||||
boolean isList = PluginUtil.isCollectionOrArray(fieldType);
|
||||
Type genericType = field.getGenericType();
|
||||
Type elementClass = null;
|
||||
if (generic != null && isList) {
|
||||
elementClass = generic;
|
||||
}
|
||||
// 如果有明确的泛型,如List<Order>
|
||||
if (elementClass == null && PluginUtil.isGenericType(genericType)) {
|
||||
elementClass = PluginUtil.getGenericType(genericType);
|
||||
if (elementClass instanceof TypeVariable) {
|
||||
TypeVariable<?> typeVariable = (TypeVariable<?>) elementClass;
|
||||
Class<?> genericDeclaration = (Class<?>)typeVariable.getGenericDeclaration();
|
||||
Class<?> realClass = this.getGenericParamClass(genericDeclaration, typeVariable.getName());
|
||||
if (realClass != null) {
|
||||
elementClass = realClass;
|
||||
} else {
|
||||
elementClass = Object.class;
|
||||
}
|
||||
}
|
||||
} else if (fieldType.isArray()) {
|
||||
elementClass = fieldType.getComponentType();
|
||||
}
|
||||
|
||||
if (elementClass != null && elementClass != Object.class && elementClass != Void.class) {
|
||||
// 如果是嵌套泛型类型,如:List<List<String>>
|
||||
if (PluginUtil.isGenericType(elementClass)) {
|
||||
Type genericRawType = PluginUtil.getGenericRawType(elementClass);
|
||||
if (PluginUtil.isCollectionOrArray((Class<?>) genericRawType)) {
|
||||
Type genericElType = PluginUtil.getGenericType(elementClass);
|
||||
if (isList) {
|
||||
Class<?> elType = (Class<?>) genericElType;
|
||||
boolean primitive = ClassUtil.isPrimitive(elType.getName());
|
||||
fieldDocInfo.setType("List<List<"+ (primitive ? elType.getSimpleName() : "Object") +">>");
|
||||
List<FieldDocInfo> fieldDocInfos = buildFieldDocInfosByType(elType, true, null);
|
||||
fieldDocInfo.setChildren(fieldDocInfos);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Class<?> clazz = (Class<?>) elementClass;
|
||||
if (PluginUtil.isPojo(clazz)) {
|
||||
List<FieldDocInfo> fieldDocInfos = isCycle(clazz, field)
|
||||
? Collections.emptyList()
|
||||
: buildFieldDocInfosByType(clazz, false, null);
|
||||
fieldDocInfo.setChildren(fieldDocInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldDocInfo;
|
||||
}
|
||||
|
||||
protected Class<?> getGenericParamClass(Class<?> clazz, String name) {
|
||||
String genericParamKey = PluginUtil.getGenericParamKey(clazz, name);
|
||||
return genericParamMap.get(genericParamKey);
|
||||
}
|
||||
|
||||
protected FieldDocInfo buildFieldDocInfoByClass(ApiModelProperty apiModelProperty, Class<?> clazz, Field field) {
|
||||
ApiModelPropertyWrapper apiModelPropertyWrapper = new ApiModelPropertyWrapper(apiModelProperty, field);
|
||||
Objects.requireNonNull(clazz);
|
||||
Objects.requireNonNull(field);
|
||||
String name = getFieldName(field, apiModelPropertyWrapper);
|
||||
String type = PluginUtil.getDataType(clazz);
|
||||
String description = apiModelPropertyWrapper.getDescription();
|
||||
boolean required = apiModelPropertyWrapper.getRequired();
|
||||
String example = apiModelPropertyWrapper.getExample();
|
||||
|
||||
FieldDocInfo fieldDocInfo = new FieldDocInfo();
|
||||
fieldDocInfo.setName(name);
|
||||
fieldDocInfo.setType(type);
|
||||
fieldDocInfo.setRequired(getRequiredValue(required));
|
||||
fieldDocInfo.setExample(example);
|
||||
fieldDocInfo.setDescription(description);
|
||||
fieldDocInfo.setOrderIndex(apiModelPropertyWrapper.getPosition());
|
||||
fieldDocInfo.setMaxLength(apiModelPropertyWrapper.getMaxLength());
|
||||
|
||||
if (PluginUtil.isCollection(clazz)) {
|
||||
Class<?> elementClass = this.getCollectionElementClass(clazz);
|
||||
if (elementClass != null) {
|
||||
clazz = elementClass;
|
||||
}
|
||||
}
|
||||
// 解决循环依赖问题
|
||||
boolean cycle = isCycle(clazz, field);
|
||||
Type genericType = PluginUtil.getGenericType(field);
|
||||
Class<?> generic = genericType instanceof Class<?> && genericType != clazz? (Class<?>) genericType : null;
|
||||
List<FieldDocInfo> children = cycle ? Collections.emptyList()
|
||||
: buildFieldDocInfosByType(clazz, false, generic);
|
||||
fieldDocInfo.setChildren(children);
|
||||
|
||||
return fieldDocInfo;
|
||||
}
|
||||
|
||||
protected FieldDocInfo buildFieldDocInfoByEnumClass(Class<Enum> enumClass, ApiModelProperty apiModelProperty, Field field) {
|
||||
ApiModelPropertyWrapper apiModelPropertyWrapper = new ApiModelPropertyWrapper(apiModelProperty, field);
|
||||
Enum[] enumConstants = enumClass.getEnumConstants();
|
||||
FieldDocInfo fieldDocInfo = new FieldDocInfo();
|
||||
fieldDocInfo.setName(apiModelPropertyWrapper.getName());
|
||||
fieldDocInfo.setType(DataType.ENUM.getValue());
|
||||
fieldDocInfo.setRequired(Booleans.toValue(apiModelPropertyWrapper.getRequired()));
|
||||
fieldDocInfo.setOrderIndex(apiModelPropertyWrapper.getPosition());
|
||||
List<String> examples = new ArrayList<>(enumConstants.length);
|
||||
for (Enum enumConstant : enumConstants) {
|
||||
examples.add(enumConstant.name());
|
||||
}
|
||||
if (examples.size() > 0) {
|
||||
fieldDocInfo.setExample(examples.get(0));
|
||||
}
|
||||
fieldDocInfo.setDescription(String.join("、", examples));
|
||||
fieldDocInfo.setMaxLength(apiModelPropertyWrapper.getMaxLength());
|
||||
fieldDocInfo.setMaxLength(apiModelPropertyWrapper.getMaxLength());
|
||||
return fieldDocInfo;
|
||||
}
|
||||
|
||||
protected boolean isCycle(Class<?> clazz, Field field) {
|
||||
String key = clazz.getName() + field.getType().getName() + field.getName();
|
||||
AtomicInteger atomicInteger = cycleFieldsMap.computeIfAbsent(key, k -> new AtomicInteger());
|
||||
return atomicInteger.getAndIncrement() >= 1;
|
||||
}
|
||||
|
||||
private boolean isClassFieldHidden(Class<?> clazz, Field field) {
|
||||
String key = clazz.getName() + "." + field.getName();
|
||||
return hiddenColumns.contains(key);
|
||||
}
|
||||
|
||||
private static byte getRequiredValue(boolean b) {
|
||||
return (byte) (b ? 1 : 0);
|
||||
}
|
||||
|
||||
private static String getFieldType(Field field, ApiModelPropertyWrapper apiModelProperty) {
|
||||
String dataType = apiModelProperty.getDataType();
|
||||
if (StringUtils.hasText(dataType)) {
|
||||
return dataType;
|
||||
}
|
||||
Class<?> type = field.getType();
|
||||
String rawTypeName = type.getSimpleName();
|
||||
String multipartFile = "MultipartFile";
|
||||
if (rawTypeName.contains(multipartFile)) {
|
||||
return type.isArray() ? DataType.FILES.getValue() : DataType.FILE.getValue();
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(type)) {
|
||||
Type genericType = field.getGenericType();
|
||||
return genericType.getTypeName().contains(multipartFile) ?
|
||||
DataType.FILES.getValue() :
|
||||
DataType.ARRAY.getValue();
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return DataType.ARRAY.getValue();
|
||||
}
|
||||
if (type == Date.class) {
|
||||
return DataType.DATE.getValue();
|
||||
}
|
||||
if (type == Timestamp.class) {
|
||||
return DataType.DATETIME.getValue();
|
||||
}
|
||||
return PluginUtil.isPojo(type) ? DataType.OBJECT.getValue() : field.getType().getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
private static String getFieldName(Field field, ApiModelPropertyWrapper apiModelPropertyWrapper) {
|
||||
String name = apiModelPropertyWrapper.getName();
|
||||
if (StringUtils.hasText(name)) {
|
||||
return name;
|
||||
}
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
private static String getFieldDescription(ApiModelProperty apiModelProperty) {
|
||||
if (apiModelProperty == null) {
|
||||
return "";
|
||||
}
|
||||
String desc = apiModelProperty.value();
|
||||
if (StringUtils.isEmpty(desc)) {
|
||||
desc = apiModelProperty.notes();
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
private void resetCycle() {
|
||||
cycleFieldsMap.clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
/**
|
||||
* 数据类型
|
||||
* @author tanghc
|
||||
*/
|
||||
public enum DataType {
|
||||
BYTE("byte"), SHORT("short"), INT("integer"), LONG("long"), FLOAT("float"), DOUBLE("double")
|
||||
, CHAR("char"), BOOLEAN("boolean"), ARRAY("array"), OBJECT("object"),STRING("string")
|
||||
, FILE("file"), FILES("file[]"), DATE("date"),DATETIME("datetime"), ENUM("enum");
|
||||
|
||||
DataType(String v) {
|
||||
val = v;
|
||||
}
|
||||
|
||||
private String val;
|
||||
|
||||
public String getValue() {
|
||||
return this.val;
|
||||
}
|
||||
|
||||
public static DataType parse(String name) {
|
||||
return DataType.valueOf(DataType.class, name);
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文档参数字段信息
|
||||
*
|
||||
* @author tanghc
|
||||
*
|
||||
*/
|
||||
@Data
|
||||
public class FieldDocInfo {
|
||||
|
||||
/** 字段名称 */
|
||||
private String name;
|
||||
|
||||
/** 字段类型 */
|
||||
private String type;
|
||||
|
||||
/** 是否必须,1:是,0:否 */
|
||||
private Byte required;
|
||||
|
||||
/** 最大长度 */
|
||||
private String maxLength;
|
||||
|
||||
/** 示例值 */
|
||||
private String example;
|
||||
|
||||
/** 描述 */
|
||||
private String description;
|
||||
|
||||
/** 父节点, 没有填空字符串 */
|
||||
private String parentId;
|
||||
|
||||
/** 排序 */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 子节点 */
|
||||
private List<FieldDocInfo> children;
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.ControllerInfo;
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public abstract class HttpMethodInfoBuilder implements RequestInfoBuilder {
|
||||
|
||||
private ControllerInfo controllerInfo;
|
||||
private TornaConfig tornaConfig;
|
||||
private Method method;
|
||||
private ApiOperation apiOperation;
|
||||
|
||||
public HttpMethodInfoBuilder(ControllerInfo controllerInfo, Method method, TornaConfig tornaConfig) {
|
||||
this.controllerInfo = controllerInfo;
|
||||
this.tornaConfig = tornaConfig;
|
||||
this.method = method;
|
||||
this.apiOperation = method.getAnnotation(ApiOperation.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHttpMethod() {
|
||||
String httpMethod = apiOperation.httpMethod();
|
||||
if (StringUtils.hasText(httpMethod)) {
|
||||
return httpMethod;
|
||||
}
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
|
||||
if (requestMapping != null) {
|
||||
RequestMethod[] methods = requestMapping.method();
|
||||
if (methods.length == 0) {
|
||||
return this.tornaConfig.getMethodWhenMulti();
|
||||
} else {
|
||||
return methods[0].name();
|
||||
}
|
||||
}
|
||||
return tornaConfig.getDefaultHttpMethod();
|
||||
}
|
||||
|
||||
public TornaConfig getTornaConfig() {
|
||||
return tornaConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiOperation getApiOperation() {
|
||||
return apiOperation;
|
||||
}
|
||||
|
||||
public ControllerInfo getControllerInfo() {
|
||||
return controllerInfo;
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.ControllerInfo;
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import com.gitee.sop.support.annotation.Open;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class MvcRequestInfoBuilder extends HttpMethodInfoBuilder {
|
||||
|
||||
public MvcRequestInfoBuilder(ControllerInfo controllerInfo, Method method, TornaConfig tornaConfig) {
|
||||
super(controllerInfo, method, tornaConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildUrl() {
|
||||
Method method = getMethod();
|
||||
Open open = AnnotationUtils.findAnnotation(method, Open.class);
|
||||
if (open == null) {
|
||||
throw new RuntimeException("接口[" + method + "]未定义@Open注解");
|
||||
}
|
||||
return open.value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
Method method = getMethod();
|
||||
Open open = AnnotationUtils.findAnnotation(method, Open.class);
|
||||
if (open == null) {
|
||||
throw new RuntimeException("接口[" + method + "]未定义@Open注解");
|
||||
}
|
||||
return open.version();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildContentType() {
|
||||
return MediaType.APPLICATION_JSON_VALUE;
|
||||
}
|
||||
|
||||
private String[] getConsumes() {
|
||||
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(getMethod(), RequestMapping.class);
|
||||
if (requestMapping != null) {
|
||||
return requestMapping.consumes();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package cn.torna.swaggerplugin.builder;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.ControllerInfo;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Copyright © 2021 DHF Info. Tech Ltd. All rights reserved.
|
||||
* <p></p>
|
||||
*
|
||||
* @author tanghc
|
||||
* @version 1.0.0
|
||||
* @description
|
||||
* @date 2021/7/14/014
|
||||
*/
|
||||
public interface RequestInfoBuilder {
|
||||
String buildUrl();
|
||||
|
||||
String getHttpMethod();
|
||||
|
||||
String buildContentType();
|
||||
|
||||
Method getMethod();
|
||||
|
||||
ApiOperation getApiOperation();
|
||||
|
||||
ControllerInfo getControllerInfo();
|
||||
|
||||
default String getVersion() {
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package cn.torna.swaggerplugin.exception;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class HiddenException extends Exception {
|
||||
|
||||
public HiddenException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package cn.torna.swaggerplugin.exception;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class IgnoreException extends Exception {
|
||||
public IgnoreException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
package cn.torna.swaggerplugin.scaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.MetadataReader;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.core.type.filter.AssignableTypeFilter;
|
||||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* class扫描
|
||||
* @author tanghc
|
||||
*/
|
||||
public class ClassScanner {
|
||||
|
||||
private static final String RESOURCE_PATTERN = "/**/*.class";
|
||||
|
||||
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||
|
||||
private List<String> packagesList = new LinkedList<String>();
|
||||
|
||||
private List<TypeFilter> typeIncludes = new LinkedList<TypeFilter>();
|
||||
|
||||
private Set<Class<?>> classSet = new HashSet<Class<?>>();
|
||||
|
||||
public ClassScanner(String[] entityPackage) {
|
||||
this(entityPackage, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ClassScanner(String[] entityPackage, Class<?> scanClass) {
|
||||
for (String packagePath : entityPackage) {
|
||||
this.packagesList.add(packagePath);
|
||||
}
|
||||
if (scanClass != null) {
|
||||
// scanClass是注解类型
|
||||
if(scanClass.isAnnotation()) {
|
||||
// AnnotationTypeFilter 有scanClass注解的类将被过滤出来,不能过滤接口
|
||||
typeIncludes.add(new AnnotationTypeFilter((Class<? extends Annotation>) scanClass, false));
|
||||
}else {
|
||||
// AssignableTypeFilter 继承或实现superClass的类将被过滤出来
|
||||
// superClass可以是接口
|
||||
typeIncludes.add(new AssignableTypeFilter(scanClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 将符合条件的Bean以Class集合的形式返回
|
||||
*
|
||||
* @return 返回Mapper的class集合
|
||||
* @throws IOException IO异常
|
||||
* @throws ClassNotFoundException 文件找不到异常
|
||||
*/
|
||||
public Set<Class<?>> getClassSet() throws IOException, ClassNotFoundException {
|
||||
this.classSet.clear();
|
||||
if (!this.packagesList.isEmpty()) {
|
||||
for (String pkg : this.packagesList) {
|
||||
// classpath*:com/xx/dao/**/*.class
|
||||
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
|
||||
+ ClassUtils.convertClassNameToResourcePath(pkg) + RESOURCE_PATTERN;
|
||||
Resource[] resources = this.resourcePatternResolver.getResources(pattern);
|
||||
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
|
||||
for (Resource resource : resources) {
|
||||
if (resource.isReadable()) {
|
||||
MetadataReader reader = readerFactory.getMetadataReader(resource);
|
||||
if (matchesEntityTypeFilter(reader, readerFactory)) {
|
||||
String className = reader.getClassMetadata().getClassName();
|
||||
this.classSet.add(Class.forName(className));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.classSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前扫描到的Bean含有任何一个指定的注解标记
|
||||
*
|
||||
* @param reader
|
||||
* @param readerFactory
|
||||
* @return 返回true表示它是一个Mapper
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory)
|
||||
throws IOException {
|
||||
if (!this.typeIncludes.isEmpty()) {
|
||||
for (TypeFilter filter : this.typeIncludes) {
|
||||
if (filter.match(reader, readerFactory)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
package cn.torna.swaggerplugin.util;
|
||||
|
||||
import cn.torna.swaggerplugin.scaner.ClassScanner;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.security.Principal;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
|
||||
/**
|
||||
* 扫描2包下的所有类
|
||||
* <p>Title: ClassUtil.java</p>
|
||||
* <p>Description: </p>
|
||||
*
|
||||
* @author lichao1
|
||||
* @version 1.0
|
||||
* @date 2018年12月3日
|
||||
*/
|
||||
public class ClassUtil {
|
||||
|
||||
/**
|
||||
* 从包package中获取所有的Class
|
||||
*
|
||||
* @param packageName
|
||||
* @return 返回class集合
|
||||
*/
|
||||
public static Set<Class<?>> getClasses(String packageName, Class<?> scanClass) {
|
||||
try {
|
||||
return new ClassScanner(new String[]{packageName}, scanClass).getClassSet();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自身以及上层父类class,父类的父类也算,不包括Object
|
||||
*
|
||||
* @param clazz 当前class
|
||||
* @return 没有返回空
|
||||
*/
|
||||
public static List<Class<?>> getAllClasses(Class<?> clazz) {
|
||||
List<Class<?>> allClasses = new ArrayList<>();
|
||||
allClasses.add(clazz);
|
||||
findSuperclass(clazz, allClasses);
|
||||
return allClasses;
|
||||
}
|
||||
|
||||
private static void findSuperclass(Class<?> clazz, List<Class<?>> allClasses) {
|
||||
if (clazz == null) {
|
||||
return;
|
||||
}
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass != Object.class) {
|
||||
allClasses.add(superclass);
|
||||
findSuperclass(superclass, allClasses);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isSpecialType(Class<?> paramType) {
|
||||
// 特殊参数
|
||||
boolean special = (
|
||||
WebRequest.class.isAssignableFrom(paramType) ||
|
||||
MultipartRequest.class.isAssignableFrom(paramType) ||
|
||||
Principal.class.isAssignableFrom(paramType) ||
|
||||
InputStream.class.isAssignableFrom(paramType) ||
|
||||
Reader.class.isAssignableFrom(paramType) ||
|
||||
HttpMethod.class == paramType ||
|
||||
Locale.class == paramType ||
|
||||
TimeZone.class == paramType ||
|
||||
ZoneId.class == paramType ||
|
||||
OutputStream.class.isAssignableFrom(paramType) ||
|
||||
Writer.class.isAssignableFrom(paramType)
|
||||
);
|
||||
String name = paramType.getName();
|
||||
return name.startsWith("javax") ||
|
||||
name.startsWith("jakarta")
|
||||
|| special;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is the basic data type of json data
|
||||
*
|
||||
* @param type0 java class name
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isPrimitive(String type0) {
|
||||
if (Objects.isNull(type0)) {
|
||||
return true;
|
||||
}
|
||||
String type = type0.contains("java.lang") ? type0.substring(type0.lastIndexOf(".") + 1, type0.length()) : type0;
|
||||
type = type.toLowerCase();
|
||||
switch (type) {
|
||||
case "string":
|
||||
case "integer":
|
||||
case "int":
|
||||
case "object":
|
||||
case "void":
|
||||
case "long":
|
||||
case "double":
|
||||
case "float":
|
||||
case "short":
|
||||
case "bigdecimal":
|
||||
case "char":
|
||||
case "character":
|
||||
case "number":
|
||||
case "boolean":
|
||||
case "byte":
|
||||
case "uuid":
|
||||
case "biginteger":
|
||||
case "java.sql.timestamp":
|
||||
case "java.util.date":
|
||||
case "java.time.localdatetime":
|
||||
case "java.time.localtime":
|
||||
case "localtime":
|
||||
case "date":
|
||||
case "localdatetime":
|
||||
case "localdate":
|
||||
case "zoneddatetime":
|
||||
case "java.time.localdate":
|
||||
case "java.time.zoneddatetime":
|
||||
case "java.math.bigdecimal":
|
||||
case "java.math.biginteger":
|
||||
case "java.util.uuid":
|
||||
case "java.io.serializable":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
// Set<Class<?>> classes = getClasses("cn.torna.swaggerplugin", Api.class);
|
||||
// classes.forEach(System.out::println);
|
||||
// }
|
||||
|
||||
}
|
@@ -0,0 +1,407 @@
|
||||
package cn.torna.swaggerplugin.util;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.ControllerInfo;
|
||||
import cn.torna.swaggerplugin.bean.PushFeature;
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import cn.torna.swaggerplugin.builder.DataType;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.Transient;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class PluginUtil {
|
||||
|
||||
public static final String METHOD_GET_GENERIC_INFO = "getGenericInfo";
|
||||
|
||||
private static final List<String> SYSTEM_PACKAGE_LIST = Arrays.asList(
|
||||
"java.", "sun.", "org.springframework.", "javax.", "jakarta."
|
||||
);
|
||||
|
||||
public static String getParameterType(Parameter parameter) {
|
||||
Class<?> type = parameter.getType();
|
||||
String simpleName = type.getSimpleName().toLowerCase();
|
||||
if ("MultipartFile".equalsIgnoreCase(simpleName)) {
|
||||
return "file";
|
||||
}
|
||||
if ("MultipartFile[]".equalsIgnoreCase(simpleName)) {
|
||||
return "file[]";
|
||||
}
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否普通的java类
|
||||
*
|
||||
* @param clazz 类class
|
||||
* @return true是
|
||||
*/
|
||||
public static boolean isPojo(Class<?> clazz) {
|
||||
if (clazz.isArray() || Collection.class.isAssignableFrom(clazz) || clazz.isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
String name = clazz.getName();
|
||||
for (String prefix : SYSTEM_PACKAGE_LIST) {
|
||||
if (name.startsWith(prefix)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段是否被transient关键字修饰或有@Transient注解
|
||||
*
|
||||
* @param field
|
||||
* @return 是返回true
|
||||
*/
|
||||
public static boolean isTransientField(Field field) {
|
||||
Transient transientAnno = field.getAnnotation(Transient.class);
|
||||
return transientAnno != null || Modifier.isTransient(field.getModifiers());
|
||||
}
|
||||
|
||||
public static <T extends Annotation> boolean isExistAnnotation(Annotation[] annotations, Class<T> annoClass) {
|
||||
if (annotations == null) {
|
||||
return false;
|
||||
}
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.getClass() == annoClass) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Type getGenericType(Field field) {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Method[] allDeclaredMethods = ReflectionUtils.getAllDeclaredMethods(Field.class);
|
||||
for (Method allDeclaredMethod : allDeclaredMethods) {
|
||||
if (allDeclaredMethod.getName().equals("getGenericSignature")) {
|
||||
ReflectionUtils.makeAccessible(allDeclaredMethod);
|
||||
Object o = ReflectionUtils.invokeMethod(allDeclaredMethod, field);
|
||||
// Lcn/torna/tornaexample/controller/eg/vo/CommonPage<Lcn/torna/tornaexample/controller/eg/vo/LockingDto;>;
|
||||
String signature = String.valueOf(o);
|
||||
if (signature.startsWith("L") && signature.endsWith(";>;")) {
|
||||
String className = signature.replace(";", "");
|
||||
// Lcn/torna/tornaexample/controller/eg/vo/LockingDto
|
||||
className = StringUtil.findBetweenChar(className, '<', '>');
|
||||
if (className.startsWith("L")) {
|
||||
// cn/torna/tornaexample/controller/eg/vo/LockingDto
|
||||
className = className.substring(1);
|
||||
}
|
||||
className = StringUtil.splashToDot(className);
|
||||
try {
|
||||
return ClassUtils.forName(className, Thread.currentThread().getContextClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return field.getGenericType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取泛型参数类型,如:List{@literal <String> } 返回String
|
||||
*
|
||||
* @param type 泛型参数
|
||||
* @return 返回泛型参数类型
|
||||
*/
|
||||
public static Type getGenericType(Type type) {
|
||||
Type[] params = ((ParameterizedType) type).getActualTypeArguments();
|
||||
if (params.length == 0) {
|
||||
return Object.class;
|
||||
}
|
||||
Type param = params[0];
|
||||
// List<? extends Pojo>,
|
||||
if (param instanceof WildcardType) {
|
||||
WildcardType wildcardType = (WildcardType) param;
|
||||
Type[] upperBounds = wildcardType.getUpperBounds();
|
||||
if (upperBounds != null && upperBounds.length > 0) {
|
||||
// Pojo.class
|
||||
return upperBounds[0];
|
||||
}
|
||||
Type[] lowerBounds = wildcardType.getLowerBounds();
|
||||
if (lowerBounds != null && lowerBounds.length > 0) {
|
||||
return lowerBounds[0];
|
||||
}
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取泛型参数的原理类型,如:List{@literal <String> } 返回List
|
||||
*
|
||||
* @param type 泛型参数
|
||||
* @return 返回泛型参数原始类型
|
||||
*/
|
||||
public static Type getGenericRawType(Type type) {
|
||||
Type rawType = ((ParameterizedType) type).getRawType();
|
||||
if (rawType == null) {
|
||||
return Object.class;
|
||||
}
|
||||
return rawType;
|
||||
}
|
||||
|
||||
public static Type getGenericTypeDeeply(Type type) {
|
||||
if (isGenericType(type)) {
|
||||
Type genericType = getGenericType(type);
|
||||
return getGenericTypeDeeply(genericType);
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是泛型类型
|
||||
*
|
||||
* @param type 类型
|
||||
* @return true:是泛型类型
|
||||
*/
|
||||
public static boolean isGenericType(Type type) {
|
||||
return type instanceof ParameterizedType;
|
||||
}
|
||||
|
||||
public static String getGenericParamKey(Class<?> rawType, String name) {
|
||||
// return rawType.getName() + "." + name;
|
||||
return rawType.getName();
|
||||
}
|
||||
|
||||
public static boolean isCollectionOrArray(Class<?> type) {
|
||||
return type.isArray() || isCollection(type);
|
||||
}
|
||||
|
||||
public static boolean isCollection(Class<?> type) {
|
||||
return Collection.class.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public static String getDataType(Class<?> type) {
|
||||
if (isCollectionOrArray(type)) {
|
||||
return DataType.ARRAY.getValue();
|
||||
}
|
||||
return isPojo(type) ? DataType.OBJECT.getValue() : type.getSimpleName().toLowerCase();
|
||||
}
|
||||
|
||||
public static List<String> getClassGenericParamName(Class<?> clazz) {
|
||||
TypeVariable<? extends Class<?>>[] typeParameters = clazz.getTypeParameters();
|
||||
List<String> paramNames = new ArrayList<>();
|
||||
for (TypeVariable<?> typeParameter : typeParameters) {
|
||||
paramNames.add(typeParameter.getName());
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将泛型实际参数存储到genericParamMap中。<br>
|
||||
* <pre>
|
||||
* {@literal
|
||||
* Result<Page<Order>>
|
||||
* key: xxx.Result.T value: Page.class
|
||||
* key: xxx.Page.T value: Order.class
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param genericParamMap 存储泛型信息
|
||||
* @param genericParameterType 泛型类型
|
||||
*/
|
||||
public static void appendGenericParamMap1(Map<String, Class<?>> genericParamMap, Type genericParameterType) {
|
||||
// 如果有泛型
|
||||
if (PluginUtil.isGenericType(genericParameterType)) {
|
||||
ParameterizedType parameterType = (ParameterizedType) genericParameterType;
|
||||
// 泛型参数
|
||||
Type[] actualTypeArguments = parameterType.getActualTypeArguments();
|
||||
// 原始类型
|
||||
Class<?> rawType = (Class<?>) parameterType.getRawType();
|
||||
Class<? extends Class> rawTypeClass = rawType.getClass();
|
||||
TypeVariable<?>[] typeParameters = rawTypeClass.getTypeParameters();
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
String key = getGenericParamKey(rawType, typeParameters[i].getName());
|
||||
Type actualTypeArgument = actualTypeArguments[i];
|
||||
// 如果泛型填的?,即:Result<?>
|
||||
if (actualTypeArgument instanceof WildcardType) {
|
||||
genericParamMap.put(key, Object.class);
|
||||
continue;
|
||||
}
|
||||
boolean isGeneric = PluginUtil.isGenericType(actualTypeArgument);
|
||||
Class<?> value = isGeneric ?
|
||||
(Class<?>) ((ParameterizedType) actualTypeArgument).getRawType() : (Class<?>) actualTypeArgument;
|
||||
genericParamMap.put(key, value);
|
||||
if (isGeneric) {
|
||||
appendGenericParamMap1(genericParamMap, actualTypeArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将泛型实际参数存储到genericParamMap中。<br>
|
||||
* <pre>
|
||||
* {@literal
|
||||
* Result<Page<Order>>
|
||||
* key: xxx.Result value: Page.class
|
||||
* key: xxx.Page value: Order.class
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param genericParamMap 存储泛型信息
|
||||
* @param genericParameterType 泛型类型
|
||||
*/
|
||||
public static void appendGenericParamMap(ControllerInfo controllerInfo, Map<String, Class<?>> genericParamMap, Type genericParameterType) {
|
||||
// 如果有泛型
|
||||
if (PluginUtil.isGenericType(genericParameterType)) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
Type target = parameterizedType.getActualTypeArguments()[0];
|
||||
Class targetClass;
|
||||
if (target instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedTypeTarget = (ParameterizedType) target;
|
||||
targetClass = (Class) parameterizedTypeTarget.getRawType();
|
||||
genericParamMap.put(rawType.getTypeName(), targetClass);
|
||||
appendGenericParamMap(controllerInfo, genericParamMap, target);
|
||||
} else if (target instanceof TypeVariable) {
|
||||
TypeVariable parameterizedTypeTarget = (TypeVariable) target;
|
||||
Class<?> genericDeclaration = (Class<?>) parameterizedTypeTarget.getGenericDeclaration();
|
||||
String key = genericDeclaration.getName() + "#" + parameterizedTypeTarget.getName();
|
||||
Class<?> realClass = controllerInfo.getGenericParamMap().get(key);
|
||||
if (realClass != null) {
|
||||
targetClass = realClass;
|
||||
genericParamMap.put(rawType.getTypeName(), targetClass);
|
||||
}
|
||||
} else {
|
||||
if (target instanceof WildcardType) {
|
||||
return;
|
||||
}
|
||||
targetClass = (Class) target;
|
||||
genericParamMap.put(rawType.getTypeName(), targetClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性拷贝,第一个参数中的属性值拷贝到第二个参数中<br>
|
||||
* 注意:当第一个参数中的属性有null值时,不会拷贝进去
|
||||
*
|
||||
* @param from 源对象
|
||||
* @param to 目标对象
|
||||
* @throws BeansException
|
||||
*/
|
||||
public static void copyPropertiesIgnoreNull(Object from, Object to, String... ignoreProperties)
|
||||
throws BeansException {
|
||||
Assert.notNull(from, "Source must not be null");
|
||||
Assert.notNull(to, "Target must not be null");
|
||||
|
||||
Class<?> actualEditable = to.getClass();
|
||||
PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
|
||||
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
|
||||
|
||||
for (PropertyDescriptor targetPd : targetPds) {
|
||||
Method writeMethod = targetPd.getWriteMethod();
|
||||
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
|
||||
PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(from.getClass(), targetPd.getName());
|
||||
if (sourcePd != null) {
|
||||
Method readMethod = sourcePd.getReadMethod();
|
||||
if (readMethod != null &&
|
||||
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
|
||||
try {
|
||||
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
|
||||
readMethod.setAccessible(true);
|
||||
}
|
||||
Object value = readMethod.invoke(from);
|
||||
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
|
||||
writeMethod.setAccessible(true);
|
||||
}
|
||||
// 这里判断value是否为空 当然这里也能进行一些特殊要求的处理
|
||||
// 例如绑定时格式转换等等
|
||||
if (value != null) {
|
||||
if (!Modifier.isPublic(writeMethod
|
||||
.getDeclaringClass().getModifiers())) {
|
||||
writeMethod.setAccessible(true);
|
||||
}
|
||||
writeMethod.invoke(to, value);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
throw new FatalBeanException(
|
||||
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFileParameter(Parameter parameter) {
|
||||
Class<?> type = parameter.getType();
|
||||
boolean pojo = PluginUtil.isPojo(type);
|
||||
if (pojo) {
|
||||
AtomicInteger fileCount = new AtomicInteger();
|
||||
ReflectionUtils.doWithFields(type, field -> {
|
||||
String fieldType = field.getType().getSimpleName();
|
||||
if (fieldType.contains("MultipartFile")) {
|
||||
fileCount.incrementAndGet();
|
||||
}
|
||||
});
|
||||
return fileCount.get() > 0;
|
||||
} else {
|
||||
String parameterType = PluginUtil.getParameterType(parameter);
|
||||
return parameterType.equals("file") || parameterType.equals("file[]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段是否包含某些注解
|
||||
*
|
||||
* @param field 字段
|
||||
* @param annotationClassname 注解名称
|
||||
* @return 如果有返回true
|
||||
*/
|
||||
public static boolean hasAnyAnnotation(Field field, List<String> annotationClassname) {
|
||||
Annotation[] annotations = field.getAnnotations();
|
||||
for (Annotation annotation : annotations) {
|
||||
String annotationClassName = annotation.annotationType().getName();
|
||||
for (String name : annotationClassname) {
|
||||
if (annotationClassName.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEnableFeature(TornaConfig config, PushFeature feature) {
|
||||
List<String> features = config.getFeatures();
|
||||
if (features == null) {
|
||||
return false;
|
||||
}
|
||||
for (String feat : features) {
|
||||
if (feature.name().equalsIgnoreCase(feat)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package cn.torna.swaggerplugin.util;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class StringUtil {
|
||||
private StringUtil() {
|
||||
|
||||
}
|
||||
/**
|
||||
* "file:/home/whf/cn/fh" -> "/home/whf/cn/fh"
|
||||
* "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar"
|
||||
*/
|
||||
public static String getRootPath(URL url) {
|
||||
String fileUrl = url.getFile();
|
||||
int pos = fileUrl.indexOf('!');
|
||||
|
||||
if (-1 == pos) {
|
||||
return fileUrl;
|
||||
}
|
||||
|
||||
return fileUrl.substring(5, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* "cn.fh.lightning" -> "cn/fh/lightning"
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static String dotToSplash(String name) {
|
||||
return name.replaceAll("\\.", "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* "cn/fh/lightning" -> "cn.fh.lightning"
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static String splashToDot(String name) {
|
||||
return name.replace('/', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* "Apple.class" -> "Apple"
|
||||
*/
|
||||
public static String trimExtension(String name) {
|
||||
int pos = name.indexOf('.');
|
||||
if (-1 != pos) {
|
||||
return name.substring(0, pos);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* /application/home -> /home
|
||||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public static String trimURI(String uri) {
|
||||
String trimmed = uri.substring(1);
|
||||
int splashIndex = trimmed.indexOf('/');
|
||||
|
||||
return trimmed.substring(splashIndex);
|
||||
}
|
||||
|
||||
public static String findBetweenChar(String input, char begin, char end) {
|
||||
String regex = begin + "(.*?)" + end;
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package cn.torna.swaggerplugin;
|
||||
|
||||
import cn.torna.swaggerplugin.util.StringUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ClassTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
List<Class<?>> parents = getSuperclass(RedApple.class);
|
||||
List<Class<?>> parents2 = getSuperclass(Apple.class);
|
||||
List<Class<?>> parents3 = getSuperclass(Food.class);
|
||||
System.out.println(parents);
|
||||
System.out.println(parents2);
|
||||
System.out.println(parents3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
String s = "CommonPage<Lcn/torna/tornaexample/controller/eg/vo/LockingDto;>";
|
||||
String content = StringUtil.findBetweenChar( s, '<', '>');
|
||||
Assert.assertEquals("Lcn/torna/tornaexample/controller/eg/vo/LockingDto;", content);
|
||||
}
|
||||
|
||||
private static List<Class<?>> getSuperclass(Class<?> clazz) {
|
||||
List<Class<?>> parents = new ArrayList<>();
|
||||
findSuperclass(clazz, parents);
|
||||
return parents;
|
||||
}
|
||||
|
||||
private static void findSuperclass(Class<?> clazz, List<Class<?>> parents) {
|
||||
if (clazz == null) {
|
||||
return;
|
||||
}
|
||||
Class<?> superclass = clazz.getSuperclass();
|
||||
if (superclass != Object.class) {
|
||||
parents.add(superclass);
|
||||
findSuperclass(superclass, parents);
|
||||
}
|
||||
}
|
||||
|
||||
static class Food {}
|
||||
static class Apple extends Food {}
|
||||
static class RedApple extends Apple {}
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
package cn.torna.swaggerplugin;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import org.junit.Test;
|
||||
|
||||
public class EnumTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Class<GenderEnum> genderEnumClass = GenderEnum.class;
|
||||
Enum[] enumConstants = genderEnumClass.getEnumConstants();
|
||||
for (Enum enumConstant : enumConstants) {
|
||||
System.out.println(enumConstant.name() + "=" + enumConstant.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
public enum GenderEnum {
|
||||
@ApiModelProperty(value = "男")
|
||||
MALE(1),
|
||||
FEMALE(0)
|
||||
;
|
||||
|
||||
private final int gender;
|
||||
|
||||
GenderEnum(int gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package cn.torna.swaggerplugin;
|
||||
|
||||
import cn.torna.swaggerplugin.util.PluginUtil;
|
||||
import junit.framework.TestCase;
|
||||
import lombok.Data;
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class GenericParadigmUtilTest extends TestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testA() {
|
||||
Class<Controller> controllerClass = Controller.class;
|
||||
Method method = ReflectionUtils.findMethod(controllerClass, "page");
|
||||
Type genericReturnType = method.getGenericReturnType();
|
||||
|
||||
ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) genericReturnType;
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
Type target = parameterizedType.getActualTypeArguments()[0];
|
||||
Class targetClass;
|
||||
if (target instanceof ParameterizedTypeImpl) {
|
||||
ParameterizedTypeImpl parameterizedTypeTarget = (ParameterizedTypeImpl) target;
|
||||
targetClass = parameterizedTypeTarget.getRawType();
|
||||
System.out.println(rawType.getTypeName() + ":" + targetClass.getName());
|
||||
} else {
|
||||
targetClass = (Class) target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class Controller {
|
||||
|
||||
public Result<Page<User>> page() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Page<T> {
|
||||
private List<T> list;
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class User {
|
||||
private Integer id;
|
||||
}
|
||||
|
||||
|
||||
public static class Result<T> {
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package cn.torna.swaggerplugin;
|
||||
|
||||
import cn.torna.swaggerplugin.bean.TornaConfig;
|
||||
import cn.torna.swaggerplugin.util.ClassUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class SwaggerPluginServiceTest {
|
||||
|
||||
@Test
|
||||
public void exclude() {
|
||||
TornaConfig tornaConfig = new TornaConfig();
|
||||
tornaConfig.setExcludePackage("cn.torna.swaggerplugin.pkg1.pkg2;cn.torna.swaggerplugin.pkg1.pkg3");
|
||||
tornaConfig.setBasePackage("cn.torna.swaggerplugin.pkg1");
|
||||
tornaConfig.setScanApis(Arrays.asList("cn.torna.swaggerplugin.pkg1.UserInterface"));
|
||||
SwaggerPluginService swaggerPluginService = new SwaggerPluginService(tornaConfig);
|
||||
Set<Class<?>> clazzs = ClassUtil.getClasses(tornaConfig.getBasePackage(), Api.class);
|
||||
for (Class<?> clazz : clazzs) {
|
||||
for (Method method : clazz.getMethods()) {
|
||||
if (swaggerPluginService.match(method)) {
|
||||
System.out.println(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package cn.torna.swaggerplugin.pkg1;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Api
|
||||
public interface UserInterface {
|
||||
|
||||
String getUserName();
|
||||
String getAge(Long userId);
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
package cn.torna.swaggerplugin.pkg1.pkg2;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Api
|
||||
public interface OrderInterface {
|
||||
String getOrder();
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package cn.torna.swaggerplugin.pkg1.pkg3;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Api
|
||||
public interface AddressInterface {
|
||||
|
||||
String getAddress(String a);
|
||||
}
|
@@ -20,6 +20,11 @@
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>2.14.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
|
@@ -27,7 +27,7 @@ public @interface Open {
|
||||
String version() default "1.0";
|
||||
|
||||
/**
|
||||
* 指定接口是否需要授权才能访问,可在admin中进行修改
|
||||
* 指定接口是否需要授权才能访问,可在admin中进行授权
|
||||
*/
|
||||
boolean permission() default false;
|
||||
|
||||
@@ -36,4 +36,27 @@ public @interface Open {
|
||||
*/
|
||||
boolean needToken() default false;
|
||||
|
||||
/**
|
||||
* 是否有公共响应参数,默认true
|
||||
* <pre>
|
||||
* 如果设置true,返回结果如下
|
||||
* {
|
||||
* "code": "0",
|
||||
* "msg": "",
|
||||
* "sub_code": "",
|
||||
* "sub_msg": "",
|
||||
* "data": {
|
||||
* "id": 1,
|
||||
* "name": "Jim"
|
||||
* }
|
||||
* }
|
||||
* 如果设置false,只返回data部分:
|
||||
* {
|
||||
* "id": 1,
|
||||
* "name": "Jim"
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
boolean hasCommonResponse() default true;
|
||||
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import com.gitee.sop.support.annotation.Open;
|
||||
import com.gitee.sop.support.service.ApiRegisterService;
|
||||
import com.gitee.sop.support.service.dto.RegisterDTO;
|
||||
import com.gitee.sop.support.service.dto.RegisterResult;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -109,6 +110,11 @@ public class ApiRegister {
|
||||
registerDTO.setParamInfo(JSON.toJSONString(paramInfos));
|
||||
registerDTO.setIsPermission(parseBoolean(open.permission()));
|
||||
registerDTO.setIsNeedToken(parseBoolean(open.needToken()));
|
||||
registerDTO.setHasCommonResponse(parseBoolean(open.hasCommonResponse()));
|
||||
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
|
||||
if (apiOperation != null) {
|
||||
registerDTO.setDescription(apiOperation.value());
|
||||
}
|
||||
LOG.info("注册开放接口, apiInfo=" + registerDTO);
|
||||
RegisterResult result = apiRegisterService.register(registerDTO);
|
||||
if (!result.getSuccess()) {
|
||||
|
@@ -61,5 +61,10 @@ public class RegisterDTO implements Serializable {
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
/**
|
||||
* 是否有公共响应参数
|
||||
*/
|
||||
private Integer hasCommonResponse;
|
||||
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,11 @@
|
||||
<artifactId>sop-service-support</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-doc-plugin</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
Reference in New Issue
Block a user