From 3e900cf0fe71f7d08391cb296b02feb58acb5444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AD=E5=A6=82?= <8775@163.com> Date: Mon, 30 Dec 2024 20:49:17 +0800 Subject: [PATCH] 5.1 --- changelog.md | 4 + sop-example/example-payment/pom.xml | 19 + sop-example/example-payment/push-doc.sh | 1 + .../gitee/sop/payment/open/OpenPayment.java | 25 +- .../open/req/PayTradeWapPayRequest.java | 180 ++-- .../open/resp/PayTradeWapPayResponse.java | 14 +- .../src/main/resources/smart-doc.json | 13 + sop-example/example-story/pom.xml | 19 + .../gitee/sop/storyweb/open/OpenStory.java | 9 +- .../storyweb/open/req/StorySaveRequest.java | 9 + .../src/main/resources/smart-doc.json | 13 + .../gitee/sop/gateway/message/CodeEnum.java | 27 +- .../gitee/sop/gateway/ErrorCodePrintTest.java | 79 ++ sop-support/sop-service-support/pom.xml | 6 + .../sop/support/doc/OpenAnnotationInfo.java | 22 + .../sop/support/doc/SopDocBuildTemplate.java | 235 +++++ .../constants/OpenAnnotationConstants.java | 13 + .../sop/support/doc/helper/BaseHelper.java | 295 ++++++ .../support/doc/helper/ParamsBuildHelper.java | 962 ++++++++++++++++++ .../com.ly.doc.template.IDocBuildTemplate | 1 + .../sop-website-frontend/mock/asyncRoutes.ts | 8 + .../public/static/code.json | 334 ++++++ .../src/utils/http/index.ts | 4 +- .../src/views/doc/api/index.vue | 14 +- .../src/views/doc/code/index.vue | 31 + .../src/views/doc/sign/index.ts | 3 - .../src/views/doc/sign/index.vue | 9 +- 27 files changed, 2232 insertions(+), 117 deletions(-) create mode 100644 sop-example/example-payment/push-doc.sh create mode 100644 sop-example/example-payment/src/main/resources/smart-doc.json create mode 100644 sop-example/example-story/src/main/resources/smart-doc.json create mode 100644 sop-gateway/src/test/java/com/gitee/sop/gateway/ErrorCodePrintTest.java create mode 100644 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/OpenAnnotationInfo.java create mode 100644 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/SopDocBuildTemplate.java create mode 100644 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/constants/OpenAnnotationConstants.java create mode 100644 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/BaseHelper.java create mode 100644 sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/ParamsBuildHelper.java create mode 100644 sop-support/sop-service-support/src/main/resources/META-INF/services/com.ly.doc.template.IDocBuildTemplate create mode 100644 sop-website/sop-website-frontend/public/static/code.json create mode 100644 sop-website/sop-website-frontend/src/views/doc/code/index.vue delete mode 100755 sop-website/sop-website-frontend/src/views/doc/sign/index.ts diff --git a/changelog.md b/changelog.md index b60e47d4..1a19b4c4 100755 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # changelog +## 5.1 + +接入smart-doc + ## 5.0 全面重构,欢迎体验:[文档](https://www.yuque.com/u1604442/sop) diff --git a/sop-example/example-payment/pom.xml b/sop-example/example-payment/pom.xml index 0c3489a7..8182f257 100755 --- a/sop-example/example-payment/pom.xml +++ b/sop-example/example-payment/pom.xml @@ -84,6 +84,25 @@ org.springframework.boot spring-boot-maven-plugin + + + com.ly.smart-doc + smart-doc-maven-plugin + 3.0.9 + + + ./src/main/resources/smart-doc.json + + ${project.artifactId} + + + + com.gitee.sop + sop-service-support + 5.0.0-SNAPSHOT + + + diff --git a/sop-example/example-payment/push-doc.sh b/sop-example/example-payment/push-doc.sh new file mode 100644 index 00000000..7068d908 --- /dev/null +++ b/sop-example/example-payment/push-doc.sh @@ -0,0 +1 @@ +mvn -Dfile.encoding=UTF-8 -Dcheckstyle.skip=true smart-doc:torna-rpc diff --git a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/OpenPayment.java b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/OpenPayment.java index 0f1e78c6..007122b2 100755 --- a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/OpenPayment.java +++ b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/OpenPayment.java @@ -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()结果。" + - "该结果用于跳转到页面,返回到用户浏览器渲染或重定向跳转到页面。" + - "具体使用方法请参考 接入指南" - ) + /** + * 手机网站支付接口 + * + * @apiNote 该接口是页面跳转接口,用于生成用户访问跳转链接。 + * 请在服务端执行SDK中pageExecute方法,读取响应中的body()结果。 + * 该结果用于跳转到页面,返回到用户浏览器渲染或重定向跳转到页面。 + * 具体使用方法请参考 接入指南 + */ @Open("pay.trade.wap.pay") PayTradeWapPayResponse tradeWapPay(PayTradeWapPayRequest request); - @ApiOperation(value = "订单查询接口") + /** + * 订单查询接口 + * + * @param request + * @return + */ @Open("pay.order.search") PayOrderSearchResponse orderSearch(PayOrderSearchRequest request); diff --git a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java index 94b4948a..e57387a2 100755 --- a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java +++ b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/req/PayTradeWapPayRequest.java @@ -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; - @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; diff --git a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/resp/PayTradeWapPayResponse.java b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/resp/PayTradeWapPayResponse.java index 3b2ae2b8..a38675ed 100755 --- a/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/resp/PayTradeWapPayResponse.java +++ b/sop-example/example-payment/src/main/java/com/gitee/sop/payment/open/resp/PayTradeWapPayResponse.java @@ -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表单,在浏览器渲染即可
使用GET方法会得到支付平台URL,需要打开或重定向到该URL。建议使用POST方式。 + * + * @mock 请参考响应示例 + */ + @NotNull private String pageRedirectionData; } diff --git a/sop-example/example-payment/src/main/resources/smart-doc.json b/sop-example/example-payment/src/main/resources/smart-doc.json new file mode 100644 index 00000000..29e2956a --- /dev/null +++ b/sop-example/example-payment/src/main/resources/smart-doc.json @@ -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 +} diff --git a/sop-example/example-story/pom.xml b/sop-example/example-story/pom.xml index d52fb69b..c8e31402 100755 --- a/sop-example/example-story/pom.xml +++ b/sop-example/example-story/pom.xml @@ -89,6 +89,25 @@ org.springframework.boot spring-boot-maven-plugin + + + com.ly.smart-doc + smart-doc-maven-plugin + 3.0.9 + + + ./src/main/resources/smart-doc.json + + ${project.artifactId} + + + + com.gitee.sop + sop-service-support + 5.0.0-SNAPSHOT + + + diff --git a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java index 38ca2914..b3d49308 100755 --- a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java +++ b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/OpenStory.java @@ -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); diff --git a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java index d59f8ccf..d21ad5d6 100755 --- a/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java +++ b/sop-example/example-story/src/main/java/com/gitee/sop/storyweb/open/req/StorySaveRequest.java @@ -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; diff --git a/sop-example/example-story/src/main/resources/smart-doc.json b/sop-example/example-story/src/main/resources/smart-doc.json new file mode 100644 index 00000000..3f337f59 --- /dev/null +++ b/sop-example/example-story/src/main/resources/smart-doc.json @@ -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 +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/message/CodeEnum.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/message/CodeEnum.java index f260165d..62602e69 100755 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/message/CodeEnum.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/message/CodeEnum.java @@ -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; diff --git a/sop-gateway/src/test/java/com/gitee/sop/gateway/ErrorCodePrintTest.java b/sop-gateway/src/test/java/com/gitee/sop/gateway/ErrorCodePrintTest.java new file mode 100644 index 00000000..8e7c43e1 --- /dev/null +++ b/sop-gateway/src/test/java/com/gitee/sop/gateway/ErrorCodePrintTest.java @@ -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> codeMap = Stream.of(values) + .collect(Collectors.groupingBy(ErrorEnum::getCode)); + + AtomicInteger id = new AtomicInteger(); + List 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 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 children; + } + + @Data + static class SubError { + private Integer id; + private String sub_code; + private String sub_msg; + private String solution; + } +} diff --git a/sop-support/sop-service-support/pom.xml b/sop-support/sop-service-support/pom.xml index 59223027..68a09eb1 100755 --- a/sop-support/sop-service-support/pom.xml +++ b/sop-support/sop-service-support/pom.xml @@ -37,6 +37,12 @@ 1.18.34 true + + + com.ly.smart-doc + smart-doc + 3.0.9 + junit junit diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/OpenAnnotationInfo.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/OpenAnnotationInfo.java new file mode 100644 index 00000000..29abcacc --- /dev/null +++ b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/OpenAnnotationInfo.java @@ -0,0 +1,22 @@ +package com.gitee.sop.support.doc; + +import lombok.Data; + +/** + * @author 六如 + */ +@Data +public class OpenAnnotationInfo { + + /** + * 接口名 + */ + private String value; + + /** + * 版本号 + */ + private String version; + + +} diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/SopDocBuildTemplate.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/SopDocBuildTemplate.java new file mode 100644 index 00000000..1509b67b --- /dev/null +++ b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/SopDocBuildTemplate.java @@ -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; + + +/** + * 解析文档 + *

+ * 参考:smart-doc扩展 + *

+ * + * @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 requestParams(final JavaMethod javaMethod, ProjectDocConfigBuilder builder, + AtomicInteger atomicInteger, Map actualTypesMap) { + boolean isStrict = builder.getApiConfig().isStrict(); + boolean isShowJavaType = builder.getApiConfig().getShowJavaType(); + boolean isShowValidation = builder.getApiConfig().isShowValidation(); + String className = javaMethod.getDeclaringClass().getCanonicalName(); + Map paramTagMap = DocUtil.getCommentsByTag(javaMethod, DocTags.PARAM, className); + List parameterList = javaMethod.getParameters(); + if (parameterList.isEmpty()) { + return null; + } + ClassLoader classLoader = builder.getApiConfig().getClassLoader(); + List 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 annotations = parameter.getAnnotations(); + for (JavaAnnotation a : annotations) { + if (JavaClassValidateUtil.isJSR303Required(a.getType().getValue())) { + required = true; + } + } + comment.append(JavaFieldUtil.getJsrComment(isShowValidation, classLoader, annotations)); + Set groupClasses = JavaClassUtil.getParamGroupJavaClass(annotations, + builder.getJavaProjectBuilder()); + Set paramJsonViewClasses = JavaClassUtil.getParamJsonViewClasses(annotations, builder); + if (JavaClassValidateUtil.isCollection(fullTypeName) || JavaClassValidateUtil.isArray(fullTypeName)) { + if (JavaClassValidateUtil.isCollection(typeName)) { + typeName = typeName + ""; + } + 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 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 actualTypesMap) { + RpcJavaMethod rpcJavaMethod = super.convertToJavadocJavaMethod(apiConfig, method, actualTypesMap); + Optional 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 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 getOpenAnnotationInfo(JavaMethod method) { + List 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 methods = javaClass.getMethods(); + if (methods == null || methods.isEmpty()) { + return false; + } + for (JavaMethod method : methods) { + if (getOpenAnnotationInfo(method).isPresent()) { + return true; + } + } + return false; + } + +} diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/constants/OpenAnnotationConstants.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/constants/OpenAnnotationConstants.java new file mode 100644 index 00000000..462b4a0b --- /dev/null +++ b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/constants/OpenAnnotationConstants.java @@ -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"; +} diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/BaseHelper.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/BaseHelper.java new file mode 100644 index 00000000..93e00f74 --- /dev/null +++ b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/BaseHelper.java @@ -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 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 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 groupClasses, Set 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 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 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; + } + +} diff --git a/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/ParamsBuildHelper.java b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/ParamsBuildHelper.java new file mode 100644 index 00000000..67d0a09d --- /dev/null +++ b/sop-support/sop-service-support/src/main/java/com/gitee/sop/support/doc/helper/ParamsBuildHelper.java @@ -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. + *

+ * 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 buildParams(String className, String pre, int level, String isRequired, boolean isResp, + Map registryClasses, ProjectDocConfigBuilder projectBuilder, Set groupClasses, + Set 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 processFields(String className, String pre, int level, String isRequired, + boolean isResp, Map registryClasses, ProjectDocConfigBuilder projectBuilder, + Set groupClasses, Set 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 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 clsAnnotation = cls.getAnnotations(); + fieldNameConvert = PropertyNameHelper.translate(clsAnnotation); + } + + String[] globGicName = DocClassUtil.getSimpleGicName(className); + Map genericMap = new HashMap<>(globGicName.length); + JavaClassUtil.genericParamMap(genericMap, cls, globGicName); + + Map ignoreFields = JavaClassUtil.getClassJsonIgnoreFields(cls); + List 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 javaAnnotations = docField.getAnnotations(); + + Map 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 extensions = DocUtil.getCommentsByTag(field.getTagsByName(DocTags.EXTENSION), + DocTags.EXTENSION); + Map 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 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 + ""; + } + 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 buildMapParam(String[] globGicName, String pre, int level, String isRequired, + boolean isResp, Map registryClasses, ProjectDocConfigBuilder projectBuilder, + Set groupClasses, Set 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 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 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 addValueParams(String valueSimpleName, String[] globGicName, int level, + String isRequired, boolean isResp, Map registryClasses, + ProjectDocConfigBuilder projectBuilder, Set groupClasses, Set 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 enumDataDict) { + return enumDataDict.stream() + .map(apiDataDictionary -> apiDataDictionary.getName() + "-(\"" + apiDataDictionary.getValue() + "\",\"" + + apiDataDictionary.getDesc() + "\")") + .collect(Collectors.joining(",")); + } + + public List 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 paramList = new ArrayList<>(); + paramList.add(apiParam); + return paramList; + } + + public void commonHandleParam(List 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 + "
[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 + "
[Enum: " + dictionaryListComment(dataDictionary.getEnumDataDict(enumClass)) + + "]"; + } + } else { + if (StringUtil.isNotEmpty(enumComments)) { + comment = comment + "
(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 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 handleCollectionOrArrayType(String[] globGicName, String pre, int level, + String isRequired, boolean isResp, Map registryClasses, + ProjectDocConfigBuilder projectBuilder, Set groupClasses, Set methodJsonViewClasses, + int pid, boolean jsonRequest, AtomicInteger atomicInteger, String simpleName) { + List 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 buildGenericObjectParam(String className, String pre, String isRequired, + AtomicInteger atomicInteger, int pid) { + List 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; + } + +} diff --git a/sop-support/sop-service-support/src/main/resources/META-INF/services/com.ly.doc.template.IDocBuildTemplate b/sop-support/sop-service-support/src/main/resources/META-INF/services/com.ly.doc.template.IDocBuildTemplate new file mode 100644 index 00000000..2f27426a --- /dev/null +++ b/sop-support/sop-service-support/src/main/resources/META-INF/services/com.ly.doc.template.IDocBuildTemplate @@ -0,0 +1 @@ +com.gitee.sop.support.doc.SopDocBuildTemplate diff --git a/sop-website/sop-website-frontend/mock/asyncRoutes.ts b/sop-website/sop-website-frontend/mock/asyncRoutes.ts index d95d20cf..5305f416 100755 --- a/sop-website/sop-website-frontend/mock/asyncRoutes.ts +++ b/sop-website/sop-website-frontend/mock/asyncRoutes.ts @@ -28,6 +28,14 @@ const permissionRouter = { title: "签名算法", showLink: false } + }, + { + path: "/doc/code", + name: "DocCode", + meta: { + title: "公共错误码", + showLink: false + } } ] }; diff --git a/sop-website/sop-website-frontend/public/static/code.json b/sop-website/sop-website-frontend/public/static/code.json new file mode 100644 index 00000000..228ecf37 --- /dev/null +++ b/sop-website/sop-website-frontend/public/static/code.json @@ -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":"业务异常" + } +] diff --git a/sop-website/sop-website-frontend/src/utils/http/index.ts b/sop-website/sop-website-frontend/src/utils/http/index.ts index 1d63071e..91020554 100755 --- a/sop-website/sop-website-frontend/src/utils/http/index.ts +++ b/sop-website/sop-website-frontend/src/utils/http/index.ts @@ -224,7 +224,5 @@ export const http = new PureHttp(); * @param path 相对于public文件夹路径,如文件在public/static/sign.md,填:static/sign.md */ export function getFile(path) { - return Axios.get(path, { - responseType: "text" - }); + return Axios.get(path); } diff --git a/sop-website/sop-website-frontend/src/views/doc/api/index.vue b/sop-website/sop-website-frontend/src/views/doc/api/index.vue index d878afd6..7670e9dd 100755 --- a/sop-website/sop-website-frontend/src/views/doc/api/index.vue +++ b/sop-website/sop-website-frontend/src/views/doc/api/index.vue @@ -138,7 +138,19 @@ const defaultProps = { - + + +

业务返回参数

diff --git a/sop-website/sop-website-frontend/src/views/doc/code/index.vue b/sop-website/sop-website-frontend/src/views/doc/code/index.vue new file mode 100644 index 00000000..10cdd45c --- /dev/null +++ b/sop-website/sop-website-frontend/src/views/doc/code/index.vue @@ -0,0 +1,31 @@ + + diff --git a/sop-website/sop-website-frontend/src/views/doc/sign/index.ts b/sop-website/sop-website-frontend/src/views/doc/sign/index.ts deleted file mode 100755 index 379fd088..00000000 --- a/sop-website/sop-website-frontend/src/views/doc/sign/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { ref } from "vue"; - -export const value = ref(""); diff --git a/sop-website/sop-website-frontend/src/views/doc/sign/index.vue b/sop-website/sop-website-frontend/src/views/doc/sign/index.vue index 4ab8ce19..ee579370 100755 --- a/sop-website/sop-website-frontend/src/views/doc/sign/index.vue +++ b/sop-website/sop-website-frontend/src/views/doc/sign/index.vue @@ -1,10 +1,13 @@