From 6f4c635855fd8952bc788628abe8ae0305a47a80 Mon Sep 17 00:00:00 2001 From: tanghc Date: Thu, 29 Oct 2020 15:58:34 +0800 Subject: [PATCH 1/3] 4.0.3 --- doc/docs/files/10021_开发流程.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/docs/files/10021_开发流程.md b/doc/docs/files/10021_开发流程.md index 45352643..d4cad179 100644 --- a/doc/docs/files/10021_开发流程.md +++ b/doc/docs/files/10021_开发流程.md @@ -10,3 +10,11 @@ - 你的项目接入到SOP,参考[项目接入到SOP](10011_项目接入到SOP.md),在微服务端开发接口,编写swagger注解文档 - 接口开发完成,启动微服务,注册到注册中心。 - 【可选】编写sdk + +## ISV对接流程 + +假设接口开发完毕,开始对接ISV,大致步骤如下: + +1. 在admin新建一个ISV,然后`导出秘钥`,文件给到对方 +2. 告知对方接口文档,开始接口调用。 +3. 如果有SDK,把SDK给到对方,让其直接调用 \ No newline at end of file From dc584c36d6f9cabb28765cf3a53f2b3fb8681429 Mon Sep 17 00:00:00 2001 From: tanghc Date: Thu, 29 Oct 2020 16:27:37 +0800 Subject: [PATCH 2/3] 4.0.3 --- .../src/main/resources/META-INF/sop-admin.properties | 2 +- sop-gateway/src/main/resources/META-INF/gateway.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties index 00b51aa0..50f585f0 100644 --- a/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties +++ b/sop-admin/sop-admin-server/src/main/resources/META-INF/sop-admin.properties @@ -26,7 +26,7 @@ spring.datasource.username=${mysql.username} spring.datasource.password=${mysql.password} #连接池 -spring.datasource.hikari.pool-name=DatebookHikariCP +spring.datasource.hikari.pool-name=HikariCP spring.datasource.hikari.max-lifetime=500000 # 固定不用改 diff --git a/sop-gateway/src/main/resources/META-INF/gateway.properties b/sop-gateway/src/main/resources/META-INF/gateway.properties index 43f3979e..332006c1 100644 --- a/sop-gateway/src/main/resources/META-INF/gateway.properties +++ b/sop-gateway/src/main/resources/META-INF/gateway.properties @@ -22,7 +22,7 @@ spring.datasource.username=${mysql.username} spring.datasource.password=${mysql.password} #连接池 -spring.datasource.hikari.pool-name=DatebookHikariCP +spring.datasource.hikari.pool-name=HikariCP spring.datasource.hikari.max-lifetime=500000 spring.cloud.gateway.discovery.locator.lower-case-service-id=true From 4ce2fc826dafce9b06d2fa50e19d8e0cd0f527c7 Mon Sep 17 00:00:00 2001 From: tanghc Date: Fri, 30 Oct 2020 10:27:37 +0800 Subject: [PATCH 3/3] 4.0.3 --- changelog.md | 1 + pom.xml | 7 + sop-common/sop-service-common/pom.xml | 5 + .../AbstractValidationAnnotationBuilder.java | 37 ++++ .../param/ApiArgumentResolver.java | 48 ++++- .../BaseValidationAnnotationBuilder.java | 15 ++ .../param/SingleParameterClassCreator.java | 186 ++++++++++++++++ .../param/SingleParameterContext.java | 44 ++++ .../param/SingleParameterContextValue.java | 24 +++ .../param/SingleParameterWrapper.java | 27 +++ .../param/ValidationAnnotationBuilder.java | 16 ++ .../param/ValidationAnnotationDefinition.java | 27 +++ ...ValidationAnnotationDefinitionFactory.java | 117 ++++++++++ .../sop/servercommon/util/FieldUtil.java | 128 +++++++++++ .../sop/servercommon/util/ReflectionUtil.java | 199 ++++++++++++++++++ .../Example1001_BaseController.java | 33 +++ .../storyweb/controller/param/TypeEnum.java | 8 + .../interceptor/MyRouteInterceptor.java | 10 +- .../java/com/gitee/sop/test/OneParamTest.java | 126 +++++++++++ 19 files changed, 1050 insertions(+), 8 deletions(-) create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/AbstractValidationAnnotationBuilder.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/BaseValidationAnnotationBuilder.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterClassCreator.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContext.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContextValue.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterWrapper.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationBuilder.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinition.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/FieldUtil.java create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/ReflectionUtil.java create mode 100644 sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/TypeEnum.java create mode 100644 sop-test/src/test/java/com/gitee/sop/test/OneParamTest.java diff --git a/changelog.md b/changelog.md index 551ac68e..e29b698c 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ - 可定义业务错误码(见`@Open`注解中的`bizCode`属性) - 文档参数可指定最大长度(使用`@Length(max = xx)`) - 修复返回大文本导致的错误(Exceeded limit on max bytes to buffer : 262144) +- 增强参数绑定 ## 4.0.2 diff --git a/pom.xml b/pom.xml index 989d13f2..1f8546a2 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ 2.9.2 2.9.2 1.16.9 + 6.2 @@ -195,6 +196,12 @@ ${commons-logging.version} + + org.ow2.asm + asm + ${asm.version} + + org.projectlombok lombok diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index 651adae6..5633391c 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -51,6 +51,11 @@ hibernate-validator + + org.ow2.asm + asm + + com.alibaba.cloud diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/AbstractValidationAnnotationBuilder.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/AbstractValidationAnnotationBuilder.java new file mode 100644 index 00000000..8546d641 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/AbstractValidationAnnotationBuilder.java @@ -0,0 +1,37 @@ +package com.gitee.sop.servercommon.param; + +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.Set; + +/** + * @author tanghc + */ +public abstract class AbstractValidationAnnotationBuilder implements ValidationAnnotationBuilder { + @Override + public ValidationAnnotationDefinition build(T jsr303Annotation) { + ValidationAnnotationDefinition validationAnnotationDefinition = new ValidationAnnotationDefinition(); + + validationAnnotationDefinition.setAnnotationClass(jsr303Annotation.annotationType()); + Map properties = AnnotationUtils.getAnnotationAttributes(jsr303Annotation); + properties = this.formatProperties(properties); + validationAnnotationDefinition.setProperties(properties); + return validationAnnotationDefinition; + } + + protected Map formatProperties(Map properties) { + Set> entrySet = properties.entrySet(); + for (Map.Entry entry : entrySet) { + Object value = entry.getValue(); + if (value.getClass().isArray()) { + Object[] arr = (Object[])value; + if (arr.length == 0) { + properties.put(entry.getKey(), null); + } + } + } + return properties; + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java index da92232e..bed09eaf 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java @@ -8,6 +8,7 @@ import com.gitee.sop.servercommon.bean.OpenContext; import com.gitee.sop.servercommon.bean.OpenContextImpl; import com.gitee.sop.servercommon.bean.ParamNames; import com.gitee.sop.servercommon.bean.ServiceContext; +import com.gitee.sop.servercommon.util.FieldUtil; import com.gitee.sop.servercommon.util.OpenUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; @@ -35,6 +36,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; +import java.lang.reflect.Parameter; import java.security.Principal; import java.time.ZoneId; import java.util.ArrayList; @@ -82,8 +84,8 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { - // 是否有注解 - if (!methodParameter.hasMethodAnnotation(Open.class)) { + Open open = methodParameter.getMethodAnnotation(Open.class); + if (open == null) { return false; } Class paramType = methodParameter.getParameterType(); @@ -106,7 +108,25 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { Writer.class.isAssignableFrom(paramType) ); // 除此之外都匹配 - return !exclude; + boolean support = !exclude; + if (support) { + this.wrapSingleParam(methodParameter, open); + } + return support; + } + + /** + * 包装单个值参数 + * @param methodParameter 参数信息 + * @param open open注解 + */ + private void wrapSingleParam(MethodParameter methodParameter, Open open) { + Parameter parameter = methodParameter.getParameter(); + boolean isNumberStringEnumType = FieldUtil.isNumberStringEnumType(parameter.getType()); + if (isNumberStringEnumType) { + log.debug("包装参数,方法:{},参数名:{}", methodParameter.getMethod(), parameter.getName()); + SingleParameterContext.add(parameter, open); + } } @Override @@ -123,8 +143,15 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { Object paramObj = this.getParamObject(methodParameter, nativeWebRequest); if (paramObj != null) { // JSR-303验证 - paramValidator.validateBizParam(paramObj); - return paramObj; + + if (paramObj instanceof SingleParameterWrapper) { + SingleParameterWrapper parameterWrapper = (SingleParameterWrapper) paramObj; + paramValidator.validateBizParam(parameterWrapper.getWrapperObject()); + return parameterWrapper.getParamValue(); + } else { + paramValidator.validateBizParam(paramObj); + return paramObj; + } } HandlerMethodArgumentResolver resolver = getOtherArgumentResolver(methodParameter); if (resolver != null) { @@ -164,6 +191,17 @@ public class ApiArgumentResolver implements SopHandlerMethodArgumentResolver { } // 方法参数类型 Class parameterType = methodParameter.getParameterType(); + SingleParameterContextValue singleValue = SingleParameterContext.get(openContext.getMethod(), openContext.getVersion()); + // 如果是单值参数 + if (singleValue != null) { + JSONObject jsonObj = JSON.parseObject(bizContent); + Object paramValue = jsonObj.getObject(singleValue.getParameterName(), parameterType); + Object wrapperObject = jsonObj.toJavaObject(singleValue.getWrapClass()); + SingleParameterWrapper singleParameterWrapper = new SingleParameterWrapper(); + singleParameterWrapper.setParamValue(paramValue); + singleParameterWrapper.setWrapperObject(wrapperObject); + return singleParameterWrapper; + } Object param; // 如果是json字符串 if (JSONValidator.from(bizContent).validate()) { diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/BaseValidationAnnotationBuilder.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/BaseValidationAnnotationBuilder.java new file mode 100644 index 00000000..3a3b5c0f --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/BaseValidationAnnotationBuilder.java @@ -0,0 +1,15 @@ +package com.gitee.sop.servercommon.param; + +import com.gitee.sop.servercommon.util.ReflectionUtil; + +import java.lang.annotation.Annotation; + +/** + * @author tanghc + */ +public abstract class BaseValidationAnnotationBuilder extends AbstractValidationAnnotationBuilder { + public BaseValidationAnnotationBuilder() { + Class clazz = ReflectionUtil.getSuperClassGenricType(getClass(), 0); + ValidationAnnotationDefinitionFactory.addBuilder(clazz, this); + } +} \ No newline at end of file diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterClassCreator.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterClassCreator.java new file mode 100644 index 00000000..157166b4 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterClassCreator.java @@ -0,0 +1,186 @@ +package com.gitee.sop.servercommon.param; + + +import com.gitee.sop.servercommon.util.FieldUtil; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 包装单个参数 + * + * public class WrapperParam0id implements SingleValue { + * public String id; + * + * public Object fetchValue() { + * return this.id; + * } + * } + * + * + * @author tanghc + */ +public class SingleParameterClassCreator implements Opcodes { + + private static final Logger logger = LoggerFactory.getLogger(SingleParameterClassCreator.class); + + private static final String FOLDER_END_CHAR = "/"; + + private static final String CLASS_FILE_SUFFIX = ".class"; + + private static final String WRAPPER_PARAM = "WrapperParam"; + + private static final String PKG = "com/gitee/sop/servercommon/gen/"; + private static final String OBJECT = "java/lang/Object"; + + private static final ParameterClassLoader CLASS_LOADER = new ParameterClassLoader(); + + private static final AtomicInteger i = new AtomicInteger(); + + // 生成class文件的保存路径 + private String savePath; + + /** + * 生成一个类,里面指放这个字段 + * + * @param parameter 字段,只能是基本类型或字符串类型 + * @return 如果不是基本类型或字符串类型,返回null + */ + public Class create(Parameter parameter, String paramName) { + Class paramType = parameter.getType(); + if (!FieldUtil.isNumberStringEnumType(paramType)) { + return null; + } + /* *******************************class********************************************** */ + // 创建一个ClassWriter, 以生成一个新的类 + ClassWriter classWriter = new ClassWriter(0); + // 类名 + String className = WRAPPER_PARAM + i.incrementAndGet() + paramName; + // 类路径名:com/gitee/sop/service/gen/WrapperParam + String classpathName = PKG + className; + classWriter.visit(V1_8, ACC_PUBLIC, classpathName, null, OBJECT, null); + + /* ********************************constructor********************************************* */ + MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, + null); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitMethodInsn(INVOKESPECIAL, OBJECT, "", "()V", false); + methodVisitor.visitInsn(RETURN); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + + + /* ************************************parameter***************************************** */ + // 生成String name字段, Ljava/lang/String; + Type type = Type.getType(paramType); + FieldVisitor fieldVisitor = classWriter.visitField(ACC_PUBLIC, paramName, type.getDescriptor(), null, null); + // 生成验证注解 + Annotation[] annotations = parameter.getAnnotations(); + for (Annotation annotation : annotations) { + ValidationAnnotationDefinition validationAnnotationDefinition = ValidationAnnotationDefinitionFactory.build(annotation); + if (validationAnnotationDefinition == null) { + continue; + } + Class annoClass = validationAnnotationDefinition.getAnnotationClass(); + Type annoType = Type.getType(annoClass); + AnnotationVisitor annotationVisitor = fieldVisitor.visitAnnotation(annoType.getDescriptor(), true); + Map properties = validationAnnotationDefinition.getProperties(); + if (properties != null) { + try { + Set> entrySet = properties.entrySet(); + for (Map.Entry entry : entrySet) { + Object val = entry.getValue(); + if (val != null) { + // 设置枚举值 + if (val.getClass().isEnum()) { + Type eType = Type.getType(val.getClass()); + annotationVisitor.visitEnum(entry.getKey(), eType.getDescriptor(), val.toString()); + } else if (val instanceof Class) { + // val是Class类型 + Type vType = Type.getType((Class) val); + annotationVisitor.visit(entry.getKey(), vType); + } else { + annotationVisitor.visit(entry.getKey(), val); + } + } + } + } catch (Exception e) { + logger.error("ASM生成注解出错", e); + } + } + // 结束生成注解 + annotationVisitor.visitEnd(); + logger.debug("ASM生成参数注解,参数:{},注解:{},注解属性:{}", paramName, annoClass.getName(), properties); + } + fieldVisitor.visitEnd(); + + classWriter.visitEnd(); + + /* **********************************generate and load******************************************* */ + byte[] code = classWriter.toByteArray(); + + if (StringUtils.isNotBlank(savePath)) { + if (!savePath.endsWith(FOLDER_END_CHAR)) { + savePath = savePath + FOLDER_END_CHAR; + } + this.writeClassFile(code, savePath + className + CLASS_FILE_SUFFIX); + } + + Class clazz = CLASS_LOADER.defineClass(code); + logger.debug("生成参数包装类:{},包装参数名:{},参数类型:{}", clazz.getName(), paramName, paramType); + return clazz; + } + + protected void writeClassFile(byte[] code, String filepath) { + // 将二进制流写到本地磁盘上 + try { + FileUtils.writeByteArrayToFile(new File(filepath), code); + } catch (IOException e) { + logger.error("写文件错误,filepath:" + filepath, e); + } + } + + public void setSavePath(String savePath) { + this.savePath = savePath; + } + + + // 自定义类加载器 + static class ParameterClassLoader extends ClassLoader { + public ParameterClassLoader() { + /* + 指定父加载器,不指定默认为AppClassLoader + springboot启动会使用自带的org.springframework.boot.loader.LaunchedURLClassLoader + 如果不指定,会出现加载器不一致,导致ASM生成的class获取不到字段的注解。 + 因此ASM生成的class必须使用当前ClassLoader进行生成。 + */ + super(Thread.currentThread().getContextClassLoader()); + } + + /** + * 加载class + * + * @param clazz 字节码 + * @return + */ + public Class defineClass(byte[] clazz) { + return this.defineClass(null, clazz, 0, clazz.length); + } + } + +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContext.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContext.java new file mode 100644 index 00000000..d2574df6 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContext.java @@ -0,0 +1,44 @@ +package com.gitee.sop.servercommon.param; + +import com.gitee.sop.servercommon.annotation.Open; +import com.gitee.sop.servercommon.bean.ServiceConfig; + +import java.lang.reflect.Parameter; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 存放单个参数 + * @author tanghc + */ +public class SingleParameterContext { + + private static SingleParameterClassCreator singleFieldWrapper = new SingleParameterClassCreator(); + + private static final Map context = new ConcurrentHashMap<>(16); + + /** + * 添加单个参数 + * @param parameter 接口单个参数 + * @param open open注解 + */ + public static void add(Parameter parameter, Open open) { + String version = open.version(); + version = "".equals(version) ? ServiceConfig.getInstance().getDefaultVersion() : version; + String key = open.value() + version; + String parameterName = parameter.getName(); + Class wrapClass = singleFieldWrapper.create(parameter, parameterName); + SingleParameterContextValue value = new SingleParameterContextValue(); + value.setParameterName(parameterName); + value.setWrapClass(wrapClass); + context.put(key, value); + } + + public static SingleParameterContextValue get(String name, String version) { + return context.get(name + version); + } + + public static void setSingleFieldWrapper(SingleParameterClassCreator singleFieldWrapper) { + SingleParameterContext.singleFieldWrapper = singleFieldWrapper; + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContextValue.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContextValue.java new file mode 100644 index 00000000..a8d8af93 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterContextValue.java @@ -0,0 +1,24 @@ +package com.gitee.sop.servercommon.param; + +public class SingleParameterContextValue { + /** 参数名称 */ + private String parameterName; + private Class wrapClass; + + public String getParameterName() { + return parameterName; + } + + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + + public Class getWrapClass() { + return wrapClass; + } + + public void setWrapClass(Class wrapClass) { + this.wrapClass = wrapClass; + } + +} \ No newline at end of file diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterWrapper.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterWrapper.java new file mode 100644 index 00000000..91b8ceec --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/SingleParameterWrapper.java @@ -0,0 +1,27 @@ +package com.gitee.sop.servercommon.param; + +/** + * @author tanghc + */ +public class SingleParameterWrapper { + /** 包裹类实例 */ + private Object wrapperObject; + /** 参数值 */ + private Object paramValue; + + public Object getWrapperObject() { + return wrapperObject; + } + + public void setWrapperObject(Object wrapperObject) { + this.wrapperObject = wrapperObject; + } + + public Object getParamValue() { + return paramValue; + } + + public void setParamValue(Object paramValue) { + this.paramValue = paramValue; + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationBuilder.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationBuilder.java new file mode 100644 index 00000000..8d92b313 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationBuilder.java @@ -0,0 +1,16 @@ +package com.gitee.sop.servercommon.param; + +import java.lang.annotation.Annotation; + +/** + * @author tanghc + */ +public interface ValidationAnnotationBuilder { + /** + * 构建验证注解 + * + * @param jsr303Annotation JSR-303注解 + * @return 返回注解定义 + */ + ValidationAnnotationDefinition build(T jsr303Annotation); +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinition.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinition.java new file mode 100644 index 00000000..97475c50 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinition.java @@ -0,0 +1,27 @@ +package com.gitee.sop.servercommon.param; + +import java.util.Map; + +/** + * @author tanghc + */ +public class ValidationAnnotationDefinition { + private Class annotationClass; + private Map properties; + + public Class getAnnotationClass() { + return annotationClass; + } + + public void setAnnotationClass(Class annotationClass) { + this.annotationClass = annotationClass; + } + + public Map getProperties() { + return properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java new file mode 100644 index 00000000..83bf8428 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ValidationAnnotationDefinitionFactory.java @@ -0,0 +1,117 @@ +package com.gitee.sop.servercommon.param; + +import io.swagger.annotations.ApiModelProperty; +import org.hibernate.validator.constraints.CodePointLength; +import org.hibernate.validator.constraints.ConstraintComposition; +import org.hibernate.validator.constraints.CreditCardNumber; +import org.hibernate.validator.constraints.Currency; +import org.hibernate.validator.constraints.EAN; +import org.hibernate.validator.constraints.ISBN; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.LuhnCheck; +import org.hibernate.validator.constraints.Mod10Check; +import org.hibernate.validator.constraints.Mod11Check; +import org.hibernate.validator.constraints.ParameterScriptAssert; +import org.hibernate.validator.constraints.Range; +import org.hibernate.validator.constraints.SafeHtml; +import org.hibernate.validator.constraints.ScriptAssert; +import org.hibernate.validator.constraints.URL; +import org.hibernate.validator.constraints.UniqueElements; + +import javax.validation.constraints.AssertFalse; +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Digits; +import javax.validation.constraints.Email; +import javax.validation.constraints.Future; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Negative; +import javax.validation.constraints.NegativeOrZero; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Null; +import javax.validation.constraints.Past; +import javax.validation.constraints.PastOrPresent; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Positive; +import javax.validation.constraints.PositiveOrZero; +import javax.validation.constraints.Size; +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; + +/** + * @author tanghc + */ +public class ValidationAnnotationDefinitionFactory { + + private static final Map, ValidationAnnotationBuilder> store = new HashMap<>(64); + + static { + new BaseValidationAnnotationBuilder(){}; + + // validation-api-2.0.1.Final.jar下javax.validation.constraints + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + + // hibernate-validator-6.0.10.Final.jar下org.hibernate.validator.constraints + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + new BaseValidationAnnotationBuilder(){}; + } + + /** + * 添加注解对应的构建器 + * + * @param annoClass + * @param builder + */ + public static void addBuilder(Class annoClass, ValidationAnnotationBuilder builder) { + store.put(annoClass, builder); + } + + public static ValidationAnnotationDefinition build(Annotation annotation) { + Class jsr303Anno = annotation.annotationType(); + ValidationAnnotationBuilder validationAnnotationBuilder = store.get(jsr303Anno); + if (validationAnnotationBuilder == null) { + return null; + } + return validationAnnotationBuilder.build(annotation); + } + +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/FieldUtil.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/FieldUtil.java new file mode 100644 index 00000000..c74d25a6 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/FieldUtil.java @@ -0,0 +1,128 @@ +package com.gitee.sop.servercommon.util; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ClassUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; + +/** + * @author tanghc + */ +public class FieldUtil { + + private static final Logger log = LoggerFactory.getLogger(FieldUtil.class); + + private static final String CLASS_SUFFIX = ".class"; + + private FieldUtil() { + } + + /** + * 判断这个字段是否是数字类型或字符串类型或枚举类型 + * + * @param type 字段类型 + * @return true:是数字或字符串类型或枚举类型 + */ + public static boolean isNumberStringEnumType(Class type) { + if (type == String.class) { + return true; + } + if (type.getGenericSuperclass() == Number.class) { + return true; + } + if (type.isPrimitive()) { + return true; + } + if (type.isEnum()) { + return true; + } + return false; + } + + /** + * 获取指定类指定方法的参数名 + * @param clazz 要获取参数名的方法所属的类 + * @param method 要获取参数名的方法 + * @param index 参数索引,从0开始 + * @return 返回指定类指定方法的参数名,没有返回空字符串 + */ + public static String getMethodParameterName(Class clazz, final Method method, int index) { + String[] names = getMethodParameterNamesByAsm(clazz, method); + if (names.length == 0) { + return ""; + } + return names[index]; + } + + /** + * 获取指定类指定方法的参数名 + * + * @param clazz 要获取参数名的方法所属的类 + * @param method 要获取参数名的方法 + * @return 按参数顺序排列的参数名列表,如果没有参数,则返回空数组 + */ + public static String[] getMethodParameterNamesByAsm(Class clazz, final Method method) { + final Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes == null || parameterTypes.length == 0) { + return new String[0]; + } + final Type[] types = new Type[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + types[i] = Type.getType(parameterTypes[i]); + } + final String[] parameterNames = new String[parameterTypes.length]; + // 解决clazz对象是cglib对象导致空指针异常 + // 获取真实的class,如果是cglib类,则返回父类class + Class realClass = ClassUtils.getUserClass(clazz); + String className = realClass.getName(); + int lastDotIndex = className.lastIndexOf('.'); + className = className.substring(lastDotIndex + 1) + CLASS_SUFFIX; + InputStream is = realClass.getResourceAsStream(className); + try { + ClassReader classReader = new ClassReader(is); + classReader.accept(new ClassVisitor(Opcodes.ASM6) { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // 只处理指定的方法 + Type[] argumentTypes = Type.getArgumentTypes(desc); + if (!method.getName().equals(name) || !Arrays.equals(argumentTypes, types)) { + return null; + } + return new MethodVisitor(Opcodes.ASM6) { + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + int i = index - 1; + // 如果是静态方法,则第一就是参数 + // 如果不是静态方法,则第一个是"this",然后才是方法的参数 + if (Modifier.isStatic(method.getModifiers())) { + i = index; + } + if (i >= 0 && i < parameterNames.length) { + parameterNames[i] = name; + } + super.visitLocalVariable(name, desc, signature, start, + end, index); + } + + }; + + } + }, 0); + } catch (IOException e) { + log.error("生成asm失败,oriClass:{}, realClass:{} method:{}", clazz.getName(), realClass.getName(), method.toGenericString(), e); + } + return parameterNames; + } + +} diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/ReflectionUtil.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/ReflectionUtil.java new file mode 100644 index 00000000..ade0ad96 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/ReflectionUtil.java @@ -0,0 +1,199 @@ +package com.gitee.sop.servercommon.util; + +import org.springframework.beans.FatalBeanException; +import org.springframework.context.ApplicationContext; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 反射相关 + * @author tanghc + */ +public class ReflectionUtil { + + public static final String PREFIX_SET = "set"; + + private static final String[] EMPTY_STRING_ARRAY = {}; + + private static Map> classGenricTypeCache = new HashMap<>(16); + + /** key:obj.getClass().getName() + genericClass.getName() */ + private static Map genericTypeFieldCache = new HashMap<>(); + + /** + * 设置某个字段的值 + * @param target 实体类,必须有字段的set方法 + * @param fieldName 字段名 + * @param val 值 + */ + public static void invokeFieldValue(Object target,String fieldName, Object val) { + String setMethodName = getSetMethodName(fieldName); + Method[] methods = target.getClass().getDeclaredMethods(); + for (Method method : methods) { + String methodName = method.getName(); + Class[] methodParams = method.getParameterTypes(); + + if (setMethodName.equals(methodName)) { + // 能否拷贝 + boolean canCopy = + // 并且只有一个参数 + methodParams.length == 1 + // val是methodParams[0]或他的子类 + && methodParams[0].isInstance(val) || Number.class.isInstance(val); + + if (canCopy) { + try { + if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) { + method.setAccessible(true); + } + method.invoke(target, val); + break; + } catch (Throwable ex) { + throw new FatalBeanException( + "Could not set property '" + fieldName + "' value to target", ex); + } + } + } + } + } + + /** + * 返回实体类中具有指定泛型的字段 + * @param obj 实体类 + * @param genericClass 指定泛型 + * @return 没有返回null + */ + public static Field getListFieldWithGeneric(Object obj, Class genericClass) { + Class objClass = obj.getClass(); + String key = objClass.getName() + genericClass.getName(); + Field value = genericTypeFieldCache.get(key); + if (value != null) { + return value; + } + Field[] fields = objClass.getDeclaredFields(); + for (Field field : fields) { + Type genericType = getListGenericType(field); + if (genericType == genericClass) { + genericTypeFieldCache.put(key, field); + return field; + } + } + return null; + } + + /** + * 返回集合字段的泛型类型。
+ * 如:List<User> list;返回User.class + * + * @param field + * 类中的一个属性 + * @return 返回类型 + */ + public static Type getListGenericType(Field field) { + if (isListType(field.getType())) { + Type genericType = field.getGenericType(); + + if (genericType instanceof ParameterizedType) { + Type[] params = ((ParameterizedType) genericType).getActualTypeArguments(); + if (params.length == 1) { + return params[0]; + } + } + } + return Object.class; + } + + public static boolean isListType(Type type) { + return type == List.class; + } + + /** + * 返回set方法名。name -> setName + * @param fieldName + * @return 返回方法名 + */ + public static String getSetMethodName(String fieldName) { + return PREFIX_SET + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); + } + + /** + * 构建字段名称 + * @param methodName 根据get或set方法返回字段名称 + * @return 字段名称 + */ + public static String buildFieldName(String methodName) { + return methodName.substring(3, 4).toLowerCase() + methodName.substring(4); + } + + /** + * 返回定义类时的泛型参数的类型.
+ * 如:定义一个BookManager类
+ * {@literal public BookManager extends GenricManager}{...} + *
+ * 调用getSuperClassGenricType(getClass(),0)将返回Book的Class类型
+ * 调用getSuperClassGenricType(getClass(),1)将返回Address的Class类型 + * + * @param clazz 从哪个类中获取 + * @param index 泛型参数索引,从0开始 + * @return 返回泛型参数类型 + */ + public static Class getSuperClassGenricType(Class clazz, int index) throws IndexOutOfBoundsException { + String cacheKey = clazz.getName() + index; + Class cachedClass = classGenricTypeCache.get(cacheKey); + if (cachedClass != null) { + return cachedClass; + } + + Type genType = clazz.getGenericSuperclass(); + + // 没有泛型参数 + if (!(genType instanceof ParameterizedType)) { + throw new RuntimeException("class " + clazz.getName() + " 没有指定父类泛型"); + } else { + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) { + throw new RuntimeException("泛型索引不正确,index:" + index); + } + if (!(params[index] instanceof Class)) { + throw new RuntimeException(params[index] + "不是Class类型"); + } + + Class retClass = (Class) params[index]; + // 缓存起来 + classGenricTypeCache.put(cacheKey, retClass); + + return retClass; + } + } + + /** + * 找到所有被注解标记的类名 + * @param ctx ApplicationContext + * @param annotationClass 注解class + * @return 返回类名称数组,没有返回空数组 + */ + public static String[] findBeanNamesByAnnotationClass(ApplicationContext ctx, Class annotationClass) { + String[] beans = ctx.getBeanNamesForAnnotation(annotationClass); + // 如果没找到,去父容器找 + if (beans == null || beans.length == 0) { + ApplicationContext parentCtx = ctx.getParent(); + if (parentCtx != null) { + beans = parentCtx.getBeanNamesForAnnotation(annotationClass); + } + } + if (beans == null) { + beans = EMPTY_STRING_ARRAY; + } + return beans; + } + +} 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 7e70af33..238e2370 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 @@ -10,6 +10,7 @@ import com.gitee.sop.servercommon.exception.ServiceException; import com.gitee.sop.storyweb.controller.param.CategoryParam; import com.gitee.sop.storyweb.controller.param.LargeTextParam; import com.gitee.sop.storyweb.controller.param.StoryParam; +import com.gitee.sop.storyweb.controller.param.TypeEnum; import com.gitee.sop.storyweb.controller.result.CategoryResult; import com.gitee.sop.storyweb.controller.result.StoryResult; import com.gitee.sop.storyweb.controller.result.TestResult; @@ -27,6 +28,9 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -98,6 +102,35 @@ public class Example1001_BaseController { return result; } + // 参数绑定,少量参数可以这样写,参数多了建议放进类里面 + @Open(value = "story.oneparam") + @GetMapping("/oneParam/v1") + public StoryResult oneParam(@NotBlank(message = "id不能为空") String id, @NotBlank(message = "name不能为空") String name) { + StoryResult result = new StoryResult(); + result.setName("id:" + id + ", name:" + name); + return result; + } + + // 参数绑定 + @Open(value = "story.oneparam", version = "1.1") + @GetMapping("/oneParam/v2") + public StoryResult oneParamV2( + @NotNull(message = "id不能为空") + @Min(value = 1, message = "id必须大于0") Integer id) { + StoryResult result = new StoryResult(); + result.setName("id:" + id); + return result; + } + + // 参数绑定,枚举 + @Open(value = "story.oneparam", version = "1.2") + @GetMapping("/oneParam/v3") + public StoryResult oneParamV2(@NotNull(message = "typeEnum不能为空") TypeEnum typeEnum) { + StoryResult result = new StoryResult(); + result.setName("typeEnum:" + typeEnum.name()); + return result; + } + // 参数绑定 @Open(value = "story.param.bind", mergeResult = false) @RequestMapping("/get/param/v1") diff --git a/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/TypeEnum.java b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/TypeEnum.java new file mode 100644 index 00000000..56e387f5 --- /dev/null +++ b/sop-example/sop-story/src/main/java/com/gitee/sop/storyweb/controller/param/TypeEnum.java @@ -0,0 +1,8 @@ +package com.gitee.sop.storyweb.controller.param; + +/** + * @author tanghc + */ +public enum TypeEnum { + MOBILE, COMPUTER +} diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java index fcaab14f..9a017ce9 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/interceptor/MyRouteInterceptor.java @@ -3,6 +3,7 @@ package com.gitee.sop.gateway.interceptor; import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor; import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext; import com.gitee.sop.gatewaycommon.param.ApiParam; +import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.client.ServiceInstance; import org.springframework.stereotype.Component; @@ -11,20 +12,23 @@ import org.springframework.stereotype.Component; * * @author tanghc */ +@Slf4j @Component public class MyRouteInterceptor implements RouteInterceptor { @Override public void preRoute(RouteInterceptorContext context) { ApiParam apiParam = context.getApiParam(); - System.out.println(String.format("请求接口:%s, ip:%s", apiParam.fetchNameVersion(), apiParam.fetchIp())); + log.info("请求接口:{}, ip:{}", apiParam.fetchNameVersion(), apiParam.fetchIp()); } @Override public void afterRoute(RouteInterceptorContext context) { ServiceInstance serviceInstance = context.getServiceInstance(); - System.out.println("请求成功,serviceId:" + serviceInstance.getServiceId() + "(" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + ")" + - ",微服务返回结果:" + context.getServiceResult()); + log.info("请求成功,serviceId:{}({}:{}),微服务返回结果:{}", + serviceInstance.getServiceId(), + serviceInstance.getHost(),serviceInstance.getPort(), + context.getServiceResult()); } @Override diff --git a/sop-test/src/test/java/com/gitee/sop/test/OneParamTest.java b/sop-test/src/test/java/com/gitee/sop/test/OneParamTest.java new file mode 100644 index 00000000..407a05b7 --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/OneParamTest.java @@ -0,0 +1,126 @@ +package com.gitee.sop.test; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.test.alipay.AlipaySignature; +import org.junit.Test; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 模仿支付宝客户端请求接口 + */ +public class OneParamTest extends TestBase { + + String url = "http://localhost:8081"; + String appId = "2019032617262200001"; + // 平台提供的私钥 + String 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="; + + @Test + public void testGet() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.oneparam"); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "aaa"); + bizContent.put("name", "jim"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = get(url, params);// 发送请求 + System.out.println(responseData); + } + + @Test + public void testGet2() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.oneparam"); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", "1.1"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "1"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = get(url, params);// 发送请求 + System.out.println(responseData); + } + + @Test + public void testGet3() throws Exception { + + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.oneparam"); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", "1.2"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("typeEnum", "MOBILE"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + String content = AlipaySignature.getSignContent(params); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + params.put("sign", sign); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + System.out.println("待签名内容:" + content); + System.out.println("签名(sign):" + sign); + System.out.println("URL参数:" + buildUrlQuery(params)); + + System.out.println("----------- 返回结果 -----------"); + String responseData = get(url, params);// 发送请求 + System.out.println(responseData); + } + + +}