Merge branch 'master' into eureka

# Conflicts:
#	sop-admin/sop-admin-server/pom.xml
#	sop-gateway/pom.xml
#	sop-website/pom.xml
eureka
tanghc 4 years ago
commit 8052f308dd
  1. 5
      changelog.md
  2. 11
      doc/docs/files/10041_编写文档.md
  3. 2
      doc/docs/files/10100_提供restful接口.md
  4. BIN
      doc/docs/files/images/10041_4.png
  5. 13
      sop-admin/sop-admin-server/src/main/java/com/gitee/sop/adminserver/api/config/ConfigListApi.java
  6. 2
      sop-auth/pom.xml
  7. 4
      sop-common/sop-bridge-eureka/pom.xml
  8. 3
      sop-common/sop-bridge-eureka/src/main/java/com/gitee/sop/bridge/route/EurekaRegistryListener.java
  9. 4
      sop-common/sop-bridge-nacos/pom.xml
  10. 25
      sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/SopRegisterAutoConfiguration.java
  11. 48
      sop-common/sop-bridge-nacos/src/main/java/com/gitee/sop/bridge/route/NacosRegistryListener.java
  12. 2
      sop-common/sop-gateway-common/pom.xml
  13. 2
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java
  14. 5
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/BaseRegistryListener.java
  15. 1
      sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/route/ServiceHolder.java
  16. 2
      sop-common/sop-service-common/pom.xml
  17. 37
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/BizCode.java
  18. 5
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/annotation/Open.java
  19. 16
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/configuration/ServiceConfiguration.java
  20. 19
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/CustomSwaggerParameterBuilder.java
  21. 87
      sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/swagger/DocumentationPluginsManagerExt.java
  22. 2
      sop-example/sop-springmvc/pom.xml
  23. 2
      sop-example/sop-springmvc/src/main/java/com/gitee/app/config/OpenServletContextListener.java
  24. 2
      sop-example/sop-story/pom.xml
  25. 13
      sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/Example1001_BaseController.java
  26. 2
      sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/StoryParam.java
  27. 5
      sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/result/StoryResult.java
  28. 1
      sop-example/sop-story/src/main/resources/application-dev.properties
  29. 17
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java
  30. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/DemoFileUploadRequest.java
  31. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/GetStoryRequest.java
  32. 2
      sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/BaseResponse.java
  33. 1
      sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/BaseTest.java
  34. 8
      sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java
  35. 16
      sop-sdk/sdk-nodejs/.gitignore
  36. 1
      sop-sdk/sdk-nodejs/aa.txt
  37. 1
      sop-sdk/sdk-nodejs/bb.txt
  38. 125
      sop-sdk/sdk-nodejs/common/Class.js
  39. 151
      sop-sdk/sdk-nodejs/common/OpenClient.js
  40. 6
      sop-sdk/sdk-nodejs/common/RequestType.js
  41. 88
      sop-sdk/sdk-nodejs/common/SignUtil.js
  42. 43
      sop-sdk/sdk-nodejs/main.js
  43. 17
      sop-sdk/sdk-nodejs/package.json
  44. 10
      sop-sdk/sdk-nodejs/readme.md
  45. 48
      sop-sdk/sdk-nodejs/request/BaseRequest.js
  46. 22
      sop-sdk/sdk-nodejs/request/StoryGetRequest.js
  47. 52
      sop-sdk/sdk-nodejs/testClass.js
  48. 13
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/BizCode.java
  49. 1
      sop-website/src/main/java/com/gitee/sop/websiteserver/bean/DocItem.java
  50. 20
      sop-website/src/main/java/com/gitee/sop/websiteserver/manager/SwaggerDocParser.java
  51. 2
      sop-website/src/main/resources/application-dev.properties
  52. 23
      sop-website/src/main/resources/public/pages/doc/doc.html
  53. 19
      sop-website/src/main/resources/public/pages/doc/docEvent.js

@ -1,5 +1,10 @@
# changelog
## 4.0.3
- 可定义业务错误码(见`@Open`注解中的`bizCode`属性)
- 文档参数可指定最大长度(使用`@Length(max = xx)`)
## 4.0.2
- 支持swagger排序(position属性)

@ -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")
![预览](images/10041_3.png "10041_3.png")
![预览](images/10041_4.png "10041_4.png")

@ -1,4 +1,4 @@
# 提供rest接口
# 提供restful接口
有些接口没有被开放,但是也想要通过网关来访问,SOP提供一个固定的请求格式来访问。

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

@ -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 {
}

@ -25,7 +25,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<!-- sop相关配置 end-->

@ -10,7 +10,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
<artifactId>sop-bridge-eureka</artifactId>
@ -18,7 +18,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<dependency>

@ -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<ServiceHolder> currentServices = new HashSet<>(serviceList);
@ -57,7 +58,7 @@ public class EurekaRegistryListener extends BaseRegistryListener {
if (currentServices.size() > 0) {
List<Application> newApplications = registeredApplications.stream()
.filter(application ->
this.canOperator(application.getName()) && containsService(currentServices, application.getName()))
containsService(currentServices, application.getName()))
.collect(Collectors.toList());
this.doRegister(newApplications);

@ -10,7 +10,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
<artifactId>sop-bridge-nacos</artifactId>
@ -18,7 +18,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<dependency>

@ -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> taskScheduler, Environment environment) {
Map<String, String> 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);
}
/**
* 微服务路由加载
*/

@ -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<NacosServiceHolder> cacheServices = new HashSet<>();
@Autowired
@ -73,6 +74,7 @@ public class NacosRegistryListener extends BaseRegistryListener {
/**
* 获取建康的服务实例
*
* @return 没有返回空的list
*/
private List<NacosServiceHolder> getServiceList() {
@ -89,36 +91,36 @@ public class NacosRegistryListener extends BaseRegistryListener {
return servicesOfServer
.getData()
.stream()
.filter(this::canOperator)
.map(serviceName -> {
List<Instance> allInstances;
try {
// 获取服务实例
List<Instance> 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());
}

@ -11,7 +11,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>sop-gateway-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>

@ -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";
}

@ -20,7 +20,7 @@ public abstract class BaseRegistryListener implements RegistryListener {
private static final int FIVE_SECONDS = 1000 * 5;
private Map<String, Long> updateTimeMap = new ConcurrentHashMap<>(16);
private final Map<String, Long> updateTimeMap = new ConcurrentHashMap<>(16);
public static List<String> 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;

@ -1,7 +1,6 @@
package com.gitee.sop.gatewaycommon.route;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@ -12,7 +12,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>sop-service-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>

@ -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 "";
}

@ -44,4 +44,9 @@ public @interface Open {
* 是否需要appAuthToken设置为true网关端会校验token是否存在
*/
boolean needToken() default false;
/**
* 定义业务错误码用于文档显示
*/
BizCode[] bizCode() default {};
}

@ -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> taskScheduler, Environment environment) {
Map<String, String> 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() {

@ -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<VendorExtension> getVendorExtension(ParameterExpansionContext context) {
List<VendorExtension> vendorExtensions = new ArrayList<>(4);
Optional<Length> 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);

@ -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<Api> 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<VendorExtension> 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<RawField> memberFields = returnType.getMemberFields();
Map<String, Map<String, Object>> fieldProperties = new HashMap<>(16);
for (RawField memberField : memberFields) {
String key = memberField.getName();
Length length = AnnotationUtils.findAnnotation(memberField.getRawMember(), Length.class);
if (length != null) {
Map<String, Object> 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<String, Object> 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<VendorExtension> vendorExtensions) {
BizCode[] bizCodes = open.bizCode();
List<BizCodeObj> 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();

@ -26,7 +26,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<!-- nacos -->
<dependency>

@ -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);
}

@ -19,7 +19,7 @@
<dependency>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId>
<version>4.0.2-SNAPSHOT</version>
<version>4.0.3-SNAPSHOT</version>
</dependency>
<dependency>

@ -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();

@ -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;
}

@ -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")

@ -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/

@ -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 extends BaseResponse> T parseResponse(String resp, BaseRequest<T> 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;
}

@ -8,6 +8,6 @@ import com.gitee.sop.sdk.response.DemoFileUploadResponse;
public class DemoFileUploadRequest extends BaseRequest<DemoFileUploadResponse> {
@Override
protected String method() {
return "demo.file.upload";
return "file.upload";
}
}

@ -5,7 +5,7 @@ import com.gitee.sop.sdk.response.GetStoryResponse;
public class GetStoryRequest extends BaseRequest<GetStoryResponse> {
@Override
protected String method() {
return "alipay.story.find";
return "story.get";
}
}

@ -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")

@ -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 {

@ -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<String, Object> bizModel = new HashMap<>();
bizModel.put("name", "白雪公主");

@ -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

@ -0,0 +1 @@
hello你好123

@ -0,0 +1 @@
文件bb的内容

@ -0,0 +1,125 @@
/**
* 面相对象辅助类可实现类的创建继承方法重写
*
<pre>
//-------------------------
// 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));
</pre>
*
*/
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);
}
};
})();

@ -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 应用私钥2048PKCS8
* @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

@ -0,0 +1,6 @@
exports.RequestType = {
GET: 'GET',
POST_FORM: 'POST_FORM',
POST_JSON: 'POST_JSON',
POST_FILE: 'POST_FILE'
}

@ -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算法SHA256withRSASHA1withRSA
* @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('&')
}
}

@ -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()

@ -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"
}
}

@ -0,0 +1,10 @@
- 执行`npm install --registry=https://registry.npm.taobao.org`
- 执行`node main.js`进行测试
## 封装步骤
1. 新建request类,继承BaseRequest,参考`StoryGetRequest.js`
2. 调用接口,参考`main.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;
}
})

@ -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

@ -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()

@ -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;
}

@ -30,6 +30,7 @@ public class DocItem {
List<DocParameter> requestParameters;
List<DocParameter> responseParameters;
List<BizCode> bizCodeList;
public String getNameVersion() {
return name + version;

@ -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<DocParameter> 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<DocParameter> 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<DocParameter> refs = buildDocParameters(subRef, docRoot, nextDoRef);

@ -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

@ -367,6 +367,29 @@
</pre>
</div>
<div class="site-title">
<fieldset class="layui-elem-field layui-field-title site-title">
<legend>业务错误码</legend>
</fieldset>
</div>
<div class="site-text">
<p>
<a href="code.html" target="_blank">公共错误码</a>
</p>
<div class="site-text">
<table id="bizCode" class="layui-table">
<thead>
<tr>
<th>错误码</th>
<th>错误描述</th>
<th>解决方案</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div><!-- layui-main end~ -->

@ -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('<tr>')
html.push('<td>'+bizCode.code+'</td>')
html.push('<td>'+bizCode.msg+'</td>')
html.push('<td>'+bizCode.solution+'</td>')
html.push('</tr>')
}
$('#bizCode').find('tbody').html(html.join(''));
} else {
$('#bizCode').find('tbody').html('<tr><td colspan="3" style="text-align: center">暂无数据</td></tr>');
}
}
Loading…
Cancel
Save