diff --git a/changelog.md b/changelog.md index 27ea95c3..2fb8a76c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # changelog +## 4.0.3 + +- 可定义业务错误码(见`@Open`注解中的`bizCode`属性) +- 文档参数可指定最大长度(使用`@Length(max = xx)`) + ## 4.0.2 - 支持swagger排序(position属性) diff --git a/doc/docs/files/10041_编写文档.md b/doc/docs/files/10041_编写文档.md index 25ad741e..cc389727 100644 --- a/doc/docs/files/10041_编写文档.md +++ b/doc/docs/files/10041_编写文档.md @@ -84,7 +84,11 @@ public class StoryResult { * @return */ @ApiOperation(value = "获取故事信息", notes = "说明接口的详细信息,介绍,用途,注意事项等。") -@Open(value = "alipay.story.find") +@Open(value = "alipay.story.find", bizCode = { + // 定义业务错误码,用于文档显示 + @BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"), + @BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"), + }) public StoryResult getStory2(StoryParam story) { log.info("获取故事信息参数, story: {}", story); // 获取其它参数 @@ -116,4 +120,7 @@ swagger注解和文档界面显示关系如下图所示: ![预览](images/10041_2.png "10041_2.png") -![预览](images/10041_3.png "10041_3.png") \ No newline at end of file +![预览](images/10041_3.png "10041_3.png") + + +![预览](images/10041_4.png "10041_4.png") \ No newline at end of file diff --git a/doc/docs/files/10100_提供restful接口.md b/doc/docs/files/10100_提供restful接口.md index 2aef7b1f..1be388a3 100644 --- a/doc/docs/files/10100_提供restful接口.md +++ b/doc/docs/files/10100_提供restful接口.md @@ -1,4 +1,4 @@ -# 提供rest接口 +# 提供restful接口 有些接口没有被开放,但是也想要通过网关来访问,SOP提供一个固定的请求格式来访问。 diff --git a/doc/docs/files/images/10041_4.png b/doc/docs/files/images/10041_4.png new file mode 100644 index 00000000..89f55a40 Binary files /dev/null and b/doc/docs/files/images/10041_4.png differ diff --git a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java b/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java deleted file mode 100644 index 3ee17826..00000000 --- a/sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.gitee.sop.adminserver.api.config; - -import com.gitee.easyopen.annotation.ApiService; -import com.gitee.easyopen.doc.annotation.ApiDoc; - -/** - * @author tanghc - */ -@ApiService -@ApiDoc("配置列表") -public class ConfigListApi { - -} diff --git a/sop-auth/pom.xml b/sop-auth/pom.xml index b01d416f..3c192894 100644 --- a/sop-auth/pom.xml +++ b/sop-auth/pom.xml @@ -25,7 +25,7 @@ com.gitee.sop sop-service-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT diff --git a/sop-common/sop-bridge-eureka/pom.xml b/sop-common/sop-bridge-eureka/pom.xml index f367b7a9..a21ae837 100644 --- a/sop-common/sop-bridge-eureka/pom.xml +++ b/sop-common/sop-bridge-eureka/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT sop-bridge-eureka @@ -18,7 +18,7 @@ com.gitee.sop sop-gateway-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT diff --git a/sop-common/sop-bridge-eureka/src/main/java/com/gitee/sop/bridge/route/EurekaRegistryListener.java b/sop-common/sop-bridge-eureka/src/main/java/com/gitee/sop/bridge/route/EurekaRegistryListener.java index f0ca2758..7b7e3797 100644 --- a/sop-common/sop-bridge-eureka/src/main/java/com/gitee/sop/bridge/route/EurekaRegistryListener.java +++ b/sop-common/sop-bridge-eureka/src/main/java/com/gitee/sop/bridge/route/EurekaRegistryListener.java @@ -48,6 +48,7 @@ public class EurekaRegistryListener extends BaseRegistryListener { return instanceInfos.get(0); }) .map(instanceInfo -> new ServiceHolder(instanceInfo.getAppName(), instanceInfo.getLastUpdatedTimestamp())) + .filter(this::canOperator) .collect(Collectors.toList()); final Set currentServices = new HashSet<>(serviceList); @@ -57,7 +58,7 @@ public class EurekaRegistryListener extends BaseRegistryListener { if (currentServices.size() > 0) { List newApplications = registeredApplications.stream() .filter(application -> - this.canOperator(application.getName()) && containsService(currentServices, application.getName())) + containsService(currentServices, application.getName())) .collect(Collectors.toList()); this.doRegister(newApplications); diff --git a/sop-common/sop-bridge-nacos/pom.xml b/sop-common/sop-bridge-nacos/pom.xml index 822b8234..21504e9e 100644 --- a/sop-common/sop-bridge-nacos/pom.xml +++ b/sop-common/sop-bridge-nacos/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT sop-bridge-nacos @@ -18,7 +18,7 @@ com.gitee.sop sop-gateway-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT diff --git a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/SopRegisterAutoConfiguration.java b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/SopRegisterAutoConfiguration.java index 6929b702..63c49e23 100644 --- a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/SopRegisterAutoConfiguration.java +++ b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/SopRegisterAutoConfiguration.java @@ -1,20 +1,9 @@ package com.gitee.sop.bridge; -import com.alibaba.cloud.nacos.NacosDiscoveryProperties; -import com.alibaba.cloud.nacos.discovery.NacosWatch; import com.gitee.sop.bridge.route.NacosRegistryListener; import com.gitee.sop.gatewaycommon.route.RegistryListener; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.scheduling.TaskScheduler; - -import java.util.Map; /** * @author tanghc @@ -22,20 +11,6 @@ import java.util.Map; @Configuration public class SopRegisterAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties, ObjectProvider taskScheduler, Environment environment) { - Map metadata = nacosDiscoveryProperties.getMetadata(); - String contextPath = environment.getProperty("server.servlet.context-path"); - // 将context-path信息加入到metadata中 - if (contextPath != null) { - metadata.put("context-path", contextPath); - } - // 在元数据中新增启动时间,不能修改这个值,不然网关拉取接口会有问题 - metadata.put("time.startup", String.valueOf(System.currentTimeMillis())); - return new NacosWatch(nacosDiscoveryProperties, taskScheduler); - } - /** * 微服务路由加载 */ diff --git a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java index cc871f80..48058ad4 100644 --- a/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java +++ b/sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java @@ -6,9 +6,12 @@ import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.gitee.sop.gatewaycommon.bean.InstanceDefinition; +import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.route.BaseRegistryListener; import com.gitee.sop.gatewaycommon.route.RegistryEvent; +import com.gitee.sop.gatewaycommon.route.ServiceHolder; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.math.NumberUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.util.CollectionUtils; @@ -29,8 +32,6 @@ import java.util.stream.Collectors; @Slf4j public class NacosRegistryListener extends BaseRegistryListener { - private static final String METADATA_KEY_TIME_STARTUP = "time.startup"; - private volatile Set cacheServices = new HashSet<>(); @Autowired @@ -73,6 +74,7 @@ public class NacosRegistryListener extends BaseRegistryListener { /** * 获取建康的服务实例 + * * @return 没有返回空的list */ private List getServiceList() { @@ -89,36 +91,36 @@ public class NacosRegistryListener extends BaseRegistryListener { return servicesOfServer .getData() .stream() - .filter(this::canOperator) .map(serviceName -> { + List allInstances; try { // 获取服务实例 - List allInstances = namingService.getAllInstances(serviceName); - if (CollectionUtils.isEmpty(allInstances)) { - return null; - } - Instance instance = allInstances.stream() - // 只获取建康实例 - .filter(Instance::isHealthy) - // 根据启动时间倒叙,找到最新的服务器 - .max(Comparator.comparing(ins -> { - String startupTime = ins.getMetadata().getOrDefault(METADATA_KEY_TIME_STARTUP, "0"); - return Long.valueOf(startupTime); - })) - .orElse(null); - if (instance == null) { - return null; - } else { - String startupTime = instance.getMetadata().getOrDefault("time.startup", "0"); - long time = Long.parseLong(startupTime); - return new NacosServiceHolder(serviceName, time, instance); - } + allInstances = namingService.getAllInstances(serviceName); } catch (NacosException e) { log.error("namingService.getAllInstances(serviceName)错误,serviceName:{}", serviceName, e); return null; } + if (CollectionUtils.isEmpty(allInstances)) { + return null; + } + return allInstances.stream() + // 只获取建康实例 + .filter(Instance::isHealthy) + .map(instance -> { + String startupTime = instance.getMetadata().get(SopConstants.METADATA_KEY_TIME_STARTUP); + if (startupTime == null) { + return null; + } + long time = NumberUtils.toLong(startupTime, 0); + return new NacosServiceHolder(serviceName, time, instance); + }) + .filter(Objects::nonNull) + .max(Comparator.comparing(ServiceHolder::getLastUpdatedTimestamp)) + .orElse(null); + }) .filter(Objects::nonNull) + .filter(this::canOperator) .collect(Collectors.toList()); } diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index 0346be41..3b4d5df7 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -11,7 +11,7 @@ 4.0.0 sop-gateway-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT jar diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java index 47abfbd3..7c04cec7 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java @@ -54,4 +54,6 @@ public class SopConstants { public static final String TARGET_SERVICE = "sop-target-service"; public static final String RESTFUL_REQUEST = "sop-restful-request"; + public static final String METADATA_KEY_TIME_STARTUP = "time.startup"; + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java index 2e7128b5..06db6e24 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java @@ -20,7 +20,7 @@ public abstract class BaseRegistryListener implements RegistryListener { private static final int FIVE_SECONDS = 1000 * 5; - private Map updateTimeMap = new ConcurrentHashMap<>(16); + private final Map updateTimeMap = new ConcurrentHashMap<>(16); public static List EXCLUDE_SERVICE_ID_LIST = new ArrayList<>(8); @@ -54,7 +54,8 @@ public abstract class BaseRegistryListener implements RegistryListener { serviceListener.onAddInstance(instance); } - protected boolean canOperator(String serviceId) { + protected boolean canOperator(ServiceHolder serviceHolder) { + String serviceId = serviceHolder.getServiceId(); // 被排除的服务,不能操作 if (isExcludeService(serviceId)) { return false; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceHolder.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceHolder.java index a5641f08..568ada29 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceHolder.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceHolder.java @@ -1,7 +1,6 @@ package com.gitee.sop.gatewaycommon.route; import lombok.AllArgsConstructor; -import lombok.Data; import lombok.Getter; import lombok.Setter; diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index 91d00021..582ab35d 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -12,7 +12,7 @@ 4.0.0 sop-service-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT jar diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/BizCode.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/BizCode.java new file mode 100644 index 00000000..892df082 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/BizCode.java @@ -0,0 +1,37 @@ +package com.gitee.sop.servercommon.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 错误码 + * + * @author tanghc + */ +@Target(ElementType.ANNOTATION_TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface BizCode { + + /** + * 错误码 + * + * @return + */ + String code(); + + /** + * 错误描述 + * @return + */ + String msg(); + + /** + * 解决方案 + * @return + */ + String solution() default ""; +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/Open.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/Open.java index 661471fe..f2eb882d 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/Open.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/Open.java @@ -44,4 +44,9 @@ public @interface Open { * 是否需要appAuthToken,设置为true,网关端会校验token是否存在 */ boolean needToken() default false; + + /** + * 定义业务错误码,用于文档显示 + */ + BizCode[] bizCode() default {}; } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java index 217a3614..e502b0f3 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java @@ -59,6 +59,22 @@ public class ServiceConfiguration implements WebMvcConfigurer { registry.addInterceptor(new ServiceContextInterceptor()); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr") + public NacosWatch nacosWatch(NacosDiscoveryProperties nacosDiscoveryProperties, ObjectProvider taskScheduler, Environment environment) { + Map metadata = nacosDiscoveryProperties.getMetadata(); + String contextPath = environment.getProperty("server.servlet.context-path"); + // 将context-path信息加入到metadata中 + if (contextPath != null) { + metadata.put("context-path", contextPath); + } + // 在元数据中新增启动时间,不能修改这个值,不然网关拉取接口会有问题 + // 如果没有这个值,网关会忽略这个服务 + metadata.put("time.startup", String.valueOf(System.currentTimeMillis())); + return new NacosWatch(nacosDiscoveryProperties, taskScheduler); + } + @Bean @ConditionalOnMissingBean GlobalExceptionHandler globalExceptionHandler() { diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/CustomSwaggerParameterBuilder.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/CustomSwaggerParameterBuilder.java index b3e4974b..e45ef2c3 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/CustomSwaggerParameterBuilder.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/CustomSwaggerParameterBuilder.java @@ -6,9 +6,12 @@ import com.google.common.base.Strings; import com.google.common.collect.Lists; import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiParam; +import org.hibernate.validator.constraints.Length; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.service.AllowableListValues; import springfox.documentation.service.AllowableValues; +import springfox.documentation.service.StringVendorExtension; +import springfox.documentation.service.VendorExtension; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.schema.EnumTypeDeterminer; import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin; @@ -18,6 +21,7 @@ import springfox.documentation.swagger.common.SwaggerPluginSupport; import springfox.documentation.swagger.readers.parameter.Examples; import springfox.documentation.swagger.schema.ApiModelProperties; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -76,6 +80,8 @@ public class CustomSwaggerParameterBuilder implements ExpandedParameterBuilderPl .scalarExample(apiParam.example()) .complexExamples(Examples.examples(apiParam.examples())) .order(SWAGGER_PLUGIN_ORDER) + // 添加额外属性 + .vendorExtensions(this.getVendorExtension(context)) .build(); } @@ -94,9 +100,22 @@ public class CustomSwaggerParameterBuilder implements ExpandedParameterBuilderPl .scalarExample(apiModelProperty.example()) //源码这里是: SWAGGER_PLUGIN_ORDER,需要修正 .order(apiModelProperty.position()) + // 添加额外属性 + .vendorExtensions(this.getVendorExtension(context)) .build(); } + private List getVendorExtension(ParameterExpansionContext context) { + List vendorExtensions = new ArrayList<>(4); + Optional annotation = context.findAnnotation(Length.class); + if (annotation.isPresent()) { + Length length = annotation.get(); + vendorExtensions.add(new StringVendorExtension("maxLength", String.valueOf(length.max()))); + vendorExtensions.add(new StringVendorExtension("minLength", String.valueOf(length.min()))); + } + return vendorExtensions; + } + private ParameterBuilder maybeSetParameterName(ParameterExpansionContext context, String parameterName) { if (!Strings.isNullOrEmpty(parameterName)) { context.getParameterBuilder().name(parameterName); diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/DocumentationPluginsManagerExt.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/DocumentationPluginsManagerExt.java index a7ed04f3..c44c9d8a 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/DocumentationPluginsManagerExt.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/DocumentationPluginsManagerExt.java @@ -1,10 +1,19 @@ package com.gitee.sop.servercommon.swagger; +import com.alibaba.fastjson.JSON; +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.classmate.members.RawField; +import com.gitee.sop.servercommon.annotation.BizCode; import com.gitee.sop.servercommon.annotation.Open; import com.gitee.sop.servercommon.bean.ServiceConfig; import com.google.common.base.Optional; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiOperation; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import springfox.documentation.service.Operation; import springfox.documentation.service.StringVendorExtension; @@ -12,7 +21,15 @@ import springfox.documentation.service.VendorExtension; import springfox.documentation.spi.service.contexts.OperationContext; import springfox.documentation.spring.web.plugins.DocumentationPluginsManager; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author tanghc @@ -23,6 +40,7 @@ public class DocumentationPluginsManagerExt extends DocumentationPluginsManager private static final String SOP_VERSION = "sop_version"; private static final String MODULE_ORDER = "module_order"; private static final String API_ORDER = "api_order"; + private static final String BIZ_CODE = "biz_code"; @Override public Operation operation(OperationContext operationContext) { @@ -40,6 +58,8 @@ public class DocumentationPluginsManagerExt extends DocumentationPluginsManager String version = buildVersion(open.version()); vendorExtensions.add(new StringVendorExtension(SOP_NAME, name)); vendorExtensions.add(new StringVendorExtension(SOP_VERSION, version)); + this.setBizCode(open, vendorExtensions); + this.setResultExtProperties(operationContext); } Optional apiOptional = operationContext.findControllerAnnotation(Api.class); int order = 0; @@ -60,6 +80,73 @@ public class DocumentationPluginsManagerExt extends DocumentationPluginsManager vendorExtensions.add(new StringVendorExtension(API_ORDER, String.valueOf(methodOrder))); } + /** + * 设置返回结果额外属性,如最大长度 + * @param operationContext + */ + private void setResultExtProperties(OperationContext operationContext) { + List vendorExtensions = operationContext.getDocumentationContext().getVendorExtentions(); + ResolvedType returnType = operationContext.getReturnType(); + Class erasedType = returnType.getErasedType(); + String className = erasedType.getSimpleName(); + boolean exist = vendorExtensions.stream().anyMatch(p -> Objects.equals(p.getName(), className)); + if (!exist) { + List memberFields = returnType.getMemberFields(); + Map> fieldProperties = new HashMap<>(16); + for (RawField memberField : memberFields) { + String key = memberField.getName(); + Length length = AnnotationUtils.findAnnotation(memberField.getRawMember(), Length.class); + if (length != null) { + Map properties = fieldProperties.computeIfAbsent(key, k -> new HashMap<>(16)); + properties.computeIfAbsent("maxLength", k -> length.max()); + properties.computeIfAbsent("minLength", k -> length.max()); + } + ApiModelProperty apiModelProperty = AnnotationUtils.findAnnotation(memberField.getRawMember(), ApiModelProperty.class); + if (apiModelProperty != null) { + Map properties = fieldProperties.computeIfAbsent(key, k -> new HashMap<>(16)); + boolean required = apiModelProperty.required(); + // 只有在必填的情况下设置 + if (required) { + properties.put("required", required); + } + } + } + vendorExtensions.add(new StringVendorExtension(className, JSON.toJSONString(fieldProperties))); + } + } + + private Class getGenericType(Field curField) { + // 当前集合的泛型类型 + Type genericType = curField.getGenericType(); + if (genericType instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) genericType; + // 得到泛型里的class类型对象 + return (Class) pt.getActualTypeArguments()[0]; + } + return null; + } + + /** + * 设置业务错误码 + * @param open + * @param vendorExtensions + */ + private void setBizCode(Open open, List vendorExtensions) { + BizCode[] bizCodes = open.bizCode(); + List codeObjList = Stream.of(bizCodes) + .map(bizCode -> new BizCodeObj(bizCode.code(), bizCode.msg(), bizCode.solution())) + .collect(Collectors.toList()); + vendorExtensions.add(new StringVendorExtension(BIZ_CODE, JSON.toJSONString(codeObjList))); + } + + @Data + @AllArgsConstructor + private static class BizCodeObj { + private String code; + private String msg; + private String solution; + } + private String buildVersion(String version) { if ("".equals(version)) { return ServiceConfig.getInstance().getDefaultVersion(); diff --git a/sop-example/sop-springmvc/pom.xml b/sop-example/sop-springmvc/pom.xml index 959313eb..23060d5d 100644 --- a/sop-example/sop-springmvc/pom.xml +++ b/sop-example/sop-springmvc/pom.xml @@ -26,7 +26,7 @@ com.gitee.sop sop-service-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT diff --git a/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServletContextListener.java b/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServletContextListener.java index fc99a94f..8b65ed64 100644 --- a/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServletContextListener.java +++ b/sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServletContextListener.java @@ -27,7 +27,7 @@ public class OpenServletContextListener implements ServletContextListener { NamingService namingService = webApplicationContext.getBean(NamingService.class); try { - namingService.deregisterInstance(serviceId, ip, Integer.valueOf(port)); + namingService.deregisterInstance(serviceId, ip, Integer.parseInt(port)); } catch (NacosException e) { log.error("注销nacos服务失败,serviceId:{}, ip:{}, port:{}", serviceId, ip, port); } diff --git a/sop-example/sop-story/pom.xml b/sop-example/sop-story/pom.xml index 1f806150..f45edd17 100644 --- a/sop-example/sop-story/pom.xml +++ b/sop-example/sop-story/pom.xml @@ -19,7 +19,7 @@ com.gitee.sop sop-service-common - 4.0.2-SNAPSHOT + 4.0.3-SNAPSHOT diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java index a291d960..b2eb6e2c 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java @@ -1,5 +1,6 @@ package com.gitee.sop.storyweb.controller; +import com.gitee.sop.servercommon.annotation.BizCode; import com.gitee.sop.servercommon.annotation.Open; import com.gitee.sop.storyweb.controller.param.CategoryParam; import com.gitee.sop.storyweb.controller.param.LargeTextParam; @@ -42,7 +43,11 @@ public class Example1001_BaseController { // 基础用法 @ApiOperation(value = "获取故事信息(首位)", notes = "获取故事信息的详细信息", position = -100/* position默认0,值越小越靠前 */) - @Open("story.get") + @Open(value = "story.get", bizCode = { + // 定义业务错误码,用于文档显示 + @BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"), + @BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"), + }) @RequestMapping("/get/v1") public StoryResult get_v1(StoryParam param) { StoryResult story = new StoryResult(); @@ -53,7 +58,11 @@ public class Example1001_BaseController { // 指定版本号 @ApiOperation(value = "获取故事信息", notes = "获取故事信息的详细信息") - @Open(value = "story.get", version = "2.0") + @Open(value = "story.get", version = "2.0", bizCode = { + // 定义业务错误码,用于文档显示 + @BizCode(code = "100001", msg = "姓名错误", solution = "填写正确的姓名"), + @BizCode(code = "100002", msg = "备注错误", solution = "填写正确备注"), + }) @RequestMapping("/get/v2") public StoryResult get_v2(StoryParam param) { StoryResult story = new StoryResult(); diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/StoryParam.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/StoryParam.java index 6095f7c9..84ae44ea 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/StoryParam.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/StoryParam.java @@ -4,6 +4,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.hibernate.validator.constraints.Length; +import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; @Data @@ -17,5 +18,6 @@ public class StoryParam { private String name; @ApiModelProperty(value = "备注 (第二)", example = "xx", position = 2) + @Length(max = 64, message = "长度不能超过64") private String remark; } \ No newline at end of file diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java index b3bf99ef..80f6f69b 100644 --- a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java @@ -12,10 +12,11 @@ import java.util.Date; */ @Data public class StoryResult { - @ApiModelProperty(value = "故事ID", example = "1") + @ApiModelProperty(value = "故事ID", required = true, example = "1") private Long id; - @ApiModelProperty(value = "故事名称", example = "海底小纵队") + @ApiModelProperty(value = "故事名称", required = true, example = "海底小纵队") + @Length(max = 20) private String name; @ApiModelProperty(value = "创建时间", example = "2019-04-14 19:02:12") diff --git a/sop-example/sop-story/src/main/resources/application-dev.properties b/sop-example/sop-story/src/main/resources/application-dev.properties index 435c9b99..ea5b6216 100644 --- a/sop-example/sop-story/src/main/resources/application-dev.properties +++ b/sop-example/sop-story/src/main/resources/application-dev.properties @@ -2,5 +2,4 @@ server.port=2222 spring.application.name=story-service # 注册中心 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java index 342a5af1..c7232504 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java @@ -2,6 +2,7 @@ package com.gitee.sop.sdk.client; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; import com.gitee.sop.sdk.common.DataNameBuilder; import com.gitee.sop.sdk.common.OpenConfig; import com.gitee.sop.sdk.common.RequestForm; @@ -36,17 +37,17 @@ public class OpenClient { /** * 接口请求url */ - private String url; + private final String url; /** * 平台提供的appId */ - private String appId; + private final String appId; /** * 开放平台提供的私钥 */ - private String privateKey; + private final String privateKey; /** * 开放平台提供的公钥 @@ -56,17 +57,17 @@ public class OpenClient { /** * 配置项 */ - private OpenConfig openConfig; + private final OpenConfig openConfig; /** * 请求对象 */ - private OpenRequest openRequest; + private final OpenRequest openRequest; /** * 节点处理 */ - private DataNameBuilder dataNameBuilder; + private final DataNameBuilder dataNameBuilder; /** * 构建请求客户端 @@ -193,7 +194,7 @@ public class OpenClient { protected T parseResponse(String resp, BaseRequest request) { String method = request.getMethod(); String rootNodeName = dataNameBuilder.build(method); - JSONObject jsonObject = JSON.parseObject(resp); + JSONObject jsonObject = JSON.parseObject(resp, Feature.OrderedField); String errorResponseName = this.openConfig.getErrorResponseName(); boolean errorResponse = jsonObject.containsKey(errorResponseName); if (errorResponse) { @@ -210,7 +211,7 @@ public class OpenClient { } } T t = data.toJavaObject(request.getResponseClass()); - t.setBody(data.toJSONString()); + t.setBody(jsonObject.getString(rootNodeName)); return t; } diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/DemoFileUploadRequest.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/DemoFileUploadRequest.java index b5b6c340..c9943d0c 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/DemoFileUploadRequest.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/DemoFileUploadRequest.java @@ -8,6 +8,6 @@ import com.gitee.sop.sdk.response.DemoFileUploadResponse; public class DemoFileUploadRequest extends BaseRequest { @Override protected String method() { - return "demo.file.upload"; + return "file.upload"; } } diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/GetStoryRequest.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/GetStoryRequest.java index e8a1f062..077d1e9f 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/GetStoryRequest.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/GetStoryRequest.java @@ -5,7 +5,7 @@ import com.gitee.sop.sdk.response.GetStoryResponse; public class GetStoryRequest extends BaseRequest { @Override protected String method() { - return "alipay.story.find"; + return "story.get"; } } diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java index 23bbf469..9536c043 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java @@ -22,6 +22,8 @@ import lombok.Setter; @Getter public abstract class BaseResponse { + @JSONField(name = "request_id") + private String requestId; private String code; private String msg; @JSONField(name = "sub_code") diff --git a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/BaseTest.java b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/BaseTest.java index 9798d2a1..310beea5 100644 --- a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/BaseTest.java +++ b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/BaseTest.java @@ -1,6 +1,5 @@ package com.gitee.sop.sdk; -import com.gitee.sop.sdk.client.OpenClient; import junit.framework.TestCase; public class BaseTest extends TestCase { diff --git a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java index 4a843610..8ba796a2 100644 --- a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java +++ b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java @@ -48,19 +48,19 @@ public class SdkTest extends TestCase { if (response.isSuccess()) { // 返回结果 - System.out.println(String.format("成功!response:%s\n响应原始内容:%s", - JSON.toJSONString(response), response.getBody())); + System.out.println(String.format("response:%s", + JSON.toJSONString(response))); } else { System.out.println("错误,subCode:" + response.getSubCode() + ", subMsg:" + response.getSubMsg()); } } - // 懒人版,如果不想添加Request,Response,Model。可以用这种方式,返回全部是String,后续自己处理json + // 懒人版,如果不想添加Request,Response,可以用这种方式,返回全部是String,后续自己处理json @Test public void testLazy() { // 创建请求对象 - CommonRequest request = new CommonRequest("alipay.story.find"); + CommonRequest request = new CommonRequest("story.get"); // 请求参数 Map bizModel = new HashMap<>(); bizModel.put("name", "白雪公主"); diff --git a/sop-sdk/sdk-nodejs/.gitignore b/sop-sdk/sdk-nodejs/.gitignore new file mode 100644 index 00000000..9ad28d23 --- /dev/null +++ b/sop-sdk/sdk-nodejs/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +tests/**/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/sop-sdk/sdk-nodejs/aa.txt b/sop-sdk/sdk-nodejs/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-nodejs/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/bb.txt b/sop-sdk/sdk-nodejs/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-nodejs/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/common/Class.js b/sop-sdk/sdk-nodejs/common/Class.js new file mode 100644 index 00000000..e066def1 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/Class.js @@ -0,0 +1,125 @@ +/** + * 面相对象辅助类,可实现类的创建,继承,方法重写 + * +
+ //-------------------------
+ // JS类的创建,继承
+ //-------------------------
+
+ // 例子1:-------------------------
+ // 创建一个父类
+ var Person = Class.create({
+        // 构造函数
+        init:function(option){
+            this.name = option.name;
+        }
+        ,getName:function() {
+            return this.name;
+        }
+    });
+
+ // 声明类实例
+ var Jim = new Person({name:'Jim'});
+ console.log('Jim name:' + Jim.getName())
+
+ //例子2:-------------------------
+
+ // 创建一个类,继承Person类,并重写getName
+ var Man = Class.create({
+        init:function(option) {
+            this._super(option);// 调用父类构造函数
+            this.age = option.age;
+        }
+        // 重写父类方法
+        ,getName:function() {
+            // 调用父类的getName()
+            var name = this._super();
+            return '我重写了getName方法:{'+name+'}';
+        }
+    },Person);
+
+ var man = new Man({name:'Tom',age:22});
+ console.log('man name:' + man.getName())
+
+ console.log('Jim instanceof Person: ' + (Jim instanceof Person));
+ console.log('man instanceof Person: ' + (man instanceof Person));
+ 
+ * + */ +exports.Class = (function () { + // ------Class Creation------ + var initializing = false, + fnTest = /xyz/.test(function () { + xyz; + }) ? /\b_super\b/ : /.*/; + + // The base Class implementation (does nothing) + this.Class = function () { + }; + + // Create a new Class that inherits from this class + Class.extend = function (prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) { + return function () { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if (!initializing && this.init) this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + };// ------Class Creation end------ + + + + return { + /** + * 创建一个类 + * @param option 类方法,json数据 + * @param parentClass 父类 + */ + create: function (option, parentClass) { + if (!parentClass) { + parentClass = Class; + } + return parentClass.extend(option); + } + }; + +})(); diff --git a/sop-sdk/sdk-nodejs/common/OpenClient.js b/sop-sdk/sdk-nodejs/common/OpenClient.js new file mode 100644 index 00000000..1707ddf9 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/OpenClient.js @@ -0,0 +1,151 @@ +const needle = require('needle') +const moment = require('moment') + +const {Class} = require("./Class"); +const {RequestType} = require('./RequestType') +const {SignUtil} = require('./SignUtil') +const {BaseRequest} = require('../request/BaseRequest') + +const HEADERS = {'Accept-Encoding': 'identity'} + +const getHeaders = function (headers) { + if (!headers) { + return HEADERS + } + for (const key in HEADERS) { + headers[key] = HEADERS[key]; + } + return headers +} + +const OpenClient = Class.create({ + /** + * 初始化客户端 + * @param appId 应用ID + * @param privateKey 应用私钥,2048位,PKCS8 + * @param url 请求url + */ + init: function (appId, privateKey, url) { + this.appId = appId; + this.privateKey = privateKey; + this.url = url; + }, + /** + * 发送请求 + * @param request 请求类 + * @param callback 回调函数,参数json + */ + execute: function (request, callback) { + this.executeToken(request, null, callback) + }, + /** + * 发送请求 + * @param request 请求类 + * @param token token + * @param callback 回调函数,参数json + */ + executeToken: function (request, token, callback) { + if (!(request instanceof BaseRequest)) { + throw 'request类未继承BaseRequest' + } + const requestType = request.getRequestType(); + if (request.files) { + this._postFile(request, callback); + } else { + switch (requestType) { + case RequestType.GET: + this._get(request, callback); + break + case RequestType.POST_FORM: + this._postForm(request, callback); + break + case RequestType.POST_JSON: + this._postJson(request, callback); + break + case RequestType.POST_FILE: + this._postFile(request, callback); + break + default :{ + throw 'request.getRequestType()类型不正确' + } + } + } + }, + _get: function(request, callback) { + const allParams = this._buildParams(request) + const that = this + // needle.request(method, url, data[, options][, callback]) + needle.request('GET',this.url, allParams, { + headers: getHeaders() + }, function(error, response) { + callback(that._parseResponse(error, response, request)) + }); + }, + _postForm: function (request, callback) { + const allParams = this._buildParams(request) + const that = this + needle.request('POST',this.url, allParams, { + headers: getHeaders({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) + }, function(error, response) { + callback(that._parseResponse(error, response, request)) + }); + }, + _postJson: function (request, callback) { + const allParams = this._buildParams(request) + const that = this + needle.request('POST',this.url, allParams, { + headers: getHeaders(), json: true + }, function(error, response) { + callback(that._parseResponse(error, response, request)) + }); + }, + _postFile: function (request, callback) { + const allParams = this._buildParams(request) + const files = request.files; + files.forEach(row => { + // 设置成{ file: row.path, content_type: 'application/octet-stream' }格式 + // needle会认为是上传文件 + allParams[row.name] = { file: row.path, content_type: 'application/octet-stream' } + }) + const that = this + needle.request('POST',this.url, allParams, { + headers: getHeaders(), multipart: true + }, function(error, response) { + callback(that._parseResponse(error, response, request)) + }); + }, + _parseResponse: function (error, response, request) { + if (!error && response.statusCode === 200) { + return request.parseResponse(response.body) + } else { + throw '请求异常:' + error + } + }, + _buildParams: function (request, token) { + const allParams = { + 'app_id': this.appId, + 'method': request.getMethod(), + 'charset': 'UTF-8', + 'sign_type': 'RSA2', + 'timestamp': moment().format('YYYY-MM-DD HH:mm:ss'), + 'version': request.getVersion() + } + const bizModel = request.bizModel + + for (const key in bizModel) { + allParams[key] = bizModel[key] + } + + if (token) { + allParams['app_auth_token'] = token + } + // 创建签名 + const sign = SignUtil.createSign(allParams, this.privateKey, 'RSA2') + allParams.sign = sign + return allParams; + } +}) + +module.exports = OpenClient diff --git a/sop-sdk/sdk-nodejs/common/RequestType.js b/sop-sdk/sdk-nodejs/common/RequestType.js new file mode 100644 index 00000000..e64db59d --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/RequestType.js @@ -0,0 +1,6 @@ +exports.RequestType = { + GET: 'GET', + POST_FORM: 'POST_FORM', + POST_JSON: 'POST_JSON', + POST_FILE: 'POST_FILE' +} diff --git a/sop-sdk/sdk-nodejs/common/SignUtil.js b/sop-sdk/sdk-nodejs/common/SignUtil.js new file mode 100644 index 00000000..03d757a6 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/SignUtil.js @@ -0,0 +1,88 @@ +const {KJUR, hextob64} = require('jsrsasign') + +const HashMap = { + SHA256withRSA: 'SHA256withRSA', + SHA1withRSA: 'SHA1withRSA' +} + +const PEM_BEGIN = '-----BEGIN PRIVATE KEY-----\n' +const PEM_END = '\n-----END PRIVATE KEY-----' + +/** + * rsa签名参考:https://www.jianshu.com/p/145eab95322c + */ +exports.SignUtil = { + /** + * 创建签名 + * @param params 请求参数 + * @param privateKey 私钥,PKCS8 + * @param signType 签名类型,RSA,RSA2 + * @returns 返回签名内容 + */ + createSign(params, privateKey, signType) { + const content = this.getSignContent(params) + return this.sign(content, privateKey, signType) + }, + sign: function (content, privateKey, signType) { + if (signType.toUpperCase() === 'RSA') { + return this.rsaSign(content, privateKey, HashMap.SHA1withRSA) + } else if (signType.toUpperCase() === 'RSA2') { + return this.rsaSign(content, privateKey, HashMap.SHA256withRSA) + } else { + throw 'signType错误' + } + }, + /** + * rsa签名 + * @param content 签名内容 + * @param privateKey 私钥 + * @param hash hash算法,SHA256withRSA,SHA1withRSA + * @returns 返回签名字符串,base64 + */ + rsaSign: function (content, privateKey, hash) { + privateKey = this._formatKey(privateKey) + // 创建 Signature 对象 + const signature = new KJUR.crypto.Signature({ + alg: hash, + //!这里指定 私钥 pem! + prvkeypem: privateKey + }) + signature.updateString(content) + const signData = signature.sign() + // 将内容转成base64 + return hextob64(signData) + }, + _formatKey: function (key) { + if (!key.startsWith(PEM_BEGIN)) { + key = PEM_BEGIN + key + } + if (!key.endsWith(PEM_END)) { + key = key + PEM_END + } + return key + }, + /** + * 获取签名内容 + * @param params 请求参数 + * @returns {string} + */ + getSignContent: function (params) { + const paramNames = [] + for(const key in params) { + paramNames.push(key) + } + + paramNames.sort() + + const paramNameValue = [] + + for (let i = 0, len = paramNames.length; i < len; i++) { + const paramName = paramNames[i]; + const val = params[paramName]; + if (paramName && val) { + paramNameValue.push(`${paramName}=${val}`) + } + } + return paramNameValue.join('&') + } +} diff --git a/sop-sdk/sdk-nodejs/main.js b/sop-sdk/sdk-nodejs/main.js new file mode 100644 index 00000000..4745e990 --- /dev/null +++ b/sop-sdk/sdk-nodejs/main.js @@ -0,0 +1,43 @@ +const OpenClient = require('./common/OpenClient') + +const {StoryGetRequest} = require('./request/StoryGetRequest') + +// 应用ID +const appId = '2019032617262200001' +// 应用私钥,2048位,PKCS8 +const privateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=' +// 接口url +const url = 'http://localhost:8081' + +// 创建客户端 +const openClient = new OpenClient(appId, privateKey, url) + +function test() { + // 创建请求 + const request = new StoryGetRequest() + + // 设置业务参数 + request.bizModel = { + name: 'jim' + } + + // 添加上传文件 + // request.files = [ + // // name: 表单名称,path:文件全路径 + // {name: 'file1', path: `${__dirname}/aa.txt`}, + // {name: 'file2', path: `${__dirname}/bb.txt`} + // ] + + openClient.execute(request, data => { + // 成功 + if (!data.sub_code) { + console.log('成功', data); + } else { + console.error('失败', data) + } + }) +} + + +test() + diff --git a/sop-sdk/sdk-nodejs/package.json b/sop-sdk/sdk-nodejs/package.json new file mode 100644 index 00000000..d1611bd4 --- /dev/null +++ b/sop-sdk/sdk-nodejs/package.json @@ -0,0 +1,17 @@ +{ + "name": "sdk-nodejs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "jsrsasign": "^8.0.19", + "moment": "^2.27.0", + "needle": "^2.5.0" + } +} diff --git a/sop-sdk/sdk-nodejs/readme.md b/sop-sdk/sdk-nodejs/readme.md new file mode 100644 index 00000000..79b852e5 --- /dev/null +++ b/sop-sdk/sdk-nodejs/readme.md @@ -0,0 +1,10 @@ +- 执行`npm install --registry=https://registry.npm.taobao.org` + +- 执行`node main.js`进行测试 + +## 封装步骤 + +1. 新建request类,继承BaseRequest,参考`StoryGetRequest.js` + +2. 调用接口,参考`main.js` + diff --git a/sop-sdk/sdk-nodejs/request/BaseRequest.js b/sop-sdk/sdk-nodejs/request/BaseRequest.js new file mode 100644 index 00000000..7720828b --- /dev/null +++ b/sop-sdk/sdk-nodejs/request/BaseRequest.js @@ -0,0 +1,48 @@ +const {Class} = require("../common/Class"); + +/** + * 请求类父类 + */ +exports.BaseRequest = Class.create({ + init: function(){ + this.bizModel = {} + /* + [ + {name: 'file1', path: 'd:/dd/1.txt'}, + {name: 'file2', path: 'd:/dd/2.txt'} + ] + */ + this.files = [] + }, + /** + * 返回接口名称 + */ + getMethod: function() { + throw `未实现BaseRequest类getMethod()方法`; + }, + /** + * 返回版本号 + */ + getVersion: function() { + throw '未实现BaseRequest类getVersion()方法'; + }, + /** + * 返回请求类型,使用RequestType.js + */ + getRequestType: function() { + throw '未实现BaseRequest类getRequestType()方法'; + }, + /** + * 解析返回结果,子类可以覆盖实现 + * @param responseData 服务器返回内容 + * @returns 返回结果 + */ + parseResponse: function (responseData) { + let data = responseData['error_response']; + if (!data) { + const dataNodeName = this.getMethod().replace(/\./g, '_') + '_response' + data = responseData[dataNodeName] + } + return data; + } +}) diff --git a/sop-sdk/sdk-nodejs/request/StoryGetRequest.js b/sop-sdk/sdk-nodejs/request/StoryGetRequest.js new file mode 100644 index 00000000..fb66c2f6 --- /dev/null +++ b/sop-sdk/sdk-nodejs/request/StoryGetRequest.js @@ -0,0 +1,22 @@ +const {Class} = require("../common/Class"); +const {RequestType} = require("../common/RequestType"); +const {BaseRequest} = require('./BaseRequest') + +/** + * 创建一个请求类,继承BaseRequest,重写三个函数 + */ +const StoryGetRequest = Class.create({ + + getMethod: function () { + return "story.get" + }, + getVersion: function () { + return "1.0" + }, + getRequestType: function () { + return RequestType.GET + } + +}, BaseRequest) // 继承BaseRequest + +module.exports.StoryGetRequest = StoryGetRequest diff --git a/sop-sdk/sdk-nodejs/testClass.js b/sop-sdk/sdk-nodejs/testClass.js new file mode 100644 index 00000000..0e2fe2a8 --- /dev/null +++ b/sop-sdk/sdk-nodejs/testClass.js @@ -0,0 +1,52 @@ +/** + * 演示JS面相对象,包括类的创建,继承,方法重写 + * 运行:node testClass.js + */ + +const {Class} = require('./common/Class') + +function testClass() { + //------------------------- + // JS类的创建,继承 + //------------------------- + + // 例子1:------------------------- + // 创建一个父类 + var Person = Class.create({ + // 构造函数 + init:function(option){ + this.name = option.name; + } + ,getName:function() { + return this.name; + } + }); + + // 声明类实例 + var Jim = new Person({name:'Jim'}); + console.log('Jim name:' + Jim.getName()) + + //例子2:------------------------- + + // 创建一个类,继承Person类,并重写getName + var Man = Class.create({ + init:function(option) { + this._super(option);// 调用父类构造函数 + this.age = option.age; + } + // 重写父类方法 + ,getName:function() { + // 调用父类的getName() + var name = this._super(); + return '我重写了getName方法:{'+name+'}'; + } + },Person); + + var man = new Man({name:'Tom',age:22}); + console.log('man name:' + man.getName()) + + console.log('Jim instanceof Person: ' + (Jim instanceof Person)); + console.log('man instanceof Person: ' + (man instanceof Person)); +} + +testClass() diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java b/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java new file mode 100644 index 00000000..f158e4b5 --- /dev/null +++ b/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java @@ -0,0 +1,13 @@ +package com.gitee.sop.websiteserver.bean; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class BizCode { + private String code; + private String msg; + private String solution; +} diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java b/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java index 5a6236b6..929d20ad 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java +++ b/sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java @@ -30,6 +30,7 @@ public class DocItem { List requestParameters; List responseParameters; + List bizCodeList; public String getNameVersion() { return name + version; diff --git a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java b/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java index 95240efd..0f41da00 100644 --- a/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java +++ b/sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java @@ -1,7 +1,9 @@ package com.gitee.sop.websiteserver.manager; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.websiteserver.bean.BizCode; import com.gitee.sop.websiteserver.bean.DocInfo; import com.gitee.sop.websiteserver.bean.DocItem; import com.gitee.sop.websiteserver.bean.DocModule; @@ -121,6 +123,10 @@ public class SwaggerDocParser implements DocParser { docItem.setDescription(docInfo.getString("description")); docItem.setMultiple(docInfo.getString("multiple") != null); docItem.setProduces(docInfo.getJSONArray("produces").toJavaList(String.class)); + String bizCodeStr = docInfo.getString("biz_code"); + if (bizCodeStr != null) { + docItem.setBizCodeList(JSON.parseArray(bizCodeStr, BizCode.class)); + } docItem.setModuleOrder(NumberUtils.toInt(docInfo.getString("module_order"), 0)); docItem.setApiOrder(NumberUtils.toInt(docInfo.getString("api_order"), 0)); String moduleName = this.buildModuleName(docInfo, docRoot); @@ -208,6 +214,8 @@ public class SwaggerDocParser implements DocParser { protected List buildDocParameters(String ref, JSONObject docRoot, boolean doSubRef) { JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref); + String className = responseObject.getString("title"); + JSONObject extProperties = docRoot.getJSONObject(className); JSONObject properties = responseObject.getJSONObject("properties"); List docParameterList = new ArrayList<>(); if (properties == null) { @@ -224,10 +232,20 @@ public class SwaggerDocParser implements DocParser { JSONObject fieldInfo = properties.getJSONObject(fieldName); DocParameter docParameter = fieldInfo.toJavaObject(DocParameter.class); docParameter.setName(fieldName); + if (extProperties != null) { + JSONObject prop = extProperties.getJSONObject(fieldName); + if (prop != null) { + String maxLength = prop.getString("maxLength"); + docParameter.setMaxLength(maxLength == null ? "-" : maxLength); + String required = prop.getString("required"); + if (required != null) { + docParameter.setRequired(Boolean.parseBoolean(required)); + } + } + } docParameterList.add(docParameter); RefInfo refInfo = this.getRefInfo(fieldInfo); if (refInfo != null && doSubRef) { - // 如果是树状菜单的话,这里可能触发死循环 String subRef = refInfo.ref; boolean nextDoRef = !Objects.equals(ref, subRef); List refs = buildDocParameters(subRef, docRoot, nextDoRef); diff --git a/sop-website/src/main/resources/application-dev.properties b/sop-website/src/main/resources/application-dev.properties index 0bda14f7..a2f2a1f2 100644 --- a/sop-website/src/main/resources/application-dev.properties +++ b/sop-website/src/main/resources/application-dev.properties @@ -12,8 +12,6 @@ gateway.url=http://localhost:8081 ## nacos cloud配置 spring.cloud.nacos.discovery.server-addr=${register.url} -# eureka地址 -eureka.client.serviceUrl.defaultZone=${register.url} # 页面上显示的测试环境地址 api.url-test=http://open-test.yourdomain.com diff --git a/sop-website/src/main/resources/public/pages/doc/doc.html b/sop-website/src/main/resources/public/pages/doc/doc.html index 65d0b6d5..b89b55a7 100644 --- a/sop-website/src/main/resources/public/pages/doc/doc.html +++ b/sop-website/src/main/resources/public/pages/doc/doc.html @@ -367,6 +367,29 @@ +
+
+ 业务错误码 +
+
+
+

+ 公共错误码 +

+
+ + + + + + + + + + +
错误码错误描述解决方案
+
+
diff --git a/sop-website/src/main/resources/public/pages/doc/docEvent.js b/sop-website/src/main/resources/public/pages/doc/docEvent.js index 5f58b327..9f6c60d9 100644 --- a/sop-website/src/main/resources/public/pages/doc/docEvent.js +++ b/sop-website/src/main/resources/public/pages/doc/docEvent.js @@ -16,6 +16,7 @@ function selectItem(docItem, layui) { createRequestParameter(docItem); createResponseParameter(docItem); createResponseCode(docItem); + buildBizCode(docItem); var $li = $('#docItemTree').find('li[nameversion="'+nameVersion+'"]'); $li.addClass('layui-this').siblings().removeClass('layui-this'); @@ -124,3 +125,21 @@ function buildExample(parameter) { return '\"' + parameter.example + '\"'; } } + +function buildBizCode(docItem) { + var html = [] + var bizCodeList = docItem.bizCodeList; + if (bizCodeList && bizCodeList.length > 0) { + for (var i = 0; i < bizCodeList.length; i++) { + var bizCode = bizCodeList[i]; + html.push('') + html.push(''+bizCode.code+'') + html.push(''+bizCode.msg+'') + html.push(''+bizCode.solution+'') + html.push('') + } + $('#bizCode').find('tbody').html(html.join('')); + } else { + $('#bizCode').find('tbody').html('暂无数据'); + } +} \ No newline at end of file