This commit is contained in:
六如
2024-12-30 20:49:17 +08:00
parent 40865ab049
commit 3e900cf0fe
27 changed files with 2232 additions and 117 deletions

View File

@@ -1,5 +1,9 @@
# changelog
## 5.1
接入smart-doc
## 5.0
全面重构,欢迎体验:[文档](https://www.yuque.com/u1604442/sop)

View File

@@ -84,6 +84,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 文档推送 -->
<plugin>
<groupId>com.ly.smart-doc</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>3.0.9</version>
<configuration>
<!--指定生成文档的使用的配置文件-->
<configFile>./src/main/resources/smart-doc.json</configFile>
<!--指定项目名称-->
<projectName>${project.artifactId}</projectName>
</configuration>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-support</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View File

@@ -0,0 +1 @@
mvn -Dfile.encoding=UTF-8 -Dcheckstyle.skip=true smart-doc:torna-rpc

View File

@@ -5,29 +5,32 @@ import com.gitee.sop.payment.open.req.PayTradeWapPayRequest;
import com.gitee.sop.payment.open.resp.PayOrderSearchResponse;
import com.gitee.sop.payment.open.resp.PayTradeWapPayResponse;
import com.gitee.sop.support.annotation.Open;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
* 支付接口
*
* @author 六如
*/
@Api("支付接口")
public interface OpenPayment {
@ApiOperation(
value = "手机网站支付接口",
notes = "该接口是页面跳转接口,用于生成用户访问跳转链接。" +
"请在服务端执行SDK中pageExecute方法读取响应中的body()结果。" +
"该结果用于跳转到页面,返回到用户浏览器渲染或重定向跳转到页面。" +
"具体使用方法请参考 <a href=\"https://torna.cn\" target=\"_blank\">接入指南</a>"
)
/**
* 手机网站支付接口
*
* @apiNote 该接口是页面跳转接口,用于生成用户访问跳转链接。
* 请在服务端执行SDK中pageExecute方法读取响应中的body()结果。
* 该结果用于跳转到页面,返回到用户浏览器渲染或重定向跳转到页面。
* 具体使用方法请参考 <a href="https://torna.cn" target="_blank">接入指南</a>
*/
@Open("pay.trade.wap.pay")
PayTradeWapPayResponse tradeWapPay(PayTradeWapPayRequest request);
@ApiOperation(value = "订单查询接口")
/**
* 订单查询接口
*
* @param request
* @return
*/
@Open("pay.order.search")
PayOrderSearchResponse orderSearch(PayOrderSearchRequest request);

View File

@@ -1,6 +1,5 @@
package com.gitee.sop.payment.open.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@@ -19,81 +18,93 @@ import java.util.List;
public class PayTradeWapPayRequest {
@ApiModelProperty(value = "商户网站唯一订单号", required = true, example = "70501111111S001111119")
/**
* 商户网站唯一订单号
*
* @mock 70501111111S001111119
*/
@Length(max = 64)
@NotBlank(message = "商户网站唯一订单号必填")
private String outTradeNo;
@ApiModelProperty(value = "订单总金额.单位为元,精确到小数点后两位,取值范围:[0.01,100000000] ",
required = true, example = "9.00")
/**
* 订单总金额.单位为元,精确到小数点后两位,取值范围:[0.01,100000000]
*
* @mock 9.00
*/
@NotNull(message = "订单总金额不能为空")
private BigDecimal totalAmount;
@ApiModelProperty(
value = "订单标题。注意:不可使用特殊字符,如 /=& 等。",
required = true,
example = "大乐透"
)
/**
* 订单标题。注意:不可使用特殊字符,如 /=& 等。
*
* @mock 大乐透
*/
@Length(max = 256)
@NotBlank(message = "订单标题不能为空")
private String subject;
@ApiModelProperty(
value = "销售产品码商家和支付平台签约的产品码。手机网站支付为QUICK_WAP_WAY",
required = true,
example = "QUICK_WAP_WAY"
)
/**
* 销售产品码商家和支付平台签约的产品码。手机网站支付为QUICK_WAP_WAY
*
* @mock QUICK_WAP_WAY
*/
@NotBlank(message = "销售产品码不能为空")
@Length(max = 64)
private String productCode;
@ApiModelProperty(
value = "针对用户授权接口,获取用户相关数据时,用于标识用户授权关系",
example = "appopenBb64d181d0146481ab6a762c00714cC27"
)
/**
* 针对用户授权接口,获取用户相关数据时,用于标识用户授权关系
*
* @mock appopenBb64d181d0146481ab6a762c00714cC27
*/
@Length(max = 40)
private String authToken;
@ApiModelProperty(
value = "用户付款中途退出返回商户网站的地址",
example = "http://www.taobao.com/product/113714.html"
)
/**
* 用户付款中途退出返回商户网站的地址
*
* @mock http://www.taobao.com/product/113714.html
*/
@Length(max = 400)
private String quit_url;
@ApiModelProperty(
value = "订单包含的商品列表信息json格式其它说明详见商品明细说明"
)
/**
* 订单包含的商品列表信息json格式其它说明详见商品明细说明
*/
private List<GoodsDetail> goodsDetail;
@ApiModelProperty(
value = "绝对超时时间格式为yyyy-MM-dd HH:mm:ss。超时时间范围1m~15d。",
example = "2016-12-31 10:05:00"
)
/**
* 绝对超时时间格式为yyyy-MM-dd HH:mm:ss。超时时间范围1m~15d。
*
* @mock 2016-12-31 10:05:00
*/
@Length(max = 32)
private String timeExpire;
@ApiModelProperty(
value = "商户传入业务信息具体值要和支付平台约定应用于安全营销等参数直传场景格式为json格式",
example = "{\"mc_create_trade_ip\":\"127.0.0.1\"}"
)
/**
* 商户传入业务信息具体值要和支付平台约定应用于安全营销等参数直传场景格式为json格式
*
* @mock {"mc_create_trade_ip":"127.0.0.1"}
*/
@Length(max = 512)
private String businessParams;
@ApiModelProperty(
value = "公用回传参数如果请求时传递了该参数则返回给商户时会回传该参数。支付平台只会在同步返回包括跳转回商户网站和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付平台。",
example = "merchantBizType%3d3C%26merchantBizNo%3d2016010101111"
)
/**
* 公用回传参数如果请求时传递了该参数则返回给商户时会回传该参数。支付平台只会在同步返回包括跳转回商户网站和异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付平台。
*
* @mock merchantBizType%3d3C%26merchantBizNo%3d2016010101111
*/
@Length(max = 512)
private String passbackParams;
@ApiModelProperty(
value = "商户原始订单号最大长度限制32位",
example = "{\"mc_create_trade_ip\":\"127.0.0.1\"}"
)
/**
* 商户原始订单号最大长度限制32位
*
* @mock {"mc_create_trade_ip":"127.0.0.1"}
*/
@Length(max = 32)
private String merchantOrderNo;
@@ -101,73 +112,78 @@ public class PayTradeWapPayRequest {
@Data
public static class GoodsDetail {
@ApiModelProperty(
value = "商品的编号",
required = true,
example = "apple-01"
)
/**
* 商品的编号
*
* @mock apple-01
*/
@NotBlank
@Length(max = 64)
private String goodsId;
@ApiModelProperty(
value = "商品名称",
required = true,
example = "ipad"
)
/**
* 商品名称
*
* @mock ipad
*/
@NotBlank
@Length(max = 256)
private String goodsName;
@ApiModelProperty(
value = "商品数量",
required = true,
example = "1"
)
/**
* 商品数量
*
* @mock 1
*/
@NotNull
private Integer quantity;
@ApiModelProperty(
value = "商品单价,单位为元",
required = true,
example = "2000"
)
/**
* 商品单价,单位为元
*
* @mock 2000
*/
@NotNull
private BigDecimal price;
@ApiModelProperty(
value = "支付平台定义的统一商品编号",
example = "20010001"
)
/**
* 支付平台定义的统一商品编号
*
* @mock 20010001
*/
@Length(max = 32)
private String alipayGoodsId;
@ApiModelProperty(
value = "商品类目",
example = "34543238"
)
/**
* 商品类目
*
* @mock 34543238
*/
@Length(max = 24)
private String goodsCategory;
@ApiModelProperty(
value = "商品类目树从商品类目根节点到叶子节点的类目id组成类目id值使用|分割",
example = "124868003|126232002|126252004"
)
/**
* 商品类目树从商品类目根节点到叶子节点的类目id组成类目id值使用|分割
*
* @mock 124868003|126232002|126252004
*/
@Length(max = 128)
private String categoriesTree;
@ApiModelProperty(
value = "商品描述信息",
example = "特价手机"
)
/**
* 商品描述信息
*
* @mock 特价手机
*/
@Length(max = 1000)
private String body;
@ApiModelProperty(
value = "商品的展示地址",
example = "http://www.alipay.com/xxx.jpg"
)
/**
* 商品的展示地址
*
* @mock http://www.alipay.com/xxx.jpg
*/
@Length(max = 400)
private String showUrl;

View File

@@ -1,19 +1,21 @@
package com.gitee.sop.payment.open.resp;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author 六如
*/
@Data
public class PayTradeWapPayResponse {
@ApiModelProperty(
value = "用于跳转支付平台页面的信息POST和GET方法生成内容不同使用POST方法执行结果为html form表单在浏览器渲染即可使用GET方法会得到支付平台URL需要打开或重定向到该URL。建议使用POST方式。具体使用方法请参考",
required = true,
example = "请参考响应示例"
)
/**
* 用于跳转支付平台页面的信息POST和GET方法生成内容不同使用POST方法执行结果为html form表单在浏览器渲染即可<br>使用GET方法会得到支付平台URL需要打开或重定向到该URL。建议使用POST方式。
*
* @mock 请参考响应示例
*/
@NotNull
private String pageRedirectionData;
}

View File

@@ -0,0 +1,13 @@
{
"framework": "sop",
"outPath": "target/doc",
"projectName": "项目",
"packageFilters": "com.gitee.sop.payment.open.*",
"openUrl": "http://localhost:7700/api", // torna服务器地址
"appToken": "34ff76952462413982d21219cf099d46", // torna应用token
"debugEnvName":"本地环境",
"debugEnvUrl":"http://127.0.0.1:8081",
"tornaDebug": true,
"replace": true,
"showValidation": false
}

View File

@@ -89,6 +89,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 文档推送 -->
<plugin>
<groupId>com.ly.smart-doc</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>3.0.9</version>
<configuration>
<!--指定生成文档的使用的配置文件-->
<configFile>./src/main/resources/smart-doc.json</configFile>
<!--指定项目名称-->
<projectName>${project.artifactId}</projectName>
</configuration>
<dependencies>
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-support</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View File

@@ -13,13 +13,20 @@ import javax.validation.constraints.Size;
import java.util.List;
/**
* 开放接口定义
* 故事服务
*
* @author 六如
* @dubbo
*/
@Api("故事服务")
public interface OpenStory {
/**
* 新增故事
*
* @param storySaveRequest 入参
* @return 返回id
*/
@Open("story.save")
Integer save(StorySaveRequest storySaveRequest);

View File

@@ -1,6 +1,8 @@
package com.gitee.sop.storyweb.open.req;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.context.annotation.Lazy;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@@ -14,9 +16,16 @@ import java.util.Date;
public class StorySaveRequest implements Serializable {
private static final long serialVersionUID = -1214422742659231037L;
/**
* 故事名称
*/
@NotBlank(message = "故事名称必填")
@Length(max = 64)
private String storyName;
/**
* 添加时间
*/
@NotNull(message = "添加时间必填")
private Date addTime;

View File

@@ -0,0 +1,13 @@
{
"framework": "sop",
"outPath": "target/doc",
"projectName": "项目",
"packageFilters": "com.gitee.sop.storyweb.open.*",
"openUrl": "http://localhost:7700/api", // torna服务器地址
"appToken": "a358e4059b17440aae66343f4ec89001", // torna应用token
"debugEnvName":"本地环境",
"debugEnvUrl":"http://127.0.0.1:8081",
"tornaDebug": true,
"replace": true,
"showValidation": false
}

View File

@@ -4,39 +4,52 @@ import com.gitee.sop.support.message.I18nMessage;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* @author 六如
*/
@Getter
@AllArgsConstructor
public enum CodeEnum implements I18nMessage {
SUCCESS("0"),
SUCCESS("0", "成功"),
/**
* 认证异常
*/
AUTH("20001"),
AUTH("20001", "认证异常"),
/**
* 缺少参数
*/
MISSING("40001"),
MISSING("40001", "缺少参数"),
/**
* 错误参数
*/
INVALID("40002"),
INVALID("40002", "错误参数"),
/**
* 业务异常
*/
BIZ("50003"),
BIZ("50003", "业务异常"),
/**
* 权限异常
*/
ISV_PERM("40006"),
ISV_PERM("40006", "权限异常"),
/**
* 未知异常
*/
UNKNOWN("99999");
UNKNOWN("99999", "未知异常");
private final String configKey;
private final String configValue;
public static CodeEnum of(String code) {
for (CodeEnum value : CodeEnum.values()) {
if (Objects.equals(value.configKey, code)) {
return value;
}
}
return UNKNOWN;
}
public String getCode() {
return configKey;

View File

@@ -0,0 +1,79 @@
package com.gitee.sop.gateway;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.gitee.sop.gateway.message.CodeEnum;
import com.gitee.sop.gateway.message.ErrorEnum;
import com.gitee.sop.support.message.OpenMessageFactory;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author 六如
*/
public class ErrorCodePrintTest {
@Test
public void print() {
OpenMessageFactory.initMessage();
ErrorEnum[] values = ErrorEnum.values();
Map<String, List<ErrorEnum>> codeMap = Stream.of(values)
.collect(Collectors.groupingBy(ErrorEnum::getCode));
AtomicInteger id = new AtomicInteger();
List<ErrorInfo> errorList = codeMap.entrySet()
.stream()
.map(entry -> {
ErrorInfo errorInfo = new ErrorInfo();
errorInfo.setId(id.incrementAndGet());
errorInfo.setCode(entry.getKey());
errorInfo.setMsg(CodeEnum.of(entry.getKey()).getConfigValue());
List<SubError> collect = entry.getValue()
.stream()
.filter(errorEnum -> StringUtils.hasText(errorEnum.getSubCode()))
.map(errorEnum -> {
SubError subError = new SubError();
subError.setId(id.incrementAndGet());
subError.setSub_code(errorEnum.getSubCode());
subError.setSub_msg(errorEnum.getError(Locale.CHINESE).getSubMsg());
subError.setSolution("");
return subError;
})
.collect(Collectors.toList());
errorInfo.setChildren(collect);
return errorInfo;
})
.collect(Collectors.toList());
System.out.println(JSON.toJSONString(errorList, JSONWriter.Feature.PrettyFormat));
}
@Data
static class ErrorInfo {
private Integer id;
private String code;
private String msg;
private List<SubError> children;
}
@Data
static class SubError {
private Integer id;
private String sub_code;
private String sub_msg;
private String solution;
}
}

View File

@@ -37,6 +37,12 @@
<version>1.18.34</version>
<optional>true</optional>
</dependency>
<!-- 文档生成 -->
<dependency>
<groupId>com.ly.smart-doc</groupId>
<artifactId>smart-doc</artifactId>
<version>3.0.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View File

@@ -0,0 +1,22 @@
package com.gitee.sop.support.doc;
import lombok.Data;
/**
* @author 六如
*/
@Data
public class OpenAnnotationInfo {
/**
* 接口名
*/
private String value;
/**
* 版本号
*/
private String version;
}

View File

@@ -0,0 +1,235 @@
package com.gitee.sop.support.doc;
import com.gitee.sop.support.doc.constants.OpenAnnotationConstants;
import com.gitee.sop.support.doc.helper.ParamsBuildHelper;
import com.ly.doc.builder.ProjectDocConfigBuilder;
import com.ly.doc.constants.DocGlobalConstants;
import com.ly.doc.constants.DocTags;
import com.ly.doc.constants.ParamTypeConstants;
import com.ly.doc.model.ApiConfig;
import com.ly.doc.model.ApiParam;
import com.ly.doc.model.RpcJavaMethod;
import com.ly.doc.model.annotation.FrameworkAnnotations;
import com.ly.doc.template.RpcDocBuildTemplate;
import com.ly.doc.utils.DocClassUtil;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.JavaClassUtil;
import com.ly.doc.utils.JavaClassValidateUtil;
import com.ly.doc.utils.JavaFieldUtil;
import com.power.common.util.StringUtil;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.JavaType;
import com.thoughtworks.qdox.model.expression.AnnotationValue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解析文档
* <p>
* 参考:<a href="https://smart-doc-group.github.io/zh/guide/advanced/expand">smart-doc扩展</a>
* </p>
*
* @author 六如
*/
public class SopDocBuildTemplate extends RpcDocBuildTemplate {
public static final String DEFAULT_VERSION = "1.0";
@Override
public boolean supportsFramework(String s) {
return "sop".equalsIgnoreCase(s);
}
@Override
public List<ApiParam> requestParams(final JavaMethod javaMethod, ProjectDocConfigBuilder builder,
AtomicInteger atomicInteger, Map<String, JavaType> actualTypesMap) {
boolean isStrict = builder.getApiConfig().isStrict();
boolean isShowJavaType = builder.getApiConfig().getShowJavaType();
boolean isShowValidation = builder.getApiConfig().isShowValidation();
String className = javaMethod.getDeclaringClass().getCanonicalName();
Map<String, String> paramTagMap = DocUtil.getCommentsByTag(javaMethod, DocTags.PARAM, className);
List<JavaParameter> parameterList = javaMethod.getParameters();
if (parameterList.isEmpty()) {
return null;
}
ClassLoader classLoader = builder.getApiConfig().getClassLoader();
List<ApiParam> paramList = new ArrayList<>();
ParamsBuildHelper paramsBuildHelper = new ParamsBuildHelper();
for (JavaParameter parameter : parameterList) {
boolean required = false;
String paramName = parameter.getName();
String typeName = this.replaceTypeName(parameter.getType().getGenericCanonicalName(), actualTypesMap,
Boolean.FALSE);
String simpleName = this.replaceTypeName(parameter.getType().getValue(), actualTypesMap, Boolean.FALSE)
.toLowerCase();
String fullTypeName = this.replaceTypeName(parameter.getType().getFullyQualifiedName(), actualTypesMap,
Boolean.FALSE);
String paramPre = "";
if (!paramTagMap.containsKey(paramName) && JavaClassValidateUtil.isPrimitive(fullTypeName) && isStrict) {
throw new RuntimeException("ERROR: Unable to find javadoc @param for actual param \"" + paramName
+ "\" in method " + javaMethod.getName() + " from " + className);
}
StringBuilder comment = new StringBuilder(this.paramCommentResolve(paramTagMap.get(paramName)));
String mockValue = JavaFieldUtil.createMockValue(paramTagMap, paramName, typeName, typeName);
JavaClass javaClass = builder.getJavaProjectBuilder().getClassByName(fullTypeName);
List<JavaAnnotation> annotations = parameter.getAnnotations();
for (JavaAnnotation a : annotations) {
if (JavaClassValidateUtil.isJSR303Required(a.getType().getValue())) {
required = true;
}
}
comment.append(JavaFieldUtil.getJsrComment(isShowValidation, classLoader, annotations));
Set<String> groupClasses = JavaClassUtil.getParamGroupJavaClass(annotations,
builder.getJavaProjectBuilder());
Set<String> paramJsonViewClasses = JavaClassUtil.getParamJsonViewClasses(annotations, builder);
if (JavaClassValidateUtil.isCollection(fullTypeName) || JavaClassValidateUtil.isArray(fullTypeName)) {
if (JavaClassValidateUtil.isCollection(typeName)) {
typeName = typeName + "<T>";
}
String[] gicNameArr = DocClassUtil.getSimpleGicName(typeName);
String gicName = gicNameArr[0];
if (JavaClassValidateUtil.isArray(gicName)) {
gicName = gicName.substring(0, gicName.indexOf("["));
}
if (JavaClassValidateUtil.isPrimitive(gicName)) {
String processedType = isShowJavaType ? JavaClassUtil.getClassSimpleName(typeName)
: DocClassUtil.processTypeNameForParams(simpleName);
ApiParam param = ApiParam.of()
.setId(atomicInteger.incrementAndGet())
.setField(paramName)
.setDesc(comment + " (children type : " + gicName + ")")
.setRequired(required)
.setType(processedType);
paramList.add(param);
} else {
paramList.addAll(paramsBuildHelper.buildParams(gicNameArr[0], paramPre, 0, "true", Boolean.FALSE,
new HashMap<>(16), builder, groupClasses, paramJsonViewClasses, 0, Boolean.FALSE,
atomicInteger));
}
} else if (JavaClassValidateUtil.isPrimitive(fullTypeName)) {
ApiParam param = ApiParam.of()
.setId(atomicInteger.incrementAndGet())
.setField(paramName)
.setType(JavaClassUtil.getClassSimpleName(typeName))
.setDesc(comment.toString())
.setRequired(required)
.setMaxLength(JavaFieldUtil.getParamMaxLength(parameter.getAnnotations()))
.setValue(mockValue)
.setVersion(DocGlobalConstants.DEFAULT_VERSION);
paramList.add(param);
} else if (JavaClassValidateUtil.isMap(fullTypeName)) {
if (JavaClassValidateUtil.isMap(typeName)) {
ApiParam apiParam = ApiParam.of()
.setId(atomicInteger.incrementAndGet())
.setField(paramName)
.setType(typeName)
.setDesc(comment.toString())
.setRequired(required)
.setVersion(DocGlobalConstants.DEFAULT_VERSION);
paramList.add(apiParam);
continue;
}
String[] gicNameArr = DocClassUtil.getSimpleGicName(typeName);
paramList.addAll(paramsBuildHelper.buildParams(gicNameArr[1], paramPre, 0, "true", Boolean.FALSE,
new HashMap<>(16), builder, groupClasses, paramJsonViewClasses, 0, Boolean.FALSE,
atomicInteger));
} else if (javaClass.isEnum()) {
ApiParam param = ApiParam.of()
.setId(atomicInteger.incrementAndGet())
.setField(paramName)
.setType(ParamTypeConstants.PARAM_TYPE_ENUM)
.setRequired(required)
.setDesc(comment.toString())
.setVersion(DocGlobalConstants.DEFAULT_VERSION);
paramList.add(param);
} else {
List<ApiParam> aTrue = paramsBuildHelper.buildParams(typeName, paramPre, 0, "true", Boolean.FALSE, new HashMap<>(16),
builder, groupClasses, paramJsonViewClasses, 0, Boolean.FALSE, atomicInteger);
paramList.addAll(
aTrue);
}
}
return paramList;
}
@Override
public RpcJavaMethod convertToJavadocJavaMethod(ApiConfig apiConfig, JavaMethod method, Map<String, JavaType> actualTypesMap) {
RpcJavaMethod rpcJavaMethod = super.convertToJavadocJavaMethod(apiConfig, method, actualTypesMap);
Optional<OpenAnnotationInfo> openAnnotationInfoOpt = getOpenAnnotationInfo(method);
rpcJavaMethod.setVersion(openAnnotationInfoOpt.map(OpenAnnotationInfo::getVersion).orElse(DEFAULT_VERSION));
String desc = DocUtil.getEscapeAndCleanComment(method.getComment());
if (StringUtil.isEmpty(desc)) {
desc = openAnnotationInfoOpt.map(OpenAnnotationInfo::getValue).orElse("");
}
rpcJavaMethod.setDesc(desc);
return rpcJavaMethod;
}
// 返回接口名称+版本号
// 格式:接口名称#版本号
@Override
public String methodDefinition(JavaMethod method, Map<String, JavaType> actualTypesMap) {
return getOpenAnnotationInfo(method)
.map(this::buildUrl)
.orElseThrow(() -> new RuntimeException("未指定@Open"));
}
protected String buildUrl(OpenAnnotationInfo openAnnotationInfo) {
return openAnnotationInfo.getValue() + "#" + openAnnotationInfo.getVersion();
}
protected String getVersion(JavaMethod method) {
return getOpenAnnotationInfo(method).map(OpenAnnotationInfo::getVersion)
.orElse(DEFAULT_VERSION);
}
protected Optional<OpenAnnotationInfo> getOpenAnnotationInfo(JavaMethod method) {
List<JavaAnnotation> annotations = method.getAnnotations();
for (JavaAnnotation annotation : annotations) {
String simpleAnnotationName = annotation.getType().getValue();
if (OpenAnnotationConstants.OPEN_ANNOTATION_NAME.equalsIgnoreCase(simpleAnnotationName)) {
AnnotationValue value = annotation.getProperty(OpenAnnotationConstants.PROP_VALUE);
AnnotationValue version = annotation.getProperty(OpenAnnotationConstants.PROP_VERSION);
OpenAnnotationInfo openAnnotationInfo = new OpenAnnotationInfo();
openAnnotationInfo.setValue(StringUtil.removeQuotes(value.toString()));
openAnnotationInfo.setVersion(Optional.ofNullable(version)
.map(annotationValue -> StringUtil.removeQuotes(annotationValue.toString()))
.orElse(DEFAULT_VERSION));
return Optional.of(openAnnotationInfo);
}
}
return Optional.empty();
}
@Override
public boolean isEntryPoint(JavaClass javaClass, FrameworkAnnotations frameworkAnnotations) {
// 必须是接口
if (!javaClass.isInterface()) {
return false;
}
List<JavaMethod> methods = javaClass.getMethods();
if (methods == null || methods.isEmpty()) {
return false;
}
for (JavaMethod method : methods) {
if (getOpenAnnotationInfo(method).isPresent()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,13 @@
package com.gitee.sop.support.doc.constants;
/**
* @author 六如
*/
public class OpenAnnotationConstants {
public static final String OPEN_ANNOTATION_NAME = "Open";
public static final String PROP_VALUE = "value";
public static final String PROP_VERSION = "version";
}

View File

@@ -0,0 +1,295 @@
/*
* Copyright (C) 2018-2024 smart-doc
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.gitee.sop.support.doc.helper;
import com.ly.doc.builder.ProjectDocConfigBuilder;
import com.ly.doc.constants.DocAnnotationConstants;
import com.ly.doc.constants.DocTags;
import com.ly.doc.constants.JSRAnnotationConstants;
import com.ly.doc.model.CustomField;
import com.ly.doc.model.CustomFieldInfo;
import com.ly.doc.model.DocJavaField;
import com.ly.doc.model.FieldJsonAnnotationInfo;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.JavaClassUtil;
import com.ly.doc.utils.JavaClassValidateUtil;
import com.power.common.util.CollectionUtil;
import com.power.common.util.StringEscapeUtil;
import com.power.common.util.StringUtil;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.expression.AnnotationValue;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Abstract Base helper
*
* @author yu3.sun on 2022/10/14
*/
public abstract class BaseHelper {
/**
* get field value from mock tag
*
* @param subTypeName subType name
* @param tagsMap tags map
* @param typeSimpleName type simple name
* @return field value
*/
protected String getFieldValueFromMockForJson(String subTypeName, Map<String, String> tagsMap,
String typeSimpleName) {
String fieldValue = "";
if (tagsMap.containsKey(DocTags.MOCK) && StringUtil.isNotEmpty(tagsMap.get(DocTags.MOCK))) {
fieldValue = tagsMap.get(DocTags.MOCK);
fieldValue = StringEscapeUtil.unescapeJava(fieldValue);
if (!DocUtil.javaPrimaryType(typeSimpleName) && !JavaClassValidateUtil.isCollection(subTypeName)
&& !JavaClassValidateUtil.isMap(subTypeName) && !JavaClassValidateUtil.isArray(subTypeName)) {
fieldValue = StringEscapeUtil.escapeJava(fieldValue, true);
fieldValue = DocUtil.handleJsonStr(fieldValue);
}
}
return fieldValue;
}
/**
* get field value from mock tag
*
* @param tagsMap tags map
* @return field value
*/
protected String getFieldValueFromMock(Map<String, String> tagsMap) {
String fieldValue = "";
if (tagsMap.containsKey(DocTags.MOCK) && StringUtil.isNotEmpty(tagsMap.get(DocTags.MOCK))) {
fieldValue = StringEscapeUtil.unescapeJava(tagsMap.get(DocTags.MOCK));
}
return fieldValue;
}
/**
* check custom field is ignored
*
* @param docField doc field
* @param isResp is resp
* @param customRequestField custom request field
* @param customResponseField custom response field
* @return boolean
*/
protected boolean isIgnoreCustomField(DocJavaField docField, boolean isResp, CustomField customRequestField,
CustomField customResponseField) {
if (Objects.nonNull(customRequestField) && JavaClassUtil.isTargetChildClass(docField.getDeclaringClassName(),
customRequestField.getOwnerClassName()) && (customRequestField.isIgnore()) && !isResp) {
return true;
}
return Objects.nonNull(customResponseField) && JavaClassUtil
.isTargetChildClass(docField.getDeclaringClassName(), customResponseField.getOwnerClassName())
&& (customResponseField.isIgnore()) && isResp;
}
/**
* check field is transient
*
* @param field field
* @param projectBuilder project builder
* @param isResp is resp
* @return boolean
*/
protected boolean isTransientField(JavaField field, ProjectDocConfigBuilder projectBuilder, boolean isResp) {
if (field.isTransient()) {
return (projectBuilder.getApiConfig().isSerializeRequestTransients() && !isResp)
|| (projectBuilder.getApiConfig().isSerializeResponseTransients() && isResp);
}
return false;
}
/**
* Get a field JSON annotation information for a given field.
*
* @param projectBuilder the project builder
* @param docField the doc of java field
* @param isResp the response flag for the parameter
* @param groupClasses the group classes
* @param methodJsonViewClasses the method JSON view classes
* @return the field JSON annotation information {@link FieldJsonAnnotationInfo}
*/
protected FieldJsonAnnotationInfo getFieldJsonAnnotationInfo(ProjectDocConfigBuilder projectBuilder,
DocJavaField docField, boolean isResp, Set<String> groupClasses, Set<String> methodJsonViewClasses) {
FieldJsonAnnotationInfo fieldJsonAnnotationInfo = new FieldJsonAnnotationInfo();
// Handle @JsonView; if the field is not annotated with @JsonView, skip
if (!methodJsonViewClasses.isEmpty() && isResp && docField.getAnnotations().isEmpty()) {
return fieldJsonAnnotationInfo;
}
for (JavaAnnotation annotation : docField.getAnnotations()) {
// if the field is annotated with @JsonIgnore || @JsonProperty, then
// check if it belongs to the groupClasses
if (JavaClassValidateUtil.isIgnoreFieldJson(annotation, isResp)) {
fieldJsonAnnotationInfo.setIgnore(true);
continue;
}
// Handle @JsonView
if (JavaClassUtil.shouldExcludeFieldFromJsonView(annotation, methodJsonViewClasses, isResp,
projectBuilder)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}
String annotationName = annotation.getType().getValue();
// if the field is annotated with @JsonSerialize
if (DocAnnotationConstants.SHORT_JSON_SERIALIZE.equals(annotationName)
&& DocAnnotationConstants.TO_STRING_SERIALIZER_USING
.equals(annotation.getNamedParameter(DocAnnotationConstants.USING_PROP))) {
fieldJsonAnnotationInfo.setToStringSerializer(true);
continue;
}
// if the field is annotated with @Null And isResp is false
if (JSRAnnotationConstants.NULL.equals(annotationName) && !isResp) {
if (CollectionUtil.isEmpty(groupClasses)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}
Set<String> groupClassList = JavaClassUtil.getParamGroupJavaClass(annotation);
for (String javaClass : groupClassList) {
if (groupClasses.contains(javaClass)) {
fieldJsonAnnotationInfo.setIgnore(true);
return fieldJsonAnnotationInfo;
}
}
}
// Handle @JSONField
if (DocAnnotationConstants.SHORT_JSON_FIELD.equals(annotationName)) {
if (null != annotation.getProperty(DocAnnotationConstants.NAME_PROP)) {
AnnotationValue annotationValue = annotation.getProperty(DocAnnotationConstants.NAME_PROP);
String fieldName = DocUtil.resolveAnnotationValue(projectBuilder.getApiConfig().getClassLoader(),
annotationValue);
fieldJsonAnnotationInfo.setFieldName(fieldName);
}
}
// Handle @JsonProperty
else if (DocAnnotationConstants.SHORT_JSON_PROPERTY.equals(annotationName)
|| DocAnnotationConstants.GSON_ALIAS_NAME.equals(annotationName)) {
AnnotationValue annotationValue = annotation.getProperty(DocAnnotationConstants.VALUE_PROP);
String fieldName = DocUtil.resolveAnnotationValue(projectBuilder.getApiConfig().getClassLoader(),
annotationValue);
fieldJsonAnnotationInfo.setFieldName(fieldName);
}
// Handle JSR303 required
if (JavaClassValidateUtil.isJSR303Required(annotationName) && !isResp) {
Set<String> groupClassList = JavaClassUtil.getParamGroupJavaClass(annotation);
// Check if groupClasses contains any element from
// groupClassList
boolean hasGroup = groupClassList.stream().anyMatch(groupClasses::contains);
if (hasGroup) {
fieldJsonAnnotationInfo.setStrRequired(true);
} else if (CollectionUtil.isEmpty(groupClasses)) {
// If the annotation is @Valid or @Validated, the Default
// group is added by default and groupClasses will not be
// empty;
// In other cases, if groupClasses is still empty, then
// strRequired is false.
fieldJsonAnnotationInfo.setStrRequired(false);
}
}
// Handle @JsonFormat
if (DocAnnotationConstants.JSON_FORMAT.equals(annotationName)) {
fieldJsonAnnotationInfo.setFieldJsonFormatType(
DocUtil.processFieldTypeNameByJsonFormat(projectBuilder.getApiConfig().getShowJavaType(),
docField.getTypeFullyQualifiedName(), annotation));
fieldJsonAnnotationInfo
.setFieldJsonFormatValue(DocUtil.getJsonFormatString(docField.getJavaField(), annotation));
}
}
return fieldJsonAnnotationInfo;
}
/**
* Get the custom field information for a given field.
*
* @param projectBuilder the project builder
* @param docField the doc of java field
* @param customResponseField the custom response field
* @param customRequestField the custom request field
* @param isResp the response flag for the parameter
* @param simpleName the simple name of the field
* @return the custom field information {@link CustomFieldInfo}
*/
protected CustomFieldInfo getCustomFieldInfo(ProjectDocConfigBuilder projectBuilder, DocJavaField docField,
CustomField customResponseField, CustomField customRequestField, boolean isResp, String simpleName) {
CustomFieldInfo customFieldInfo = new CustomFieldInfo();
// ignore custom field, if true return quickly
if (isIgnoreCustomField(docField, isResp, customRequestField, customResponseField)) {
customFieldInfo.setIgnore(true);
return customFieldInfo;
}
// cover response value
if (Objects.nonNull(customResponseField) && isResp && Objects.nonNull(customResponseField.getValue())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName())) {
customFieldInfo.setFieldValue(String.valueOf(customResponseField.getValue()));
}
// cover request value
if (Objects.nonNull(customRequestField) && !isResp && Objects.nonNull(customRequestField.getValue())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName())) {
customFieldInfo.setFieldValue(String.valueOf(customRequestField.getValue()));
}
// cover required
if (Objects.nonNull(customRequestField) && !isResp
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName())
&& customRequestField.isRequire()) {
customFieldInfo.setStrRequired(true);
}
// cover comment
if (Objects.nonNull(customRequestField) && StringUtil.isNotEmpty(customRequestField.getDesc())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName()) && !isResp) {
customFieldInfo.setComment(customRequestField.getDesc());
}
if (Objects.nonNull(customResponseField) && StringUtil.isNotEmpty(customResponseField.getDesc())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName()) && isResp) {
customFieldInfo.setComment(customResponseField.getDesc());
}
// cover fieldName
if (Objects.nonNull(customRequestField) && StringUtil.isNotEmpty(customRequestField.getReplaceName())
&& JavaClassUtil.isTargetChildClass(simpleName, customRequestField.getOwnerClassName()) && !isResp) {
customFieldInfo.setFieldName(customRequestField.getReplaceName());
}
if (Objects.nonNull(customResponseField) && StringUtil.isNotEmpty(customResponseField.getReplaceName())
&& JavaClassUtil.isTargetChildClass(simpleName, customResponseField.getOwnerClassName()) && isResp) {
customFieldInfo.setFieldName(customResponseField.getReplaceName());
}
return customFieldInfo;
}
}

View File

@@ -0,0 +1,962 @@
/*
* smart-doc https://github.com/smart-doc-group/smart-doc
*
* Copyright (C) 2018-2024 smart-doc
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.gitee.sop.support.doc.helper;
import com.ly.doc.builder.ProjectDocConfigBuilder;
import com.ly.doc.constants.DocGlobalConstants;
import com.ly.doc.constants.DocTags;
import com.ly.doc.constants.JavaTypeConstants;
import com.ly.doc.constants.ParamTypeConstants;
import com.ly.doc.extension.json.PropertyNameHelper;
import com.ly.doc.extension.json.PropertyNamingStrategies;
import com.ly.doc.model.ApiConfig;
import com.ly.doc.model.ApiDataDictionary;
import com.ly.doc.model.ApiParam;
import com.ly.doc.model.CustomField;
import com.ly.doc.model.CustomFieldInfo;
import com.ly.doc.model.DocJavaField;
import com.ly.doc.model.FieldJsonAnnotationInfo;
import com.ly.doc.model.torna.EnumInfoAndValues;
import com.ly.doc.utils.DocClassUtil;
import com.ly.doc.utils.DocUtil;
import com.ly.doc.utils.JavaClassUtil;
import com.ly.doc.utils.JavaClassValidateUtil;
import com.ly.doc.utils.JavaFieldUtil;
import com.ly.doc.utils.ParamUtil;
import com.power.common.model.EnumDictionary;
import com.power.common.util.StringUtil;
import com.thoughtworks.qdox.model.JavaAnnotation;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* ApiParam Builder {@link ApiParam }
*
* @author yu 2019/12/21.
*/
public class ParamsBuildHelper extends BaseHelper {
/**
* Builds a parameter list based on field information.
*
* @param className The name of the generic type.
* @param pre A prefix builder for nested fields.
* @param level The next level of nesting.
* @param isRequired Indicates whether the parameter is required.
* @param isResp Indicates whether the parameter is a response parameter.
* @param registryClasses A collection of registered classes.
* @param projectBuilder A project builder instance.
* @param groupClasses A collection of JSR303 grouped classes.
* @param methodJsonViewClasses A set of valid `@JsonView` classes on controller
* method.
* @param pid The parent ID of the field.
* @param jsonRequest The JSON request object.
* @param atomicInteger An AtomicInteger for ID generation.
* <p>
* This method handles various types of fields and their values, including handling
* self-referential loops, maps, arrays, objects, and primitive types. It adds
* parameters to the paramList based on the type and structure of the given field,
* recursively calling itself for nested or complex types.
* @return A List of ApiParam instances representing the built parameters.
*/
public List<ApiParam> buildParams(String className, String pre, int level, String isRequired, boolean isResp,
Map<String, String> registryClasses, ProjectDocConfigBuilder projectBuilder, Set<String> groupClasses,
Set<String> methodJsonViewClasses, int pid, boolean jsonRequest, AtomicInteger atomicInteger) {
if (StringUtil.isEmpty(className)) {
throw new RuntimeException("Class name can't be null or empty.");
}
// Recursion limit check cached for efficiency
ApiConfig apiConfig = projectBuilder.getApiConfig();
int recursionLimit = apiConfig.getRecursionLimit();
if (level > recursionLimit) {
return Collections.emptyList();
}
int nextLevel = level + 1;
// Check circular reference
if (registryClasses.containsKey(className) && level > registryClasses.size()) {
return Collections.emptyList();
}
// Registry class
registryClasses.put(className, className);
String simpleName = DocClassUtil.getSimpleName(className);
String[] globGicName = DocClassUtil.getSimpleGicName(className);
if (Objects.isNull(globGicName) || globGicName.length < 1) {
JavaClass cls = projectBuilder.getClassByName(simpleName);
// obtain generics from parent class
JavaClass superJavaClass = Objects.nonNull(cls) ? cls.getSuperJavaClass() : null;
if (Objects.nonNull(superJavaClass)
&& !JavaTypeConstants.OBJECT_SIMPLE_NAME.equals(superJavaClass.getSimpleName())) {
globGicName = DocClassUtil.getSimpleGicName(superJavaClass.getGenericFullyQualifiedName());
}
}
boolean isShowJavaType = apiConfig.getShowJavaType();
// if is primitive
if (JavaClassValidateUtil.isPrimitive(simpleName)) {
return handlePrimitiveType(simpleName, isShowJavaType, atomicInteger, pid);
}
// Handle collection types
if (JavaClassValidateUtil.isCollection(simpleName) || JavaClassValidateUtil.isArray(simpleName)) {
return handleCollectionOrArrayType(globGicName, pre, level, isRequired, isResp, registryClasses,
projectBuilder, groupClasses, methodJsonViewClasses, pid, jsonRequest, atomicInteger, simpleName);
}
// Handle map types
if (JavaClassValidateUtil.isMap(simpleName)) {
return buildMapParam(globGicName, pre, level, isRequired, isResp, registryClasses, projectBuilder,
groupClasses, methodJsonViewClasses, pid, jsonRequest, nextLevel, atomicInteger);
}
// Handle generic object types
if (JavaTypeConstants.JAVA_OBJECT_FULLY.equals(className)) {
return buildGenericObjectParam(className, pre, isRequired, atomicInteger, pid);
}
// Handle Reactor types
if (JavaClassValidateUtil.isReactor(simpleName)) {
if (globGicName.length > 0) {
return buildParams(globGicName[0], pre, nextLevel, isRequired, isResp, registryClasses, projectBuilder,
groupClasses, methodJsonViewClasses, pid, jsonRequest, atomicInteger);
}
return Collections.emptyList();
}
// handle Class Field
return processFields(className, pre, level, isRequired, isResp, registryClasses, projectBuilder, groupClasses,
methodJsonViewClasses, pid, jsonRequest, atomicInteger);
}
/**
* Processes fields of a given class and populates a list of API parameters based on
* the field types and properties.
*
* @param className The name of the class containing the fields.
* @param pre A prefix to prepend to field names.
* @param level The current level of nested fields.
* @param isRequired Indicates if the field is required.
* @param isResp Indicates if the field is part of a response.
* @param registryClasses A map of registry classes used for type resolution.
* @param projectBuilder The project builder used to access project-specific details.
* @param groupClasses A set of group classes relevant to the field processing.
* @param methodJsonViewClasses A set of JSON view classes applicable to methods.
* @param pid The parent ID of the current field.
* @param jsonRequest Indicates if the field is part of a JSON request.
* @param atomicInteger An AtomicInteger used for generating unique IDs.
* @return A list of API parameters representing the processed fields.
*/
public List<ApiParam> processFields(String className, String pre, int level, String isRequired,
boolean isResp, Map<String, String> registryClasses, ProjectDocConfigBuilder projectBuilder,
Set<String> groupClasses, Set<String> methodJsonViewClasses, int pid, boolean jsonRequest,
AtomicInteger atomicInteger) {
if (StringUtil.isEmpty(className)) {
throw new RuntimeException("Class name can't be null or empty.");
}
// Check for recursion limit to avoid infinite loops
int recursionLimit = projectBuilder.getApiConfig().getRecursionLimit();
// Early exit when recursion limit is hit
if (level > recursionLimit) {
return Collections.emptyList();
}
// Avoid processing the same class multiple times
if (registryClasses.containsKey(className) && level > registryClasses.size()) {
return Collections.emptyList();
}
List<ApiParam> paramList = new ArrayList<>();
String simpleName = DocClassUtil.getSimpleName(className);
JavaClass cls = projectBuilder.getClassByName(simpleName);
boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
boolean requestFieldToUnderline = projectBuilder.getApiConfig().isRequestFieldToUnderline();
boolean responseFieldToUnderline = projectBuilder.getApiConfig().isResponseFieldToUnderline();
boolean displayActualType = projectBuilder.getApiConfig().isDisplayActualType();
ClassLoader classLoader = projectBuilder.getApiConfig().getClassLoader();
PropertyNamingStrategies.NamingBase fieldNameConvert = null;
// ignore
if (Objects.nonNull(cls)) {
List<JavaAnnotation> clsAnnotation = cls.getAnnotations();
fieldNameConvert = PropertyNameHelper.translate(clsAnnotation);
}
String[] globGicName = DocClassUtil.getSimpleGicName(className);
Map<String, String> genericMap = new HashMap<>(globGicName.length);
JavaClassUtil.genericParamMap(genericMap, cls, globGicName);
Map<String, String> ignoreFields = JavaClassUtil.getClassJsonIgnoreFields(cls);
List<DocJavaField> fields = JavaClassUtil.getFields(cls, 0, new LinkedHashMap<>(), classLoader);
for (DocJavaField docField : fields) {
JavaField field = docField.getJavaField();
// ignore transient field
if (isTransientField(field, projectBuilder, isResp)) {
continue;
}
String maxLength = JavaFieldUtil.getParamMaxLength(field.getAnnotations());
StringBuilder comment = new StringBuilder();
comment.append(docField.getComment());
String fieldName = docField.getFieldName();
if (Objects.nonNull(fieldNameConvert)) {
fieldName = fieldNameConvert.translate(fieldName);
}
if (ignoreFields.containsKey(fieldName)) {
continue;
}
String subTypeName = docField.getTypeFullyQualifiedName();
boolean needToUnderline = (responseFieldToUnderline && isResp) || (requestFieldToUnderline && !isResp);
if (needToUnderline) {
fieldName = StringUtil.camelToUnderline(fieldName);
}
String typeSimpleName = field.getType().getSimpleName();
String fieldGicName = docField.getTypeGenericCanonicalName();
List<JavaAnnotation> javaAnnotations = docField.getAnnotations();
Map<String, String> tagsMap = DocUtil.getFieldTagsValue(field, docField);
// since tag value
String since = DocGlobalConstants.DEFAULT_VERSION;
if (tagsMap.containsKey(DocTags.SINCE)) {
since = tagsMap.get(DocTags.SINCE);
}
// handle extension
Map<String, String> extensions = DocUtil.getCommentsByTag(field.getTagsByName(DocTags.EXTENSION),
DocTags.EXTENSION);
Map<String, Object> extensionParams = new HashMap<>(extensions.size());
if (!extensions.isEmpty()) {
extensions.forEach((k, v) -> extensionParams.put(k, DocUtil.detectTagValue(v)));
}
boolean strRequired = false;
CustomField.Key key = CustomField.Key.create(docField.getDeclaringClassName(), fieldName);
CustomField customResponseField = CustomField.nameEquals(key, projectBuilder.getCustomRespFieldMap());
CustomField customRequestField = CustomField.nameEquals(key, projectBuilder.getCustomReqFieldMap());
CustomFieldInfo customFieldInfo = getCustomFieldInfo(projectBuilder, docField, customResponseField,
customRequestField, isResp, simpleName);
// ignore custom field
if (Boolean.TRUE.equals(customFieldInfo.getIgnore())) {
continue;
}
// field json annotation
FieldJsonAnnotationInfo annotationInfo = getFieldJsonAnnotationInfo(projectBuilder, docField, isResp,
groupClasses, methodJsonViewClasses);
if (Boolean.TRUE.equals(annotationInfo.getIgnore())) {
continue;
}
// the param type from @JsonFormat
String fieldJsonFormatType = annotationInfo.getFieldJsonFormatType();
// the param value from @JsonFormat
String fieldJsonFormatValue = annotationInfo.getFieldJsonFormatValue();
// has Annotation @JsonSerialize And using ToStringSerializer
boolean toStringSerializer = Boolean.TRUE.equals(annotationInfo.getToStringSerializer());
if (Objects.nonNull(annotationInfo.getFieldName())) {
fieldName = annotationInfo.getFieldName();
}
if (Objects.nonNull(annotationInfo.getStrRequired())) {
strRequired = annotationInfo.getStrRequired();
}
for (JavaAnnotation annotation : field.getAnnotations()) {
if (JavaClassValidateUtil.isJSR303Required(annotation.getType().getValue())) {
strRequired = true;
break;
}
}
comment.append(JavaFieldUtil.getJsrComment(projectBuilder.getApiConfig().isShowValidation(), classLoader,
javaAnnotations));
// fix mock post form curl example error
String fieldValue = getFieldValueFromMock(tagsMap);
if (StringUtil.isNotEmpty(customFieldInfo.getFieldValue())) {
fieldValue = customFieldInfo.getFieldValue();
}
if (StringUtil.isNotEmpty(customFieldInfo.getFieldName())) {
fieldName = customFieldInfo.getFieldName();
}
if (StringUtil.isNotEmpty(customFieldInfo.getComment())) {
comment = new StringBuilder(customFieldInfo.getComment());
}
if (Objects.nonNull(customFieldInfo.getStrRequired())) {
strRequired = customFieldInfo.getStrRequired();
}
fieldName = fieldName.trim();
int nextLevel = level + 1;
// Analyzing File Type Field
if (JavaClassValidateUtil.isFile(fieldGicName)) {
ApiParam param = ApiParam.of()
.setField(pre + fieldName)
.setType(ParamTypeConstants.PARAM_TYPE_FILE)
.setClassName(className)
.setPid(pid)
.setId(atomicOrDefault(atomicInteger, paramList.size() + pid + 1))
.setMaxLength(maxLength)
.setDesc(comment.toString())
.setRequired(strRequired)
.setVersion(since)
.setExtensions(extensionParams);
if (fieldGicName.contains("[]") || fieldGicName.endsWith(">")) {
param.setType(ParamTypeConstants.PARAM_TYPE_FILE);
param.setDesc(comment.append("(array of file)").toString());
param.setHasItems(true);
}
paramList.add(param);
continue;
}
// Analyzing Map Type Field
if (JavaClassValidateUtil.isMap(subTypeName)) {
ApiParam param = ApiParam.of()
.setField(pre + fieldName)
.setType(ParamTypeConstants.PARAM_TYPE_OBJECT)
.setClassName(className)
.setPid(pid)
.setId(atomicOrDefault(atomicInteger, paramList.size() + pid + 1))
.setMaxLength(maxLength)
.setDesc(comment.toString())
.setRequired(strRequired)
.setVersion(since)
.setExtensions(extensionParams);
paramList.add(param);
List<ApiParam> apiParams = buildMapParam(DocClassUtil.getSimpleGicName(fieldGicName),
DocUtil.getIndentByLevel(level), level + 1, isRequired, isResp, registryClasses, projectBuilder,
groupClasses, methodJsonViewClasses, param.getId(), jsonRequest, nextLevel, atomicInteger);
paramList.addAll(apiParams);
continue;
}
// Analyzing Primitive Type Field
if (JavaClassValidateUtil.isPrimitive(subTypeName)) {
if (StringUtil.isEmpty(fieldValue)) {
fieldValue = StringUtil.isNotEmpty(fieldJsonFormatValue) ? fieldJsonFormatValue : StringUtil
.removeQuotes(DocUtil.getValByTypeAndFieldName(typeSimpleName, field.getName()));
}
ApiParam param = ApiParam.of()
.setClassName(className)
.setField(pre + fieldName)
.setPid(pid)
.setMaxLength(maxLength)
.setValue(fieldValue);
param.setId(atomicOrDefault(atomicInteger, paramList.size() + param.getPid() + 1));
String processedType = (isResp && toStringSerializer) ? "string"
: StringUtil.isNotEmpty(fieldJsonFormatType) ? fieldJsonFormatType
: processFieldTypeName(isShowJavaType, subTypeName);
param.setType(processedType);
param.setExtensions(extensionParams);
// handle param
commonHandleParam(paramList, param, isRequired, comment.toString(), since, strRequired);
JavaClass enumClass = ParamUtil.handleSeeEnum(param, field, projectBuilder, isResp || jsonRequest,
tagsMap, fieldJsonFormatValue);
if (Objects.nonNull(enumClass)) {
String enumClassComment = DocGlobalConstants.EMPTY;
if (StringUtil.isNotEmpty(enumClass.getComment())) {
enumClassComment = enumClass.getComment();
}
comment = new StringBuilder(
StringUtils.isEmpty(comment.toString()) ? enumClassComment : comment.toString());
String enumComment = handleEnumComment(enumClass, projectBuilder);
param.setDesc(comment + enumComment);
}
} else {
String appendComment = "";
if (displayActualType) {
if (globGicName.length > 0) {
String gicName = genericMap.get(subTypeName) != null ? genericMap.get(subTypeName)
: globGicName[0];
if (!simpleName.equals(gicName)) {
appendComment = " (ActualType: " + JavaClassUtil.getClassSimpleName(gicName) + ")";
}
}
if (Objects.nonNull(docField.getActualJavaType())) {
appendComment = " (ActualType: "
+ JavaClassUtil.getClassSimpleName(docField.getActualJavaType()) + ")";
}
}
StringBuilder preBuilder = DocUtil.getStringBuilderByLevel(level);
int fieldPid;
ApiParam param = ApiParam.of()
.setField(pre + fieldName)
.setClassName(className)
.setPid(pid)
.setMaxLength(maxLength);
param.setId(atomicOrDefault(atomicInteger, paramList.size() + param.getPid() + 1));
param.setExtensions(extensionParams);
String processedType;
if (fieldGicName.length() == 1) {
String gicName = JavaTypeConstants.JAVA_OBJECT_FULLY;
if (Objects.nonNull(genericMap.get(typeSimpleName))) {
gicName = genericMap.get(subTypeName);
} else {
if (globGicName.length > 0) {
gicName = globGicName[0];
}
}
if (JavaClassValidateUtil.isPrimitive(gicName)) {
processedType = DocClassUtil.processTypeNameForParams(gicName);
} else {
processedType = DocClassUtil.processTypeNameForParams(typeSimpleName.toLowerCase());
}
} else {
processedType = StringUtil.isNotEmpty(fieldJsonFormatType) ? fieldJsonFormatType
: processFieldTypeName(isShowJavaType, subTypeName);
}
param.setType(processedType);
JavaClass javaClass = field.getType();
if (javaClass.isEnum()) {
comment.append(handleEnumComment(javaClass, projectBuilder));
ParamUtil.handleSeeEnum(param, field, projectBuilder, isResp || jsonRequest, tagsMap,
fieldJsonFormatValue);
// hand Param
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
} else if (JavaClassValidateUtil.isCollection(subTypeName)
|| JavaClassValidateUtil.isArray(subTypeName)) {
if (isShowJavaType) {
// rpc
param.setType(
JavaFieldUtil.convertToSimpleTypeName(docField.getTypeGenericFullyQualifiedName()));
} else {
param.setType(ParamTypeConstants.PARAM_TYPE_ARRAY);
}
if (tagsMap.containsKey(DocTags.MOCK) && StringUtil.isNotEmpty(tagsMap.get(DocTags.MOCK))) {
param.setValue(fieldValue);
}
if (globGicName.length > 0 && JavaTypeConstants.JAVA_LIST_FULLY.equals(fieldGicName)) {
// no generic, just object
fieldGicName = fieldGicName + "<T>";
}
if (JavaClassValidateUtil.isArray(subTypeName)) {
fieldGicName = fieldGicName.substring(0, fieldGicName.lastIndexOf("["));
fieldGicName = "java.util.List<" + fieldGicName + ">";
}
String[] gNameArr = DocClassUtil.getSimpleGicName(fieldGicName);
if (gNameArr.length == 0) {
continue;
} else {
String gName = DocClassUtil.getSimpleGicName(fieldGicName)[0];
JavaClass javaClass1 = projectBuilder.getJavaProjectBuilder().getClassByName(gName);
comment.append(handleEnumComment(javaClass1, projectBuilder));
}
String gName = gNameArr[0];
if (JavaClassValidateUtil.isPrimitive(gName)) {
String builder = DocUtil.jsonValueByType(gName) + "," + DocUtil.jsonValueByType(gName);
if (StringUtil.isEmpty(fieldValue)) {
param.setValue(DocUtil.handleJsonStr(builder));
} else {
param.setValue(fieldValue);
}
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
} else {
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
fieldPid = Optional.ofNullable(atomicInteger).isPresent() ? param.getId()
: paramList.size() + pid;
if (!simpleName.equals(gName)) {
JavaClass arraySubClass = projectBuilder.getJavaProjectBuilder().getClassByName(gName);
if (arraySubClass.isEnum()) {
EnumInfoAndValues enumInfoAndValue = JavaClassUtil.getEnumInfoAndValue(arraySubClass,
projectBuilder, Boolean.FALSE);
if (Objects.nonNull(enumInfoAndValue)) {
param.setValue("[\"" + enumInfoAndValue.getValue() + "\"]")
.setEnumInfoAndValues(enumInfoAndValue)
.setType(enumInfoAndValue.getType());
}
} else if (gName.length() == 1) {
// handle generic
int len = globGicName.length;
if (len < 1) {
continue;
}
String gicName = genericMap.get(gName) != null ? genericMap.get(gName) : globGicName[0];
if (!JavaClassValidateUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) {
paramList.addAll(buildParams(gicName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses,
methodJsonViewClasses, fieldPid, jsonRequest, atomicInteger));
}
} else {
paramList.addAll(buildParams(gName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses, methodJsonViewClasses,
fieldPid, jsonRequest, atomicInteger));
}
} else {
param.setSelfReferenceLoop(true);
}
}
} else if (JavaClassValidateUtil.isMap(subTypeName)) {
if (tagsMap.containsKey(DocTags.MOCK) && StringUtil.isNotEmpty(tagsMap.get(DocTags.MOCK))) {
param.setType(ParamTypeConstants.PARAM_TYPE_MAP);
param.setValue(fieldValue);
}
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
fieldPid = Optional.ofNullable(atomicInteger).isPresent() ? param.getId() : paramList.size() + pid;
String valType = DocClassUtil.getMapKeyValueType(fieldGicName).length == 0 ? fieldGicName
: DocClassUtil.getMapKeyValueType(fieldGicName)[1];
if (JavaClassValidateUtil.isMap(fieldGicName)
|| JavaTypeConstants.JAVA_OBJECT_FULLY.equals(valType)) {
ApiParam param1 = ApiParam.of()
.setField(preBuilder + "any object")
.setId(atomicOrDefault(atomicInteger, fieldPid + 1))
.setPid(fieldPid)
.setClassName(className)
.setMaxLength(maxLength)
.setType(ParamTypeConstants.PARAM_TYPE_OBJECT)
.setDesc(DocGlobalConstants.ANY_OBJECT_MSG)
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setExtensions(extensionParams);
paramList.add(param1);
continue;
}
if (!JavaClassValidateUtil.isPrimitive(valType)) {
if (valType.length() == 1) {
String gicName = genericMap.get(valType);
if (!JavaClassValidateUtil.isPrimitive(gicName) && !simpleName.equals(gicName)) {
paramList.addAll(buildParams(gicName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses, methodJsonViewClasses,
fieldPid, jsonRequest, atomicInteger));
}
} else {
paramList.addAll(buildParams(valType, preBuilder.toString(), nextLevel, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, methodJsonViewClasses, fieldPid,
jsonRequest, atomicInteger));
}
}
} else if (JavaTypeConstants.JAVA_OBJECT_FULLY.equals(fieldGicName)) {
if (StringUtil.isEmpty(param.getDesc())) {
param.setDesc(DocGlobalConstants.ANY_OBJECT_MSG);
}
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
} else if (fieldGicName.length() == 1) {
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
fieldPid = Optional.ofNullable(atomicInteger).isPresent() ? param.getId() : paramList.size() + pid;
// handle java generic or object
if (!simpleName.equals(className)) {
if (globGicName.length > 0) {
String gicName = genericMap.get(subTypeName) != null ? genericMap.get(subTypeName)
: globGicName[0];
String simple = DocClassUtil.getSimpleName(gicName);
// set type array
if (JavaClassValidateUtil.isArray(gicName)) {
param.setType(ParamTypeConstants.PARAM_TYPE_ARRAY);
}
if (JavaClassValidateUtil.isPrimitive(simple)) {
// do nothing
} else if (gicName.contains("<")) {
if (JavaClassValidateUtil.isCollection(simple)) {
param.setType(ParamTypeConstants.PARAM_TYPE_ARRAY);
String gName = DocClassUtil.getSimpleGicName(gicName)[0];
if (!JavaClassValidateUtil.isPrimitive(gName)) {
paramList.addAll(buildParams(gName, preBuilder.toString(), nextLevel,
isRequired, isResp, registryClasses, projectBuilder, groupClasses,
methodJsonViewClasses, fieldPid, jsonRequest, atomicInteger));
}
} else {
paramList.addAll(buildParams(gicName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses,
methodJsonViewClasses, fieldPid, jsonRequest, atomicInteger));
}
} else {
paramList.addAll(buildParams(gicName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses, methodJsonViewClasses,
fieldPid, jsonRequest, atomicInteger));
}
} else {
paramList.addAll(buildParams(subTypeName, preBuilder.toString(), nextLevel, isRequired,
isResp, registryClasses, projectBuilder, groupClasses, methodJsonViewClasses,
fieldPid, jsonRequest, atomicInteger));
}
}
} else if (simpleName.equals(subTypeName)) {
// reference self
ApiParam param1 = ApiParam.of()
.setField(pre + fieldName)
.setPid(pid)
.setId(atomicOrDefault(atomicInteger, paramList.size() + pid + 1))
.setClassName(subTypeName)
.setMaxLength(maxLength)
.setType(ParamTypeConstants.PARAM_TYPE_OBJECT)
.setDesc(comment.append(" $ref... self").toString())
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setExtensions(extensionParams);
paramList.add(param1);
} else {
commonHandleParam(paramList, param, isRequired, comment + appendComment, since, strRequired);
fieldGicName = DocUtil.formatFieldTypeGicName(genericMap, fieldGicName);
fieldPid = Optional.ofNullable(atomicInteger).isPresent() ? param.getId() : paramList.size() + pid;
paramList.addAll(buildParams(fieldGicName, preBuilder.toString(), nextLevel, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, methodJsonViewClasses, fieldPid, jsonRequest,
atomicInteger));
}
}
}
return paramList;
// end field
}
/**
* Builds a list of {@link ApiParam} objects for a map parameter.
*
* @param globGicName the global generic name array
* @param pre the prefix string
* @param level the level of the parameter
* @param isRequired the requirement status of the parameter
* @param isResp the response flag
* @param registryClasses the map of registry classes
* @param projectBuilder the project configuration builder
* @param groupClasses the set of group classes
* @param jsonViewClasses A set of valid `@JsonView` classes.
* @param pid the parent ID
* @param jsonRequest the JSON request flag
* @param nextLevel the next level of the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @return a list of {@link ApiParam} objects
*/
public List<ApiParam> buildMapParam(String[] globGicName, String pre, int level, String isRequired,
boolean isResp, Map<String, String> registryClasses, ProjectDocConfigBuilder projectBuilder,
Set<String> groupClasses, Set<String> jsonViewClasses, int pid, boolean jsonRequest, int nextLevel,
AtomicInteger atomicInteger) {
if (globGicName.length != 2) {
return Collections.emptyList();
}
// mock map key param
String mapKeySimpleName = DocClassUtil.getSimpleName(globGicName[0]);
String valueSimpleName = DocClassUtil.getSimpleName(globGicName[1]);
// get map key class
JavaClass mapKeyClass = projectBuilder.getJavaProjectBuilder().getClassByName(mapKeySimpleName);
boolean isShowJavaType = projectBuilder.getApiConfig().getShowJavaType();
String valueSimpleNameType = processFieldTypeName(isShowJavaType, valueSimpleName);
List<ApiParam> paramList = new ArrayList<>();
// map key is enum
if (Objects.nonNull(mapKeyClass) && mapKeyClass.isEnum() && !mapKeyClass.getEnumConstants().isEmpty()) {
Integer keyParentId = null;
for (JavaField enumConstant : mapKeyClass.getEnumConstants()) {
ApiParam apiParam = ApiParam.of()
.setField(pre + enumConstant.getName())
.setType(valueSimpleNameType)
.setClassName(valueSimpleName)
.setDesc(StringUtil.isEmpty(enumConstant.getComment()) ? enumConstant.getName()
: enumConstant.getComment() + " "
+ Optional.ofNullable(projectBuilder.getClassByName(valueSimpleName))
.map(JavaClass::getComment)
.orElse(DocGlobalConstants.DEFAULT_MAP_KEY_DESC))
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setPid(null == keyParentId ? pid : keyParentId);
apiParam.setId(apiParam.getPid() + paramList.size() + 1);
if (null == keyParentId) {
keyParentId = apiParam.getPid();
}
paramList.add(apiParam);
// in foreach, need remove enum class in registry
registryClasses.remove(valueSimpleName);
List<ApiParam> apiParams = addValueParams(valueSimpleName, globGicName, level, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, jsonViewClasses, apiParam.getId(), jsonRequest,
nextLevel, atomicInteger);
paramList.addAll(apiParams);
}
return paramList;
}
// map key is primitive
if (JavaClassValidateUtil.isPrimitive(mapKeySimpleName)) {
ApiParam apiParam = ApiParam.of()
.setField(pre + "mapKey")
.setType(valueSimpleNameType)
.setClassName(valueSimpleName)
.setDesc(Optional.ofNullable(projectBuilder.getClassByName(valueSimpleName))
.map(JavaClass::getComment)
.orElse(DocGlobalConstants.DEFAULT_MAP_KEY_DESC))
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setPid(pid)
.setId(atomicOrDefault(atomicInteger, ++pid));
paramList.add(apiParam);
}
paramList.addAll(addValueParams(valueSimpleName, globGicName, level, isRequired, isResp, registryClasses,
projectBuilder, groupClasses, jsonViewClasses, pid, jsonRequest, nextLevel, atomicInteger));
return paramList;
}
/**
* Adds parameters for the map value to the parameter list.
*
* @param valueSimpleName the simple name of the value type
* @param globGicName the global generic name array
* @param level the level of the parameter
* @param isRequired the requirement status of the parameter
* @param isResp the response flag
* @param registryClasses the map of registry classes
* @param projectBuilder the project configuration builder
* @param groupClasses the set of group classes
* @param jsonViewClasses A set of valid `@JsonView` classes.
* @param pid the parent ID
* @param jsonRequest the JSON request flag
* @param nextLevel the next level of the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @return the list of {@link ApiParam} objects
*/
public List<ApiParam> addValueParams(String valueSimpleName, String[] globGicName, int level,
String isRequired, boolean isResp, Map<String, String> registryClasses,
ProjectDocConfigBuilder projectBuilder, Set<String> groupClasses, Set<String> jsonViewClasses, int pid,
boolean jsonRequest, int nextLevel, AtomicInteger atomicInteger) {
// build param when map value is not primitive
if (JavaClassValidateUtil.isPrimitive(valueSimpleName)) {
return Collections.emptyList();
}
return buildParams(globGicName[1], DocUtil.getIndentByLevel(level), nextLevel, isRequired, isResp,
registryClasses, projectBuilder, groupClasses, jsonViewClasses, pid, jsonRequest, atomicInteger);
}
public String dictionaryListComment(List<EnumDictionary> enumDataDict) {
return enumDataDict.stream()
.map(apiDataDictionary -> apiDataDictionary.getName() + "-(\"" + apiDataDictionary.getValue() + "\",\""
+ apiDataDictionary.getDesc() + "\")")
.collect(Collectors.joining(","));
}
public List<ApiParam> primitiveReturnRespComment(String typeName, AtomicInteger atomicInteger, int pid) {
String comments = "Return " + typeName + ".";
ApiParam apiParam = ApiParam.of()
.setClassName(typeName)
.setId(atomicOrDefault(atomicInteger, pid + 1))
.setField("-")
.setPid(pid)
.setType(typeName)
.setDesc(comments)
.setVersion(DocGlobalConstants.DEFAULT_VERSION);
List<ApiParam> paramList = new ArrayList<>();
paramList.add(apiParam);
return paramList;
}
public void commonHandleParam(List<ApiParam> paramList, ApiParam param, String isRequired, String comment,
String since, boolean strRequired) {
if (StringUtil.isEmpty(isRequired)) {
param.setDesc(comment).setVersion(since);
} else {
param.setDesc(comment).setVersion(since).setRequired(strRequired);
}
paramList.add(param);
}
/**
* Handles the generation of comments for enum types in a Java class. If the class is
* an enum, it generates the corresponding enum comment based on the project
* configuration; otherwise, it returns an empty comment string.
*
* @param javaClass The JavaClass object containing class information.
* @param projectBuilder The ProjectDocConfigBuilder object containing project
* configuration information.
* @return The generated enum comment string.
*/
public String handleEnumComment(JavaClass javaClass, ProjectDocConfigBuilder projectBuilder) {
String comment = "";
if (!javaClass.isEnum()) {
return comment;
}
String enumComments = javaClass.getComment();
if (Boolean.TRUE.equals(projectBuilder.getApiConfig().getInlineEnum())) {
ApiDataDictionary dataDictionary = projectBuilder.getApiConfig()
.getDataDictionary(javaClass.getCanonicalName());
if (Objects.isNull(dataDictionary)) {
// the output format should be unified ( as same as the "else" output)
comment = comment + "<br/>[Enum: " + JavaClassUtil.getEnumParams(javaClass) + "]";
} else {
Class<?> enumClass = dataDictionary.getEnumClass();
if (enumClass.isInterface()) {
ClassLoader classLoader = projectBuilder.getApiConfig().getClassLoader();
try {
enumClass = classLoader.loadClass(javaClass.getFullyQualifiedName());
} catch (ClassNotFoundException e) {
return comment;
}
}
comment = comment + "<br/>[Enum: " + dictionaryListComment(dataDictionary.getEnumDataDict(enumClass))
+ "]";
}
} else {
if (StringUtil.isNotEmpty(enumComments)) {
comment = comment + "<br/>(See: " + enumComments + ")";
}
comment = StringUtil.removeQuotes(comment);
}
return comment;
}
/**
* Returns the next value of the specified atomic integer or the default value if the
* atomic integer is null.
*
* @param atomicInteger the atomic integer
* @param defaultVal the default value
* @return the next value of the atomic integer or the default value
*/
public int atomicOrDefault(AtomicInteger atomicInteger, int defaultVal) {
if (null != atomicInteger) {
return atomicInteger.incrementAndGet();
}
return defaultVal;
}
/**
* Processes the field type name based on the specified flag.
*
* @param isShowJavaType a flag indicating whether to show the Java type or not
* @param fieldTypeName the field type name to be processed
* @return the processed field type name
*/
public String processFieldTypeName(boolean isShowJavaType, String fieldTypeName) {
if (isShowJavaType) {
return JavaFieldUtil.convertToSimpleTypeName(fieldTypeName);
} else {
return DocClassUtil.processTypeNameForParams(fieldTypeName.toLowerCase());
}
}
/**
* Handles primitive types and returns a list of {@link ApiParam} objects.
*
* @param simpleName the simple name of the primitive type
* @param isShowJavaType a flag indicating whether to show the Java type or not
* @param atomicInteger the atomic integer for generating unique IDs
* @param pid the parent ID
* @return the list of {@link ApiParam} objects
*/
public List<ApiParam> handlePrimitiveType(String simpleName, boolean isShowJavaType,
AtomicInteger atomicInteger, int pid) {
String processedType = processFieldTypeName(isShowJavaType, simpleName);
return primitiveReturnRespComment(processedType, atomicInteger, pid);
}
/**
* Builds a list of {@link ApiParam} objects for a map type.
*
* @param globGicName the generic canonical name of the map type
* @param pre the prefix for the field name
* @param level the level of the parameter
* @param isRequired the required flag for the parameter
* @param isResp the response flag for the parameter
* @param registryClasses the registry classes
* @param projectBuilder the project builder
* @param groupClasses the group classes
* @param methodJsonViewClasses the method JSON view classes
* @param pid the parent ID
* @param jsonRequest the request flag for the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @param simpleName the simple name of the collection type
*/
public List<ApiParam> handleCollectionOrArrayType(String[] globGicName, String pre, int level,
String isRequired, boolean isResp, Map<String, String> registryClasses,
ProjectDocConfigBuilder projectBuilder, Set<String> groupClasses, Set<String> methodJsonViewClasses,
int pid, boolean jsonRequest, AtomicInteger atomicInteger, String simpleName) {
List<ApiParam> paramList = new ArrayList<>();
if (!JavaClassValidateUtil.isCollection(globGicName[0])) {
String gNameTemp = globGicName[0];
String gName = JavaClassValidateUtil.isArray(gNameTemp) ? gNameTemp.substring(0, gNameTemp.indexOf("["))
: globGicName[0];
if (JavaClassValidateUtil.isPrimitive(gName)) {
String processedType = projectBuilder.getApiConfig().getShowJavaType()
? JavaFieldUtil.convertToSimpleTypeName(simpleName)
: DocClassUtil.processTypeNameForParams(gName);
ApiParam param = ApiParam.of()
.setId(atomicOrDefault(atomicInteger, pid + 1))
.setField(pre + " -")
.setType("array[" + processedType + "]")
.setPid(pid)
.setDesc("array of " + processedType)
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setRequired(Boolean.parseBoolean(isRequired));
paramList.add(param);
} else {
if (JavaClassValidateUtil.isArray(gNameTemp)) {
gNameTemp = gNameTemp.substring(0, gNameTemp.indexOf("["));
}
paramList.addAll(buildParams(gNameTemp, pre, level + 1, isRequired, isResp, registryClasses,
projectBuilder, groupClasses, methodJsonViewClasses, pid, jsonRequest, atomicInteger));
}
}
return paramList;
}
/**
* Builds a list of {@link ApiParam} objects for a generic object type.
*
* @param className the canonical name of the generic object type
* @param pre the prefix for the field name
* @param isRequired the required flag for the parameter
* @param atomicInteger the atomic integer for generating unique IDs
* @param pid the parent ID
* @return the list of {@link ApiParam} objects
*/
public List<ApiParam> buildGenericObjectParam(String className, String pre, String isRequired,
AtomicInteger atomicInteger, int pid) {
List<ApiParam> paramList = new ArrayList<>();
ApiParam apiParam = ApiParam.of()
.setClassName(className)
.setId(atomicOrDefault(atomicInteger, pid + 1))
.setField(pre + "any object")
.setType(ParamTypeConstants.PARAM_TYPE_OBJECT)
.setPid(pid)
.setDesc(DocGlobalConstants.ANY_OBJECT_MSG)
.setVersion(DocGlobalConstants.DEFAULT_VERSION)
.setRequired(Boolean.parseBoolean(isRequired));
paramList.add(apiParam);
return paramList;
}
}

View File

@@ -0,0 +1 @@
com.gitee.sop.support.doc.SopDocBuildTemplate

View File

@@ -28,6 +28,14 @@ const permissionRouter = {
title: "签名算法",
showLink: false
}
},
{
path: "/doc/code",
name: "DocCode",
meta: {
title: "公共错误码",
showLink: false
}
}
]
};

View File

@@ -0,0 +1,334 @@
[
{
"children":[
],
"code":"0",
"id":1,
"msg":"成功"
},
{
"children":[
{
"id":3,
"solution":"重新获取token",
"sub_code":"aop.invalid-auth-token",
"sub_msg":"无效的访问令牌"
},
{
"id":4,
"solution":"重新获取token",
"sub_code":"aop.auth-token-time-out",
"sub_msg":"访问令牌已过期"
},
{
"id":5,
"solution":"重新获取token",
"sub_code":"aop.invalid-app-auth-token",
"sub_msg":"无效的应用授权令牌"
},
{
"id":6,
"solution":"联系平台进行授权",
"sub_code":"aop.invalid-app-auth-token-no-api",
"sub_msg":"商户未授权当前接口"
},
{
"id":7,
"solution":"重新获取token",
"sub_code":"aop.app-auth-token-time-out",
"sub_msg":"应用授权令牌已过期"
},
{
"id":8,
"solution":"联系平台处理",
"sub_code":"aop.no-product-reg-by-partner",
"sub_msg":"商户未签约任何产品"
}
],
"code":"20001",
"id":2,
"msg":"认证异常"
},
{
"children":[
{
"id":10,
"solution":"检查参数正确",
"sub_code":"isv.invalid-parameter",
"sub_msg":"参数无效"
},
{
"id":11,
"solution":"检查传参",
"sub_code":"isv.error-parameter",
"sub_msg":"参数不正确"
},
{
"id":12,
"solution":"检查上传文件",
"sub_code":"isv.upload-fail",
"sub_msg":"文件上传失败"
},
{
"id":13,
"solution":"检查文件后缀名",
"sub_code":"isv.invalid-file-extension",
"sub_msg":"文件扩展名无效"
},
{
"id":14,
"solution":"检查文档大小",
"sub_code":"isv.invalid-file-size",
"sub_msg":"{0}文件大小无效,单文件不得超过{1}"
},
{
"id":15,
"solution":"检查接口名称是否正确",
"sub_code":"isv.invalid-method",
"sub_msg":"不存在的方法名"
},
{
"id":16,
"solution":"检查format参数是否正确",
"sub_code":"isv.invalid-format",
"sub_msg":"无效的数据格式"
},
{
"id":17,
"solution":"检查sign_type是否正确",
"sub_code":"isv.invalid-signature-type",
"sub_msg":"无效的签名类型"
},
{
"id":18,
"solution":"检查签名结果是否正确",
"sub_code":"isv.invalid-signature",
"sub_msg":"无效签名"
},
{
"id":19,
"solution":"检查签名结果是否正确",
"sub_code":"isv.invalid-encrypt-type",
"sub_msg":"无效的加密类型"
},
{
"id":20,
"solution":"检查签名结果是否正确",
"sub_code":"isv.invalid-encrypt",
"sub_msg":"解密异常"
},
{
"id":21,
"solution":"检查appId是否正确",
"sub_code":"isv.invalid-app-id",
"sub_msg":"无效的 appId 参数"
},
{
"id":22,
"solution":"检查时间戳格式是否正确",
"sub_code":"isv.invalid-timestamp",
"sub_msg":"非法的时间戳参数"
},
{
"id":23,
"solution":"检查charset参数是否正确",
"sub_code":"isv.invalid-charset",
"sub_msg":"字符集错误"
},
{
"id":24,
"solution":"检查签名",
"sub_code":"isv.invalid-digest",
"sub_msg":"摘要错误"
},
{
"id":25,
"solution":"",
"sub_code":"isv.decryption-error-not-valid-encrypt-type",
"sub_msg":"解密出错,不支持的加密算法"
},
{
"id":26,
"solution":"",
"sub_code":"isv.decryption-error-not-valid-encrypt-key",
"sub_msg":"解密出错, 未配置加密密钥或加密密钥格式错误"
},
{
"id":27,
"solution":"",
"sub_code":"isv.decryption-error-unknown",
"sub_msg":"解密出错,未知异常"
},
{
"id":28,
"solution":"",
"sub_code":"isv.missing-signature-config",
"sub_msg":"验签出错, 未配置对应签名算法的公钥或者证书"
},
{
"id":29,
"solution":"",
"sub_code":"isv.not-support-app-auth",
"sub_msg":"本接口不支持第三方代理调用"
},
{
"id":30,
"solution":"",
"sub_code":"isv.suspected-attack",
"sub_msg":"可疑的攻击请求"
},
{
"id":31,
"solution":"",
"sub_code":"isv.invalid-content-type",
"sub_msg":"无效的 content-type"
}
],
"code":"40002",
"id":9,
"msg":"错误参数"
},
{
"children":[
{
"id":33,
"solution":"检查接口名",
"sub_code":"isv.missing-method",
"sub_msg":"缺少方法名参数"
},
{
"id":34,
"solution":"检查签名sign参数",
"sub_code":"isv.missing-signature",
"sub_msg":"缺少签名参数"
},
{
"id":35,
"solution":"检查签名类型参数",
"sub_code":"isv.missing-signature-type",
"sub_msg":"缺少签名类型参数"
},
{
"id":36,
"solution":"",
"sub_code":"isv.missing-signature-key",
"sub_msg":"缺少签名配置"
},
{
"id":37,
"solution":"appId参数必填",
"sub_code":"isv.missing-app-id",
"sub_msg":"缺少 appId 参数"
},
{
"id":38,
"solution":"时间戳参数必填",
"sub_code":"isv.missing-timestamp",
"sub_msg":"缺少时间戳参数"
},
{
"id":39,
"solution":"版本号参数必填",
"sub_code":"isv.missing-version",
"sub_msg":"缺少版本参数"
},
{
"id":40,
"solution":"",
"sub_code":"isv.decryption-error-missing-encrypt-type",
"sub_msg":"解密出错, 未指定加密算法"
}
],
"code":"40001",
"id":32,
"msg":"缺少参数"
},
{
"children":[
{
"id":42,
"solution":"网关异常",
"sub_code":"isp.unknown-error",
"sub_msg":"服务暂不可用"
},
{
"id":43,
"solution":"服务提供异常",
"sub_code":"isp.service-unknown-error",
"sub_msg":"服务不可用"
},
{
"id":44,
"solution":"服务不可用",
"sub_code":"isp.service-not-available",
"sub_msg":"服务暂不可用"
},
{
"id":45,
"solution":"联系平台排查日志",
"sub_code":"isp.gateway-response-timeout",
"sub_msg":"网关响应超时"
},
{
"id":46,
"solution":"联系平台排查日志",
"sub_code":"isv.service-busy",
"sub_msg":"服务器忙"
}
],
"code":"99999",
"id":41,
"msg":"未知异常"
},
{
"children":[
{
"id":48,
"solution":"接口无权限,联系平台授权",
"sub_code":"isv.insufficient-isv-permissions",
"sub_msg":"请检查配置的账户是否有当前接口权限"
},
{
"id":49,
"solution":"联系平台授权",
"sub_code":"isv.insufficient-user-permissions",
"sub_msg":"代理的商户没有当前接口权限"
},
{
"id":50,
"solution":"联系平台授权",
"sub_code":"isv.route-no-permissions",
"sub_msg":"没有当前接口权限"
},
{
"id":51,
"solution":"访问首先",
"sub_code":"isv.access-forbidden",
"sub_msg":"无权访问"
},
{
"id":52,
"solution":"IP受限",
"sub_code":"isv.ip-forbidden",
"sub_msg":"IP无权访问"
}
],
"code":"40006",
"id":47,
"msg":"权限异常"
},
{
"children":[
{
"id":54,
"solution":"",
"sub_code":"isp.biz-error",
"sub_msg":"业务异常"
}
],
"code":"50003",
"id":53,
"msg":"业务异常"
}
]

View File

@@ -224,7 +224,5 @@ export const http = new PureHttp();
* @param path 相对于public文件夹路径如文件在public/static/sign.mdstatic/sign.md
*/
export function getFile(path) {
return Axios.get(path, {
responseType: "text"
});
return Axios.get(path);
}

View File

@@ -138,7 +138,19 @@ const defaultProps = {
</span>
</template>
</el-table-column>
<el-table-column label="描述" prop="description" />
<el-table-column label="描述" prop="description">
<template #default="scope">
<span v-if="scope.row.name === 'code'">
网关返回码详见
<router-link :to="{ name: 'DocCode' }" target="_blank">
公共错误码
</router-link>
</span>
<span v-else>
{{ scope.row.description }}
</span>
</template>
</el-table-column>
<el-table-column label="示例值" prop="example" width="200" />
</el-table>
<h4>业务返回参数</h4>

View File

@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { getFile } from "@/utils/http";
const tableData = ref([]);
onMounted(() => {
getFile("static/code.json").then(resp => {
console.log(resp.data);
tableData.value = resp.data;
});
});
</script>
<template>
<div class="app-container">
<h3>公共错误码</h3>
<div class="isp-info">
<el-table :data="tableData" row-key="id" default-expand-all border>
<el-table-column label="code" prop="code" width="200" />
<el-table-column label="msg" prop="msg" width="200" />
<el-table-column
label="sub_code详细错误码"
prop="sub_code"
width="250"
/>
<el-table-column label="sub_msg详细错误信息" prop="sub_msg" />
<el-table-column label="解决方案" prop="solution" />
</el-table>
</div>
</div>
</template>

View File

@@ -1,3 +0,0 @@
import { ref } from "vue";
export const value = ref("");

View File

@@ -1,11 +1,14 @@
<script setup lang="ts">
import { value } from "./index";
import { onMounted, ref } from "vue";
import { getFile } from "@/utils/http";
import MarkdownEditor from "@/components/MarkdownEditor";
const value = ref("");
onMounted(() => {
getFile("static/md/sign.md").then(resp => {
value.value = resp.data;
});
});
</script>
<template>
<MarkdownEditor :value="value" readonly />