1.8
- 2.3.2.RELEASE
+ 3.0.2
Hoxton.SR8
diff --git a/sop-example/pom.xml b/sop-example/pom.xml
index bfa9f619..55501b45 100644
--- a/sop-example/pom.xml
+++ b/sop-example/pom.xml
@@ -5,7 +5,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
@@ -17,4 +17,4 @@
sop-story
sop-springmvc
-
\ No newline at end of file
+
diff --git a/sop-example/sop-springmvc/pom.xml b/sop-example/sop-springmvc/pom.xml
index 055db4cc..cd0e23cb 100644
--- a/sop-example/sop-springmvc/pom.xml
+++ b/sop-example/sop-springmvc/pom.xml
@@ -5,7 +5,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../../pom.xml
diff --git a/sop-example/sop-story/pom.xml b/sop-example/sop-story/pom.xml
index 1d78e946..35664fe4 100644
--- a/sop-example/sop-story/pom.xml
+++ b/sop-example/sop-story/pom.xml
@@ -4,7 +4,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../../pom.xml
diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml
index 493d0a4c..dae11134 100644
--- a/sop-gateway/pom.xml
+++ b/sop-gateway/pom.xml
@@ -4,7 +4,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
diff --git a/sop-index/pom.xml b/sop-index/pom.xml
new file mode 100644
index 00000000..47f8e36e
--- /dev/null
+++ b/sop-index/pom.xml
@@ -0,0 +1,68 @@
+
+
+ 4.0.0
+
+ com.gitee.sop
+ sop-parent
+ 5.0.0-SNAPSHOT
+ ../pom.xml
+
+ sop-index
+ 5.0.0-SNAPSHOT
+ sop-index
+ sop-index
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.apache.dubbo
+ dubbo-spring-boot-starter
+
+
+
+ commons-codec
+ commons-codec
+
+
+
+ org.hibernate
+ hibernate-validator
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/sop-index/src/main/java/com/gitee/sop/index/SopIndexApplication.java b/sop-index/src/main/java/com/gitee/sop/index/SopIndexApplication.java
new file mode 100644
index 00000000..07b6c2e3
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/SopIndexApplication.java
@@ -0,0 +1,13 @@
+package com.gitee.sop.index;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SopIndexApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SopIndexApplication.class, args);
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/common/ParamNames.java b/sop-index/src/main/java/com/gitee/sop/index/common/ParamNames.java
new file mode 100644
index 00000000..a0d6e7f2
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/common/ParamNames.java
@@ -0,0 +1,51 @@
+package com.gitee.sop.index.common;
+
+/**
+ * 请求参数名定义
+ *
+ * 参数 类型 是否必填 最大长度 描述 示例值
+ * app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
+ * method String 是 128 接口名称 alipay.trade.fastpay.refund.query
+ * format String 否 40 仅支持JSON JSON
+ * charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
+ * sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
+ * sign String 是 344 商户请求参数的签名串,详见签名 详见示例
+ * timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
+ * version String 是 3 调用的接口版本,固定为:1.0 1.0
+ * notify_url String 否 256 支付宝服务器主动通知商户服务器里指定的页面http/https路径。 http://api.test.alipay.net/atinterface/receive_notify.htm
+ * app_auth_token String 否 40 详见应用授权概述
+ * biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ *
+ * @author tanghc
+ */
+public class ParamNames {
+ /** 分配给开发者的应用ID */
+ public static String APP_KEY_NAME = "app_id";
+ /** 接口名称 */
+ public static String API_NAME = "method";
+ /** 仅支持JSON */
+ public static String FORMAT_NAME = "format";
+ /** 请求使用的编码格式 */
+ public static String CHARSET_NAME = "charset";
+ /** 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 */
+ public static String SIGN_TYPE_NAME = "sign_type";
+ /** 商户请求参数的签名串 */
+ public static String SIGN_NAME = "sign";
+ /** 发送请求的时间 */
+ public static String TIMESTAMP_NAME = "timestamp";
+ /** 调用的接口版本 */
+ public static String VERSION_NAME = "version";
+ /** 开放平台主动通知商户服务器里指定的页面http/https路径 */
+ public static String NOTIFY_URL_NAME = "notify_url";
+ /** OAuth 2.0授权token */
+ public static String APP_AUTH_TOKEN_NAME = "app_auth_token";
+ /** 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档 */
+ public static String BIZ_CONTENT_NAME = "biz_content";
+
+ /** 时间戳格式 */
+ public static String TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ public static String HEADER_VERSION_NAME = "sop-version";
+
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/controller/IndexController.java b/sop-index/src/main/java/com/gitee/sop/index/controller/IndexController.java
new file mode 100644
index 00000000..a8cd8dbd
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/controller/IndexController.java
@@ -0,0 +1,39 @@
+package com.gitee.sop.index.controller;
+
+import com.gitee.sop.index.controller.param.ApiRequest;
+import com.gitee.sop.index.controller.param.ApiResponse;
+import com.gitee.sop.index.service.RouteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 开放平台入口
+ *
+ * @author 六如
+ */
+@RestController
+public class IndexController {
+
+ @Autowired
+ private RouteService routeService;
+
+ @GetMapping("/")
+ public String home() {
+ return "api";
+ }
+
+ /**
+ * 请求入口
+ *
+ * @return 返回响应内容
+ */
+ @PostMapping("api")
+ public ApiResponse index(@Validated @RequestBody ApiRequest request) {
+ return routeService.route(request);
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java b/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java
new file mode 100644
index 00000000..b145c7f2
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiRequest.java
@@ -0,0 +1,123 @@
+package com.gitee.sop.index.controller.param;
+
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 请求参数名定义
+ *
+ * 参数 类型 是否必填 最大长度 描述 示例值
+ * app_id String 是 32 支付宝分配给开发者的应用ID 2014072300007148
+ * method String 是 128 接口名称 alipay.trade.fastpay.refund.query
+ * format String 否 40 仅支持JSON JSON
+ * charset String 是 10 请求使用的编码格式,如utf-8,gbk,gb2312等 utf-8
+ * sign_type String 是 10 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 RSA2
+ * sign String 是 344 商户请求参数的签名串,详见签名 详见示例
+ * timestamp String 是 19 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" 2014-07-24 03:07:50
+ * version String 是 3 调用的接口版本,固定为:1.0 1.0
+ * notify_url String 否 256 支付宝服务器主动通知商户服务器里指定的页面http/https路径。 http://api.test.alipay.net/atinterface/receive_notify.htm
+ * app_auth_token String 否 40 详见应用授权概述
+ * biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ *
+ * @author tanghc
+ */
+@Data
+public class ApiRequest implements Serializable {
+ private static final long serialVersionUID = 1815097687653555654L;
+
+ /**
+ * 分配给开发者的应用ID
+ *
+ * @mock 2014072300007148
+ */
+ @NotBlank(message = "app_id不能为空")
+ @Length(max = 32)
+ private String app_id;
+
+ /**
+ * 接口名称
+ *
+ * @mock alipay.trade.fastpay.refund.query
+ */
+ @NotBlank(message = "method不能为空")
+ @Length(max = 128)
+ private String method;
+
+ /**
+ * 仅支持JSON
+ *
+ * @mock json
+ */
+ @NotBlank(message = "format不能为空")
+ @Length(max = 40)
+ private String format;
+
+ /**
+ * 请求使用的编码格式,如utf-8,gbk,gb2312等
+ *
+ * @mock utf-8
+ */
+ @NotBlank(message = "charset不能为空")
+ @Length(max = 10)
+ private String charset;
+
+ /**
+ * 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2
+ *
+ * @mock RSA2
+ */
+ @NotBlank(message = "sign_type不能为空")
+ @Length(max = 10)
+ private String sign_type;
+
+ /**
+ * 商户请求参数的签名串,详见签名
+ */
+ @NotBlank(message = "sign不能为空")
+ private String sign;
+
+ /**
+ * 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
+ *
+ * @mock 2014-07-24 03:07:50
+ */
+ @NotBlank(message = "timestamp不能为空")
+ @Length(max = 19)
+ private String timestamp;
+
+ /**
+ * 调用的接口版本,固定为:1.0
+ *
+ * @mock 1.0
+ */
+ @NotBlank(message = "version不能为空")
+ @Length(max = 10)
+ private String version;
+
+ /**
+ * 支付宝服务器主动通知商户服务器里指定的页面http/https路径
+ *
+ * @mock http://ww.xx.com/callback
+ */
+ @Length(max = 256)
+ private String notify_url;
+
+ /**
+ * 授权token,详见应用授权概述
+ */
+ @Length(max = 64)
+ private String app_auth_token;
+
+ /**
+ * 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
+ */
+ @NotBlank(message = "biz_content不能为空")
+ private String biz_content;
+
+ public String takeApiName() {
+ return method + version;
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java b/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java
new file mode 100644
index 00000000..9b031811
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/controller/param/ApiResponse.java
@@ -0,0 +1,82 @@
+package com.gitee.sop.index.controller.param;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 默认的结果封装类.
+ *
+ *
+ * xml返回结果:
+ *
+ * 50
+ * Remote service error
+ * isv.invalid-parameter
+ * 非法参数
+ *
+ * 成功情况:
+ *
+ * 0
+ * 成功消息
+ *
+ * ...返回内容
+ *
+ *
+ *
+ * json返回格式:
+ * {
+ * "code":"50",
+ * "msg":"Remote service error",
+ * "sub_code":"isv.invalid-parameter",
+ * "sub_msg":"非法参数"
+ * }
+ * 成功情况:
+ * {
+ * "code":"0",
+ * "msg":"成功消息内容。。。",
+ * "data":{
+ * ...返回内容
+ * }
+ * }
+ *
+ *
+ * 字段说明:
+ * code:网关异常码
+ * msg:网关异常信息
+ * sub_code:业务异常码
+ * sub_msg:业务异常信息
+ *
+ * @author tanghc
+ */
+@Data
+// 驼峰转下划线
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+public class ApiResponse implements Serializable {
+ private static final long serialVersionUID = 6208496044702199437L;
+
+ /**
+ * 网关异常码,范围0~100 成功返回"0"
+ */
+ private String code = "0";
+
+ /**
+ * 网关异常信息
+ */
+ private String msg;
+
+ /**
+ * 业务异常码
+ */
+ private String subCode;
+
+ /**
+ * 业务异常信息
+ */
+ private String subMsg;
+
+
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/exception/ApiException.java b/sop-index/src/main/java/com/gitee/sop/index/exception/ApiException.java
new file mode 100644
index 00000000..fd543e1d
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/exception/ApiException.java
@@ -0,0 +1,44 @@
+package com.gitee.sop.index.exception;
+
+
+
+import com.gitee.sop.index.message.ErrorFactory;
+import com.gitee.sop.index.message.ErrorMeta;
+
+import java.util.Locale;
+
+/**
+ * @author tanghc
+ */
+public class ApiException extends RuntimeException {
+
+ private transient Error error;
+
+ private transient ErrorMeta errorMeta;
+ private transient Object[] params;
+
+ public ApiException(ErrorMeta errorMeta, Object... params) {
+ this.errorMeta = errorMeta;
+ this.params = params;
+ }
+
+ public ApiException(Throwable cause, ErrorMeta errorMeta, Object... params) {
+ super(cause);
+ this.errorMeta = errorMeta;
+ this.params = params;
+ }
+
+ public Error getError(Locale locale) {
+ if (error == null) {
+ error = ErrorFactory.getError(this.errorMeta, locale, params);
+ }
+ return error;
+ }
+
+ @Override
+ public String getMessage() {
+ String message = super.getMessage();
+ return message == null ? errorMeta.toString() : message;
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/message/ErrorEnum.java b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorEnum.java
new file mode 100644
index 00000000..b9ab6791
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorEnum.java
@@ -0,0 +1,130 @@
+package com.gitee.sop.index.message;
+
+/**
+ * 网关错误定义
+ * @author tanghc
+ */
+public enum ErrorEnum {
+ /** 成功 */
+ SUCCESS(Codes.CODE_SUCCESS, ""),
+
+ /** 服务暂不可用 */
+ ISP_UNKNOWN_ERROR(Codes.CODE_UNKNOWN, "isp.unknown-error"),
+ /** 微服务未知错误 */
+ ISP_SERVICE_UNKNOWN_ERROR(Codes.CODE_UNKNOWN, "isp.service-unknown-error"),
+ /** 服务不可用,路由被禁用 */
+ ISP_API_DISABLED(Codes.CODE_UNKNOWN, "isp.service-not-available"),
+ /** 网关响应超时 */
+ ISP_GATEWAY_RESPONSE_TIMEOUT(Codes.CODE_UNKNOWN, "isp.gateway-response-timeout"),
+ /** 限流处理 */
+ ISV_REQUEST_LIMIT(Codes.CODE_UNKNOWN, "isv.service-busy"),
+
+ /** 无效的访问令牌 */
+ AOP_INVALID_AUTH_TOKEN(Codes.CODE_AUTH, "aop.invalid-auth-token"),
+ /** 访问令牌已过期 */
+ AOP_AUTH_TOKEN_TIME_OUT(Codes.CODE_AUTH, "aop.auth-token-time-out"),
+ /** 无效的应用授权令牌 */
+ AOP_INVALID_APP_AUTH_TOKEN(Codes.CODE_AUTH, "aop.invalid-app-auth-token"),
+ /** 商户未授权当前接口 */
+ AOP_INVALID_APP_AUTH_TOKEN_NO_API(Codes.CODE_AUTH, "aop.invalid-app-auth-token-no-api"),
+ /** 应用授权令牌已过期 */
+ AOP_APP_AUTH_TOKEN_TIME_OUT(Codes.CODE_AUTH, "aop.app-auth-token-time-out"),
+ /** 商户未签约任何产品 */
+ AOP_NO_PRODUCT_REG_BY_PARTNER(Codes.CODE_AUTH, "aop.no-product-reg-by-partner"),
+
+ /** 缺少方法名参数 */
+ ISV_MISSING_METHOD(Codes.CODE_MISSING, "isv.missing-method"),
+ /** 缺少签名参数 */
+ ISV_MISSING_SIGNATURE(Codes.CODE_MISSING, "isv.missing-signature"),
+ /** 缺少签名类型参数 */
+ ISV_MISSING_SIGNATURE_TYPE(Codes.CODE_MISSING, "isv.missing-signature-type"),
+ /** 缺少签名配置 */
+ ISV_MISSING_SIGNATURE_KEY(Codes.CODE_MISSING, "isv.missing-signature-key"),
+ /** 缺少appId参数 */
+ ISV_MISSING_APP_ID(Codes.CODE_MISSING, "isv.missing-app-id"),
+ /** 缺少时间戳参数 */
+ ISV_MISSING_TIMESTAMP(Codes.CODE_MISSING, "isv.missing-timestamp"),
+ /** 缺少版本参数 */
+ ISV_MISSING_VERSION(Codes.CODE_MISSING, "isv.missing-version"),
+ /** 解密出错, 未指定加密算法 */
+ ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE(Codes.CODE_MISSING, "isv.decryption-error-missing-encrypt-type"),
+
+ /** 参数无效 */
+ ISV_INVALID_PARAMETER(Codes.CODE_INVALID, "isv.invalid-parameter"),
+ /** 文件上传失败 */
+ ISV_UPLOAD_FAIL(Codes.CODE_INVALID, "isv.upload-fail"),
+ /** 文件扩展名无效 */
+ ISV_INVALID_FILE_EXTENSION(Codes.CODE_INVALID, "isv.invalid-file-extension"),
+ /** 文件大小无效 */
+ ISV_INVALID_FILE_SIZE(Codes.CODE_INVALID, "isv.invalid-file-size"),
+ /** 不存在的方法名 */
+ ISV_INVALID_METHOD(Codes.CODE_INVALID, "isv.invalid-method"),
+ /** 无效的数据格式 */
+ ISV_INVALID_FORMAT(Codes.CODE_INVALID, "isv.invalid-format"),
+ /** 无效的签名类型 */
+ ISV_INVALID_SIGNATURE_TYPE(Codes.CODE_INVALID, "isv.invalid-signature-type"),
+ /** 无效签名 */
+ ISV_INVALID_SIGNATURE(Codes.CODE_INVALID, "isv.invalid-signature"),
+ /** 无效的加密类型 */
+ ISV_INVALID_ENCRYPT_TYPE(Codes.CODE_INVALID, "isv.invalid-encrypt-type"),
+ /** 解密异常 */
+ ISV_INVALID_ENCRYPT(Codes.CODE_INVALID, "isv.invalid-encrypt"),
+ /** 无效的appId参数 */
+ ISV_INVALID_APP_ID(Codes.CODE_INVALID, "isv.invalid-app-id"),
+ /** 非法的时间戳参数 */
+ ISV_INVALID_TIMESTAMP(Codes.CODE_INVALID, "isv.invalid-timestamp"),
+ /** 字符集错误 */
+ ISV_INVALID_CHARSET(Codes.CODE_INVALID, "isv.invalid-charset"),
+ /** 摘要错误 */
+ ISV_INVALID_DIGEST(Codes.CODE_INVALID, "isv.invalid-digest"),
+ /** 解密出错,不支持的加密算法 */
+ ISV_DECRYPTION_ERROR_NOT_VALID_ENCRYPT_TYPE(Codes.CODE_INVALID, "isv.decryption-error-not-valid-encrypt-type"),
+ /** 解密出错, 未配置加密密钥或加密密钥格式错误 */
+ ISV_DECRYPTION_ERROR_NOT_VALID_ENCRYPT_KEY(Codes.CODE_INVALID, "isv.decryption-error-not-valid-encrypt-key"),
+ /** 解密出错,未知异常 */
+ ISV_DECRYPTION_ERROR_UNKNOWN(Codes.CODE_INVALID, "isv.decryption-error-unknown"),
+ /** 验签出错, 未配置对应签名算法的公钥或者证书 */
+ ISV_MISSING_SIGNATURE_CONFIG(Codes.CODE_INVALID, "isv.missing-signature-config"),
+ /** 本接口不支持第三方代理调用 */
+ ISV_NOT_SUPPORT_APP_AUTH(Codes.CODE_INVALID, "isv.not-support-app-auth"),
+ /** 可疑的攻击请求 */
+ ISV_SUSPECTED_ATTACK(Codes.CODE_INVALID, "isv.suspected-attack"),
+ /** 无效的content-type */
+ ISV_INVALID_CONTENT_TYPE(Codes.CODE_INVALID, "isv.invalid-content-type"),
+
+ /** 业务处理失败 */
+ BIZ_ERROR(Codes.CODE_BIZ, ""),
+
+ /** 请检查配置的账户是否有当前接口权限 */
+ ISV_INSUFFICIENT_ISV_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-isv-permissions"),
+ /** 代理的商户没有当前接口权限 */
+ ISV_INSUFFICIENT_USER_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.insufficient-user-permissions"),
+ /** 没有当前接口权限 */
+ ISV_ROUTE_NO_PERMISSIONS(Codes.CODE_ISV_PERM, "isv.route-no-permissions"),
+ /** 禁止访问 */
+ ISV_ACCESS_FORBIDDEN(Codes.CODE_ISV_PERM, "isv.access-forbidden"),
+ /** 禁止IP访问 */
+ ISV_IP_FORBIDDEN(Codes.CODE_ISV_PERM, "isv.ip-forbidden"),
+
+ ;
+ private ErrorMeta errorMeta;
+
+ ErrorEnum(String code, String subCode) {
+ this.errorMeta = new ErrorMeta("open.error_", code, subCode);
+ }
+
+ public ErrorMeta getErrorMeta() {
+ return errorMeta;
+ }
+
+ private static class Codes {
+ public static final String CODE_SUCCESS = "10000";
+ public static final String CODE_AUTH = "20001";
+ public static final String CODE_MISSING = "40001";
+ public static final String CODE_INVALID = "40002";
+ public static final String CODE_BIZ = "40004";
+ public static final String CODE_ISV_PERM = "40006";
+ public static final String CODE_UNKNOWN = "20000";
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/message/ErrorFactory.java b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorFactory.java
new file mode 100644
index 00000000..a80f4fd4
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorFactory.java
@@ -0,0 +1,131 @@
+package com.gitee.sop.index.message;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.context.support.ResourceBundleMessageSource;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 负责构建错误消息
+ *
+ * @author tanghc
+ */
+@Slf4j
+public class ErrorFactory {
+
+ private ErrorFactory(){}
+
+ public static final String SYS_ERR = "系统错误";
+
+ private static final String I18N_OPEN_ERROR = "i18n/open/error";
+ public static final String UNDERLINE = "_";
+
+ private static Set noModuleCache = new HashSet<>();
+
+ private static Map errorCache = new HashMap<>(64);
+
+ private static List localeList = Arrays.asList(Locale.ENGLISH, Locale.SIMPLIFIED_CHINESE);
+
+ /**
+ * 错误信息的国际化信息
+ */
+ private static MessageSourceAccessor errorMessageSourceAccessor;
+
+ /**
+ * 设置国际化资源信息
+ */
+ public static void initMessageSource(List isvModules) {
+ HashSet baseNamesSet = new HashSet<>();
+ baseNamesSet.add(I18N_OPEN_ERROR);
+
+ if (!isvModules.isEmpty()) {
+ baseNamesSet.addAll(isvModules);
+ }
+
+ String[] totalBaseNames = baseNamesSet.toArray(new String[0]);
+
+ log.info("加载错误码国际化资源:{}", StringUtils.arrayToCommaDelimitedString(totalBaseNames));
+ ResourceBundleMessageSource bundleMessageSource = new ResourceBundleMessageSource();
+ bundleMessageSource.setBasenames(totalBaseNames);
+ MessageSourceAccessor messageSourceAccessor = new MessageSourceAccessor(bundleMessageSource);
+ setErrorMessageSourceAccessor(messageSourceAccessor);
+ }
+
+ /**
+ * 通过ErrorMeta,Locale,params构建国际化错误消息
+ *
+ * @param errorMeta 错误信息
+ * @param locale 本地化
+ * @param params 参数
+ * @return 如果没有配置国际化消息,则直接返回errorMeta中的信息
+ */
+ public static IError getError(ErrorMeta errorMeta, Locale locale, Object... params) {
+ if (locale == null) {
+ locale = Locale.SIMPLIFIED_CHINESE;
+ }
+ if (!localeList.contains(locale)) {
+ locale = Locale.ENGLISH;
+ }
+ String key = errorMeta.getModulePrefix() + errorMeta.getCode() + errorMeta.getSubCode() + locale.toString();
+ IError error = errorCache.get(key);
+ if (error == null) {
+ Assert.notNull(locale, "未设置Locale");
+ String modulePrefix = errorMeta.getModulePrefix();
+ String code = errorMeta.getCode();
+ // open.error_20000=Service is temporarily unavailable
+ String msg = getErrorMessage(modulePrefix + code, locale);
+ String subCode = errorMeta.getSubCode();
+ // open.error_20000_isp.unknown-error=Service is temporarily unavailable
+ String subMsg = getErrorMessage(modulePrefix + code + UNDERLINE + subCode, locale, params);
+ if (StringUtils.isEmpty(msg)) {
+ msg = SYS_ERR;
+ }
+ if (StringUtils.isEmpty(subMsg)) {
+ subMsg = SYS_ERR;
+ }
+ // solution暂未实现,如果要实现,可以这样配置:
+ // open.error_20000_isp.unknown-error_solution=Service is temporarily unavailable
+ // String solution = getErrorMessage(modulePrefix + code + UNDERLINE + subCode + "_solution", locale, params);
+ error = new ErrorImpl(code, msg, subCode, subMsg, null);
+ errorCache.put(key, error);
+ }
+ return error;
+ }
+
+
+ public static void setErrorMessageSourceAccessor(MessageSourceAccessor errorMessageSourceAccessor) {
+ ErrorFactory.errorMessageSourceAccessor = errorMessageSourceAccessor;
+ }
+
+ /**
+ * 返回本地化信息
+ *
+ * @param module 错误模块
+ * @param locale 本地化
+ * @param params 参数
+ * @return 返回信息
+ */
+ public static String getErrorMessage(String module, Locale locale, Object... params) {
+ if (noModuleCache.contains(module)) {
+ return null;
+ }
+ try {
+ return errorMessageSourceAccessor.getMessage(module, params, locale);
+ } catch (Exception e) {
+ noModuleCache.add(module);
+ return null;
+ }
+ }
+
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/message/ErrorImpl.java b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorImpl.java
new file mode 100644
index 00000000..d1717598
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorImpl.java
@@ -0,0 +1,24 @@
+package com.gitee.sop.index.message;
+
+import lombok.Data;
+
+/**
+ * @author tanghc
+ */
+@Data
+public class ErrorImpl implements IError {
+
+ private String code;
+ private String msg;
+ private String sub_code;
+ private String sub_msg;
+ private String solution;
+
+ public ErrorImpl(String code, String msg, String sub_code, String sub_msg, String solution) {
+ this.code = code;
+ this.msg = msg;
+ this.sub_code = sub_code;
+ this.sub_msg = sub_msg;
+ this.solution = solution;
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/message/ErrorMeta.java b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorMeta.java
new file mode 100644
index 00000000..d86dd938
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/message/ErrorMeta.java
@@ -0,0 +1,50 @@
+package com.gitee.sop.index.message;
+
+import com.gitee.sop.index.exception.ApiException;
+import lombok.Getter;
+
+import java.util.Locale;
+
+/**
+ * 错误对象
+ *
+ * @author tanghc
+ */
+@Getter
+public class ErrorMeta {
+
+ private final String modulePrefix;
+ private final String code;
+ private final String subCode;
+
+ public ErrorMeta(String modulePrefix, String code, String subCode) {
+ this.modulePrefix = modulePrefix;
+ this.code = code;
+ this.subCode = subCode;
+ }
+
+ public IError getError(Locale locale) {
+ return ErrorFactory.getError(this, locale);
+ }
+
+ /**
+ * 返回网关exception
+ *
+ * @param params 参数
+ * @return 返回exception
+ */
+ public ApiException getException(Object... params) {
+ if (params != null && params.length == 1) {
+ Object param = params[0];
+ if (param instanceof Throwable) {
+ return new ApiException((Throwable) param, this);
+ }
+ }
+ return new ApiException(this, params);
+ }
+
+ @Override
+ public String toString() {
+ return modulePrefix + code + "_" + subCode;
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/message/IError.java b/sop-index/src/main/java/com/gitee/sop/index/message/IError.java
new file mode 100644
index 00000000..3a332d2f
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/message/IError.java
@@ -0,0 +1,48 @@
+package com.gitee.sop.index.message;
+
+/**
+ * 定义错误返回
+ * code(返回码)
+ * msg(返回码描述)
+ * sub_code(明细返回码)
+ * sub_msg(明细返回码描述)
+ * 解决方案
+ * @author tanghc
+ */
+public interface IError {
+ /**
+ * 获取网关状态码
+ *
+ * @return 返回状态码
+ */
+ String getCode();
+
+ /**
+ * 获取网关错误信息
+ *
+ * @return 返回错误信息
+ */
+ String getMsg();
+
+ /**
+ * sub_code(明细返回码)
+ * @return sub_code(明细返回码)
+ */
+ String getSub_code();
+
+ /**
+ * sub_msg(明细返回码描述)
+ * @return sub_msg(明细返回码描述)
+ */
+ String getSub_msg();
+
+ /**
+ * 解决方案
+ * @return 解决方案
+ */
+ String getSolution();
+
+
+
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java b/sop-index/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java
new file mode 100644
index 00000000..85984d9b
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/GenericServiceInvoker.java
@@ -0,0 +1,75 @@
+package com.gitee.sop.index.service;
+
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ReferenceConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.springframework.beans.factory.annotation.Value;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * dubbo泛化调用
+ *
+ * @author 六如
+ */
+public class GenericServiceInvoker {
+ private ApplicationConfig applicationConfig;
+
+ @Value("${nacos.host:localhost:8848}")
+ private String nacosHost;
+
+ @Value("${nacos.group:DEFAULT}")
+ private String group;
+
+ @Value("${spring.application.name}")
+ private String appName;
+
+ @Value("${generic.timeout:5000}")
+ private int timeout;
+
+ @PostConstruct
+ public void init() {
+ RegistryConfig registryConfig = buildRegistryConfig(nacosHost, group);
+ applicationConfig = new ApplicationConfig();
+ applicationConfig.setName(appName + "-generic");
+ applicationConfig.setRegistry(registryConfig);
+ }
+
+ private RegistryConfig buildRegistryConfig(String nacosHost, String group) {
+ RegistryConfig config = new RegistryConfig();
+ config.setGroup(group);
+ config.setAddress("nacos://" + nacosHost);
+ return config;
+ }
+
+ public Object invoke(String interfaceName, String method, String[] parameterTypes, Object[] params) {
+ ReferenceConfig reference = new ReferenceConfig<>();
+ reference.setGeneric("true");
+ reference.setApplication(applicationConfig);
+ reference.setInterface(interfaceName);
+ reference.setGroup(group);
+ reference.setTimeout(timeout);
+ reference.setSticky(false);
+ reference.setCheck(false);
+ try {
+ removeGenericSymbol(parameterTypes);
+ GenericService genericService = reference.get();
+ return genericService.$invoke(method, parameterTypes, params);
+ } finally {
+ reference.destroy();
+ }
+ }
+
+ /**
+ * remove generic from parameterTypes
+ */
+ private void removeGenericSymbol(String[] parameterTypes) {
+ for (int i = 0; i < parameterTypes.length; i++) {
+ int index = parameterTypes[i].indexOf('<');
+ if (index > -1) {
+ parameterTypes[i] = parameterTypes[i].substring(0, index);
+ }
+ }
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/RouteService.java b/sop-index/src/main/java/com/gitee/sop/index/service/RouteService.java
new file mode 100644
index 00000000..df4ca25b
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/RouteService.java
@@ -0,0 +1,28 @@
+package com.gitee.sop.index.service;
+
+import com.gitee.sop.index.controller.param.ApiRequest;
+import com.gitee.sop.index.controller.param.ApiResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 接口路由
+ *
+ * @author 六如
+ */
+@Service
+public class RouteService {
+
+ @Autowired
+ private ValidateService validateService;
+
+ public ApiResponse route(ApiRequest apiRequest) {
+ // 签名校验
+ validateService.validate(apiRequest);
+
+ return null;
+ }
+
+
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/ValidateService.java b/sop-index/src/main/java/com/gitee/sop/index/service/ValidateService.java
new file mode 100644
index 00000000..c5a9ae4a
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/ValidateService.java
@@ -0,0 +1,19 @@
+package com.gitee.sop.index.service;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.gitee.sop.index.controller.param.ApiRequest;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author 六如
+ */
+@Service
+public class ValidateService {
+
+ public void validate(ApiRequest apiRequest) {
+ String jsonString = JSON.toJSONString(apiRequest);
+ JSONObject jsonObject = JSON.parseObject(jsonString);
+
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java
new file mode 100644
index 00000000..193a6f22
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/AbstractSigner.java
@@ -0,0 +1,44 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author tanghc
+ */
+@Slf4j
+public abstract class AbstractSigner implements Signer {
+
+ /**
+ * 构建服务端签名串
+ *
+ * @param params 接口参数
+ * @param secret 秘钥
+ * @return 返回服务端签名串
+ */
+ protected abstract String buildServerSign(ApiParam params, String secret);
+
+ @Override
+ public boolean checkSign(ApiParam apiParam, String secret) {
+ String clientSign = apiParam.fetchSignAndRemove();
+ if (StringUtils.isBlank(clientSign)) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException();
+ }
+ String serverSign = buildServerSign(apiParam, secret);
+ return clientSign.equals(serverSign);
+ }
+
+ protected static String byte2hex(byte[] bytes) {
+ StringBuilder sign = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i] & 0xFF);
+ if (hex.length() == 1) {
+ sign.append("0");
+ }
+ sign.append(hex);
+ }
+ return sign.toString();
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiEncrypter.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiEncrypter.java
new file mode 100644
index 00000000..c590017f
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiEncrypter.java
@@ -0,0 +1,60 @@
+package com.gitee.sop.index.service.validate;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import com.gitee.sop.gatewaycommon.util.AESUtil;
+import com.gitee.sop.gatewaycommon.util.RSANewUtil;
+import com.gitee.sop.gatewaycommon.util.RSAUtil;
+
+/**
+ * 负责各类加解密
+ * @author tanghc
+ *
+ */
+public class ApiEncrypter implements Encrypter {
+
+ @Override
+ public String aesEncryptToHex(String content, String password) throws Exception {
+ return AESUtil.encryptToHex(content, password);
+ }
+
+ @Override
+ public String aesDecryptFromHex(String hex, String password) throws Exception {
+ return AESUtil.decryptFromHex(hex, password);
+ }
+
+ @Override
+ public String aesEncryptToBase64String(String content, String password) throws Exception {
+ return AESUtil.encryptToBase64String(content, password);
+ }
+
+ @Override
+ public String aesDecryptFromBase64String(String base64String, String password) throws Exception {
+ return AESUtil.decryptFromBase64String(base64String, password);
+ }
+
+ @Override
+ public String rsaDecryptByPrivateKey(String data, String privateKey) throws Exception {
+ return RSAUtil.decryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaEncryptByPrivateKey(String data, String privateKey) throws Exception {
+ return RSAUtil.encryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaDecryptByPrivateKeyNew(String data, String privateKey) throws Exception {
+ return RSANewUtil.decryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String rsaEncryptByPrivateKeyNew(String data, String privateKey) throws Exception {
+ return RSANewUtil.encryptByPrivateKey(data, privateKey);
+ }
+
+ @Override
+ public String md5(String value) {
+ return DigestUtils.md5Hex(value);
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiSigner.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiSigner.java
new file mode 100644
index 00000000..5c771411
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiSigner.java
@@ -0,0 +1,55 @@
+package com.gitee.sop.index.service.validate;
+
+
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 签名验证实现
+ *
+ * @author tanghc
+ */
+public class ApiSigner extends AbstractSigner {
+
+ private Map signEncipherMap = new HashMap<>();
+
+ public ApiSigner() {
+ signEncipherMap.put("md5", new SignEncipherMD5());
+ signEncipherMap.put("hmac", new SignEncipherHMAC_MD5());
+ }
+
+
+ @Override
+ public String buildServerSign(ApiParam param, String secret) {
+ String signMethod = param.fetchSignMethod();
+ SignEncipher signEncipher = signEncipherMap.get(signMethod);
+ if (signEncipher == null) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(signMethod);
+ }
+
+ // 第一步:参数排序
+ Set keySet = param.keySet();
+ List paramNames = new ArrayList<>(keySet);
+ Collections.sort(paramNames);
+
+ // 第二步:把所有参数名和参数值串在一起
+ StringBuilder paramNameValue = new StringBuilder();
+ for (String paramName : paramNames) {
+ paramNameValue.append(paramName).append(param.get(paramName));
+ }
+
+ // 第三步:使用MD5/HMAC加密
+ String source = paramNameValue.toString();
+ byte[] bytes = signEncipher.encrypt(source, secret);
+
+ // 第四步:把二进制转化为大写的十六进制
+ return byte2hex(bytes).toUpperCase();
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java
new file mode 100644
index 00000000..c00d7cce
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/ApiValidator.java
@@ -0,0 +1,282 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.index.common.ParamNames;
+import com.gitee.sop.index.controller.param.ApiRequest;
+import com.gitee.sop.index.message.ErrorEnum;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.tomcat.util.http.fileupload.UploadContext;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.util.StringUtils;
+import org.springframework.util.unit.DataSize;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 负责校验,校验工作都在这里
+ *
+ * @author tanghc
+ */
+@Slf4j
+@Getter
+public class ApiValidator implements Validator {
+
+ private static final int MILLISECOND_OF_ONE_SECOND = 1000;
+ private static final int STATUS_FORBIDDEN = 2;
+
+ private static List FORMAT_LIST = Arrays.asList("json", "xml");
+
+// @Autowired
+// private IsvManager isvManager;
+//
+// @Autowired
+// private IsvRoutePermissionManager isvRoutePermissionManager;
+//
+// @Autowired
+// private IPBlacklistManager ipBlacklistManager;
+//
+// @Autowired
+// private RouteConfigManager routeConfigManager;
+
+ /**
+ * 单个文件大小
+ */
+ @Value("${upload.max-file-size:${spring.servlet.multipart.max-file-size:10MB}}")
+ private String maxFileSize;
+
+
+ @Override
+ public void validate(ApiRequest param) {
+// checkIP(param);
+// TargetRoute targetRoute = checkEnable(param);
+// initFields(targetRoute, param);
+// ApiConfig apiConfig = ApiContext.getApiConfig();
+ checkAppKey(param);
+// if (apiConfig.isIgnoreValidate()
+// || BooleanUtils.toBoolean(targetRoute.getRouteDefinition().getIgnoreValidate())) {
+// if (log.isDebugEnabled()) {
+// log.debug("忽略签名校验, name:{}, version:{}", param.fetchName(), param.fetchVersion());
+// }
+// } else {
+// checkSign(param);
+// }
+ checkSign(param);
+ checkTimeout(param);
+ checkFormat(param);
+ checkUploadFile(param);
+ checkPermission(param);
+ checkToken(param);
+ }
+
+ /**
+ * 是否在IP黑名单中
+ *
+ * @param param 接口参数
+ */
+// protected void checkIP(ApiRequest param) {
+// String ip = param.fetchIp();
+// if (ipBlacklistManager.contains(ip)) {
+// throw ErrorEnum.ISV_IP_FORBIDDEN.getErrorMeta().getException();
+// }
+// }
+
+ /**
+ * 检测能否访问
+ *
+ * @param param 接口参数
+ */
+// protected TargetRoute checkEnable(ApiRequest param) {
+// String name = param.getMethod();
+// if (name == null) {
+// throw ErrorEnum.ISV_MISSING_METHOD.getErrorMeta().getException();
+// }
+// String version = param.fetchVersion();
+// if (version == null) {
+// throw ErrorEnum.ISV_MISSING_VERSION.getErrorMeta().getException();
+// }
+// String routeId = param.fetchNameVersion();
+// // 检查路由是否存在
+// TargetRoute targetRoute = RouteRepositoryContext.getTargetRoute(routeId);
+// if (targetRoute == null) {
+// throw ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
+// }
+// // 检查路由是否启用
+// RouteConfig routeConfig = routeConfigManager.get(routeId);
+// if (!routeConfig.enable()) {
+// throw ErrorEnum.ISP_API_DISABLED.getErrorMeta().getException();
+// }
+// return targetRoute;
+// }
+
+ private void initFields(TargetRoute targetRoute, ApiRequest apiParam) {
+ apiParam.setServiceId(targetRoute.getServiceDefinition().getServiceId());
+ boolean mergeResult;
+ Boolean defaultSetting = ApiContext.getApiConfig().getMergeResult();
+ if (defaultSetting != null) {
+ mergeResult = defaultSetting;
+ } else {
+ RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
+ mergeResult = routeDefinition == null || BooleanUtils.toBoolean(routeDefinition.getMergeResult());
+ }
+ apiParam.setMergeResult(mergeResult);
+ }
+
+ /**
+ * 校验上传文件内容
+ *
+ * @param param
+ */
+ protected void checkUploadFile(ApiRequest param) {
+ UploadContext uploadContext = param.fetchUploadContext();
+ if (uploadContext != null) {
+ try {
+ List files = uploadContext.getAllFile();
+ for (MultipartFile file : files) {
+ checkSingleFileSize(file);
+ checkFileMd5(param, file);
+ }
+ } catch (IOException e) {
+ log.error("验证上传文件MD5错误", e);
+ throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
+ }
+ }
+ }
+
+ private void checkFileMd5(ApiRequest param, MultipartFile file) throws IOException {
+ // 客户端传来的文件md5
+ String clientMd5 = param.getString(file.getName());
+ if (clientMd5 != null) {
+ String fileMd5 = DigestUtils.md5Hex(file.getBytes());
+ if (!clientMd5.equals(fileMd5)) {
+ throw ErrorEnum.ISV_UPLOAD_FAIL.getErrorMeta().getException();
+ }
+ }
+ }
+
+ /**
+ * 校验单个文件大小
+ *
+ * @param file 文件
+ */
+ private void checkSingleFileSize(MultipartFile file) {
+ long fileSize = file.getSize();
+ if (fileSize > DataSize.parse(maxFileSize).toBytes()) {
+ throw ErrorEnum.ISV_INVALID_FILE_SIZE.getErrorMeta().getException(file.getName(), maxFileSize);
+ }
+ }
+
+ protected void checkTimeout(ApiRequest param) {
+ int timeoutSeconds = ApiContext.getApiConfig().getTimeoutSeconds();
+ // 如果设置为0,表示不校验
+ if (timeoutSeconds == 0) {
+ return;
+ }
+ if (timeoutSeconds < 0) {
+ throw new IllegalArgumentException("服务端timeoutSeconds设置错误");
+ }
+ String requestTime = param.fetchTimestamp();
+ try {
+ Date requestDate = new SimpleDateFormat(ParamNames.TIMESTAMP_PATTERN).parse(requestTime);
+ long requestMilliseconds = requestDate.getTime();
+ if (System.currentTimeMillis() - requestMilliseconds > timeoutSeconds * MILLISECOND_OF_ONE_SECOND) {
+ throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException();
+ }
+ } catch (ParseException e) {
+ throw ErrorEnum.ISV_INVALID_TIMESTAMP.getErrorMeta().getException(param.fetchNameVersion());
+ }
+ }
+
+ protected void checkAppKey(ApiRequest param) {
+ if (StringUtils.isEmpty(param.fetchAppKey())) {
+ throw ErrorEnum.ISV_MISSING_APP_ID.getErrorMeta().getException();
+ }
+ Isv isv = isvManager.getIsv(param.fetchAppKey());
+ // 没有用户
+ if (isv == null) {
+ throw ErrorEnum.ISV_INVALID_APP_ID.getErrorMeta().getException();
+ }
+ // 禁止访问
+ if (isv.getStatus() == null || isv.getStatus() == STATUS_FORBIDDEN) {
+ throw ErrorEnum.ISV_ACCESS_FORBIDDEN.getErrorMeta().getException();
+ }
+ }
+
+ protected void checkSign(ApiRequest param) {
+ String clientSign = param.getSign();
+ try {
+ if (StringUtils.isEmpty(clientSign)) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE.getErrorMeta().getException(param.takeApiName(), ParamNames.SIGN_NAME);
+ }
+ ApiConfig apiConfig = ApiContext.getApiConfig();
+ // 根据appId获取秘钥
+ Isv isvInfo = isvManager.getIsv(param.fetchAppKey());
+ String secret = isvInfo.getSecretInfo();
+ if (StringUtils.isEmpty(secret)) {
+ throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
+ }
+ Signer signer = apiConfig.getSigner();
+ // 错误的sign
+ if (!signer.checkSign(param, secret)) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion());
+ }
+ } finally {
+ // 校验过程中会移除sign,这里需要重新设置进去
+ param.setSign(clientSign);
+ }
+ }
+
+
+ protected void checkFormat(ApiRequest param) {
+ String format = param.fetchFormat();
+ boolean contains = FORMAT_LIST.contains(format.toLowerCase());
+
+ if (!contains) {
+ throw ErrorEnum.ISV_INVALID_FORMAT.getErrorMeta().getException(param.fetchNameVersion(), format);
+ }
+ }
+
+ /**
+ * 校验访问权限
+ *
+ * @param apiParam 参数
+ */
+ protected void checkPermission(ApiRequest apiParam) {
+ String routeId = apiParam.fetchNameVersion();
+ TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
+ RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
+ boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
+ if (needCheckPermission) {
+ String appKey = apiParam.fetchAppKey();
+ boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
+ if (!hasPermission) {
+ throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException();
+ }
+ }
+ }
+
+ /**
+ * 校验token
+ *
+ * @param apiParam 参数
+ */
+ protected void checkToken(ApiRequest apiParam) {
+ String routeId = apiParam.fetchNameVersion();
+ TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
+ RouteDefinition routeDefinition = targetRoute.getRouteDefinition();
+ boolean needToken = BooleanUtils.toBoolean(routeDefinition.getNeedToken());
+ if (needToken) {
+ TokenValidator tokenValidator = ApiConfig.getInstance().getTokenValidator();
+ boolean rightToken = tokenValidator.validateToken(apiParam);
+ if (!rightToken) {
+ throw ErrorEnum.AOP_INVALID_APP_AUTH_TOKEN.getErrorMeta().getException();
+ }
+ }
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/Encrypter.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Encrypter.java
new file mode 100644
index 00000000..3e5a8297
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Encrypter.java
@@ -0,0 +1,95 @@
+package com.gitee.sop.index.service.validate;
+
+/**
+ * 负责加解密
+ *
+ * @author tanghc
+ */
+public interface Encrypter {
+
+ /**
+ * AES文本加密
+ *
+ * @param content 明文
+ * @param password 密码
+ * @return 返回16进制内容
+ * @throws Exception
+ */
+ String aesEncryptToHex(String content, String password) throws Exception;
+
+ /**
+ * AES文本解密
+ *
+ * @param hex 待解密文本,16进制内容
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ String aesDecryptFromHex(String hex, String password) throws Exception;
+
+ /**
+ * AES文本加密
+ *
+ * @param content 明文
+ * @param password 密码
+ * @return 返回base64内容
+ * @throws Exception
+ */
+ String aesEncryptToBase64String(String content, String password) throws Exception;
+
+ /**
+ * AES文本解密
+ *
+ * @param base64String 待解密文本,16进制内容
+ * @param password 密码
+ * @return 返回明文
+ * @throws Exception
+ */
+ String aesDecryptFromBase64String(String base64String, String password) throws Exception;
+
+ /**
+ * RSA私钥解密
+ *
+ * @param data 解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ String rsaDecryptByPrivateKey(String data, String privateKey) throws Exception;
+
+ /**
+ * 新版rsa私钥解密
+ * @param data 解密内容
+ * @param privateKey 私钥
+ * @return 返回明文
+ * @throws Exception
+ */
+ String rsaDecryptByPrivateKeyNew(String data, String privateKey) throws Exception;
+
+ /**
+ * RSA私钥加密
+ *
+ * @param data 明文
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ String rsaEncryptByPrivateKey(String data, String privateKey) throws Exception;
+
+ /**
+ * 新版rsa私钥加密
+ * @param data 明文
+ * @param privateKey 私钥
+ * @return 返回密文
+ * @throws Exception
+ */
+ String rsaEncryptByPrivateKeyNew(String data, String privateKey) throws Exception;
+
+ /**
+ * md5加密,全部小写
+ *
+ * @param value
+ * @return 返回md5内容
+ */
+ String md5(String value);
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignConfig.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignConfig.java
new file mode 100644
index 00000000..ab8703e2
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignConfig.java
@@ -0,0 +1,38 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * @author tanghc
+ */
+public class SignConfig {
+ private static volatile Wrapper wrapper = new Wrapper() {};
+
+ public static void enableUrlencodeMode() {
+ wrapper = new Wrapper() {
+ @Override
+ public String wrapVal(Object val) {
+ String valStr = String.valueOf(val);
+ try {
+ return URLEncoder.encode(valStr, SopConstants.UTF8);
+ } catch (UnsupportedEncodingException e) {
+ return valStr;
+ }
+ }
+ };
+ }
+
+ public static String wrapVal(Object val) {
+ return wrapper.wrapVal(val);
+ }
+
+ interface Wrapper {
+ default String wrapVal(Object val) {
+ return String.valueOf(val);
+ }
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipher.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipher.java
new file mode 100644
index 00000000..acc1119f
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipher.java
@@ -0,0 +1,14 @@
+package com.gitee.sop.index.service.validate;
+
+/**
+ * @author tanghc
+ */
+public interface SignEncipher {
+ /**
+ * 签名的摘要算法
+ * @param input 待签名数据
+ * @param secret 秘钥
+ * @return 返回加密后的数据
+ */
+ byte[] encrypt(String input, String secret);
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherHMAC_MD5.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherHMAC_MD5.java
new file mode 100644
index 00000000..7c5a027f
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherHMAC_MD5.java
@@ -0,0 +1,37 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * HMAC_MD5加密
+ * @author tanghc
+ */
+@Slf4j
+public class SignEncipherHMAC_MD5 implements SignEncipher {
+
+ public static final String HMAC_MD5 = "HmacMD5";
+
+ @Override
+ public byte[] encrypt(String input, String secret) {
+ try {
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(SopConstants.CHARSET_UTF8), HMAC_MD5);
+ Mac mac = Mac.getInstance(secretKey.getAlgorithm());
+ mac.init(secretKey);
+ return mac.doFinal(input.getBytes(SopConstants.CHARSET_UTF8));
+ } catch (NoSuchAlgorithmException e) {
+ log.error("HMAC_MD5加密加密失败NoSuchAlgorithmException", e);
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+ } catch (InvalidKeyException e) {
+ log.error("HMAC_MD5加密加密失败InvalidKeyException", e);
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+ }
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherMD5.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherMD5.java
new file mode 100644
index 00000000..e7a6cd9d
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/SignEncipherMD5.java
@@ -0,0 +1,16 @@
+package com.gitee.sop.index.service.validate;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author tanghc
+ */
+public class SignEncipherMD5 implements SignEncipher {
+ @Override
+ public byte[] encrypt(String input, String secret) {
+ String source = secret + input + secret;
+ return DigestUtils.md5(source.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/Signer.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Signer.java
new file mode 100644
index 00000000..0f926da0
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Signer.java
@@ -0,0 +1,22 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.index.controller.param.ApiRequest;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 负责签名校验
+ * @author tanghc
+ *
+ */
+public interface Signer {
+
+ /**
+ * 签名校验
+ * @param apiParam 参数
+ * @param secret 秘钥
+ * @return true签名正确
+ */
+ boolean checkSign(ApiRequest apiParam, String secret);
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java
new file mode 100644
index 00000000..14186443
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/TokenValidator.java
@@ -0,0 +1,11 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+
+/**
+ * @author tanghc
+ */
+@FunctionalInterface
+public interface TokenValidator {
+ boolean validateToken(ApiParam apiParam);
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/Validator.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Validator.java
new file mode 100644
index 00000000..47aac94c
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/Validator.java
@@ -0,0 +1,18 @@
+package com.gitee.sop.index.service.validate;
+
+import com.gitee.sop.index.controller.param.ApiRequest;
+
+/**
+ * 校验接口
+ *
+ * @author tanghc
+ *
+ */
+public interface Validator {
+ /**
+ * 接口验证
+ * @param apiRequest 接口参数
+ */
+ void validate(ApiRequest apiRequest);
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipayConstants.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipayConstants.java
new file mode 100644
index 00000000..37bfc6c2
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipayConstants.java
@@ -0,0 +1,97 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.index.service.validate.alipay;
+
+/**
+ *
+ * @author runzhi
+ */
+public class AlipayConstants {
+
+ public static final String SIGN_TYPE = "sign_type";
+
+ public static final String SIGN_TYPE_RSA = "RSA";
+
+ /**
+ * sha256WithRsa 算法请求类型
+ */
+ public static final String SIGN_TYPE_RSA2 = "RSA2";
+
+ public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
+
+ public static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
+
+ public static final String ENCRYPT_TYPE_AES = "AES";
+
+ public static final String APP_ID = "app_id";
+
+ public static final String FORMAT = "format";
+
+ public static final String METHOD = "method";
+
+ public static final String TIMESTAMP = "timestamp";
+
+ public static final String VERSION = "version";
+
+ public static final String SIGN = "sign";
+
+ public static final String ALIPAY_SDK = "alipay_sdk";
+
+ public static final String ACCESS_TOKEN = "auth_token";
+
+ public static final String APP_AUTH_TOKEN = "app_auth_token";
+
+ public static final String TERMINAL_TYPE = "terminal_type";
+
+ public static final String TERMINAL_INFO = "terminal_info";
+
+ public static final String CHARSET = "charset";
+
+ public static final String NOTIFY_URL = "notify_url";
+
+ public static final String RETURN_URL = "return_url";
+
+ public static final String ENCRYPT_TYPE = "encrypt_type";
+
+ //-----===-------///
+
+ public static final String BIZ_CONTENT_KEY = "biz_content";
+
+ /** 默认时间格式 **/
+ public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ /** Date默认时区 **/
+ public static final String DATE_TIMEZONE = "GMT+8";
+
+ /** UTF-8字符集 **/
+ public static final String CHARSET_UTF8 = "UTF-8";
+
+ /** GBK字符集 **/
+ public static final String CHARSET_GBK = "GBK";
+
+ /** JSON 应格式 */
+ public static final String FORMAT_JSON = "json";
+
+ /** XML 应格式 */
+ public static final String FORMAT_XML = "xml";
+
+ /** SDK版本号 */
+ public static final String SDK_VERSION = "alipay-sdk-java-3.6.0.ALL";
+
+ public static final String PROD_CODE = "prod_code";
+
+ /** 老版本失败节点 */
+ public static final String ERROR_RESPONSE = "error_response";
+
+ /** 新版本节点后缀 */
+ public static final String RESPONSE_SUFFIX = "_response";
+
+ /** 加密后XML返回报文的节点名字 */
+ public static final String RESPONSE_XML_ENCRYPT_NODE_NAME = "response_encrypted";
+
+ /** 批量请求id **/
+ public static final String BATCH_REQUEST_ID = "batch_request_id";
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java
new file mode 100644
index 00000000..97aeba55
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySignature.java
@@ -0,0 +1,618 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.index.service.validate.alipay;
+
+
+import com.gitee.sop.index.message.ErrorEnum;
+import com.gitee.sop.index.service.validate.SignConfig;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author runzhi
+ */
+public class AlipaySignature {
+
+ /**
+ * RSA最大加密明文大小
+ */
+ private static final int MAX_ENCRYPT_BLOCK = 117;
+
+ /**
+ * RSA最大解密密文大小
+ */
+ private static final int MAX_DECRYPT_BLOCK = 128;
+
+
+ /**
+ * @param sortedParams
+ * @return
+ */
+ public static String getSignContent(Map sortedParams) {
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(sortedParams.keySet());
+ Collections.sort(keys);
+ int index = 0;
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = String.valueOf(sortedParams.get(key));
+ if (StringUtils.areNotEmpty(key, value)) {
+ content.append((index == 0 ? "" : "&") + key + "=" + value);
+ index++;
+ }
+ }
+ return content.toString();
+ }
+
+ /**
+ * rsa内容签名
+ *
+ * @param content
+ * @param publicKey
+ * @param charset
+ * @return
+ */
+ public static String rsaSign(String content, String publicKey, String charset,
+ String signType) {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaSign(content, publicKey, charset);
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256Sign(content, publicKey, charset);
+ } else {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
+// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ /**
+ * sha256WithRsa 加签
+ *
+ * @param content
+ * @param privateKey
+ * @param charset
+ * @return
+ */
+ public static String rsa256Sign(String content, String privateKey,
+ String charset) {
+
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(privateKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+
+ }
+
+ /**
+ * sha1WithRsa 加签
+ *
+ * @param content
+ * @param publicKey
+ * @param charset
+ * @return
+ */
+ public static String rsaSign(String content, String publicKey,
+ String charset) {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initSign(priKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ byte[] signed = signature.sign();
+
+ return new String(Base64.encodeBase64(signed));
+ } catch (InvalidKeySpecException ie) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(ie);
+// throw new AlipayApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("RSAcontent = " + content + "; charset = " + charset, e);
+ }
+ }
+
+ public static String rsaSign(Map params, String publicKey,
+ String charset, String signType) {
+ String signContent = getSignContent(params);
+
+ return rsaSign(signContent, publicKey, charset, signType);
+
+ }
+
+ public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
+ InputStream ins) throws Exception {
+ if (ins == null || StringUtils.isEmpty(algorithm)) {
+ return null;
+ }
+
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ byte[] encodedKey = StreamUtil.readText(ins, "UTF-8").getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
+ }
+
+ public static String getSignCheckContentV1(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+ params.remove("sign_type");
+
+ StringBuilder content = new StringBuilder();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = SignConfig.wrapVal(params.get(key));
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static String getSignCheckContentV2(Map params) {
+ if (params == null) {
+ return null;
+ }
+
+ params.remove("sign");
+
+ StringBuilder content = new StringBuilder();
+ List keys = new ArrayList(params.keySet());
+ Collections.sort(keys);
+
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ String value = SignConfig.wrapVal(params.get(key));
+ content.append((i == 0 ? "" : "&") + key + "=" + value);
+ }
+
+ return content.toString();
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV1(Map params, String publicKey,
+ String charset, String signType) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV1(params);
+
+ return rsaCheck(content, sign, publicKey, charset, signType);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset) {
+ String sign = params.get("sign");
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+ }
+
+ public static boolean rsaCheckV2(Map params, String publicKey,
+ String charset, String signType) {
+ String sign = String.valueOf(params.get("sign"));
+ String content = getSignCheckContentV2(params);
+
+ return rsaCheck(content, sign, publicKey, charset, signType);
+ }
+
+ public static boolean rsaCheck(String content, String sign, String publicKey, String charset,
+ String signType) {
+
+ if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
+
+ return rsaCheckContent(content, sign, publicKey, charset);
+
+ } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
+
+ return rsa256CheckContent(content, sign, publicKey, charset);
+
+ } else {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException();
+// throw new AlipayApiException("Sign Type is Not Support : signType=" + signType);
+ }
+
+ }
+
+ public static boolean rsa256CheckContent(String content, String sign, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException(
+// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static boolean rsaCheckContent(String content, String sign, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509("RSA",
+ new ByteArrayInputStream(publicKey.getBytes()));
+
+ java.security.Signature signature = java.security.Signature
+ .getInstance(AlipayConstants.SIGN_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+
+ if (StringUtils.isEmpty(charset)) {
+ signature.update(content.getBytes());
+ } else {
+ signature.update(content.getBytes(charset));
+ }
+
+ return signature.verify(Base64.decodeBase64(sign.getBytes()));
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException(
+// "RSAcontent = " + content + ",sign=" + sign + ",charset = " + charset, e);
+ }
+ }
+
+ public static PublicKey getPublicKeyFromX509(String algorithm,
+ InputStream ins) throws Exception {
+ KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
+
+ StringWriter writer = new StringWriter();
+ StreamUtil.io(new InputStreamReader(ins), writer);
+
+ byte[] encodedKey = writer.toString().getBytes();
+
+ encodedKey = Base64.decodeBase64(encodedKey);
+
+ return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ *
{
+ *
biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ *
sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ *
charset=UTF-8
+ *
}
+ *
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt) {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset)) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 验签并解密
+ *
+ * 目前适用于公众号
+ * params参数示例:
+ *
{
+ *
biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
+ *
sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
+ * sign_type=RSA,
+ *
charset=UTF-8
+ *
}
+ *
+ *
+ * @param params
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param isCheckSign 是否验签
+ * @param isDecrypt 是否解密
+ * @return 解密后明文,验签失败则异常抛出
+ */
+ public static String checkSignAndDecrypt(Map params, String alipayPublicKey,
+ String cusPrivateKey, boolean isCheckSign,
+ boolean isDecrypt, String signType) {
+ String charset = params.get("charset");
+ String bizContent = params.get("biz_content");
+ if (isCheckSign) {
+ if (!rsaCheckV2(params, alipayPublicKey, charset, signType)) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException();
+// throw new AlipayApiException("rsaCheck failure:rsaParams=" + params);
+ }
+ }
+
+ if (isDecrypt) {
+ return rsaDecrypt(bizContent, cusPrivateKey, charset);
+ }
+
+ return bizContent;
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ *
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign) {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + "");
+ sb.append("RSA");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset);
+ sb.append("" + sign + "");
+ sb.append("RSA");
+ }
+ sb.append("");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + "");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset);
+ sb.append("" + sign + "");
+ sb.append("RSA");
+ sb.append("");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 加密并签名
+ * 目前适用于公众号
+ *
+ * @param bizContent 待加密、签名内容
+ * @param alipayPublicKey 支付宝公钥
+ * @param cusPrivateKey 商户私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @param isEncrypt 是否加密,true-加密 false-不加密
+ * @param isSign 是否签名,true-签名 false-不签名
+ * @return 加密、签名后xml内容字符串
+ *
+ * 返回示例:
+ *
+ * 密文
+ * RSA
+ * sign
+ * RSA
+ *
+ *
+ */
+ public static String encryptAndSign(String bizContent, String alipayPublicKey,
+ String cusPrivateKey, String charset, boolean isEncrypt,
+ boolean isSign, String signType) {
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isEmpty(charset)) {
+ charset = AlipayConstants.CHARSET_GBK;
+ }
+ sb.append("");
+ if (isEncrypt) {// 加密
+ sb.append("");
+ String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
+ sb.append("" + encrypted + "");
+ sb.append("RSA");
+ if (isSign) {
+ String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
+ sb.append("" + sign + "");
+ sb.append("");
+ sb.append(signType);
+ sb.append("");
+ }
+ sb.append("");
+ } else if (isSign) {// 不加密,但需要签名
+ sb.append("");
+ sb.append("" + bizContent + "");
+ String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
+ sb.append("" + sign + "");
+ sb.append("");
+ sb.append(signType);
+ sb.append("");
+ sb.append("");
+ } else {// 不加密,不加签
+ sb.append(bizContent);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 公钥加密
+ *
+ * @param content 待加密内容
+ * @param publicKey 公钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 密文内容
+ */
+ public static String rsaEncrypt(String content, String publicKey,
+ String charset) {
+ try {
+ PublicKey pubKey = getPublicKeyFromX509(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.ENCRYPT_MODE, pubKey);
+ byte[] data = StringUtils.isEmpty(charset) ? content.getBytes()
+ : content.getBytes(charset);
+ int inputLen = data.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段加密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
+ cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(data, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_ENCRYPT_BLOCK;
+ }
+ byte[] encryptedData = Base64.encodeBase64(out.toByteArray());
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(encryptedData)
+ : new String(encryptedData, charset);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("EncryptContent = " + content + ",charset = " + charset,
+// e);
+ }
+ }
+
+ /**
+ * 私钥解密
+ *
+ * @param content 待解密内容
+ * @param publicKey 私钥
+ * @param charset 字符集,如UTF-8, GBK, GB2312
+ * @return 明文内容
+ */
+ public static String rsaDecrypt(String content, String publicKey,
+ String charset) {
+ try {
+ PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
+ new ByteArrayInputStream(publicKey.getBytes()));
+ Cipher cipher = Cipher.getInstance(AlipayConstants.SIGN_TYPE_RSA);
+ cipher.init(Cipher.DECRYPT_MODE, priKey);
+ byte[] encryptedData = StringUtils.isEmpty(charset)
+ ? Base64.decodeBase64(content.getBytes())
+ : Base64.decodeBase64(content.getBytes(charset));
+ int inputLen = encryptedData.length;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int offSet = 0;
+ byte[] cache;
+ int i = 0;
+ // 对数据分段解密
+ while (inputLen - offSet > 0) {
+ if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
+ cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
+ } else {
+ cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
+ }
+ out.write(cache, 0, cache.length);
+ i++;
+ offSet = i * MAX_DECRYPT_BLOCK;
+ }
+ byte[] decryptedData = out.toByteArray();
+ out.close();
+
+ return StringUtils.isEmpty(charset) ? new String(decryptedData)
+ : new String(decryptedData, charset);
+ } catch (Exception e) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(e);
+// throw new AlipayApiException("EncodeContent = " + content + ",charset = " + charset, e);
+ }
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java
new file mode 100644
index 00000000..64fe7ccc
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/AlipaySigner.java
@@ -0,0 +1,35 @@
+package com.gitee.sop.index.service.validate.alipay;
+
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.gitee.sop.index.controller.param.ApiRequest;
+import com.gitee.sop.index.message.ErrorEnum;
+import com.gitee.sop.index.service.validate.Signer;
+
+/**
+ * 支付宝签名验证实现。
+ *
+ * @author tanghc
+ * @see 支付宝签名
+ */
+public class AlipaySigner implements Signer {
+
+ @Override
+ public boolean checkSign(ApiRequest apiParam, String secret) {
+ // 服务端存的是公钥
+ String publicKey = secret;
+ String charset = apiParam.getCharset();
+ String signType = apiParam.getSign_type();
+ if (signType == null) {
+ throw ErrorEnum.ISV_DECRYPTION_ERROR_MISSING_ENCRYPT_TYPE.getErrorMeta().getException();
+ }
+ if (charset == null) {
+ throw ErrorEnum.ISV_INVALID_CHARSET.getErrorMeta().getException();
+ }
+ String json = JSON.toJSONString(apiParam);
+ JSONObject params = JSON.parseObject(json);
+ return AlipaySignature.rsaCheckV2(params, publicKey, charset, signType);
+ }
+
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StreamUtil.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StreamUtil.java
new file mode 100644
index 00000000..ab0de39d
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StreamUtil.java
@@ -0,0 +1,141 @@
+/**
+ * Alipay.com Inc.
+ * Copyright (c) 2004-2012 All Rights Reserved.
+ */
+package com.gitee.sop.index.service.validate.alipay;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ *
+ * @author runzhi
+ */
+public class StreamUtil {
+ private StreamUtil(){}
+
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ public static void io(InputStream in, OutputStream out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(InputStream in, OutputStream out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE;
+ }
+
+ byte[] buffer = new byte[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static void io(Reader in, Writer out) throws IOException {
+ io(in, out, -1);
+ }
+
+ public static void io(Reader in, Writer out, int bufferSize) throws IOException {
+ if (bufferSize == -1) {
+ bufferSize = DEFAULT_BUFFER_SIZE >> 1;
+ }
+
+ char[] buffer = new char[bufferSize];
+ int amount;
+
+ while ((amount = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, amount);
+ }
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out) {
+ return new SynchronizedOutputStream(out);
+ }
+
+ public static OutputStream synchronizedOutputStream(OutputStream out, Object lock) {
+ return new SynchronizedOutputStream(out, lock);
+ }
+
+ public static String readText(InputStream in) throws IOException {
+ return readText(in, null, -1);
+ }
+
+ public static String readText(InputStream in, String encoding) throws IOException {
+ return readText(in, encoding, -1);
+ }
+
+ public static String readText(InputStream in, String encoding, int bufferSize)
+ throws IOException {
+ Reader reader = (encoding == null) ? new InputStreamReader(in) : new InputStreamReader(in,
+ encoding);
+
+ return readText(reader, bufferSize);
+ }
+
+ public static String readText(Reader reader) throws IOException {
+ return readText(reader, -1);
+ }
+
+ public static String readText(Reader reader, int bufferSize) throws IOException {
+ StringWriter writer = new StringWriter();
+
+ io(reader, writer, bufferSize);
+ return writer.toString();
+ }
+
+ private static class SynchronizedOutputStream extends OutputStream {
+ private OutputStream out;
+ private Object lock;
+
+ SynchronizedOutputStream(OutputStream out) {
+ this(out, out);
+ }
+
+ SynchronizedOutputStream(OutputStream out, Object lock) {
+ this.out = out;
+ this.lock = lock;
+ }
+
+ @Override
+ public void write(int datum) throws IOException {
+ synchronized (lock) {
+ out.write(datum);
+ }
+ }
+
+ @Override
+ public void write(byte[] data) throws IOException {
+ synchronized (lock) {
+ out.write(data);
+ }
+ }
+
+ @Override
+ public void write(byte[] data, int offset, int length) throws IOException {
+ synchronized (lock) {
+ out.write(data, offset, length);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ synchronized (lock) {
+ out.flush();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ synchronized (lock) {
+ out.close();
+ }
+ }
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StringUtils.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StringUtils.java
new file mode 100644
index 00000000..e1be5b0b
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/alipay/StringUtils.java
@@ -0,0 +1,171 @@
+package com.gitee.sop.index.service.validate.alipay;
+
+/**
+ * 字符串工具类。
+ *
+ * @author carver.gu
+ * @since 1.0, Sep 12, 2009
+ */
+public class StringUtils {
+
+ private StringUtils() {}
+
+ /**
+ * 检查指定的字符串是否为空。
+ *
+ * - SysUtils.isEmpty(null) = true
+ * - SysUtils.isEmpty("") = true
+ * - SysUtils.isEmpty(" ") = true
+ * - SysUtils.isEmpty("abc") = false
+ *
+ *
+ * @param value 待检查的字符串
+ * @return true/false
+ */
+ public static boolean isEmpty(String value) {
+ int strLen;
+ if (value == null || (strLen = value.length()) == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if ((Character.isWhitespace(value.charAt(i)) == false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查对象是否为数字型字符串,包含负数开头的。
+ */
+ public static boolean isNumeric(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ char[] chars = obj.toString().toCharArray();
+ int length = chars.length;
+ if(length < 1) {
+ return false;
+ }
+
+ int i = 0;
+ if(length > 1 && chars[0] == '-') {
+ i = 1;
+ }
+
+ for (; i < length; i++) {
+ if (!Character.isDigit(chars[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * 检查指定的字符串列表是否不为空。
+ */
+ public static boolean areNotEmpty(String... values) {
+ boolean result = true;
+ if (values == null || values.length == 0) {
+ result = false;
+ } else {
+ for (String value : values) {
+ result &= !isEmpty(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 把通用字符编码的字符串转化为汉字编码。
+ */
+ public static String unicodeToChinese(String unicode) {
+ StringBuilder out = new StringBuilder();
+ if (!isEmpty(unicode)) {
+ for (int i = 0; i < unicode.length(); i++) {
+ out.append(unicode.charAt(i));
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * 过滤不可见字符
+ */
+ public static String stripNonValidXMLCharacters(String input) {
+ if (input == null || ("".equals(input))) {
+ return "";
+ }
+ StringBuilder out = new StringBuilder();
+ char current;
+ for (int i = 0; i < input.length(); i++) {
+ current = input.charAt(i);
+ if ((current == 0x9) || (current == 0xA) || (current == 0xD)
+ || ((current >= 0x20) && (current <= 0xD7FF))
+ || ((current >= 0xE000) && (current <= 0xFFFD))
+ || ((current >= 0x10000) && (current <= 0x10FFFF))) {
+ out.append(current);
+ }
+ }
+ return out.toString();
+ }
+
+ public static String leftPad(String str, int size, char padChar) {
+ if (str == null) {
+ return null;
+ } else {
+ int pads = size - str.length();
+ if (pads <= 0) {
+ return str;
+ } else {
+ return pads > 8192 ? leftPad(str, size, String.valueOf(padChar)) : padding(pads, padChar).concat(str);
+ }
+ }
+ }
+
+ public static String leftPad(String str, int size, String padStr) {
+ if (str == null) {
+ return null;
+ } else {
+ if (isEmpty(padStr)) {
+ padStr = " ";
+ }
+
+ int padLen = padStr.length();
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str;
+ } else if (padLen == 1 && pads <= 8192) {
+ return leftPad(str, size, padStr.charAt(0));
+ } else if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ char[] padding = new char[pads];
+ char[] padChars = padStr.toCharArray();
+
+ for(int i = 0; i < pads; ++i) {
+ padding[i] = padChars[i % padLen];
+ }
+
+ return (new String(padding)).concat(str);
+ }
+ }
+ }
+
+ private static String padding(int repeat, char padChar) {
+ if (repeat < 0) {
+ throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat);
+ } else {
+ char[] buf = new char[repeat];
+
+ for(int i = 0; i < buf.length; ++i) {
+ buf[i] = padChar;
+ }
+
+ return new String(buf);
+ }
+ }
+}
diff --git a/sop-index/src/main/java/com/gitee/sop/index/service/validate/taobao/TaobaoSigner.java b/sop-index/src/main/java/com/gitee/sop/index/service/validate/taobao/TaobaoSigner.java
new file mode 100644
index 00000000..2de3536e
--- /dev/null
+++ b/sop-index/src/main/java/com/gitee/sop/index/service/validate/taobao/TaobaoSigner.java
@@ -0,0 +1,66 @@
+package com.gitee.sop.index.service.validate.taobao;
+
+
+import com.gitee.sop.gatewaycommon.bean.SopConstants;
+import com.gitee.sop.gatewaycommon.message.ErrorEnum;
+import com.gitee.sop.gatewaycommon.param.ApiParam;
+import com.gitee.sop.gatewaycommon.validate.AbstractSigner;
+import com.gitee.sop.gatewaycommon.validate.SignConfig;
+import com.gitee.sop.gatewaycommon.validate.SignEncipher;
+import com.gitee.sop.gatewaycommon.validate.SignEncipherHMAC_MD5;
+import com.gitee.sop.gatewaycommon.validate.SignEncipherMD5;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 淘宝开放平台签名验证实现,http://open.taobao.com/doc.htm?docId=101617&docType=1
+ *
+ * @author tanghc
+ */
+public class TaobaoSigner extends AbstractSigner {
+
+ private Map signEncipherMap = new HashMap<>();
+
+ public TaobaoSigner() {
+ signEncipherMap.put("md5", new SignEncipherMD5());
+ signEncipherMap.put("hmac", new SignEncipherHMAC_MD5());
+ }
+
+
+ @Override
+ public String buildServerSign(ApiParam param, String secret) {
+ String signMethod = param.fetchSignMethod();
+ if (signMethod == null) {
+ signMethod = SopConstants.DEFAULT_SIGN_METHOD;
+ }
+ SignEncipher signEncipher = signEncipherMap.get(signMethod);
+ if (signEncipher == null) {
+ throw ErrorEnum.ISV_INVALID_SIGNATURE_TYPE.getErrorMeta().getException(signMethod);
+ }
+
+ // 第一步:参数排序
+ Set keySet = param.keySet();
+ List paramNames = new ArrayList<>(keySet);
+ Collections.sort(paramNames);
+
+ // 第二步:把所有参数名和参数值串在一起
+ StringBuilder paramNameValue = new StringBuilder();
+ for (String paramName : paramNames) {
+ String val = SignConfig.wrapVal(param.get(paramName));
+ paramNameValue.append(paramName).append(val);
+ }
+
+ // 第三步:使用MD5/HMAC加密
+ String source = paramNameValue.toString();
+ byte[] bytes = signEncipher.encrypt(source, secret);
+
+ // 第四步:把二进制转化为大写的十六进制
+ return byte2hex(bytes).toUpperCase();
+ }
+}
diff --git a/sop-index/src/main/resources/application.properties b/sop-index/src/main/resources/application.properties
new file mode 100644
index 00000000..8284e9f4
--- /dev/null
+++ b/sop-index/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=sop-index
diff --git a/sop-sdk/pom.xml b/sop-sdk/pom.xml
index 1861a415..9e16d90c 100644
--- a/sop-sdk/pom.xml
+++ b/sop-sdk/pom.xml
@@ -6,7 +6,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
@@ -25,4 +25,4 @@
sdk-java
-
\ No newline at end of file
+
diff --git a/sop-sdk/sdk-java/pom.xml b/sop-sdk/sdk-java/pom.xml
index 61aa2527..7d5e41ec 100644
--- a/sop-sdk/sdk-java/pom.xml
+++ b/sop-sdk/sdk-java/pom.xml
@@ -4,13 +4,13 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../../pom.xml
4.0.0
sdk-java
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
diff --git a/sop-test/pom.xml b/sop-test/pom.xml
index 1a9d3b11..4acacd17 100644
--- a/sop-test/pom.xml
+++ b/sop-test/pom.xml
@@ -5,7 +5,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
diff --git a/sop-website/pom.xml b/sop-website/pom.xml
index 58532e05..8c357f15 100644
--- a/sop-website/pom.xml
+++ b/sop-website/pom.xml
@@ -6,7 +6,7 @@
com.gitee.sop
sop-parent
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
@@ -17,4 +17,4 @@
sop-website-server
-
\ No newline at end of file
+
diff --git a/sop-website/sop-website-server/pom.xml b/sop-website/sop-website-server/pom.xml
index 170e9031..ede437ab 100644
--- a/sop-website/sop-website-server/pom.xml
+++ b/sop-website/sop-website-server/pom.xml
@@ -5,7 +5,7 @@
com.gitee.sop
sop-website
- 4.4.2-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml