This commit is contained in:
六如
2024-09-04 09:55:29 +08:00
parent 19022bdbe1
commit 7f6b3a4309
54 changed files with 2793 additions and 43 deletions

View File

@@ -6,7 +6,7 @@
```xml ```xml
<!-- springboot 版本--> <!-- springboot 版本-->
<spring-boot.version>2.3.2.RELEASE</spring-boot.version> <spring-boot.version>3.0.2</spring-boot.version>
<!-- spring cloud 版本 --> <!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 版本 --> <!-- spring cloud alibaba 版本 -->

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>doc</artifactId> <artifactId>doc</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<properties> <properties>
<!-- Generic properties --> <!-- Generic properties -->

25
pom.xml
View File

@@ -5,13 +5,13 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version> <version>2.6.13</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<description>一个开放平台解决方案项目基于Spring Cloud实现目标是能够让用户快速得搭建起自己的开放平台</description> <description>一个开放平台解决方案项目基于Spring Cloud实现目标是能够让用户快速得搭建起自己的开放平台</description>
@@ -25,6 +25,7 @@
<module>sop-test</module> <module>sop-test</module>
<module>sop-sdk</module> <module>sop-sdk</module>
<module>sop-website</module> <module>sop-website</module>
<module>sop-index</module>
</modules> </modules>
<properties> <properties>
@@ -35,21 +36,19 @@
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<!-- springboot 版本--> <!-- springboot 版本-->
<spring-boot.version>2.3.2.RELEASE</spring-boot.version> <spring-boot.version>2.6.13</spring-boot.version>
<!-- spring cloud 版本 --> <!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version> <spring-cloud.version>2021.0.5</spring-cloud.version>
<!-- spring cloud alibaba 版本 --> <!-- spring cloud alibaba 版本 -->
<!-- 具体版本对应关系见https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E --> <!-- 具体版本对应关系见https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E -->
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<!-- dubbo版本 -->
<dubbo.version>3.2.10</dubbo.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version> <logback.version>1.2.3</logback.version>
<!-- Test -->
<junit.version>4.11</junit.version> <junit.version>4.11</junit.version>
<fastjson.version>1.2.83</fastjson.version>
<fastjson.version>1.2.73</fastjson.version>
<commons-io.version>2.5</commons-io.version> <commons-io.version>2.5</commons-io.version>
<commons-fileupload.version>1.3.3</commons-fileupload.version> <commons-fileupload.version>1.3.3</commons-fileupload.version>
<commons-collection.version>3.2.2</commons-collection.version> <commons-collection.version>3.2.2</commons-collection.version>
@@ -66,6 +65,7 @@
<easyopen.version>1.16.9</easyopen.version> <easyopen.version>1.16.9</easyopen.version>
<asm.version>6.2</asm.version> <asm.version>6.2</asm.version>
<pagehelper.version>5.2.0</pagehelper.version> <pagehelper.version>5.2.0</pagehelper.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -92,6 +92,11 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -5,13 +5,12 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>sop-admin-server</artifactId> <artifactId>sop-admin-server</artifactId>
<version>4.4.2-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
@@ -38,7 +38,7 @@
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sdk-java</artifactId> <artifactId>sdk-java</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
</dependency> </dependency>
<!-- http请求 --> <!-- http请求 -->
<dependency> <dependency>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId> <artifactId>sop-common</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -5,13 +5,13 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-bridge-nacos</artifactId> <artifactId>sop-bridge-nacos</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-gateway-common</artifactId> <artifactId>sop-gateway-common</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-common</artifactId> <artifactId>sop-common</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -5,14 +5,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-service-common</artifactId> <artifactId>sop-service-common</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<!-- springboot 版本--> <!-- springboot 版本-->
<spring-boot.version>2.3.2.RELEASE</spring-boot.version> <spring-boot.version>3.0.2</spring-boot.version>
<!-- spring cloud 版本 --> <!-- spring cloud 版本 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- spring cloud alibaba 版本 --> <!-- spring cloud alibaba 版本 -->

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

68
sop-index/pom.xml Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>sop-index</artifactId>
<version>5.0.0-SNAPSHOT</version>
<name>sop-index</name>
<description>sop-index</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

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

View File

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

View File

@@ -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;
/**
* 请求参数名定义
* <pre>
* 参数 类型 是否必填 最大长度 描述 示例值
* 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 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
*</pre>
* @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;
}
}

View File

@@ -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;
/**
* 默认的结果封装类.
* <pre>
*
* xml返回结果:
* <response>
* <code>50</code>
* <msg>Remote service error</msg>
* <sub_code>isv.invalid-parameter</sub_code>
* <sub_msg>非法参数</sub_msg>
* </response>
* 成功情况:
* <response>
* <code>0</code>
* <msg>成功消息</msg>
* <data>
* ...返回内容
* </data>
* </response>
*
* json返回格式
* {
* "code":"50",
* "msg":"Remote service error",
* "sub_code":"isv.invalid-parameter",
* "sub_msg":"非法参数"
* }
* 成功情况:
* {
* "code":"0",
* "msg":"成功消息内容。。。",
* "data":{
* ...返回内容
* }
* }
* </pre>
* <p>
* 字段说明:
* code:网关异常码 <br>
* msg:网关异常信息 <br>
* sub_code:业务异常码 <br>
* sub_msg:业务异常信息 <br>
*
* @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;
}

View File

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

View File

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

View File

@@ -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<String> noModuleCache = new HashSet<>();
private static Map<String, IError> errorCache = new HashMap<>(64);
private static List<Locale> localeList = Arrays.asList(Locale.ENGLISH, Locale.SIMPLIFIED_CHINESE);
/**
* 错误信息的国际化信息
*/
private static MessageSourceAccessor errorMessageSourceAccessor;
/**
* 设置国际化资源信息
*/
public static void initMessageSource(List<String> isvModules) {
HashSet<String> 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);
}
/**
* 通过ErrorMetaLocaleparams构建国际化错误消息
*
* @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
// <code>String solution = getErrorMessage(modulePrefix + code + UNDERLINE + subCode + "_solution", locale, params);</code>
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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, SignEncipher> 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<String> keySet = param.keySet();
List<String> 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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<String, Object> sortedParams) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(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<String, Object> 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<String, String> params) {
if (params == null) {
return null;
}
params.remove("sign");
params.remove("sign_type");
StringBuilder content = new StringBuilder();
List<String> keys = new ArrayList<String>(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<String, ?> params) {
if (params == null) {
return null;
}
params.remove("sign");
StringBuilder content = new StringBuilder();
List<String> keys = new ArrayList<String>(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<String, String> 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<String, String> 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<String, String> 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<String, ?> 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));
}
/**
* 验签并解密
* <p>
* <b>目前适用于公众号</b><br>
* params参数示例
* <br>{
* <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
* <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
* sign_type=RSA,
* <br>charset=UTF-8
* <br>}
* </p>
*
* @param params
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param isCheckSign 是否验签
* @param isDecrypt 是否解密
* @return 解密后明文,验签失败则异常抛出
*/
public static String checkSignAndDecrypt(Map<String, String> 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;
}
/**
* 验签并解密
* <p>
* <b>目前适用于公众号</b><br>
* params参数示例
* <br>{
* <br>biz_content=M0qGiGz+8kIpxe8aF4geWJdBn0aBTuJRQItLHo9R7o5JGhpic/MIUjvXo2BLB++BbkSq2OsJCEQFDZ0zK5AJYwvBgeRX30gvEj6eXqXRt16/IkB9HzAccEqKmRHrZJ7PjQWE0KfvDAHsJqFIeMvEYk1Zei2QkwSQPlso7K0oheo/iT+HYE8aTATnkqD/ByD9iNDtGg38pCa2xnnns63abKsKoV8h0DfHWgPH62urGY7Pye3r9FCOXA2Ykm8X4/Bl1bWFN/PFCEJHWe/HXj8KJKjWMO6ttsoV0xRGfeyUO8agu6t587Dl5ux5zD/s8Lbg5QXygaOwo3Fz1G8EqmGhi4+soEIQb8DBYanQOS3X+m46tVqBGMw8Oe+hsyIMpsjwF4HaPKMr37zpW3fe7xOMuimbZ0wq53YP/jhQv6XWodjT3mL0H5ACqcsSn727B5ztquzCPiwrqyjUHjJQQefFTzOse8snaWNQTUsQS7aLsHq0FveGpSBYORyA90qPdiTjXIkVP7mAiYiAIWW9pCEC7F3XtViKTZ8FRMM9ySicfuAlf3jtap6v2KPMtQv70X+hlmzO/IXB6W0Ep8DovkF5rB4r/BJYJLw/6AS0LZM9w5JfnAZhfGM2rKzpfNsgpOgEZS1WleG4I2hoQC0nxg9IcP0Hs+nWIPkEUcYNaiXqeBc=,
* <br>sign=rlqgA8O+RzHBVYLyHmrbODVSANWPXf3pSrr82OCO/bm3upZiXSYrX5fZr6UBmG6BZRAydEyTIguEW6VRuAKjnaO/sOiR9BsSrOdXbD5Rhos/Xt7/mGUWbTOt/F+3W0/XLuDNmuYg1yIC/6hzkg44kgtdSTsQbOC9gWM7ayB4J4c=,
* sign_type=RSA,
* <br>charset=UTF-8
* <br>}
* </p>
*
* @param params
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param isCheckSign 是否验签
* @param isDecrypt 是否解密
* @return 解密后明文,验签失败则异常抛出
*/
public static String checkSignAndDecrypt(Map<String, String> 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;
}
/**
* 加密并签名<br>
* <b>目前适用于公众号</b>
*
* @param bizContent 待加密、签名内容
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param charset 字符集如UTF-8, GBK, GB2312
* @param isEncrypt 是否加密true-加密 false-不加密
* @param isSign 是否签名true-签名 false-不签名
* @return 加密、签名后xml内容字符串
* <p>
* 返回示例:
* <alipay>
* <response>密文</response>
* <encryption_type>RSA</encryption_type>
* <sign>sign</sign>
* <sign_type>RSA</sign_type>
* </alipay>
* </p>
*/
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("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
if (isEncrypt) {// 加密
sb.append("<alipay>");
String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
sb.append("<response>" + encrypted + "</response>");
sb.append("<encryption_type>RSA</encryption_type>");
if (isSign) {
String sign = rsaSign(encrypted, cusPrivateKey, charset);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>RSA</sign_type>");
}
sb.append("</alipay>");
} else if (isSign) {// 不加密,但需要签名
sb.append("<alipay>");
sb.append("<response>" + bizContent + "</response>");
String sign = rsaSign(bizContent, cusPrivateKey, charset);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>RSA</sign_type>");
sb.append("</alipay>");
} else {// 不加密,不加签
sb.append(bizContent);
}
return sb.toString();
}
/**
* 加密并签名<br>
* <b>目前适用于公众号</b>
*
* @param bizContent 待加密、签名内容
* @param alipayPublicKey 支付宝公钥
* @param cusPrivateKey 商户私钥
* @param charset 字符集如UTF-8, GBK, GB2312
* @param isEncrypt 是否加密true-加密 false-不加密
* @param isSign 是否签名true-签名 false-不签名
* @return 加密、签名后xml内容字符串
* <p>
* 返回示例:
* <alipay>
* <response>密文</response>
* <encryption_type>RSA</encryption_type>
* <sign>sign</sign>
* <sign_type>RSA</sign_type>
* </alipay>
* </p>
*/
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("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
if (isEncrypt) {// 加密
sb.append("<alipay>");
String encrypted = rsaEncrypt(bizContent, alipayPublicKey, charset);
sb.append("<response>" + encrypted + "</response>");
sb.append("<encryption_type>RSA</encryption_type>");
if (isSign) {
String sign = rsaSign(encrypted, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
}
sb.append("</alipay>");
} else if (isSign) {// 不加密,但需要签名
sb.append("<alipay>");
sb.append("<response>" + bizContent + "</response>");
String sign = rsaSign(bizContent, cusPrivateKey, charset, signType);
sb.append("<sign>" + sign + "</sign>");
sb.append("<sign_type>");
sb.append(signType);
sb.append("</sign_type>");
sb.append("</alipay>");
} 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);
}
}
}

View File

@@ -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 <a href="https://docs.open.alipay.com/291/106118">支付宝签名</a>
*/
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);
}
}

View File

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

View File

@@ -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() {}
/**
* 检查指定的字符串是否为空。
* <ul>
* <li>SysUtils.isEmpty(null) = true</li>
* <li>SysUtils.isEmpty("") = true</li>
* <li>SysUtils.isEmpty(" ") = true</li>
* <li>SysUtils.isEmpty("abc") = false</li>
* </ul>
*
* @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);
}
}
}

View File

@@ -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<String, SignEncipher> 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<String> keySet = param.keySet();
List<String> 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();
}
}

View File

@@ -0,0 +1 @@
spring.application.name=sop-index

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>sdk-java</artifactId> <artifactId>sdk-java</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<properties> <properties>
<!-- Generic properties --> <!-- Generic properties -->

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-parent</artifactId> <artifactId>sop-parent</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.gitee.sop</groupId> <groupId>com.gitee.sop</groupId>
<artifactId>sop-website</artifactId> <artifactId>sop-website</artifactId>
<version>4.4.2-SNAPSHOT</version> <version>5.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository --> <relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
</parent> </parent>