mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 12:56:28 +08:00
5.0
This commit is contained in:
25
sop-gateway/.gitignore
vendored
25
sop-gateway/.gitignore
vendored
@@ -1,25 +0,0 @@
|
||||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
/build/
|
@@ -1,74 +1,251 @@
|
||||
<?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"
|
||||
<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 http://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 -->
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.6.15</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-gateway</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- springboot 版本-->
|
||||
<spring-boot.version>2.6.15</spring-boot.version>
|
||||
<!-- spring cloud 版本 -->
|
||||
<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>2021.0.5.0</spring-cloud-alibaba.version>
|
||||
<!-- dubbo版本 -->
|
||||
<dubbo.version>3.2.10</dubbo.version>
|
||||
|
||||
<junit.version>4.11</junit.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>
|
||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
<commons-codec.version>1.11</commons-codec.version>
|
||||
<commons-logging.version>1.2</commons-logging.version>
|
||||
<validation-api.version>2.0.1.Final</validation-api.version>
|
||||
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
|
||||
<fastmybatis.version>3.0.10</fastmybatis.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--
|
||||
<artifactId>sop-bridge-nacos</artifactId> : 使用nacos注册中心
|
||||
<artifactId>sop-bridge-eureka</artifactId> : 使用eureka注册中心
|
||||
-->
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<artifactId>sop-bridge-nacos</artifactId>
|
||||
<!-- <artifactId>sop-bridge-eureka</artifactId>-->
|
||||
<version>${project.version}</version>
|
||||
<artifactId>sop-service-support</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.oschina.durcframework</groupId>
|
||||
<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>
|
||||
|
||||
<!-- nacos注册中心 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-nacos-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.gitee.durcframework</groupId>
|
||||
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- test-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- provided -->
|
||||
|
||||
<!-- 仅在开发中使用 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.4</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- 加了这个就不需要加版本号了 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||
<version>${spring-cloud-alibaba.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-bom</artifactId>
|
||||
<version>${dubbo.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.gitee.durcframework</groupId>
|
||||
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
||||
<version>${fastmybatis.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.14.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
<version>${validation-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-collections</groupId>
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>${commons-collection.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>${commons-codec.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>${commons-fileupload.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>${commons-logging.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>aliyun</id>
|
||||
<name>aliyun</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
@@ -85,20 +262,17 @@
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>maven2</id>
|
||||
<name>maven2</name>
|
||||
<url>https://repo1.maven.org/maven2</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
|
@@ -1,12 +0,0 @@
|
||||
# sop-gateway
|
||||
|
||||
网关入口,默认使用的是Spring Cloud Gateway,如果要使用Zuul,修改pom.xml
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.gitee.sop</groupId>
|
||||
<!--<artifactId>sop-bridge-gateway</artifactId>-->
|
||||
<artifactId>sop-bridge-zuul</artifactId>
|
||||
<version>version</version>
|
||||
</dependency>
|
||||
```
|
@@ -1,18 +1,15 @@
|
||||
package com.gitee.sop.gateway;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@MapperScan(basePackages = {
|
||||
"com.gitee.sop.gateway.mapper"
|
||||
})
|
||||
@SpringBootApplication(scanBasePackages = "com.gitee.sop")
|
||||
@SpringBootApplication
|
||||
@EnableDubbo
|
||||
public class SopGatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SopGatewayApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SopGatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,70 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Data
|
||||
public class ApiInfoDTO implements Serializable {
|
||||
private static final long serialVersionUID = 2183251167679411550L;
|
||||
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String application;
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
*/
|
||||
private String apiName;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String apiVersion;
|
||||
|
||||
/**
|
||||
* 接口描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 接口class
|
||||
*/
|
||||
private String interfaceClassName;
|
||||
|
||||
/**
|
||||
* 方法名称
|
||||
*/
|
||||
private String methodName;
|
||||
|
||||
/**
|
||||
* 参数信息
|
||||
*/
|
||||
private String paramInfo;
|
||||
|
||||
/**
|
||||
* 接口是否需要授权访问
|
||||
*/
|
||||
private Integer isPermission;
|
||||
|
||||
/**
|
||||
* 是否需要appAuthToken
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
private Integer status;
|
||||
|
||||
public String buildApiNameVersion() {
|
||||
return apiName + apiVersion;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class AttachmentNames {
|
||||
|
||||
/** 分配给开发者的应用ID */
|
||||
public static final String APP_ID_NAME = "client.app_id";
|
||||
/** 接口名称 */
|
||||
public static final String API_NAME = "client.method";
|
||||
/** 调用的接口版本 */
|
||||
public static final String VERSION_NAME = "client.version";
|
||||
/** 开放平台主动通知商户服务器里指定的页面http/https路径 */
|
||||
public static final String NOTIFY_URL_NAME = "client.notify_url";
|
||||
/** OAuth 2.0授权token */
|
||||
public static final String APP_AUTH_TOKEN_NAME = "client.app_auth_token";
|
||||
public static final String CLIENT_IP = "client.ip";
|
||||
public static final String TRACE_ID = "client.trace_id";
|
||||
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ParamInfoDTO {
|
||||
private String name;
|
||||
private String type;
|
||||
private String actualType;
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class RouteContext {
|
||||
|
||||
private ApiRequestContext apiRequestContext;
|
||||
private ApiInfoDTO apiInfo;
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class SopConstants {
|
||||
|
||||
private SopConstants() {}
|
||||
|
||||
public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||
public static final String UTF8 = "UTF-8";
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.gitee.sop.gateway.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum StatusEnum {
|
||||
ENABLE(1),
|
||||
DISABLE(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
package com.gitee.sop.gateway.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "api")
|
||||
@Data
|
||||
public class ApiConfig {
|
||||
|
||||
// ========= 请求参数名 =========
|
||||
|
||||
/**
|
||||
* 分配给开发者的应用ID
|
||||
*/
|
||||
private String appIdName = "app_id";
|
||||
/**
|
||||
* 接口名称
|
||||
*/
|
||||
private String apiName = "method";
|
||||
/**
|
||||
* 仅支持JSON
|
||||
*/
|
||||
private String formatName = "format";
|
||||
/**
|
||||
* 请求使用的编码格式
|
||||
*/
|
||||
private String charsetName = "charset";
|
||||
/**
|
||||
* 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2
|
||||
*/
|
||||
private String signTypeName = "sign_type";
|
||||
/**
|
||||
* 商户请求参数的签名串
|
||||
*/
|
||||
private String signName = "sign";
|
||||
/**
|
||||
* 发送请求的时间
|
||||
*/
|
||||
private String timestampName = "timestamp";
|
||||
/**
|
||||
* 调用的接口版本
|
||||
*/
|
||||
private String versionName = "version";
|
||||
/**
|
||||
* 开放平台主动通知商户服务器里指定的页面http/https路径
|
||||
*/
|
||||
private String notifyUrlName = "notify_url";
|
||||
/**
|
||||
* OAuth 2.0授权token
|
||||
*/
|
||||
private String appAuthTokenName = "app_auth_token";
|
||||
/**
|
||||
* 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
|
||||
*/
|
||||
private String bizContentName = "biz_content";
|
||||
|
||||
// ========= 请求参数名 end =========
|
||||
|
||||
|
||||
/**
|
||||
* 超时时间
|
||||
*/
|
||||
private int timeoutSeconds = 60 * 5;
|
||||
|
||||
/**
|
||||
* 是否开启限流功能
|
||||
*/
|
||||
private boolean openLimit = true;
|
||||
|
||||
/**
|
||||
* 显示返回sign
|
||||
*/
|
||||
private boolean showReturnSign = true;
|
||||
|
||||
/**
|
||||
* 时间戳格式
|
||||
*/
|
||||
private String timestampPattern = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 时区,默认:Asia/Shanghai
|
||||
*/
|
||||
private String zoneId = "Asia/Shanghai";
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
package com.gitee.sop.gateway.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 自定的扩展组件放这里
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Configuration
|
||||
public class CustomConfig {
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
package com.gitee.sop.gateway.config;
|
||||
|
||||
import com.gitee.sop.gateway.message.ErrorFactory;
|
||||
import com.gitee.sop.gateway.service.ParamExecutor;
|
||||
import com.gitee.sop.gateway.service.ParamExecutorImpl;
|
||||
import com.gitee.sop.gateway.service.RouteService;
|
||||
import com.gitee.sop.gateway.service.RouteServiceImpl;
|
||||
import com.gitee.sop.gateway.service.interceptor.internal.ResultRouteInterceptor;
|
||||
import com.gitee.sop.gateway.service.manager.ApiManager;
|
||||
import com.gitee.sop.gateway.service.manager.IsvManager;
|
||||
import com.gitee.sop.gateway.service.manager.SecretManager;
|
||||
import com.gitee.sop.gateway.service.manager.impl.LocalApiManagerImpl;
|
||||
import com.gitee.sop.gateway.service.manager.impl.LocalIsvManagerImpl;
|
||||
import com.gitee.sop.gateway.service.manager.impl.LocalSecretManagerImpl;
|
||||
import com.gitee.sop.gateway.service.manager.impl.RedisApiManagerImpl;
|
||||
import com.gitee.sop.gateway.service.manager.impl.RedisIsvManagerImpl;
|
||||
import com.gitee.sop.gateway.service.manager.impl.RedisSecretManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class IndexConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.api", havingValue = "local", matchIfMissing = true)
|
||||
public ApiManager localApiManager() {
|
||||
return new LocalApiManagerImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.api", havingValue = "redis")
|
||||
public ApiManager redisApiManager() {
|
||||
return new RedisApiManagerImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.isv", havingValue = "local", matchIfMissing = true)
|
||||
public IsvManager localIsvManager() {
|
||||
return new LocalIsvManagerImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.isv", havingValue = "redis")
|
||||
public IsvManager redisIsvManager() {
|
||||
return new RedisIsvManagerImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.secret", havingValue = "local", matchIfMissing = true)
|
||||
public SecretManager localSecretManager() {
|
||||
return new LocalSecretManagerImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "gateway.manager.secret", havingValue = "redis")
|
||||
public SecretManager redisSecretManager() {
|
||||
return new RedisSecretManager();
|
||||
}
|
||||
|
||||
|
||||
// DEFAULT ROUTE INTERCEPTOR
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ResultRouteInterceptor resultRouteInterceptor() {
|
||||
return new ResultRouteInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ParamExecutor paramExecutor() {
|
||||
return new ParamExecutorImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RouteService routeService() {
|
||||
return new RouteServiceImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
ErrorFactory.initMessageSource(null);
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package com.gitee.sop.gateway.config;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component
|
||||
public class MyConfig {
|
||||
|
||||
@PostConstruct
|
||||
public void after() {
|
||||
ApiConfig.getInstance().setTokenValidator(apiParam -> {
|
||||
// 获取客户端传递过来的token
|
||||
String token = apiParam.fetchAccessToken();
|
||||
return !StringUtils.isBlank(token);
|
||||
// TODO: 校验token有效性,可以从redis中读取
|
||||
|
||||
// 返回true表示这个token真实、有效
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package com.gitee.sop.gateway.controller;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.service.ParamExecutor;
|
||||
import com.gitee.sop.gateway.service.RouteService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 开放平台入口
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@Resource
|
||||
private RouteService routeService;
|
||||
|
||||
@Resource
|
||||
private ParamExecutor<HttpServletRequest, HttpServletResponse> paramExecutor;
|
||||
|
||||
@GetMapping("/")
|
||||
public void home(HttpServletResponse response) throws IOException {
|
||||
response.getWriter().write("Open Platform");
|
||||
// 跳转到网站首页
|
||||
// response.sendRedirect("https://www.baidu.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求入口
|
||||
*
|
||||
* @apiNote 参数描述
|
||||
<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
|
||||
app_auth_token String 否 40 详见应用授权概述
|
||||
biz_content String 是 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
|
||||
</pre>
|
||||
*/
|
||||
@RequestMapping(value = "${gateway.path:/api}", method = {RequestMethod.GET, RequestMethod.POST})
|
||||
public void index(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ApiRequestContext apiRequestContext = paramExecutor.build(request);
|
||||
ApiResponse apiResponse = routeService.route(apiRequestContext);
|
||||
paramExecutor.write(apiRequestContext, apiResponse, response);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
package com.gitee.sop.gateway.dao.entity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.PkStrategy;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:api_info
|
||||
* 备注:接口信息表
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "api_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
|
||||
@Data
|
||||
public class ApiInfo {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String application;
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
*/
|
||||
private String apiName;
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
private String apiVersion;
|
||||
|
||||
/**
|
||||
* 接口描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 接口class
|
||||
*/
|
||||
private String interfaceClassName;
|
||||
|
||||
/**
|
||||
* 方法名称
|
||||
*/
|
||||
private String methodName;
|
||||
|
||||
/**
|
||||
* 参数信息
|
||||
*/
|
||||
private String paramInfo;
|
||||
|
||||
/**
|
||||
* 接口是否需要授权访问
|
||||
*/
|
||||
private Integer isPermission;
|
||||
|
||||
/**
|
||||
* 是否需要appAuthToken
|
||||
*/
|
||||
private Integer isNeedToken;
|
||||
|
||||
/**
|
||||
* 注册来源,1-系统注册,2-手动注册
|
||||
*/
|
||||
private Integer regSource;
|
||||
|
||||
/**
|
||||
* 1启用,2禁用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private LocalDateTime addTime;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.gitee.sop.gateway.dao.entity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.PkStrategy;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:isv_info
|
||||
* 备注:isv信息表
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "isv_info", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
|
||||
@Data
|
||||
public class IsvInfo {
|
||||
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* appKey
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 1启用,2禁用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
private LocalDateTime addTime;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package com.gitee.sop.gateway.dao.entity;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.PkStrategy;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:isv_keys
|
||||
* 备注:ISV秘钥管理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "isv_keys", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
|
||||
@Data
|
||||
public class IsvKeys {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 秘钥格式,1:PKCS8(JAVA适用),2:PKCS1(非JAVA适用)
|
||||
*/
|
||||
private Integer keyFormat;
|
||||
|
||||
/**
|
||||
* 开发者生成的公钥
|
||||
*/
|
||||
private String publicKeyIsv;
|
||||
|
||||
/**
|
||||
* 开发者生成的私钥(交给开发者)
|
||||
*/
|
||||
private String privateKeyIsv;
|
||||
|
||||
/**
|
||||
* 平台生成的公钥(交给开发者)
|
||||
*/
|
||||
private String publicKeyPlatform;
|
||||
|
||||
/**
|
||||
* 平台生成的私钥
|
||||
*/
|
||||
private String privateKeyPlatform;
|
||||
|
||||
private LocalDateTime addTime;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.gateway.dao.mapper;
|
||||
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.BaseMapper;
|
||||
import com.gitee.sop.gateway.dao.entity.ApiInfo;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ApiInfoMapper extends BaseMapper<ApiInfo> {
|
||||
|
||||
default ApiInfo getByNameVersion(String apiName, String apiVersion) {
|
||||
return this.query()
|
||||
.eq(ApiInfo::getApiName, apiName)
|
||||
.eq(ApiInfo::getApiVersion, apiVersion)
|
||||
.get();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.gitee.sop.gateway.dao.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.BaseMapper;
|
||||
import com.gitee.sop.gateway.dao.entity.IsvInfo;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IsvInfoMapper extends BaseMapper<IsvInfo> {
|
||||
|
||||
default IsvInfo getByAppId(String appId) {
|
||||
return this.get(IsvInfo::getAppId, appId);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.gitee.sop.gateway.dao.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.BaseMapper;
|
||||
import com.gitee.sop.gateway.dao.entity.IsvKeys;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IsvKeysMapper extends BaseMapper<IsvKeys> {
|
||||
|
||||
default IsvKeys getByAppId(String appId) {
|
||||
return this.get(IsvKeys::getAppId, appId);
|
||||
}
|
||||
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray
|
||||
* 备注:服务灰度配置
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "config_gray",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class ConfigGray {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 用户key,多个用引文逗号隔开, 数据库字段:user_key_content */
|
||||
private String userKeyContent;
|
||||
|
||||
/** 需要灰度的接口,goods.get1.0=1.2,多个用英文逗号隔开, 数据库字段:name_version_content */
|
||||
private String nameVersionContent;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_gray_instance
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "config_gray_instance",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class ConfigGrayInstance {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** instance_id, 数据库字段:instance_id */
|
||||
private String instanceId;
|
||||
|
||||
/** service_id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 0:禁用,1:启用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_limit
|
||||
* 备注:限流配置
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "config_limit",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class ConfigLimit {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 限流ip,多个用英文逗号隔开, 数据库字段:limit_ip */
|
||||
private String limitIp;
|
||||
|
||||
|
||||
/** 服务id, 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 限流策略,1:窗口策略,2:令牌桶策略, 数据库字段:limit_type */
|
||||
private Byte limitType;
|
||||
|
||||
/** 每秒可处理请求数, 数据库字段:exec_count_per_second */
|
||||
private Integer execCountPerSecond;
|
||||
|
||||
/** 返回的错误码, 数据库字段:limit_code */
|
||||
private String limitCode;
|
||||
|
||||
/** 返回的错误信息, 数据库字段:limit_msg */
|
||||
private String limitMsg;
|
||||
|
||||
/** 令牌桶容量, 数据库字段:token_bucket_count */
|
||||
private Integer tokenBucketCount;
|
||||
|
||||
/** 限流开启状态,1:开启,0关闭, 数据库字段:limit_status */
|
||||
private Byte limitStatus;
|
||||
|
||||
/** 顺序,值小的优先执行, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_route_base
|
||||
* 备注:路由配置表
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "config_route_base",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class ConfigRouteBase {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 状态,1:启用,2:禁用, 数据库字段:status */
|
||||
private Byte status;
|
||||
}
|
@@ -1,66 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:config_service_route
|
||||
* 备注:路由配置
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "config_service_route",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class ConfigServiceRoute {
|
||||
/** 数据库字段:id */
|
||||
private String id;
|
||||
|
||||
/** 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 接口名, 数据库字段:name */
|
||||
private String name;
|
||||
|
||||
/** 版本号, 数据库字段:version */
|
||||
private String version;
|
||||
|
||||
/** 路由断言(SpringCloudGateway专用), 数据库字段:predicates */
|
||||
private String predicates;
|
||||
|
||||
/** 路由过滤器(SpringCloudGateway专用), 数据库字段:filters */
|
||||
private String filters;
|
||||
|
||||
/** 路由规则转发的目标uri, 数据库字段:uri */
|
||||
private String uri;
|
||||
|
||||
/** uri后面跟的path, 数据库字段:path */
|
||||
private String path;
|
||||
|
||||
/** 路由执行的顺序, 数据库字段:order_index */
|
||||
private Integer orderIndex;
|
||||
|
||||
/** 是否忽略验证,业务参数验证除外, 数据库字段:ignore_validate */
|
||||
private Byte ignoreValidate;
|
||||
|
||||
/** 状态,0:待审核,1:启用,2:禁用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** 是否合并结果, 数据库字段:merge_result */
|
||||
private Byte mergeResult;
|
||||
|
||||
/** 是否需要授权才能访问, 数据库字段:permission */
|
||||
private Byte permission;
|
||||
|
||||
/** 是否需要token, 数据库字段:need_token */
|
||||
private Byte needToken;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Data
|
||||
public class IsvDetailDTO {
|
||||
|
||||
/** appKey, 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 0启用,1禁用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** secret, 数据库字段:secret */
|
||||
private String secret;
|
||||
|
||||
/** 开发者生成的公钥, 数据库字段:public_key_isv */
|
||||
private String publicKeyIsv;
|
||||
|
||||
/** 平台生成的私钥, 数据库字段:private_key_platform */
|
||||
private String privateKeyPlatform;
|
||||
|
||||
/** 签名类型:1:RSA2,2:MD5 */
|
||||
private Byte signType;
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:isv_info
|
||||
* 备注:isv信息表
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "isv_info",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class IsvInfo {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** appKey, 数据库字段:app_key */
|
||||
private String appKey;
|
||||
|
||||
/** 1启用,2禁用, 数据库字段:status */
|
||||
private Byte status;
|
||||
|
||||
/** 1:RSA2,2:MD5, 数据库字段:sign_type */
|
||||
private Byte signType;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:monitor_info
|
||||
* 备注:接口监控信息
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "monitor_info",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class MonitorInfo {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 路由id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 接口名, 数据库字段:name */
|
||||
private String name;
|
||||
|
||||
/** 版本号, 数据库字段:version */
|
||||
private String version;
|
||||
|
||||
/** 数据库字段:service_id */
|
||||
private String serviceId;
|
||||
|
||||
/** 数据库字段:instance_id */
|
||||
private String instanceId;
|
||||
|
||||
/** 请求耗时最长时间, 数据库字段:max_time */
|
||||
private Integer maxTime;
|
||||
|
||||
/** 请求耗时最小时间, 数据库字段:min_time */
|
||||
private Integer minTime;
|
||||
|
||||
/** 总时长,毫秒, 数据库字段:total_time */
|
||||
private Long totalTime;
|
||||
|
||||
/** 总调用次数, 数据库字段:total_request_count */
|
||||
private Long totalRequestCount;
|
||||
|
||||
/** 成功次数, 数据库字段:success_count */
|
||||
private Long successCount;
|
||||
|
||||
/** 失败次数(业务主动抛出的异常算作成功,如参数校验,未知的错误算失败), 数据库字段:error_count */
|
||||
private Long errorCount;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:monitor_info_error
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "monitor_info_error",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class MonitorInfoError {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 错误id,md5Hex(instanceId + routeId + errorMsg), 数据库字段:error_id */
|
||||
private String errorId;
|
||||
|
||||
/** 实例id, 数据库字段:instance_id */
|
||||
private String instanceId;
|
||||
|
||||
/** 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:error_msg */
|
||||
private String errorMsg;
|
||||
|
||||
/** http status,非200错误, 数据库字段:error_status */
|
||||
private Integer errorStatus;
|
||||
|
||||
/** 错误次数, 数据库字段:count */
|
||||
private Integer count;
|
||||
|
||||
/** 数据库字段:is_deleted */
|
||||
@com.gitee.fastmybatis.core.annotation.LogicDelete
|
||||
private Byte isDeleted;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:perm_isv_role
|
||||
* 备注:isv角色
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "perm_isv_role",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class PermIsvRole {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** isv_info表id, 数据库字段:isv_id */
|
||||
private Long isvId;
|
||||
|
||||
/** 角色code, 数据库字段:role_code */
|
||||
private String roleCode;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
package com.gitee.sop.gateway.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import com.gitee.fastmybatis.annotation.Pk;
|
||||
import com.gitee.fastmybatis.annotation.Table;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 表名:perm_role_permission
|
||||
* 备注:角色权限表
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Table(name = "perm_role_permission",pk = @Pk(name = "id"))
|
||||
@Data
|
||||
public class PermRolePermission {
|
||||
/** 数据库字段:id */
|
||||
private Long id;
|
||||
|
||||
/** 角色表code, 数据库字段:role_code */
|
||||
private String roleCode;
|
||||
|
||||
/** api_id, 数据库字段:route_id */
|
||||
private String routeId;
|
||||
|
||||
/** 数据库字段:gmt_create */
|
||||
private Date gmtCreate;
|
||||
|
||||
/** 数据库字段:gmt_modified */
|
||||
private Date gmtModified;
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.gitee.sop.gateway.exception;
|
||||
|
||||
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import com.gitee.sop.gateway.message.IError;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class ApiException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8278005515613227643L;
|
||||
|
||||
private final transient Locale locale;
|
||||
|
||||
private final transient ErrorEnum errorEnum;
|
||||
private final transient Object[] params;
|
||||
|
||||
public ApiException(ErrorEnum errorEnum, Locale locale, Object... params) {
|
||||
this.errorEnum = errorEnum;
|
||||
this.params = params;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public ApiException(Throwable cause, ErrorEnum errorEnum, Locale locale, Object... params) {
|
||||
super(cause);
|
||||
this.errorEnum = errorEnum;
|
||||
this.params = params;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public IError getError() {
|
||||
return errorEnum.getErrorMeta().getError(locale, params);
|
||||
}
|
||||
|
||||
public ErrorEnum getErrorEnum() {
|
||||
return errorEnum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String message = super.getMessage();
|
||||
return message == null ? errorEnum.getErrorMeta().toString() : message;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package com.gitee.sop.gateway.exception;
|
||||
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.ValidationException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class ControllerGlobalExceptionHandler {
|
||||
|
||||
// 未知异常
|
||||
@ExceptionHandler(value = Exception.class)
|
||||
public ApiResponse globalExceptionHandler(Exception e, HttpServletRequest request) {
|
||||
log.error("系统出错", e);
|
||||
return ApiResponse.error(ErrorEnum.ISP_UNKNOWN_ERROR, request.getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理jsr303的字段校验异常,也可以自定义注解校验
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResponse handleValidationExceptions(
|
||||
MethodArgumentNotValidException ex, HttpServletRequest request) {
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
|
||||
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", errors.values()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证异常
|
||||
*
|
||||
* @param e e
|
||||
*/
|
||||
@ExceptionHandler(ValidationException.class)
|
||||
public ApiResponse validationException(ValidationException e, HttpServletRequest request) {
|
||||
List<String> msgList = new ArrayList<>();
|
||||
for (ConstraintViolation<?> constraintViolation : ((ConstraintViolationException) e).getConstraintViolations()) {
|
||||
msgList.add(constraintViolation.getMessage());
|
||||
}
|
||||
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", msgList));
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数绑定异常,GET请求参数校验
|
||||
*
|
||||
* @param e e
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public ApiResponse bandException(BindException e, HttpServletRequest request) {
|
||||
List<FieldError> fieldErrors = e.getFieldErrors();
|
||||
List<String> msgList = new ArrayList<>();
|
||||
for (FieldError fieldError : fieldErrors) {
|
||||
msgList.add(fieldError.getDefaultMessage());
|
||||
}
|
||||
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, request.getLocale(), String.join(",", msgList));
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.gitee.sop.gateway.exception;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ExceptionExecutor {
|
||||
|
||||
ApiResponse executeException(ApiRequestContext apiRequestContext, Exception e);
|
||||
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package com.gitee.sop.gateway.exception;
|
||||
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Getter
|
||||
public class SignException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 4049365045207852768L;
|
||||
|
||||
public SignException(ErrorEnum errorEnum) {
|
||||
this.errorEnum = errorEnum;
|
||||
}
|
||||
|
||||
public SignException(ErrorEnum errorEnum, Throwable cause) {
|
||||
super(cause);
|
||||
this.errorEnum = errorEnum;
|
||||
}
|
||||
|
||||
private final ErrorEnum errorEnum;
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package com.gitee.sop.gateway.exception.impl;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.exception.ApiException;
|
||||
import com.gitee.sop.gateway.exception.ExceptionExecutor;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import org.apache.dubbo.rpc.service.GenericException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
public class ExceptionExecutorImpl implements ExceptionExecutor {
|
||||
|
||||
private static final String CONSTRAINT_VIOLATION_EXCEPTION = "ConstraintViolationException";
|
||||
|
||||
@Override
|
||||
public ApiResponse executeException(ApiRequestContext apiRequestContext, Exception e) {
|
||||
if (e instanceof GenericException) {
|
||||
GenericException genericException = (GenericException) e;
|
||||
String exceptionClass = genericException.getExceptionClass();
|
||||
if (exceptionClass.contains(CONSTRAINT_VIOLATION_EXCEPTION)) {
|
||||
String exceptionMessage = genericException.getExceptionMessage();
|
||||
// 参数校验:Failed to validate service: com.gitee.sop.storyweb.open.StoryService, method: save, cause: [ConstraintViolationImpl{interpolatedMessage='故事名称必填', propertyPath=storyName, rootBeanClass=class com.gitee.sop.storyweb.open.req.StorySaveDTO, messageTemplate='故事名称必填'}]
|
||||
Set<String> msgs = findErrorMsg(exceptionMessage);
|
||||
return ApiResponse.error(ErrorEnum.ISV_ERROR_PARAMETER, apiRequestContext.getLocale(), String.join(",", msgs));
|
||||
}
|
||||
} else if (e instanceof ApiException) {
|
||||
return ApiResponse.error(((ApiException) e));
|
||||
}
|
||||
return ApiResponse.error(ErrorEnum.ISP_SERVICE_UNKNOWN_ERROR, apiRequestContext.getLocale());
|
||||
}
|
||||
|
||||
private static Set<String> findErrorMsg(String text) {
|
||||
Pattern pattern = Pattern.compile("'([^']*)'");
|
||||
Matcher matcher = pattern.matcher(text);
|
||||
Set<String> msgList = new LinkedHashSet<>();
|
||||
while (matcher.find()) {
|
||||
msgList.add(matcher.group(1));
|
||||
}
|
||||
return msgList;
|
||||
}
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package com.gitee.sop.gateway.interceptor;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
|
||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 用于收集监控数据
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MonitorRouteInterceptor implements RouteInterceptor {
|
||||
|
||||
@Autowired
|
||||
SopAsyncConfigurer sopAsyncConfigurer;
|
||||
|
||||
@Autowired
|
||||
MonitorRouteInterceptorService monitorRouteInterceptorService;
|
||||
|
||||
@Override
|
||||
public void preRoute(RouteInterceptorContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRoute(RouteInterceptorContext context) {
|
||||
sopAsyncConfigurer.getAsyncExecutor().execute(()-> {
|
||||
monitorRouteInterceptorService.storeRequestInfo(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,164 +0,0 @@
|
||||
package com.gitee.sop.gateway.interceptor;
|
||||
|
||||
import com.gitee.sop.gateway.manager.DbMonitorInfoManager;
|
||||
import com.gitee.sop.gatewaycommon.bean.LRUCache;
|
||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorData;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorManager;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import com.gitee.sop.gatewaycommon.sync.MyNamedThreadFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MonitorRouteInterceptorService {
|
||||
|
||||
/**
|
||||
* 刷新到数据库时间间隔,秒
|
||||
*/
|
||||
@Value("${sop.monitor.flush-period-seconds:30}")
|
||||
int flushPeriodSeconds;
|
||||
|
||||
/**
|
||||
* 定时任务每n秒,执行一次
|
||||
*/
|
||||
@Value("${sop.monitor.schedule-period-seconds:30}")
|
||||
int schedulePeriodSeconds;
|
||||
|
||||
/**
|
||||
* 错误数量容量
|
||||
*/
|
||||
@Value("${sop.monitor.error-count-capacity:50}")
|
||||
int monitorErrorCapacity;
|
||||
|
||||
@Autowired
|
||||
DbMonitorInfoManager dbMonitorInfoManager;
|
||||
|
||||
@Autowired
|
||||
MonitorManager monitorManager;
|
||||
|
||||
/**
|
||||
* 记录接口调用流量,最大时间,最小时间,总时长,平均时长,调用次数,成功次数,失败次数.
|
||||
* 需要考虑并发情况。
|
||||
*/
|
||||
public synchronized void storeRequestInfo(RouteInterceptorContext context) {
|
||||
ApiParam apiParam = context.getApiParam();
|
||||
ServiceInstance serviceInstance = context.getServiceInstance();
|
||||
String routeId = apiParam.getRouteId();
|
||||
int spendTime = (int)(context.getFinishTimeMillis() - context.getBeginTimeMillis());
|
||||
// 这步操作是线程安全的,底层调用了ConcurrentHashMap.computeIfAbsent
|
||||
String key = getMonitorKey(routeId, serviceInstance.getInstanceId());
|
||||
MonitorData monitorData = monitorManager.getMonitorInfo(key, (k) -> this.createMonitorInfo(apiParam, serviceInstance));
|
||||
monitorData.storeMaxTime(spendTime);
|
||||
monitorData.storeMinTime(spendTime);
|
||||
monitorData.getTotalRequestCount().incrementAndGet();
|
||||
monitorData.getTotalTime().addAndGet(spendTime);
|
||||
if (context.isSuccessRequest()) {
|
||||
monitorData.getSuccessCount().incrementAndGet();
|
||||
} else {
|
||||
monitorData.getErrorCount().incrementAndGet();
|
||||
String errorMsg = context.getServiceErrorMsg();
|
||||
monitorData.addErrorMsg(errorMsg, context.getResponseStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private String getMonitorKey(String routeId, String instanceId) {
|
||||
return routeId + instanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新到数据库
|
||||
*/
|
||||
private synchronized void flushDb() {
|
||||
Map<String, MonitorData> monitorData = monitorManager.getMonitorData();
|
||||
if (monitorData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
LocalDateTime checkTime = LocalDateTime.now();
|
||||
List<String> tobeRemoveKeys = new ArrayList<>();
|
||||
List<MonitorDTO> tobeSaveBatch = new ArrayList<>(monitorData.size());
|
||||
monitorData.forEach((key, value) -> {
|
||||
LocalDateTime flushTime = value.getFlushTime();
|
||||
if (flushTime.isEqual(checkTime) || flushTime.isBefore(checkTime)) {
|
||||
log.debug("刷新监控数据到数据库, MonitorData:{}", value);
|
||||
tobeRemoveKeys.add(key);
|
||||
MonitorDTO monitorDTO = getMonitorDTO(value);
|
||||
tobeSaveBatch.add(monitorDTO);
|
||||
}
|
||||
});
|
||||
dbMonitorInfoManager.saveMonitorInfoBatch(tobeSaveBatch);
|
||||
|
||||
for (String key : tobeRemoveKeys) {
|
||||
monitorData.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private MonitorDTO getMonitorDTO(MonitorData monitorData) {
|
||||
MonitorDTO monitorDTO = new MonitorDTO();
|
||||
monitorDTO.setRouteId(monitorData.getRouteId());
|
||||
monitorDTO.setName(monitorData.getName());
|
||||
monitorDTO.setVersion(monitorData.getVersion());
|
||||
monitorDTO.setServiceId(monitorData.getServiceId());
|
||||
monitorDTO.setInstanceId(monitorData.getInstanceId());
|
||||
monitorDTO.setMaxTime(monitorData.getMaxTime());
|
||||
monitorDTO.setMinTime(monitorData.getMinTime());
|
||||
monitorDTO.setTotalTime(monitorData.getTotalTime().longValue());
|
||||
monitorDTO.setTotalRequestCount(monitorData.getTotalRequestCount().longValue());
|
||||
monitorDTO.setSuccessCount(monitorData.getSuccessCount().longValue());
|
||||
monitorDTO.setErrorCount(monitorData.getErrorCount().longValue());
|
||||
monitorDTO.setErrorMsgList(monitorData.getMonitorErrorMsgMap().values());
|
||||
return monitorDTO;
|
||||
}
|
||||
|
||||
private MonitorData createMonitorInfo(ApiParam apiParam, ServiceInstance serviceInstance) {
|
||||
MonitorData monitorData = new MonitorData();
|
||||
monitorData.setRouteId(apiParam.getRouteId());
|
||||
monitorData.setName(apiParam.fetchName());
|
||||
monitorData.setVersion(apiParam.fetchVersion());
|
||||
monitorData.setServiceId(apiParam.fetchServiceId());
|
||||
monitorData.setInstanceId(serviceInstance.getInstanceId());
|
||||
monitorData.setTotalTime(new AtomicInteger());
|
||||
monitorData.setMaxTime(0);
|
||||
monitorData.setMinTime(0);
|
||||
monitorData.setSuccessCount(new AtomicInteger());
|
||||
monitorData.setTotalRequestCount(new AtomicInteger());
|
||||
monitorData.setErrorCount(new AtomicInteger());
|
||||
monitorData.setFlushTime(getFlushTime());
|
||||
monitorData.setMonitorErrorMsgMap(new LRUCache<>(monitorErrorCapacity));
|
||||
return monitorData;
|
||||
}
|
||||
|
||||
private LocalDateTime getFlushTime() {
|
||||
return LocalDateTime.now()
|
||||
.plusSeconds(flushPeriodSeconds);
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void after() {
|
||||
// 每隔schedulePeriodSeconds秒执行一次
|
||||
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new MyNamedThreadFactory("monitorSchedule"));
|
||||
// 延迟执行,随机5~14秒
|
||||
int delay = 5 + new Random().nextInt(10);
|
||||
scheduledThreadPoolExecutor.scheduleWithFixedDelay(this::flushDb, delay, schedulePeriodSeconds, TimeUnit.SECONDS);
|
||||
|
||||
}
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package com.gitee.sop.gateway.interceptor;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
|
||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* 演示拦截器
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyRouteInterceptor implements RouteInterceptor {
|
||||
|
||||
@Override
|
||||
public void preRoute(RouteInterceptorContext context) {
|
||||
ApiParam apiParam = context.getApiParam();
|
||||
ServerWebExchange exchange = (ServerWebExchange) context.getRequestContext();
|
||||
URI uri = exchange.getRequest().getURI();
|
||||
log.info("请求URL:{}, 请求接口:{}, request_id:{}, app_id:{}, ip:{}",
|
||||
uri,
|
||||
apiParam.fetchNameVersion(),
|
||||
apiParam.fetchRequestId(),
|
||||
apiParam.fetchAppKey(),
|
||||
apiParam.fetchIp()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterRoute(RouteInterceptorContext context) {
|
||||
String serviceErrorMsg = context.getServiceErrorMsg();
|
||||
if (StringUtils.hasText(serviceErrorMsg)) {
|
||||
log.error("错误信息:{}", serviceErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@@ -1,120 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.entity.ConfigGray;
|
||||
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
|
||||
import com.gitee.sop.gateway.mapper.ConfigGrayInstanceMapper;
|
||||
import com.gitee.sop.gateway.mapper.ConfigGrayMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ServiceGrayDefinition;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
||||
import com.gitee.sop.gatewaycommon.route.RegistryEvent;
|
||||
import com.gitee.sop.gatewaycommon.loadbalancer.ServiceGrayConfig;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 存放用户key,这里放在本机内容,如果灰度发布保存的用户id数量偏多,可放在redis中
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbEnvGrayManager extends DefaultEnvGrayManager implements RegistryEvent {
|
||||
|
||||
private static final int STATUS_ENABLE = 1;
|
||||
|
||||
private static final Function<String[], String> FUNCTION_KEY = arr -> arr[0];
|
||||
private static final Function<String[], String> FUNCTION_VALUE = arr -> arr[1];
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayMapper configGrayMapper;
|
||||
|
||||
@Autowired
|
||||
private ConfigGrayInstanceMapper configGrayInstanceMapper;
|
||||
|
||||
@Override
|
||||
public void onRegistry(InstanceDefinition instanceDefinition) {
|
||||
String instanceId = instanceDefinition.getInstanceId();
|
||||
ConfigGrayInstance grayInstance = configGrayInstanceMapper.getByColumn("instance_id", instanceId);
|
||||
if (grayInstance != null && grayInstance.getStatus() == STATUS_ENABLE) {
|
||||
log.info("实例[{}]开启灰度发布", grayInstance.getInstanceId());
|
||||
this.openGray(grayInstance.getInstanceId(), grayInstance.getServiceId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(String serviceId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
List<ConfigGray> list = configGrayMapper.list(new Query());
|
||||
for (ConfigGray configGray : list) {
|
||||
this.setServiceGrayConfig(configGray);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户key
|
||||
*
|
||||
* @param configGray 灰度配置
|
||||
*/
|
||||
public void setServiceGrayConfig(ConfigGray configGray) {
|
||||
if (configGray == null) {
|
||||
return;
|
||||
}
|
||||
String userKeyData = configGray.getUserKeyContent();
|
||||
String nameVersionContent = configGray.getNameVersionContent();
|
||||
String[] userKeys = StringUtils.split(userKeyData, ',');
|
||||
String[] nameVersionList = StringUtils.split(nameVersionContent, ',');
|
||||
log.info("灰度配置,userKeys.length:{}, nameVersionList:{}", userKeys.length, Arrays.toString(nameVersionList));
|
||||
|
||||
Set<String> userKeySet = Stream.of(userKeys)
|
||||
.collect(Collectors.toCollection(Sets::newConcurrentHashSet));
|
||||
|
||||
Map<String, String> grayNameVersionMap = Stream.of(nameVersionList)
|
||||
.map(nameVersion -> StringUtils.split(nameVersion, '='))
|
||||
.collect(Collectors.toConcurrentMap(FUNCTION_KEY, FUNCTION_VALUE));
|
||||
|
||||
ServiceGrayConfig serviceGrayConfig = new ServiceGrayConfig();
|
||||
serviceGrayConfig.setServiceId(configGray.getServiceId());
|
||||
serviceGrayConfig.setUserKeys(userKeySet);
|
||||
serviceGrayConfig.setGrayNameVersion(grayNameVersionMap);
|
||||
this.saveServiceGrayConfig(serviceGrayConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
ServiceGrayDefinition userKeyDefinition = channelMsg.toObject(ServiceGrayDefinition.class);
|
||||
String serviceId = userKeyDefinition.getServiceId();
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "set":
|
||||
ConfigGray configGray = configGrayMapper.getByColumn("service_id", serviceId);
|
||||
setServiceGrayConfig(configGray);
|
||||
break;
|
||||
case "open":
|
||||
openGray(userKeyDefinition.getInstanceId(), serviceId);
|
||||
break;
|
||||
case "close":
|
||||
closeGray(userKeyDefinition.getInstanceId());
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,55 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.sop.gateway.mapper.IPBlacklistMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 限流配置管理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIPBlacklistManager extends DefaultIPBlacklistManager {
|
||||
|
||||
@Autowired
|
||||
private IPBlacklistMapper ipBlacklistMapper;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
List<String> ipList = ipBlacklistMapper.listAllIP();
|
||||
log.info("加载IP黑名单, size:{}", ipList.size());
|
||||
ipList.forEach(this::add);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
final IPDto ipDto = channelMsg.toObject(IPDto.class);
|
||||
String ip = ipDto.getIp();
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "add":
|
||||
log.info("添加IP黑名单,ip:{}", ip);
|
||||
add(ip);
|
||||
break;
|
||||
case "delete":
|
||||
log.info("移除IP黑名单,ip:{}", ip);
|
||||
remove(ip);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
private static class IPDto {
|
||||
private String ip;
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.sop.gateway.entity.IsvDetailDTO;
|
||||
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
|
||||
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIsvManager extends CacheIsvManager {
|
||||
|
||||
public DbIsvManager() {
|
||||
ApiConfig.getInstance().setIsvManager(this);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private IsvInfoMapper isvInfoMapper;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
List<IsvDetailDTO> isvInfoList = isvInfoMapper.listIsvDetail();
|
||||
isvInfoList
|
||||
.forEach(isvInfo -> {
|
||||
IsvDefinition isvDefinition = new IsvDefinition();
|
||||
BeanUtils.copyProperties(isvInfo, isvDefinition);
|
||||
this.getIsvCache().put(isvDefinition.getAppKey(), isvDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
final IsvDefinition isvDefinition = channelMsg.toObject(IsvDefinition.class);
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "update":
|
||||
log.info("更新ISV信息,isvDefinition:{}", isvDefinition);
|
||||
update(isvDefinition);
|
||||
break;
|
||||
case "remove":
|
||||
log.info("删除ISV,isvDefinition:{}", isvDefinition);
|
||||
remove(isvDefinition.getAppKey());
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,149 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.entity.IsvInfo;
|
||||
import com.gitee.sop.gateway.entity.PermIsvRole;
|
||||
import com.gitee.sop.gateway.entity.PermRolePermission;
|
||||
import com.gitee.sop.gateway.mapper.IsvInfoMapper;
|
||||
import com.gitee.sop.gateway.mapper.PermIsvRoleMapper;
|
||||
import com.gitee.sop.gateway.mapper.PermRolePermissionMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.IsvRoutePermission;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* 从数据库中读取路由权限信息
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbIsvRoutePermissionManager extends DefaultIsvRoutePermissionManager {
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
@Autowired
|
||||
PermIsvRoleMapper permIsvRoleMapper;
|
||||
|
||||
@Autowired
|
||||
PermRolePermissionMapper permRolePermissionMapper;
|
||||
|
||||
@Autowired
|
||||
IsvInfoMapper isvInfoMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
// key: appKey, value: roleCodeList
|
||||
Map<String, List<String>> appKeyRoleCodeMap = this.getIsvRoleCode();
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : appKeyRoleCodeMap.entrySet()) {
|
||||
this.loadIsvRoutePermission(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIsvRoutePermission(String appKey, List<String> roleCodeList) {
|
||||
Collections.sort(roleCodeList);
|
||||
List<String> routeIdList = this.getRouteIdList(roleCodeList);
|
||||
String roleCodeListMd5 = DigestUtils.md5Hex(JSON.toJSONString(routeIdList));
|
||||
IsvRoutePermission isvRoutePermission = new IsvRoutePermission();
|
||||
isvRoutePermission.setAppKey(appKey);
|
||||
isvRoutePermission.setRouteIdList(routeIdList);
|
||||
isvRoutePermission.setRouteIdListMd5(roleCodeListMd5);
|
||||
this.update(isvRoutePermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ISV对应的角色
|
||||
* @return 返回ISV角色信息,key:appId,value:角色code列表
|
||||
*/
|
||||
public Map<String, List<String>> getIsvRoleCode() {
|
||||
Query query = new Query();
|
||||
List<PermIsvRole> permIsvRoles = permIsvRoleMapper.list(query);
|
||||
Map<String, List<String>> appKeyRoleCodeMap = permIsvRoles.stream()
|
||||
.map(permIsvRole -> {
|
||||
IsvInfo isvInfo = isvInfoMapper.getById(permIsvRole.getIsvId());
|
||||
if (isvInfo == null) {
|
||||
return null;
|
||||
}
|
||||
IsvRole isvRole = new IsvRole();
|
||||
isvRole.appKey = isvInfo.getAppKey();
|
||||
isvRole.roleCode = permIsvRole.getRoleCode();
|
||||
return isvRole;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(IsvRole::getAppKey,
|
||||
mapping(IsvRole::getRoleCode, toList()))
|
||||
);
|
||||
return appKeyRoleCodeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色对应的路由
|
||||
*
|
||||
* @param roleCodeList
|
||||
* @return
|
||||
*/
|
||||
public List<String> getRouteIdList(List<String> roleCodeList) {
|
||||
if (CollectionUtils.isEmpty(roleCodeList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Query query = new Query();
|
||||
query.in("role_code", roleCodeList);
|
||||
List<PermRolePermission> rolePermissionList = permRolePermissionMapper.list(query);
|
||||
return rolePermissionList.stream()
|
||||
.map(PermRolePermission::getRouteId)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
final IsvRoutePermission isvRoutePermission = channelMsg.toObject(IsvRoutePermission.class);
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "reload":
|
||||
log.info("重新加载路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
||||
try {
|
||||
load();
|
||||
} catch (Exception e) {
|
||||
log.error("重新加载路由权限失败, channelMsg:{}", channelMsg, e);
|
||||
}
|
||||
break;
|
||||
case "update":
|
||||
log.info("更新ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
||||
update(isvRoutePermission);
|
||||
break;
|
||||
case "remove":
|
||||
log.info("删除ISV路由权限信息,isvRoutePermission:{}", isvRoutePermission);
|
||||
remove(isvRoutePermission.getAppKey());
|
||||
break;
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
static class IsvRole {
|
||||
private String appKey;
|
||||
private String roleCode;
|
||||
}
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.entity.ConfigLimit;
|
||||
import com.gitee.sop.gateway.mapper.ConfigLimitMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.util.MyBeanUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 限流配置管理
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbLimitConfigManager extends DefaultLimitConfigManager {
|
||||
|
||||
@Autowired
|
||||
ConfigLimitMapper configLimitMapper;
|
||||
|
||||
@Override
|
||||
public void load(String serviceId) {
|
||||
Query query = new Query();
|
||||
if (StringUtils.isNotBlank(serviceId)) {
|
||||
query.eq("service_id", serviceId);
|
||||
}
|
||||
configLimitMapper.list(query)
|
||||
.forEach(this::putVal);
|
||||
|
||||
}
|
||||
|
||||
protected void putVal(ConfigLimit object) {
|
||||
ConfigLimitDto configLimitDto = new ConfigLimitDto();
|
||||
MyBeanUtil.copyPropertiesIgnoreNull(object, configLimitDto);
|
||||
this.update(configLimitDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
final ConfigLimitDto configLimitDto = channelMsg.toObject(ConfigLimitDto.class);
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "reload":
|
||||
log.info("重新加载限流配置信息,configLimitDto:{}", configLimitDto);
|
||||
load(configLimitDto.getServiceId());
|
||||
break;
|
||||
case "update":
|
||||
log.info("更新限流配置信息,configLimitDto:{}", configLimitDto);
|
||||
update(configLimitDto);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.sop.gateway.mapper.MonitorInfoErrorMapper;
|
||||
import com.gitee.sop.gateway.mapper.MonitorInfoMapper;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg;
|
||||
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
public class DbMonitorInfoManager {
|
||||
|
||||
@Autowired
|
||||
private MonitorInfoMapper monitorInfoMapper;
|
||||
|
||||
@Autowired
|
||||
private MonitorInfoErrorMapper monitorInfoErrorMapper;
|
||||
|
||||
@Value("${sop.monitor.error-count-capacity:50}")
|
||||
int limitCount;
|
||||
|
||||
public void saveMonitorInfoBatch(List<MonitorDTO> list) {
|
||||
if (CollectionUtils.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
monitorInfoMapper.saveMonitorInfoBatch(list);
|
||||
this.saveMonitorInfoErrorBatch(list);
|
||||
}
|
||||
|
||||
private void saveMonitorInfoErrorBatch(List<MonitorDTO> list) {
|
||||
List<RouteErrorCount> routeErrorCounts = monitorInfoErrorMapper.listRouteErrorCountAll();
|
||||
// 路由id对应的错误次数,key:routeId,value:错误次数
|
||||
Map<String, Integer> routeErrorCountsMap = routeErrorCounts.stream()
|
||||
.collect(Collectors.toMap(RouteErrorCount::getRouteId, RouteErrorCount::getCount));
|
||||
List<MonitorErrorMsg> monitorErrorMsgList = list.stream()
|
||||
.filter(monitorDTO -> CollectionUtils.isNotEmpty(monitorDTO.getErrorMsgList()))
|
||||
.flatMap(monitorDTO -> {
|
||||
int limit = limitCount - routeErrorCountsMap.getOrDefault(monitorDTO.getRouteId(), 0);
|
||||
// 容量已满
|
||||
if (limit <= 0) {
|
||||
return null;
|
||||
}
|
||||
// 截取剩余
|
||||
return monitorDTO.getErrorMsgList().stream().limit(limit);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isNotEmpty(monitorErrorMsgList)) {
|
||||
monitorInfoErrorMapper.saveMonitorInfoErrorBatch(monitorErrorMsgList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.gitee.sop.gateway.mapper.ConfigRouteMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.ChannelMsg;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DbRouteConfigManager extends DefaultRouteConfigManager {
|
||||
|
||||
@Autowired
|
||||
private ConfigRouteMapper configRouteMapper;
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void load(String serviceId) {
|
||||
List<RouteConfig> routeConfigs = StringUtils.isBlank(serviceId) ? configRouteMapper.listAllRouteConfig()
|
||||
: configRouteMapper.listRouteConfig(serviceId);
|
||||
routeConfigs.forEach(this::save);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChannelMsg channelMsg) {
|
||||
final RouteConfig routeConfig = channelMsg.toObject(RouteConfig.class);
|
||||
switch (channelMsg.getOperation()) {
|
||||
case "reload":
|
||||
log.info("重新加载路由配置信息,routeConfigDto:{}", routeConfig);
|
||||
load(null);
|
||||
break;
|
||||
case "update":
|
||||
log.info("更新路由配置信息,routeConfigDto:{}", routeConfig);
|
||||
update(routeConfig);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,86 +0,0 @@
|
||||
package com.gitee.sop.gateway.manager;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.gitee.fastmybatis.core.query.Query;
|
||||
import com.gitee.sop.gateway.entity.ConfigServiceRoute;
|
||||
import com.gitee.sop.gateway.mapper.ConfigServiceRouteMapper;
|
||||
import com.gitee.sop.gateway.mapper.SystemLockMapper;
|
||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.ServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.route.RoutesProcessor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DbRoutesProcessor implements RoutesProcessor {
|
||||
|
||||
@Autowired
|
||||
private ConfigServiceRouteMapper configServiceRouteMapper;
|
||||
|
||||
@Autowired
|
||||
private SystemLockMapper systemLockMapper;
|
||||
|
||||
@Override
|
||||
public void removeAllRoutes(String serviceId) {
|
||||
// 删除serviceId下所有的路由
|
||||
Query delServiceQuery = new Query().eq("service_id", serviceId);
|
||||
configServiceRouteMapper.deleteByQuery(delServiceQuery);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public synchronized void saveRoutes(ServiceRouteInfo serviceRouteInfo, InstanceDefinition instance) {
|
||||
// 抢锁,没抢到阻塞在这里
|
||||
systemLockMapper.lock();
|
||||
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
|
||||
int result = systemLockMapper.insert(time + serviceRouteInfo.getMd5());
|
||||
// 抢到锁,插入失败,表示其它实例已经处理完毕,这里直接返回
|
||||
if (result == 0) {
|
||||
return;
|
||||
}
|
||||
log.info("保存路由信息到数据库,instance: {}", instance);
|
||||
String serviceId = serviceRouteInfo.getServiceId();
|
||||
List<ConfigServiceRoute> configServiceRoutes = serviceRouteInfo
|
||||
.getRouteDefinitionList()
|
||||
.parallelStream()
|
||||
.map(routeDefinition -> {
|
||||
ConfigServiceRoute configServiceRoute = new ConfigServiceRoute();
|
||||
configServiceRoute.setId(routeDefinition.getId());
|
||||
configServiceRoute.setName(routeDefinition.getName());
|
||||
configServiceRoute.setVersion(routeDefinition.getVersion());
|
||||
configServiceRoute.setUri(routeDefinition.getUri());
|
||||
configServiceRoute.setPath(routeDefinition.getPath());
|
||||
configServiceRoute.setFilters(JSON.toJSONString(routeDefinition.getFilters()));
|
||||
configServiceRoute.setPredicates(JSON.toJSONString(routeDefinition.getPredicates()));
|
||||
configServiceRoute.setIgnoreValidate((byte) routeDefinition.getIgnoreValidate());
|
||||
configServiceRoute.setMergeResult((byte) routeDefinition.getMergeResult());
|
||||
configServiceRoute.setStatus((byte) routeDefinition.getStatus());
|
||||
configServiceRoute.setPermission((byte) routeDefinition.getPermission());
|
||||
configServiceRoute.setOrderIndex(routeDefinition.getOrder());
|
||||
configServiceRoute.setNeedToken((byte)routeDefinition.getNeedToken());
|
||||
configServiceRoute.setServiceId(serviceId);
|
||||
return configServiceRoute;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 删除serviceId下所有的路由
|
||||
this.removeAllRoutes(serviceId);
|
||||
|
||||
if (CollectionUtils.isNotEmpty(configServiceRoutes)) {
|
||||
// 批量保存
|
||||
configServiceRouteMapper.saveBatch(configServiceRoutes);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigGrayInstance;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ConfigGrayInstanceMapper extends CrudMapper<ConfigGrayInstance, Long> {
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigGray;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ConfigGrayMapper extends CrudMapper<ConfigGray, Long> {
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigLimit;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ConfigLimitMapper extends CrudMapper<ConfigLimit, Long> {
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigRouteBase;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ConfigRouteBaseMapper extends CrudMapper<ConfigRouteBase, Long> {
|
||||
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConfigRouteMapper {
|
||||
|
||||
@Select("SELECT t.id AS routeId, t2.status " +
|
||||
"FROM config_service_route t " +
|
||||
"LEFT JOIN config_route_base t2 ON t.id=t2.route_id " +
|
||||
"WHERE t.service_id=#{serviceId}")
|
||||
List<RouteConfig> listRouteConfig(@Param("serviceId") String serviceId);
|
||||
|
||||
@Select("SELECT t.id AS routeId, t2.status " +
|
||||
"FROM config_service_route t " +
|
||||
"LEFT JOIN config_route_base t2 ON t.id=t2.route_id ")
|
||||
List<RouteConfig> listAllRouteConfig();
|
||||
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.ConfigServiceRoute;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ConfigServiceRouteMapper extends CrudMapper<ConfigServiceRoute, String> {
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IP黑名单
|
||||
* @author 六如
|
||||
*/
|
||||
@Mapper
|
||||
public interface IPBlacklistMapper {
|
||||
|
||||
/**
|
||||
* 获取所有IP
|
||||
* @return
|
||||
*/
|
||||
@Select("SELECT ip FROM config_ip_blacklist")
|
||||
List<String> listAllIP();
|
||||
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.IsvDetailDTO;
|
||||
import com.gitee.sop.gateway.entity.IsvInfo;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> {
|
||||
|
||||
/**
|
||||
* 获取所有的isv信息
|
||||
* @return 所有的isv信息
|
||||
*/
|
||||
@Select("SELECT " +
|
||||
" t.app_key appKey " +
|
||||
" ,t.status " +
|
||||
" ,t2.sign_type signType " +
|
||||
" ,t2.secret " +
|
||||
" ,t2.public_key_isv publicKeyIsv " +
|
||||
" ,t2.private_key_platform privateKeyPlatform " +
|
||||
"FROM isv_info t " +
|
||||
"INNER JOIN isv_keys t2 ON t.app_key = t2.app_key")
|
||||
List<IsvDetailDTO> listIsvDetail();
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.MonitorInfoError;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorErrorMsg;
|
||||
import com.gitee.sop.gatewaycommon.monitor.RouteErrorCount;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface MonitorInfoErrorMapper extends CrudMapper<MonitorInfoError, Long> {
|
||||
|
||||
@Update("UPDATE monitor_info_error " +
|
||||
"SET is_deleted=0 " +
|
||||
",count=count + 1 " +
|
||||
"WHERE route_id=#{routeId} AND error_id=#{errorId}")
|
||||
int updateError(@Param("routeId") String routeId,@Param("errorId") String errorId);
|
||||
|
||||
int saveMonitorInfoErrorBatch(@Param("list") List<MonitorErrorMsg> list);
|
||||
|
||||
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" +
|
||||
"WHERE is_deleted=0 \n" +
|
||||
"GROUP BY route_id")
|
||||
List<RouteErrorCount> listRouteErrorCount();
|
||||
|
||||
@Select("SELECT route_id routeId, count(*) `count` FROM monitor_info_error \n" +
|
||||
"WHERE is_deleted=0 \n" +
|
||||
"GROUP BY route_id")
|
||||
List<RouteErrorCount> listRouteErrorCountAll();
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.MonitorInfo;
|
||||
import com.gitee.sop.gatewaycommon.monitor.MonitorDTO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface MonitorInfoMapper extends CrudMapper<MonitorInfo, Long> {
|
||||
|
||||
/**
|
||||
* 更新监控状态
|
||||
*
|
||||
* @return 返回影响行数
|
||||
*/
|
||||
@Update("UPDATE monitor_info " +
|
||||
"set max_time=case when max_time < #{maxTime} then #{maxTime} else max_time end " +
|
||||
",min_time=case when min_time > #{minTime} then #{minTime} else min_time end " +
|
||||
",total_request_count=total_request_count + #{totalRequestCount} " +
|
||||
",total_time=total_time + #{totalTime} " +
|
||||
",success_count=success_count + #{successCount} " +
|
||||
",error_count=error_count + #{errorCount} " +
|
||||
"where route_id=#{routeId}")
|
||||
int updateMonitorInfo(MonitorDTO monitorDTO);
|
||||
|
||||
|
||||
/**
|
||||
* 批量插入监控数据
|
||||
* @param list 监控数据
|
||||
* @return 返回影响行数
|
||||
*/
|
||||
int saveMonitorInfoBatch(@Param("list") List<MonitorDTO> list);
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.PermIsvRole;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface PermIsvRoleMapper extends CrudMapper<PermIsvRole, Long> {
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
||||
import com.gitee.sop.gateway.entity.PermRolePermission;
|
||||
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface PermRolePermissionMapper extends CrudMapper<PermRolePermission, Long> {
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
package com.gitee.sop.gateway.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.ResultType;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Mapper
|
||||
public interface SystemLockMapper {
|
||||
|
||||
/**
|
||||
* 插入唯一值
|
||||
* @param content 唯一值
|
||||
* @return 1:返回成功,0:插入失败
|
||||
*/
|
||||
@Insert("INSERT IGNORE INTO system_lock(content) VALUES (#{content})")
|
||||
@ResultType(int.class)
|
||||
int insert(@Param("content") String content);
|
||||
|
||||
@Select("SELECT id FROM system_lock WHERE id=1 FOR UPDATE")
|
||||
@ResultType(long.class)
|
||||
long lock();
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
package com.gitee.sop.gateway.message;
|
||||
|
||||
/**
|
||||
* 网关错误定义
|
||||
* @author 六如
|
||||
*/
|
||||
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_ERROR_PARAMETER(Codes.CODE_INVALID, "isv.error-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 final 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,134 @@
|
||||
package com.gitee.sop.gateway.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.CollectionUtils;
|
||||
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 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class ErrorFactory {
|
||||
|
||||
private static final String ZH = "zh";
|
||||
|
||||
private 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 ErrorFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误信息的国际化信息
|
||||
*/
|
||||
private static MessageSourceAccessor errorMessageSourceAccessor;
|
||||
|
||||
/**
|
||||
* 设置国际化资源信息
|
||||
*/
|
||||
public static void initMessageSource(List<String> isvModules) {
|
||||
HashSet<String> baseNamesSet = new HashSet<>();
|
||||
baseNamesSet.add(I18N_OPEN_ERROR);
|
||||
|
||||
if (!CollectionUtils.isEmpty(isvModules)) {
|
||||
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;
|
||||
}
|
||||
String localeStr = locale.toString();
|
||||
if (localeStr.startsWith(ZH)) {
|
||||
locale = Locale.SIMPLIFIED_CHINESE;
|
||||
}
|
||||
if (!localeList.contains(locale)) {
|
||||
locale = Locale.ENGLISH;
|
||||
}
|
||||
String key = errorMeta.getModulePrefix() + errorMeta.getCode() + errorMeta.getSubCode() + locale.toString();
|
||||
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>
|
||||
return new ErrorImpl(code, msg, subCode, subMsg, null);
|
||||
}
|
||||
|
||||
|
||||
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.gateway.message;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@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,34 @@
|
||||
package com.gitee.sop.gateway.message;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 错误对象
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@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, Object... params) {
|
||||
return ErrorFactory.getError(this, locale, params);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return modulePrefix + code + "_" + subCode;
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.gitee.sop.gateway.message;
|
||||
|
||||
/**
|
||||
* 定义错误返回
|
||||
* code(返回码)
|
||||
* msg(返回码描述)
|
||||
* sub_code(明细返回码)
|
||||
* sub_msg(明细返回码描述)
|
||||
* 解决方案
|
||||
* @author 六如
|
||||
*/
|
||||
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,126 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
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 六如
|
||||
*/
|
||||
@Data
|
||||
public class ApiRequest implements Serializable {
|
||||
private static final long serialVersionUID = 1815097687653555654L;
|
||||
|
||||
/**
|
||||
* 分配给开发者的应用ID
|
||||
*
|
||||
* @mock 2014072300007148
|
||||
*/
|
||||
@NotBlank(message = "应用ID不能为空")
|
||||
@Length(max = 32)
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
*
|
||||
* @mock shop.goods.get
|
||||
*/
|
||||
@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
|
||||
*/
|
||||
@Length(max = 10)
|
||||
private String charset = "utf-8";
|
||||
|
||||
/**
|
||||
* 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2
|
||||
*
|
||||
* @mock RSA2
|
||||
*/
|
||||
@NotBlank(message = "sign_type不能为空")
|
||||
@Length(max = 10)
|
||||
private String signType;
|
||||
|
||||
/**
|
||||
* 商户请求参数的签名串,详见签名
|
||||
*/
|
||||
@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 notifyUrl;
|
||||
|
||||
/**
|
||||
* 授权token,详见应用授权概述
|
||||
*/
|
||||
@Length(max = 64)
|
||||
private String appAuthToken;
|
||||
|
||||
/**
|
||||
* 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各产品快速接入文档
|
||||
*/
|
||||
@NotBlank(message = "biz_content不能为空")
|
||||
private String bizContent;
|
||||
|
||||
@JsonIgnore
|
||||
@JSONField(serialize = false)
|
||||
public String takeNameVersion() {
|
||||
return method + version;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
public class ApiRequestContext {
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private ApiRequest apiRequest;
|
||||
/**
|
||||
* 本地语言
|
||||
*/
|
||||
private Locale locale;
|
||||
/**
|
||||
* 客户端ip
|
||||
*/
|
||||
private String ip;
|
||||
/**
|
||||
* traceId
|
||||
*/
|
||||
private String traceId;
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
private UploadContext uploadContext;
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.gitee.sop.gateway.common.SopConstants;
|
||||
import com.gitee.sop.gateway.config.ApiConfig;
|
||||
import com.gitee.sop.gateway.util.RequestUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiRequestContextFactory {
|
||||
|
||||
private static final String CONTENT_TYPE = "content-type";
|
||||
private static final String JSON_NAME = "json";
|
||||
private static final String TEXT_NAME = "text";
|
||||
private static final String MULTIPART = "multipart";
|
||||
public static final String FORM = "form";
|
||||
|
||||
public static ApiRequestContext build(HttpServletRequest request, ApiConfig apiConfig) {
|
||||
// get请求可能返回null
|
||||
String contentType = request.getHeader(CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
contentType = "";
|
||||
}
|
||||
ApiRequest apiRequest = new ApiRequest();
|
||||
String ip = RequestUtil.getIP(request);
|
||||
byte[] body;
|
||||
try {
|
||||
body = IOUtils.toByteArray(request.getInputStream());
|
||||
} catch (IOException e) {
|
||||
log.error("获取请求体失败", e);
|
||||
body = new byte[0];
|
||||
}
|
||||
JSONObject params = null;
|
||||
UploadContext uploadContext = null;
|
||||
String contentTypeStr = contentType.toLowerCase();
|
||||
// 如果是json方式提交
|
||||
if (StringUtils.containsAny(contentTypeStr, JSON_NAME, TEXT_NAME)) {
|
||||
params = JSON.parseObject(body);
|
||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, MULTIPART)) {
|
||||
// 如果是文件上传请求
|
||||
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(request);
|
||||
params = uploadInfo.getApiParam();
|
||||
uploadContext = uploadInfo.getUploadContext();
|
||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, FORM)) {
|
||||
// APPLICATION_FORM_URLENCODED请求
|
||||
params = RequestUtil.parseQuerystring(new String(body, SopConstants.CHARSET_UTF8));
|
||||
} else {
|
||||
// get请求,参数跟在url后面
|
||||
params = RequestUtil.parseParameterMap(request.getParameterMap());
|
||||
}
|
||||
if (params != null) {
|
||||
apiRequest = params.toJavaObject(ApiRequest.class);
|
||||
}
|
||||
|
||||
return ApiRequestContext.builder()
|
||||
.apiRequest(apiRequest)
|
||||
.locale(request.getLocale())
|
||||
.ip(ip)
|
||||
.uploadContext(uploadContext)
|
||||
.traceId(UUID.randomUUID().toString().replace("-", ""))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 存放上传文件
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public class ApiUploadContext implements UploadContext {
|
||||
|
||||
/**
|
||||
* key: 表单name
|
||||
*/
|
||||
private Map<String, List<MultipartFile>> fileMap;
|
||||
private List<MultipartFile> allFile;
|
||||
|
||||
public ApiUploadContext(Map<String, List<MultipartFile>> map) {
|
||||
if (map == null) {
|
||||
map = Collections.emptyMap();
|
||||
}
|
||||
this.fileMap = map;
|
||||
this.allFile = new ArrayList<>();
|
||||
map.values().forEach(list -> this.allFile.addAll(list));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipartFile getFile(int index) {
|
||||
return this.allFile.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MultipartFile> getFile(String name) {
|
||||
return fileMap.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MultipartFile> getAllFile() {
|
||||
return this.allFile;
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RequestFormatEnum {
|
||||
NONE(""),
|
||||
JSON("json"),
|
||||
XML("xml"),
|
||||
;
|
||||
private final String value;
|
||||
|
||||
public static RequestFormatEnum of(String value) {
|
||||
for (RequestFormatEnum requestFormatEnum : RequestFormatEnum.values()) {
|
||||
if (requestFormatEnum.value.equalsIgnoreCase(value)) {
|
||||
return requestFormatEnum;
|
||||
}
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.gitee.sop.gateway.request;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 获取上传文件
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface UploadContext {
|
||||
/**
|
||||
* 根据索引获取上传文件,从0开始
|
||||
*
|
||||
* @param index
|
||||
* @return 返回上传文件信息
|
||||
*/
|
||||
MultipartFile getFile(int index);
|
||||
|
||||
/**
|
||||
* 根据表单名获取上传文件
|
||||
*
|
||||
* @param name
|
||||
* 表单名称
|
||||
* @return 返回上传文件信息
|
||||
*/
|
||||
List<MultipartFile> getFile(String name);
|
||||
|
||||
/**
|
||||
* 获取所有的上传文件
|
||||
*
|
||||
* @return 返回所有的上传文件
|
||||
*/
|
||||
List<MultipartFile> getAllFile();
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
package com.gitee.sop.gateway.response;
|
||||
|
||||
import com.gitee.sop.gateway.exception.ApiException;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import com.gitee.sop.gateway.message.ErrorMeta;
|
||||
import com.gitee.sop.gateway.message.IError;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* 默认的结果封装类.
|
||||
* <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 六如
|
||||
*/
|
||||
@Data
|
||||
public class ApiResponse {
|
||||
|
||||
public static final String SUCCESS_CODE = "0";
|
||||
public static final String SUCCESS_MSG = "success";
|
||||
/**
|
||||
* 网关异常码,范围0~100 成功返回"0"
|
||||
*/
|
||||
private String code = SUCCESS_CODE;
|
||||
|
||||
/**
|
||||
* 网关异常信息
|
||||
*/
|
||||
private String msg = "";
|
||||
|
||||
/**
|
||||
* 业务异常码
|
||||
*/
|
||||
private String sub_code = "";
|
||||
|
||||
/**
|
||||
* 业务异常信息
|
||||
*/
|
||||
private String sub_msg = "";
|
||||
|
||||
/**
|
||||
* 返回对象
|
||||
*/
|
||||
private Object data;
|
||||
|
||||
public static ApiResponse success(Object data) {
|
||||
ApiResponse apiResponse = new ApiResponse();
|
||||
apiResponse.setCode(SUCCESS_CODE);
|
||||
apiResponse.setMsg(SUCCESS_MSG);
|
||||
apiResponse.setData(data);
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
|
||||
public static ApiResponse error(ApiException e) {
|
||||
ApiResponse apiResponse = new ApiResponse();
|
||||
IError error = e.getError();
|
||||
apiResponse.setCode(error.getCode());
|
||||
apiResponse.setMsg(error.getMsg());
|
||||
apiResponse.setSub_code(error.getSub_code());
|
||||
apiResponse.setSub_msg(error.getSub_msg());
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
public static ApiResponse error(ErrorEnum errorEnum, Locale locale, String subMsg) {
|
||||
ApiResponse apiResponse = new ApiResponse();
|
||||
ErrorMeta errorMeta = errorEnum.getErrorMeta();
|
||||
IError error = errorMeta.getError(locale);
|
||||
apiResponse.setCode(error.getCode());
|
||||
apiResponse.setMsg(error.getMsg());
|
||||
apiResponse.setSub_code(error.getSub_code());
|
||||
apiResponse.setSub_msg(subMsg);
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
public static ApiResponse error(ErrorEnum errorEnum, Locale locale) {
|
||||
ApiResponse apiResponse = new ApiResponse();
|
||||
ErrorMeta errorMeta = errorEnum.getErrorMeta();
|
||||
IError error = errorMeta.getError(locale);
|
||||
apiResponse.setCode(error.getCode());
|
||||
apiResponse.setMsg(error.getMsg());
|
||||
apiResponse.setSub_code(error.getSub_code());
|
||||
apiResponse.setSub_msg(error.getSub_msg());
|
||||
return apiResponse;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import org.apache.dubbo.common.config.ReferenceCache;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.config.utils.SimpleReferenceCache;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* dubbo泛化调用
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
public class GenericServiceInvoker implements InitializingBean {
|
||||
private static final String TRUE = "true";
|
||||
|
||||
private ApplicationConfig applicationConfig;
|
||||
|
||||
@Value("${register.address:${register.type}://${register.host}")
|
||||
private String registerAddress;
|
||||
|
||||
@Value("${spring.application.name}")
|
||||
private String appName;
|
||||
|
||||
@Value("${generic.timeout:5000}")
|
||||
private int timeout;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
RegistryConfig registryConfig = buildRegistryConfig();
|
||||
applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName(appName + "-generic");
|
||||
applicationConfig.setRegistry(registryConfig);
|
||||
}
|
||||
|
||||
private RegistryConfig buildRegistryConfig() {
|
||||
RegistryConfig config = new RegistryConfig();
|
||||
config.setAddress(registerAddress);
|
||||
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.setTimeout(timeout);
|
||||
ReferenceCache referenceCache = SimpleReferenceCache.getCache();
|
||||
GenericService genericService = referenceCache.get(reference);
|
||||
return genericService.$invoke(method, parameterTypes, params);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 参数处理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ParamExecutor<Req, Resp> {
|
||||
|
||||
/**
|
||||
* 构建请求参数上下文
|
||||
*
|
||||
* @param request request
|
||||
* @return 返回请求参数上下文
|
||||
*/
|
||||
ApiRequestContext build(Req request);
|
||||
|
||||
/**
|
||||
* 结果返回写入
|
||||
*
|
||||
* @param apiRequestContext 请求参数上下文
|
||||
* @param apiResponse 最终返回结果
|
||||
* @param response response
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
void write(ApiRequestContext apiRequestContext, ApiResponse apiResponse, Resp response) throws IOException;
|
||||
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.gitee.sop.gateway.common.SopConstants;
|
||||
import com.gitee.sop.gateway.config.ApiConfig;
|
||||
import com.gitee.sop.gateway.request.ApiRequest;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.request.UploadContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.util.RequestUtil;
|
||||
import com.gitee.sop.gateway.util.ResponseUtil;
|
||||
import com.gitee.sop.support.dto.FileData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
/**
|
||||
* 请求参数默认实现
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class ParamExecutorImpl implements ParamExecutor<HttpServletRequest, HttpServletResponse> {
|
||||
|
||||
private static final String CONTENT_TYPE = "content-type";
|
||||
private static final String JSON_NAME = "json";
|
||||
private static final String TEXT_NAME = "text";
|
||||
private static final String MULTIPART = "multipart";
|
||||
private static final String FORM = "form";
|
||||
|
||||
@Resource
|
||||
private ApiConfig apiConfig;
|
||||
|
||||
@Override
|
||||
public ApiRequestContext build(HttpServletRequest request) {
|
||||
// get请求可能返回null
|
||||
String contentType = request.getHeader(CONTENT_TYPE);
|
||||
if (contentType == null) {
|
||||
contentType = "";
|
||||
}
|
||||
ApiRequest apiRequest = new ApiRequest();
|
||||
String ip = RequestUtil.getIP(request);
|
||||
byte[] body;
|
||||
try {
|
||||
body = IOUtils.toByteArray(request.getInputStream());
|
||||
} catch (IOException e) {
|
||||
log.error("获取请求体失败", e);
|
||||
body = new byte[0];
|
||||
}
|
||||
JSONObject params = null;
|
||||
UploadContext uploadContext = null;
|
||||
String contentTypeStr = contentType.toLowerCase();
|
||||
// 如果是json方式提交
|
||||
if (StringUtils.containsAny(contentTypeStr, JSON_NAME, TEXT_NAME)) {
|
||||
params = JSON.parseObject(body);
|
||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, MULTIPART)) {
|
||||
// 如果是文件上传请求
|
||||
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(request);
|
||||
params = uploadInfo.getApiParam();
|
||||
uploadContext = uploadInfo.getUploadContext();
|
||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, FORM)) {
|
||||
// APPLICATION_FORM_URLENCODED请求
|
||||
params = RequestUtil.parseQuerystring(new String(body, SopConstants.CHARSET_UTF8));
|
||||
} else {
|
||||
// get请求,参数跟在url后面
|
||||
params = RequestUtil.parseParameterMap(request.getParameterMap());
|
||||
}
|
||||
if (params != null) {
|
||||
apiRequest = convertApiRequest(params);
|
||||
}
|
||||
|
||||
return ApiRequestContext.builder()
|
||||
.apiRequest(apiRequest)
|
||||
.locale(request.getLocale())
|
||||
.ip(ip)
|
||||
.uploadContext(uploadContext)
|
||||
.traceId(UUID.randomUUID().toString().replace("-", ""))
|
||||
.build();
|
||||
}
|
||||
|
||||
protected ApiRequest convertApiRequest(JSONObject jsonObject) {
|
||||
ApiRequest apiRequest = new ApiRequest();
|
||||
apiRequest.setAppId(jsonObject.getString(apiConfig.getAppIdName()));
|
||||
apiRequest.setMethod(jsonObject.getString(apiConfig.getApiName()));
|
||||
apiRequest.setFormat(jsonObject.getString(apiConfig.getFormatName()));
|
||||
apiRequest.setCharset(jsonObject.getString(apiConfig.getCharsetName()));
|
||||
apiRequest.setSignType(jsonObject.getString(apiConfig.getSignTypeName()));
|
||||
apiRequest.setSign(jsonObject.getString(apiConfig.getSignName()));
|
||||
apiRequest.setTimestamp(jsonObject.getString(apiConfig.getTimestampName()));
|
||||
apiRequest.setVersion(jsonObject.getString(apiConfig.getVersionName()));
|
||||
apiRequest.setNotifyUrl(jsonObject.getString(apiConfig.getNotifyUrlName()));
|
||||
apiRequest.setAppAuthToken(jsonObject.getString(apiConfig.getAppAuthTokenName()));
|
||||
apiRequest.setBizContent(jsonObject.getString(apiConfig.getBizContentName()));
|
||||
return apiRequest;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(ApiRequestContext apiRequestContext, ApiResponse apiResponse, HttpServletResponse response) throws IOException {
|
||||
Object data = apiResponse.getData();
|
||||
if (data instanceof FileData) {
|
||||
FileData fileData = (FileData) data;
|
||||
ResponseUtil.writerFile(fileData, response);
|
||||
} else {
|
||||
// 此处还可以判断charset,返回xml格式
|
||||
ResponseUtil.writerText(apiRequestContext, apiResponse, response);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
|
||||
/**
|
||||
* 接口路由
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface RouteService {
|
||||
|
||||
/**
|
||||
* 路由
|
||||
*
|
||||
* @param apiRequestContext 接口上下文
|
||||
* @return 返回结果
|
||||
*/
|
||||
ApiResponse route(ApiRequestContext apiRequestContext);
|
||||
|
||||
}
|
@@ -0,0 +1,208 @@
|
||||
package com.gitee.sop.gateway.service;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.common.AttachmentNames;
|
||||
import com.gitee.sop.gateway.common.ParamInfoDTO;
|
||||
import com.gitee.sop.gateway.exception.ApiException;
|
||||
import com.gitee.sop.gateway.exception.ExceptionExecutor;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import com.gitee.sop.gateway.request.ApiRequest;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.request.UploadContext;
|
||||
import com.gitee.sop.gateway.response.ApiResponse;
|
||||
import com.gitee.sop.gateway.service.interceptor.RouteInterceptor;
|
||||
import com.gitee.sop.gateway.service.validate.Validator;
|
||||
import com.gitee.sop.gateway.util.ClassUtil;
|
||||
import com.gitee.sop.support.dto.CommonFileData;
|
||||
import com.gitee.sop.support.dto.FileData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.common.utils.ClassUtils;
|
||||
import org.apache.dubbo.rpc.RpcContext;
|
||||
import org.apache.dubbo.rpc.RpcContextAttachment;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
/**
|
||||
* 接口路由
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class RouteServiceImpl implements RouteService {
|
||||
|
||||
@Resource
|
||||
protected Validator validator;
|
||||
|
||||
@Resource
|
||||
protected GenericServiceInvoker genericServiceInvoker;
|
||||
|
||||
@Resource
|
||||
protected ExceptionExecutor exceptionExecutor;
|
||||
|
||||
@Autowired(required = false)
|
||||
private List<RouteInterceptor> routeInterceptors;
|
||||
|
||||
@Override
|
||||
public ApiResponse route(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
log.info("收到客户端请求, ip={}, apiRequest={}", apiRequestContext.getIp(), apiRequest);
|
||||
try {
|
||||
// 接口校验
|
||||
ApiInfoDTO apiInfoDTO = validator.validate(apiRequestContext);
|
||||
this.doPreRoute(apiRequestContext, apiInfoDTO);
|
||||
// 微服务结果
|
||||
Object result = doRoute(apiRequestContext, apiInfoDTO);
|
||||
result = this.doAfterRoute(apiRequestContext, apiInfoDTO, result);
|
||||
return ApiResponse.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("接口请求报错, , ip={}, apiRequest={}", apiRequestContext.getIp(), apiRequest, e);
|
||||
return exceptionExecutor.executeException(apiRequestContext, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object doRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfo) {
|
||||
setAttachment(apiRequestContext);
|
||||
String paramInfo = apiInfo.getParamInfo();
|
||||
List<ParamInfoDTO> paramInfoList = JSON.parseArray(paramInfo, ParamInfoDTO.class);
|
||||
return genericServiceInvoker.invoke(
|
||||
apiInfo.getInterfaceClassName(),
|
||||
apiInfo.getMethodName(),
|
||||
buildParamType(paramInfoList),
|
||||
buildInvokeParam(apiRequestContext, paramInfoList)
|
||||
);
|
||||
}
|
||||
|
||||
protected void setAttachment(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
RpcContextAttachment clientAttachment = RpcContext.getClientAttachment();
|
||||
clientAttachment.setAttachment(AttachmentNames.APP_ID_NAME, apiRequest.getAppId());
|
||||
clientAttachment.setAttachment(AttachmentNames.API_NAME, apiRequest.getMethod());
|
||||
clientAttachment.setAttachment(AttachmentNames.VERSION_NAME, apiRequest.getVersion());
|
||||
clientAttachment.setAttachment(AttachmentNames.APP_AUTH_TOKEN_NAME, apiRequest.getAppAuthToken());
|
||||
clientAttachment.setAttachment(AttachmentNames.NOTIFY_URL_NAME, apiRequest.getNotifyUrl());
|
||||
clientAttachment.setAttachment(AttachmentNames.CLIENT_IP, apiRequestContext.getIp());
|
||||
clientAttachment.setAttachment(AttachmentNames.TRACE_ID, apiRequestContext.getTraceId());
|
||||
}
|
||||
|
||||
protected void doPreRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO) {
|
||||
for (RouteInterceptor routeInterceptor : routeInterceptors) {
|
||||
routeInterceptor.preRoute(apiRequestContext, apiInfoDTO);
|
||||
}
|
||||
}
|
||||
|
||||
protected Object doAfterRoute(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO, Object result) {
|
||||
Object ret = result;
|
||||
for (RouteInterceptor routeInterceptor : routeInterceptors) {
|
||||
ret = routeInterceptor.afterRoute(apiRequestContext, apiInfoDTO, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected String[] buildParamType(List<ParamInfoDTO> paramInfoList) {
|
||||
if (ObjectUtils.isEmpty(paramInfoList)) {
|
||||
return new String[0];
|
||||
}
|
||||
return paramInfoList.stream()
|
||||
.map(ParamInfoDTO::getType)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
protected Object[] buildInvokeParam(ApiRequestContext apiRequestContext, List<ParamInfoDTO> paramInfoList) {
|
||||
if (ObjectUtils.isEmpty(paramInfoList)) {
|
||||
return new Object[0];
|
||||
}
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
String bizContent = apiRequest.getBizContent();
|
||||
JSONObject jsonObject = JSON.parseObject(bizContent);
|
||||
List<Object> params = new ArrayList<>();
|
||||
for (ParamInfoDTO paramInfoDTO : paramInfoList) {
|
||||
String type = paramInfoDTO.getType();
|
||||
String actualType = paramInfoDTO.getActualType();
|
||||
// 处理文件上传
|
||||
if (Objects.equals(type, FileData.class.getName()) || Objects.equals(actualType, FileData.class.getName())) {
|
||||
Optional<Object> fileParam = buildFileParam(apiRequestContext, paramInfoDTO);
|
||||
if (!fileParam.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
Object param = fileParam.get();
|
||||
params.add(param);
|
||||
} else {
|
||||
if (ClassUtil.isPrimitive(type)) {
|
||||
String paramName = paramInfoDTO.getName();
|
||||
try {
|
||||
Object value = jsonObject.getObject(paramName, ClassUtils.forName(type));
|
||||
params.add(value);
|
||||
jsonObject.remove(paramName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
log.error("找不到参数class, paramInfoDTO={}, apiRequest={}", paramInfoDTO, apiRequest, e);
|
||||
throw new RuntimeException("找不到class:" + type, e);
|
||||
}
|
||||
} else {
|
||||
params.add(jsonObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
return params.toArray(new Object[0]);
|
||||
}
|
||||
|
||||
protected Optional<Object> buildFileParam(ApiRequestContext apiRequestContext, ParamInfoDTO paramInfoDTO) {
|
||||
UploadContext uploadContext = apiRequestContext.getUploadContext();
|
||||
if (uploadContext == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<MultipartFile> files = uploadContext.getFile(paramInfoDTO.getName());
|
||||
List<FileData> fileDataList = new ArrayList<>(files.size());
|
||||
for (MultipartFile multipartFile : files) {
|
||||
CommonFileData fileData = new CommonFileData();
|
||||
fileData.setName(multipartFile.getName());
|
||||
fileData.setOriginalFilename(multipartFile.getOriginalFilename());
|
||||
fileData.setContentType(multipartFile.getContentType());
|
||||
try {
|
||||
fileData.setData(multipartFile.getBytes());
|
||||
} catch (IOException e) {
|
||||
log.error("读取文件内容失败, apiRequestContext={}", apiRequestContext, e);
|
||||
throw new ApiException(ErrorEnum.ISP_SERVICE_UNKNOWN_ERROR, apiRequestContext.getLocale());
|
||||
}
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
String type = paramInfoDTO.getType();
|
||||
Object fileParam = isCollectionClass(type) ? fileDataList : fileDataList.get(0);
|
||||
return Optional.of(fileParam);
|
||||
}
|
||||
|
||||
private boolean isCollectionClass(String className) {
|
||||
Class<?> clazz = parseClass(className);
|
||||
return clazz != null && Collection.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
private Class<?> parseClass(String className) {
|
||||
try {
|
||||
return Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (routeInterceptors == null) {
|
||||
routeInterceptors = new ArrayList<>();
|
||||
}
|
||||
routeInterceptors.sort(Comparator.comparing(RouteInterceptor::getOrder));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.gitee.sop.gateway.service.dubbo;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.common.StatusEnum;
|
||||
import com.gitee.sop.gateway.dao.entity.ApiInfo;
|
||||
import com.gitee.sop.gateway.dao.mapper.ApiInfoMapper;
|
||||
import com.gitee.sop.gateway.service.manager.ApiManager;
|
||||
import com.gitee.sop.gateway.util.CopyUtil;
|
||||
import com.gitee.sop.support.service.ApiRegisterService;
|
||||
import com.gitee.sop.support.service.dto.RegisterDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@DubboService
|
||||
public class ApiRegisterServiceImpl implements ApiRegisterService {
|
||||
|
||||
@Resource
|
||||
private ApiManager apiCacheManager;
|
||||
|
||||
@Resource
|
||||
private ApiInfoMapper apiInfoMapper;
|
||||
|
||||
@Override
|
||||
public void register(RegisterDTO registerDTO) {
|
||||
log.info("收到接口注册, registerDTO={}", registerDTO);
|
||||
ApiInfoDTO apiInfoDTO = CopyUtil.copyBean(registerDTO, ApiInfoDTO::new);
|
||||
apiInfoDTO.setStatus(StatusEnum.ENABLE.getValue());
|
||||
|
||||
ApiInfo apiInfo = apiInfoMapper.getByNameVersion(apiInfoDTO.getApiName(), apiInfoDTO.getApiVersion());
|
||||
if (apiInfo == null) {
|
||||
apiInfo = new ApiInfo();
|
||||
}
|
||||
CopyUtil.copyPropertiesIgnoreNull(apiInfoDTO, apiInfo);
|
||||
apiInfo.setRegSource(1);
|
||||
// 保存到数据库
|
||||
apiInfoMapper.saveOrUpdate(apiInfo);
|
||||
// 保存到缓存
|
||||
apiCacheManager.save(apiInfoDTO);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.gitee.sop.gateway.service.dubbo;
|
||||
|
||||
import com.gitee.sop.gateway.service.manager.IsvManager;
|
||||
import com.gitee.sop.support.service.IsvService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@DubboService
|
||||
@Slf4j
|
||||
public class IsvServiceImpl implements IsvService {
|
||||
|
||||
@Resource
|
||||
private IsvManager isvManager;
|
||||
|
||||
@Override
|
||||
public void refresh(String appId) {
|
||||
log.info("refresh Isv, appId={}", appId);
|
||||
// 刷新isv信息
|
||||
isvManager.reload(appId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package com.gitee.sop.gateway.service.interceptor;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
|
||||
/**
|
||||
* 路由拦截器
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface RouteInterceptor {
|
||||
|
||||
/**
|
||||
* 在路由转发前执行,签名校验通过后会立即执行此方法
|
||||
*
|
||||
* @param context context
|
||||
* @param apiInfoDTO 接口信息
|
||||
*/
|
||||
default void preRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 微服务返回结果后执行
|
||||
*
|
||||
* @param context context
|
||||
* @param apiInfoDTO 接口信息
|
||||
* @param result 返回结果,通常是HashMap
|
||||
* @return 返回格式化后的结果, 可对原结果进行修改
|
||||
*/
|
||||
default Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器执行顺序,值小优先执行,建议从0开始,小于0留给系统使用
|
||||
*
|
||||
* @return 返回顺序
|
||||
*/
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配,返回true执行拦截器,默认true
|
||||
*
|
||||
* @param context context
|
||||
* @return 返回true执行拦截器
|
||||
*/
|
||||
default boolean match(ApiRequestContext context) {
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.gitee.sop.gateway.service.interceptor;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class RouteInterceptorOrders {
|
||||
|
||||
public static final int RESULT_INTERCEPTOR = -1000;
|
||||
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package com.gitee.sop.gateway.service.interceptor.internal;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.service.interceptor.RouteInterceptor;
|
||||
import com.gitee.sop.gateway.service.interceptor.RouteInterceptorOrders;
|
||||
import com.gitee.sop.support.dto.CommonFileData;
|
||||
import com.gitee.sop.support.dto.FileData;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* 对结果进行处理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public class ResultRouteInterceptor implements RouteInterceptor {
|
||||
|
||||
private static final String CLASS = "class";
|
||||
private static final String KEY_NAME = "name";
|
||||
private static final String KEY_ORIGINAL_FILENAME = "originalFilename";
|
||||
private static final String KEY_CONTENT_TYPE = "contentType";
|
||||
private static final String KEY_BYTES = "bytes";
|
||||
|
||||
@Override
|
||||
public Object afterRoute(ApiRequestContext context, ApiInfoDTO apiInfoDTO, Object result) {
|
||||
if (result == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
if (result instanceof Map) {
|
||||
Map<String, Object> map = (Map<String, Object>) result;
|
||||
Object className = map.get(CLASS);
|
||||
// 处理文件下载
|
||||
if (Objects.equals(className, FileData.class.getName()) || Objects.equals(className, CommonFileData.class.getName())) {
|
||||
/*
|
||||
{
|
||||
"size": 27,
|
||||
"bytes": "c3ByaW5nLnByb2ZpbGVzLmFjdGl2ZT1kZXYK",
|
||||
"name": null,
|
||||
"inputStream": {
|
||||
"class": "java.io.ByteArrayInputStream"
|
||||
},
|
||||
"contentType": null,
|
||||
"originalFilename": "application.properties",
|
||||
"empty": false
|
||||
*/
|
||||
CommonFileData commonFileData = new CommonFileData();
|
||||
commonFileData.setName(String.valueOf(map.get(KEY_NAME)));
|
||||
commonFileData.setOriginalFilename(String.valueOf(map.get(KEY_ORIGINAL_FILENAME)));
|
||||
commonFileData.setContentType(String.valueOf(map.get(KEY_CONTENT_TYPE)));
|
||||
commonFileData.setData((byte[]) map.get(KEY_BYTES));
|
||||
return commonFileData;
|
||||
}
|
||||
}
|
||||
|
||||
// 其它情况
|
||||
if (result instanceof Map) {
|
||||
((Map<?, ?>) result).remove(CLASS);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return RouteInterceptorOrders.RESULT_INTERCEPTOR;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.gitee.sop.gateway.service.manager;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface ApiManager {
|
||||
|
||||
void save(ApiInfoDTO apiInfoDTO);
|
||||
|
||||
ApiInfoDTO get(String apiName, String apiVersion);
|
||||
|
||||
default ApiInfoDTO getOrElse(String apiName, String apiVersion, Supplier<ApiInfoDTO> supplier) {
|
||||
ApiInfoDTO apiInfoDTO = get(apiName, apiVersion);
|
||||
if (apiInfoDTO == null) {
|
||||
apiInfoDTO = supplier.get();
|
||||
}
|
||||
if (apiInfoDTO != null) {
|
||||
save(apiInfoDTO);
|
||||
}
|
||||
return apiInfoDTO;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.gateway.service.manager;
|
||||
|
||||
/**
|
||||
* IP黑名单管理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IpBlacklistManager {
|
||||
|
||||
boolean contains(String ip);
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
package com.gitee.sop.gateway.service.manager;
|
||||
|
||||
/**
|
||||
* isv接口授权管理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IsvApiPermissionManager {
|
||||
|
||||
boolean hasPermission(String appId, String apiNameVersion);
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.gitee.sop.gateway.service.manager;
|
||||
|
||||
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public interface IsvManager {
|
||||
|
||||
/**
|
||||
* 获取isv信息
|
||||
*
|
||||
* @param appId appId
|
||||
* @return 返回isv信息, 没有返回null
|
||||
*/
|
||||
IsvDTO getIsv(String appId);
|
||||
|
||||
/**
|
||||
* 重新加载isv信息到内存中
|
||||
*
|
||||
* @param appId appId
|
||||
*/
|
||||
void reload(String appId);
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
package com.gitee.sop.gateway.service.manager;
|
||||
|
||||
/**
|
||||
* 秘钥管理
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
public interface SecretManager {
|
||||
|
||||
/**
|
||||
* 获取用户上传的公钥
|
||||
*
|
||||
* @param appId appId
|
||||
* @return 返回公钥内容
|
||||
*/
|
||||
String getIsvPublicKey(String appId);
|
||||
|
||||
String reload(String appId);
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.gitee.sop.gateway.service.manager.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Data
|
||||
public class IsvDTO {
|
||||
|
||||
private String appId;
|
||||
|
||||
private Integer status;
|
||||
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.service.manager.IpBlacklistManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
public class IpBlacklistManagerImpl implements IpBlacklistManager {
|
||||
@Override
|
||||
public boolean contains(String ip) {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Service
|
||||
public class IsvApiPermissionManagerImpl implements IsvApiPermissionManager {
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String appId, String apiNameVersion) {
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.dao.entity.ApiInfo;
|
||||
import com.gitee.sop.gateway.dao.mapper.ApiInfoMapper;
|
||||
import com.gitee.sop.gateway.service.manager.ApiManager;
|
||||
import com.gitee.sop.gateway.util.CopyUtil;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 本地存储接口信息.
|
||||
* @author 六如
|
||||
*/
|
||||
public class LocalApiManagerImpl implements ApiManager {
|
||||
|
||||
private static final Map<String, Optional<ApiInfoDTO>> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Resource
|
||||
protected ApiInfoMapper apiInfoMapper;
|
||||
|
||||
@Override
|
||||
public void save(ApiInfoDTO apiInfoDTO) {
|
||||
String key = apiInfoDTO.buildApiNameVersion();
|
||||
CACHE.put(key, Optional.of(apiInfoDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiInfoDTO get(String apiName, String apiVersion) {
|
||||
String key = apiName + apiVersion;
|
||||
return CACHE.computeIfAbsent(key, k-> {
|
||||
ApiInfo apiInfo = apiInfoMapper.getByNameVersion(apiName, apiVersion);
|
||||
return Optional.ofNullable(CopyUtil.copyBean(apiInfo, ApiInfoDTO::new));
|
||||
}).orElse(null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.dao.entity.IsvInfo;
|
||||
import com.gitee.sop.gateway.dao.mapper.IsvInfoMapper;
|
||||
import com.gitee.sop.gateway.service.manager.IsvManager;
|
||||
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
|
||||
import com.gitee.sop.gateway.util.CopyUtil;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class LocalIsvManagerImpl implements IsvManager {
|
||||
|
||||
private static final Map<String, Optional<IsvDTO>> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@Resource
|
||||
protected IsvInfoMapper isvInfoMapper;
|
||||
|
||||
@Override
|
||||
public IsvDTO getIsv(String appId) {
|
||||
return CACHE.computeIfAbsent(appId, k -> {
|
||||
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
|
||||
return Optional.ofNullable(CopyUtil.copyBean(isvInfo, IsvDTO::new));
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(String appId) {
|
||||
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
|
||||
IsvDTO isvDTO = CopyUtil.copyBean(isvInfo, IsvDTO::new);
|
||||
CACHE.put(appId, Optional.ofNullable(isvDTO));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.dao.entity.IsvKeys;
|
||||
import com.gitee.sop.gateway.dao.mapper.IsvKeysMapper;
|
||||
import com.gitee.sop.gateway.service.manager.SecretManager;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
public class LocalSecretManagerImpl implements SecretManager {
|
||||
|
||||
private static final Map<String, Optional<String>> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
@Resource
|
||||
protected IsvKeysMapper isvKeysMapper;
|
||||
|
||||
@Override
|
||||
public String getIsvPublicKey(String appId) {
|
||||
return CACHE.computeIfAbsent(appId, k -> {
|
||||
String publicKey = isvKeysMapper.query()
|
||||
.eq(IsvKeys::getAppId, appId)
|
||||
.getValue(IsvKeys::getPublicKeyIsv);
|
||||
return Optional.ofNullable(publicKey);
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String reload(String appId) {
|
||||
String publicKey = isvKeysMapper.query()
|
||||
.eq(IsvKeys::getAppId, appId)
|
||||
.getValue(IsvKeys::getPublicKeyIsv);
|
||||
CACHE.put(appId, Optional.ofNullable(publicKey));
|
||||
return publicKey;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.dao.entity.ApiInfo;
|
||||
import com.gitee.sop.gateway.util.CopyUtil;
|
||||
import com.gitee.sop.gateway.util.JsonUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* redis存储接口信息
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisApiManagerImpl extends LocalApiManagerImpl {
|
||||
|
||||
private static final String KEY_API = "sop:api";
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public void save(ApiInfoDTO apiInfoDTO) {
|
||||
String key = apiInfoDTO.buildApiNameVersion();
|
||||
stringRedisTemplate.opsForHash().put(KEY_API, key, JsonUtil.toJSONString(apiInfoDTO));
|
||||
}
|
||||
|
||||
protected void cache(String key, ApiInfo apiInfo) {
|
||||
ApiInfoDTO apiInfoDTO = CopyUtil.copyBean(apiInfo, ApiInfoDTO::new);
|
||||
stringRedisTemplate.opsForHash().put(KEY_API, key, JsonUtil.toJSONString(apiInfoDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiInfoDTO get(String apiName, String apiVersion) {
|
||||
String key = apiName + apiVersion;
|
||||
try {
|
||||
Object value = stringRedisTemplate.opsForHash().get(KEY_API, key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return JsonUtil.parseObject(String.valueOf(value), ApiInfoDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("redis访问失败", e);
|
||||
return super.get(apiName, apiVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("load apiInfo to redis");
|
||||
List<ApiInfo> apiInfos = this.apiInfoMapper.listAll();
|
||||
for (ApiInfo apiInfo : apiInfos) {
|
||||
String key = apiInfo.getApiName() + apiInfo.getApiVersion();
|
||||
this.cache(key, apiInfo);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.dao.entity.IsvInfo;
|
||||
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
|
||||
import com.gitee.sop.gateway.util.CopyUtil;
|
||||
import com.gitee.sop.gateway.util.JsonUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisIsvManagerImpl extends LocalIsvManagerImpl {
|
||||
|
||||
private static final String KEY_ISV = "sop:isv";
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
|
||||
@Override
|
||||
public IsvDTO getIsv(String appId) {
|
||||
try {
|
||||
Object value = stringRedisTemplate.opsForHash().get(KEY_ISV, appId);
|
||||
if (value == null) {
|
||||
IsvInfo isvInfo = this.isvInfoMapper.getByAppId(appId);
|
||||
return this.cache(isvInfo);
|
||||
}
|
||||
return JsonUtil.parseObject(String.valueOf(value), IsvDTO.class);
|
||||
} catch (Exception e) {
|
||||
log.error("操作redis失败", e);
|
||||
return super.getIsv(appId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(String appId) {
|
||||
IsvInfo isvInfo = isvInfoMapper.getByAppId(appId);
|
||||
this.cache(isvInfo);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("load isvInfo to redis");
|
||||
List<IsvInfo> isvInfos = this.isvInfoMapper.listAll();
|
||||
for (IsvInfo isvInfo : isvInfos) {
|
||||
this.cache(isvInfo);
|
||||
}
|
||||
}
|
||||
|
||||
protected IsvDTO cache(IsvInfo isvInfo) {
|
||||
IsvDTO isvDTO = CopyUtil.copyBean(isvInfo, IsvDTO::new);
|
||||
stringRedisTemplate.opsForHash().put(KEY_ISV, isvInfo.getAppId(), JsonUtil.toJSONString(isvDTO));
|
||||
return isvDTO;
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package com.gitee.sop.gateway.service.manager.impl;
|
||||
|
||||
import com.gitee.sop.gateway.dao.entity.IsvKeys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public class RedisSecretManager extends LocalSecretManagerImpl {
|
||||
|
||||
private static final String KEY_ISV = "sop:sec";
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public String getIsvPublicKey(String appId) {
|
||||
try {
|
||||
Object value = stringRedisTemplate.opsForHash().get(KEY_ISV, appId);
|
||||
if (value == null) {
|
||||
return this.reload(appId);
|
||||
}
|
||||
return String.valueOf(value);
|
||||
} catch (Exception e) {
|
||||
log.error("操作redis失败", e);
|
||||
return super.getIsvPublicKey(appId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String reload(String appId) {
|
||||
IsvKeys isvKeys = this.isvKeysMapper.getByAppId(appId);
|
||||
return this.cache(appId, isvKeys);
|
||||
}
|
||||
|
||||
|
||||
protected String cache(String appId, IsvKeys isvKeys) {
|
||||
String publicKey = Optional.ofNullable(isvKeys).map(IsvKeys::getPublicKeyIsv).orElse("");
|
||||
stringRedisTemplate.opsForHash().put(KEY_ISV, appId, publicKey);
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("load isvKey to redis");
|
||||
List<IsvKeys> isvKeys = this.isvKeysMapper.listAll();
|
||||
for (IsvKeys isvKey : isvKeys) {
|
||||
this.cache(isvKey.getAppId(), isvKey);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.gitee.sop.gateway.service.validate;
|
||||
|
||||
import com.gitee.sop.gateway.request.ApiRequest;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.exception.ApiException;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractSigner implements Signer {
|
||||
|
||||
/**
|
||||
* 构建服务端签名串
|
||||
*
|
||||
* @param apiRequestContext 接口参数
|
||||
* @param secret 秘钥
|
||||
* @return 返回服务端签名串
|
||||
*/
|
||||
protected abstract String buildServerSign(ApiRequestContext apiRequestContext, String secret);
|
||||
|
||||
@Override
|
||||
public boolean checkSign(ApiRequestContext apiRequestContext, String publicKey) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
String clientSign = apiRequest.getSign();
|
||||
if (StringUtils.isBlank(clientSign)) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, apiRequestContext.getLocale());
|
||||
}
|
||||
String serverSign = buildServerSign(apiRequestContext, publicKey);
|
||||
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.gateway.service.validate;
|
||||
|
||||
import com.gitee.sop.gateway.util.AESUtil;
|
||||
import com.gitee.sop.gateway.util.RSANewUtil;
|
||||
import com.gitee.sop.gateway.util.RSAUtil;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
/**
|
||||
* 负责各类加解密
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
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,298 @@
|
||||
package com.gitee.sop.gateway.service.validate;
|
||||
|
||||
import com.gitee.sop.gateway.common.ApiInfoDTO;
|
||||
import com.gitee.sop.gateway.common.StatusEnum;
|
||||
import com.gitee.sop.gateway.config.ApiConfig;
|
||||
import com.gitee.sop.gateway.exception.ApiException;
|
||||
import com.gitee.sop.gateway.message.ErrorEnum;
|
||||
import com.gitee.sop.gateway.request.ApiRequest;
|
||||
import com.gitee.sop.gateway.request.ApiRequestContext;
|
||||
import com.gitee.sop.gateway.request.RequestFormatEnum;
|
||||
import com.gitee.sop.gateway.request.UploadContext;
|
||||
import com.gitee.sop.gateway.service.manager.ApiManager;
|
||||
import com.gitee.sop.gateway.service.manager.IpBlacklistManager;
|
||||
import com.gitee.sop.gateway.service.manager.IsvApiPermissionManager;
|
||||
import com.gitee.sop.gateway.service.manager.IsvManager;
|
||||
import com.gitee.sop.gateway.service.manager.SecretManager;
|
||||
import com.gitee.sop.gateway.service.manager.dto.IsvDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 负责校验,校验工作都在这里
|
||||
*
|
||||
* @author 六如
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApiValidator implements Validator {
|
||||
|
||||
private static final long MILLISECOND_OF_ONE_SECOND = 1000;
|
||||
|
||||
/**
|
||||
* 单个文件内容最大值
|
||||
*/
|
||||
@Value("${upload.one-file-max-size}")
|
||||
private DataSize oneFileMaxSize;
|
||||
|
||||
/**
|
||||
* 总文件最大值
|
||||
*/
|
||||
@Value("${upload.total-file-max-size}")
|
||||
private DataSize maxFileSize;
|
||||
|
||||
@Resource
|
||||
private Signer signer;
|
||||
|
||||
@Resource
|
||||
private ApiConfig apiConfig;
|
||||
|
||||
@Resource
|
||||
private ApiManager apiCacheManager;
|
||||
|
||||
@Resource
|
||||
private IpBlacklistManager ipBlacklistManager;
|
||||
|
||||
@Resource
|
||||
private IsvApiPermissionManager isvApiPermissionManager;
|
||||
|
||||
@Resource
|
||||
private IsvManager isvManager;
|
||||
|
||||
@Resource
|
||||
private SecretManager secretManager;
|
||||
|
||||
private DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
@Override
|
||||
public ApiInfoDTO validate(ApiRequestContext apiRequestContext) {
|
||||
// 校验字段完整性
|
||||
checkField(apiRequestContext);
|
||||
// 检查isv
|
||||
IsvDTO isvDTO = checkIsv(apiRequestContext);
|
||||
// 校验签名
|
||||
checkSign(apiRequestContext, isvDTO);
|
||||
// 检查是否超时
|
||||
checkTimeout(apiRequestContext);
|
||||
// 检查格式化
|
||||
checkFormat(apiRequestContext);
|
||||
// IP能否访问
|
||||
checkIP(apiRequestContext);
|
||||
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
ApiInfoDTO apiInfo = apiCacheManager.get(apiRequest.getMethod(), apiRequest.getVersion());
|
||||
// 检查接口信息
|
||||
checkApiInfo(apiRequestContext, apiInfo);
|
||||
|
||||
// 检查上传文件
|
||||
checkUploadFile(apiRequestContext);
|
||||
// 检查token
|
||||
checkToken(apiRequestContext);
|
||||
return apiInfo;
|
||||
}
|
||||
|
||||
public void checkApiInfo(ApiRequestContext apiRequestContext, ApiInfoDTO apiInfoDTO) {
|
||||
// 检查路由是否存在
|
||||
if (apiInfoDTO == null) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_METHOD, apiRequestContext.getLocale());
|
||||
}
|
||||
// 检查路由是否启用
|
||||
if (!BooleanUtils.toBoolean(apiInfoDTO.getStatus())) {
|
||||
throw new ApiException(ErrorEnum.ISP_API_DISABLED, apiRequestContext.getLocale());
|
||||
}
|
||||
// 校验是否需要授权访问
|
||||
boolean needCheckPermission = BooleanUtils.toBoolean(apiInfoDTO.getIsPermission());
|
||||
if (needCheckPermission) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
String appKey = apiRequest.getAppId();
|
||||
String nameVersion = apiRequest.takeNameVersion();
|
||||
boolean hasPermission = isvApiPermissionManager.hasPermission(appKey, nameVersion);
|
||||
if (!hasPermission) {
|
||||
throw new ApiException(ErrorEnum.ISV_ROUTE_NO_PERMISSIONS, apiRequestContext.getLocale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void checkField(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
if (apiRequest == null) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_PARAMETER, apiRequestContext.getLocale());
|
||||
}
|
||||
Locale locale = apiRequestContext.getLocale();
|
||||
if (ObjectUtils.isEmpty(apiRequest.getAppId())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_APP_ID, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getMethod())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_METHOD, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getVersion())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_VERSION, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getSignType())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getCharset())) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_CHARSET, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getSign())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, locale);
|
||||
}
|
||||
if (ObjectUtils.isEmpty(apiRequest.getTimestamp())) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_TIMESTAMP, locale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在IP黑名单中
|
||||
*
|
||||
* @param apiRequestContext 接口参数
|
||||
*/
|
||||
protected void checkIP(ApiRequestContext apiRequestContext) {
|
||||
String ip = apiRequestContext.getIp();
|
||||
if (ipBlacklistManager.contains(ip)) {
|
||||
throw new ApiException(ErrorEnum.ISV_IP_FORBIDDEN, apiRequestContext.getLocale());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验上传文件内容
|
||||
*
|
||||
* @param apiRequestContext apiRequestContext
|
||||
*/
|
||||
protected void checkUploadFile(ApiRequestContext apiRequestContext) {
|
||||
// 校验上传文件内容
|
||||
UploadContext uploadContext = apiRequestContext.getUploadContext();
|
||||
if (uploadContext != null) {
|
||||
List<MultipartFile> allFiles = uploadContext.getAllFile();
|
||||
if (ObjectUtils.isEmpty(allFiles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (MultipartFile multipartFile : allFiles) {
|
||||
checkSingleFileSize(apiRequestContext, multipartFile);
|
||||
}
|
||||
|
||||
long totalSize = allFiles.stream()
|
||||
.map(MultipartFile::getSize)
|
||||
.mapToLong(Long::longValue)
|
||||
.sum();
|
||||
|
||||
if (totalSize > maxFileSize.toBytes()) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_FILE_SIZE, apiRequestContext.getLocale(), totalSize, maxFileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个文件大小
|
||||
*
|
||||
* @param file 文件
|
||||
*/
|
||||
private void checkSingleFileSize(ApiRequestContext apiRequestContext, MultipartFile file) {
|
||||
long fileSize = file.getSize();
|
||||
long maxSize = oneFileMaxSize.toBytes();
|
||||
if (fileSize > maxSize) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_FILE_SIZE, apiRequestContext.getLocale(), fileSize, maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkTimeout(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
int timeoutSeconds = apiConfig.getTimeoutSeconds();
|
||||
// 如果设置为0,表示不校验
|
||||
if (timeoutSeconds == 0) {
|
||||
return;
|
||||
}
|
||||
if (timeoutSeconds < 0) {
|
||||
throw new IllegalArgumentException("服务端timeoutSeconds设置错误");
|
||||
}
|
||||
String requestTime = apiRequest.getTimestamp();
|
||||
try {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(requestTime, dateTimeFormatter);
|
||||
long diffMills = Duration.between(localDateTime, LocalDateTime.now()).toMillis();
|
||||
if (diffMills > timeoutSeconds * MILLISECOND_OF_ONE_SECOND) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_TIMESTAMP, apiRequestContext.getLocale());
|
||||
}
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_TIMESTAMP, apiRequestContext.getLocale(), apiRequest.takeNameVersion());
|
||||
}
|
||||
}
|
||||
|
||||
protected IsvDTO checkIsv(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
IsvDTO isv = isvManager.getIsv(apiRequest.getAppId());
|
||||
// 没有用户
|
||||
if (isv == null) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_APP_ID, apiRequestContext.getLocale());
|
||||
}
|
||||
// 禁止访问
|
||||
if (isv.getStatus() == null || isv.getStatus() == StatusEnum.DISABLE.getValue()) {
|
||||
throw new ApiException(ErrorEnum.ISV_ACCESS_FORBIDDEN, apiRequestContext.getLocale());
|
||||
}
|
||||
return isv;
|
||||
}
|
||||
|
||||
protected void checkSign(ApiRequestContext apiRequestContext, IsvDTO isv) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
String clientSign = apiRequest.getSign();
|
||||
if (ObjectUtils.isEmpty(clientSign)) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE, apiRequestContext.getLocale(),
|
||||
apiRequest.takeNameVersion(), apiConfig.getSignName());
|
||||
}
|
||||
// ISV上传的公钥
|
||||
String publicKey = secretManager.getIsvPublicKey(isv.getAppId());
|
||||
if (ObjectUtils.isEmpty(publicKey)) {
|
||||
throw new ApiException(ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG, apiRequestContext.getLocale(),
|
||||
apiRequest.takeNameVersion());
|
||||
}
|
||||
// 错误的sign
|
||||
if (!signer.checkSign(apiRequestContext, publicKey)) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_SIGNATURE, apiRequestContext.getLocale(),
|
||||
apiRequest.takeNameVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void checkFormat(ApiRequestContext apiRequestContext) {
|
||||
ApiRequest apiRequest = apiRequestContext.getApiRequest();
|
||||
String format = apiRequest.getFormat();
|
||||
if (ObjectUtils.isEmpty(format)) {
|
||||
return;
|
||||
}
|
||||
if (RequestFormatEnum.of(format) == RequestFormatEnum.NONE) {
|
||||
throw new ApiException(ErrorEnum.ISV_INVALID_FORMAT, apiRequestContext.getLocale(),
|
||||
apiRequest.takeNameVersion(), format);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验token
|
||||
*
|
||||
* @param apiRequestContext 参数
|
||||
*/
|
||||
protected void checkToken(ApiRequestContext apiRequestContext) {
|
||||
// todo:校验token
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
dateTimeFormatter = DateTimeFormatter.ofPattern(apiConfig.getTimestampPattern());
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user