mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
5.0
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
```xml
|
||||
<!-- springboot 版本-->
|
||||
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
|
||||
<spring-boot.version>3.0.2</spring-boot.version>
|
||||
<!-- spring cloud 版本 -->
|
||||
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
|
||||
<!-- spring cloud alibaba 版本 -->
|
||||
@@ -214,4 +214,4 @@ instance.getMetadata().put("sop.routes.path", "http://open.xxx.com/get_routes");
|
||||
namingService.registerInstance(serviceId, instance);
|
||||
```
|
||||
|
||||
完成以上步骤后,php服务注册到nacos,网关会触发监听事件,获取新注册的服务,然后会向你的服务拉取路由配置。
|
||||
完成以上步骤后,php服务注册到nacos,网关会触发监听事件,获取新注册的服务,然后会向你的服务拉取路由配置。
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>doc</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<!-- Generic properties -->
|
||||
|
25
pom.xml
25
pom.xml
@@ -5,13 +5,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<version>2.6.13</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<description>一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台</description>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<module>sop-test</module>
|
||||
<module>sop-sdk</module>
|
||||
<module>sop-website</module>
|
||||
<module>sop-index</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@@ -35,21 +36,19 @@
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
|
||||
<!-- springboot 版本-->
|
||||
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
|
||||
<spring-boot.version>2.6.13</spring-boot.version>
|
||||
<!-- spring cloud 版本 -->
|
||||
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
|
||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
||||
<!-- spring cloud alibaba 版本 -->
|
||||
<!-- 具体版本对应关系见: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>
|
||||
|
||||
<!-- Test -->
|
||||
<junit.version>4.11</junit.version>
|
||||
|
||||
<fastjson.version>1.2.73</fastjson.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<commons-io.version>2.5</commons-io.version>
|
||||
<commons-fileupload.version>1.3.3</commons-fileupload.version>
|
||||
<commons-collection.version>3.2.2</commons-collection.version>
|
||||
@@ -66,6 +65,7 @@
|
||||
<easyopen.version>1.16.9</easyopen.version>
|
||||
<asm.version>6.2</asm.version>
|
||||
<pagehelper.version>5.2.0</pagehelper.version>
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -92,6 +92,11 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
<modules>
|
||||
<module>sop-admin-server</module>
|
||||
</modules>
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,13 +5,12 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>sop-admin-server</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
@@ -70,7 +69,7 @@
|
||||
<artifactId>nacos-client</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sdk-java</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- http请求 -->
|
||||
<dependency>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-common</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -29,4 +29,4 @@
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,13 +5,13 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-bridge-nacos</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-gateway-common</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -25,4 +25,4 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-common</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
@@ -5,14 +5,14 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-service-common</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
|
||||
<!-- springboot 版本-->
|
||||
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
|
||||
<spring-boot.version>3.0.2</spring-boot.version>
|
||||
<!-- spring cloud 版本 -->
|
||||
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
|
||||
<!-- spring cloud alibaba 版本 -->
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
<module>sop-story</module>
|
||||
<module>sop-springmvc</module>
|
||||
</modules>
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
68
sop-index/pom.xml
Normal file
68
sop-index/pom.xml
Normal 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>
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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";
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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";
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过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
|
||||
// <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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
|
||||
}
|
@@ -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";
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
1
sop-index/src/main/resources/application.properties
Normal file
1
sop-index/src/main/resources/application.properties
Normal file
@@ -0,0 +1 @@
|
||||
spring.application.name=sop-index
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
<module>sdk-java</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -4,13 +4,13 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>sdk-java</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<!-- Generic properties -->
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-parent</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
<modules>
|
||||
<module>sop-website-server</module>
|
||||
</modules>
|
||||
</project>
|
||||
</project>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-website</artifactId>
|
||||
<version>4.4.2-SNAPSHOT</version>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
|
Reference in New Issue
Block a user