mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
5.0
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -13,7 +13,7 @@
|
|||||||
<artifactId>sop-parent</artifactId>
|
<artifactId>sop-parent</artifactId>
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
<version>5.0.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<description>一个开放平台解决方案项目,基于Spring Cloud实现,目标是能够让用户快速得搭建起自己的开放平台</description>
|
<description>一个开放平台解决方案项目,基于Dubbo实现,目标是能够让用户快速得搭建起自己的开放平台</description>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>doc</module>
|
<module>doc</module>
|
||||||
|
29
sop-auth/.gitignore
vendored
29
sop-auth/.gitignore
vendored
@@ -1,29 +0,0 @@
|
|||||||
HELP.md
|
|
||||||
/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/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
112
sop-auth/pom.xml
112
sop-auth/pom.xml
@@ -1,112 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-parent</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>sop-auth</artifactId>
|
|
||||||
<name>sop-auth</name>
|
|
||||||
<description>授权程序</description>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<java.version>1.8</java.version>
|
|
||||||
<oltu.version>0.31</oltu.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<!-- sop相关配置 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-service-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- sop相关配置 end-->
|
|
||||||
|
|
||||||
<!-- 使用nacos注册中心 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sdk-java</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<!-- http请求 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
|
||||||
<artifactId>okhttp</artifactId>
|
|
||||||
<version>3.14.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.oschina.durcframework</groupId>
|
|
||||||
<artifactId>fastmybatis-spring-boot-starter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>mysql</groupId>
|
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- oauth2服务端 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.oltu.oauth2</groupId>
|
|
||||||
<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
|
|
||||||
<version>${oltu.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.oltu.oauth2</groupId>
|
|
||||||
<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
|
|
||||||
<version>${oltu.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
<version>27.1-jre</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
@@ -1,11 +0,0 @@
|
|||||||
# 应用授权服务
|
|
||||||
|
|
||||||
- 启动注册中心、网关、本服务(sop-auth)
|
|
||||||
- 浏览器访问:http://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback
|
|
||||||
- 输入用户名密码登录,这里是`zhangsan/123456`
|
|
||||||
|
|
||||||
授权接口在`OAuth2Controller`中,查看回调接口在`CallbackController`中
|
|
||||||
|
|
||||||
回调接口应该由开发者实现,这里为了演示,写在一起。
|
|
||||||
|
|
||||||
token的维护,重点关注`OAuth2ManagerRedis.java`
|
|
@@ -1,15 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
|
||||||
|
|
||||||
@EnableDiscoveryClient
|
|
||||||
@SpringBootApplication
|
|
||||||
public class SopAuthApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(SopAuthApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class AccessToken {
|
|
||||||
/** 过期时间,毫秒 */
|
|
||||||
private long expireTimeMillis;
|
|
||||||
private OpenUser openUser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param expireIn 有效时长,秒
|
|
||||||
* @param openUser
|
|
||||||
*/
|
|
||||||
public AccessToken(long expireIn, OpenUser openUser) {
|
|
||||||
super();
|
|
||||||
if(expireIn <= 0) {
|
|
||||||
throw new IllegalArgumentException("expireIn必须大于0");
|
|
||||||
}
|
|
||||||
this.expireTimeMillis = System.currentTimeMillis() + (expireIn * 1000);
|
|
||||||
this.openUser = openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isExpired() {
|
|
||||||
return expireTimeMillis < System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getExpireTimeMillis() {
|
|
||||||
return expireTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpireTimeMillis(long expireTimeMillis) {
|
|
||||||
this.expireTimeMillis = expireTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenUser getOpenUser() {
|
|
||||||
return openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOpenUser(OpenUser openUser) {
|
|
||||||
this.openUser = openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface AppIdManager {
|
|
||||||
/**
|
|
||||||
* 是否是合法的appId
|
|
||||||
*
|
|
||||||
* @param appId
|
|
||||||
* @return true:合法
|
|
||||||
*/
|
|
||||||
boolean isValidAppId(String appId);
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取accessToken参数对象
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class FetchTokenParam {
|
|
||||||
/**
|
|
||||||
* 授权类型。<br>
|
|
||||||
* 如果使用app_auth_code换取token,则为authorization_code,
|
|
||||||
* 如果使用refresh_token换取新的token,则为refresh_token
|
|
||||||
*/
|
|
||||||
@NotBlank(message = "授权类型不能为空")
|
|
||||||
private String grant_type;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权码.与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的app_auth_code值
|
|
||||||
*/
|
|
||||||
private String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌.与code二选一,可为空,刷新令牌时使用
|
|
||||||
*/
|
|
||||||
private String refresh_token;
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用app_auth_code换取app_auth_token返回结果
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class FetchTokenResult {
|
|
||||||
/**
|
|
||||||
* 授权令牌
|
|
||||||
*/
|
|
||||||
private String app_auth_token;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户id
|
|
||||||
*/
|
|
||||||
private String user_id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌有效期. -1:永久有效
|
|
||||||
*/
|
|
||||||
private long expires_in = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌有效期. -1:永久有效
|
|
||||||
*/
|
|
||||||
private long re_expires_in = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌时使用。
|
|
||||||
* 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token
|
|
||||||
*/
|
|
||||||
private String app_refresh_token;
|
|
||||||
|
|
||||||
private String error;
|
|
||||||
private String error_description;
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class OAuth2Config {
|
|
||||||
|
|
||||||
private static OAuth2Config instance = new OAuth2Config();
|
|
||||||
|
|
||||||
public static OAuth2Config getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setInstance(OAuth2Config instance) {
|
|
||||||
OAuth2Config.instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用授权的app_auth_code唯一的;app_auth_code使用一次后失效,一天(从生成app_auth_code开始的24小时)未被使用自动过期;
|
|
||||||
* app_auth_token永久有效。
|
|
||||||
*/
|
|
||||||
private int codeTimeoutSeconds = 86400;
|
|
||||||
/**
|
|
||||||
* accessToken有效时间,单位秒
|
|
||||||
* -1 永久有效
|
|
||||||
*/
|
|
||||||
private int accessTokenExpiresIn = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* app_refresh_token有效时间,单位秒,accessToken有效时间的3倍。当小于0,永久有效
|
|
||||||
*/
|
|
||||||
private int refreshTokenExpiresIn = accessTokenExpiresIn * 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token
|
|
||||||
*/
|
|
||||||
private int afterRefreshExpiresIn = 60 * 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录视图页面用于,mvc视图,如:loginView
|
|
||||||
*/
|
|
||||||
private String oauth2LoginUri = "/oauth2/login";
|
|
||||||
}
|
|
@@ -1,85 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
|
|
||||||
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 认证服务,需要自己实现
|
|
||||||
* @author 六如
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface OAuth2Manager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加 auth code
|
|
||||||
*
|
|
||||||
* @param authCode
|
|
||||||
* code值
|
|
||||||
* @param authUser
|
|
||||||
* 用户
|
|
||||||
*/
|
|
||||||
void addAuthCode(String authCode, OpenUser authUser);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加accessToken
|
|
||||||
* @param accessToken token值
|
|
||||||
* @param refreshToken refreshToken
|
|
||||||
* @param authUser 用户
|
|
||||||
*/
|
|
||||||
void addAccessToken(String accessToken, String refreshToken, OpenUser authUser);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除这个accessToken
|
|
||||||
* @param accessToken
|
|
||||||
*/
|
|
||||||
void removeAccessToken(String accessToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除这个refreshToken
|
|
||||||
* @param refreshToken
|
|
||||||
*/
|
|
||||||
void removeRefreshToken(String refreshToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取RefreshToken
|
|
||||||
* @param refreshToken
|
|
||||||
* @return 返回Token信息
|
|
||||||
*/
|
|
||||||
RefreshToken getRefreshToken(String refreshToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证auth code是否有效
|
|
||||||
*
|
|
||||||
* @param authCode
|
|
||||||
* @return 无效返回false
|
|
||||||
*/
|
|
||||||
boolean checkAuthCode(String authCode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据auth code获取用户
|
|
||||||
*
|
|
||||||
* @param authCode
|
|
||||||
* @return 返回用户
|
|
||||||
*/
|
|
||||||
OpenUser getUserByAuthCode(String authCode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据access token获取用户
|
|
||||||
*
|
|
||||||
* @param accessToken
|
|
||||||
* token值
|
|
||||||
* @return 返回用户
|
|
||||||
*/
|
|
||||||
OpenUser getUserByAccessToken(String accessToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户登录,需判断是否已经登录
|
|
||||||
* @param request
|
|
||||||
* @return 返回用户对象
|
|
||||||
* @throws LoginErrorException 登录失败异常
|
|
||||||
*/
|
|
||||||
OpenUser login(HttpServletRequest request) throws LoginErrorException;
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
|
|
||||||
import org.apache.oltu.oauth2.common.message.OAuthResponse;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface OAuth2Service {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* oauth2授权,获取code.
|
|
||||||
* <pre>
|
|
||||||
* 1、首先通过如http://localhost:8080/api/authorize?client_id=test&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2callback访问授权页面;
|
|
||||||
2、该控制器首先检查clientId是否正确;如果错误将返回相应的错误信息;
|
|
||||||
3、然后判断用户是否登录了,如果没有登录首先到登录页面登录;
|
|
||||||
4、登录成功后生成相应的code即授权码,然后重定向到客户端地址,如http://localhost:8080/oauth2callback?code=6d250650831fea227749f49a5b49ccad;在重定向到的地址中会带上code参数(授权码),接着客户端可以根据授权码去换取accessToken。
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param request req
|
|
||||||
* @param response resp
|
|
||||||
* @param authConfig 配置
|
|
||||||
* @return 返回响应内容
|
|
||||||
* @throws URISyntaxException
|
|
||||||
* @throws OAuthSystemException
|
|
||||||
*/
|
|
||||||
OAuthResponse authorize(HttpServletRequest request, HttpServletResponse response, OAuth2Config authConfig)
|
|
||||||
throws URISyntaxException, OAuthSystemException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过code获取accessToken.
|
|
||||||
* @param fetchTokenParam
|
|
||||||
* @param authConfig 配置项
|
|
||||||
* @return 返回响应内容
|
|
||||||
* @throws URISyntaxException
|
|
||||||
* @throws OAuthSystemException
|
|
||||||
*/
|
|
||||||
FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig);
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果要使用oauth2功能,自己的用户类需要实现这个对象
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface OpenUser extends Serializable {
|
|
||||||
/**
|
|
||||||
* 返回用户id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
String getUserId();
|
|
||||||
/**
|
|
||||||
* 返回用户名
|
|
||||||
*
|
|
||||||
* @return 返回用户名
|
|
||||||
*/
|
|
||||||
String getUsername();
|
|
||||||
}
|
|
@@ -1,40 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class RefreshToken implements Serializable {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private String accessToken;
|
|
||||||
private OpenUser openUser;
|
|
||||||
|
|
||||||
public RefreshToken(String accessToken, OpenUser openUser) {
|
|
||||||
super();
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.openUser = openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RefreshToken() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessToken(String accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenUser getOpenUser() {
|
|
||||||
return openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOpenUser(OpenUser openUser) {
|
|
||||||
this.openUser = openUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 存放accessToken和refreshToken
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class TokenPair {
|
|
||||||
private String accessToken;
|
|
||||||
private String refreshToken;
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessToken(String accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRefreshToken() {
|
|
||||||
return refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRefreshToken(String refreshToken) {
|
|
||||||
this.refreshToken = refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth.exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class LoginErrorException extends Exception {
|
|
||||||
private static final long serialVersionUID = -6721499454527023339L;
|
|
||||||
|
|
||||||
public LoginErrorException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginErrorException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginErrorException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginErrorException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,33 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth.impl;
|
|
||||||
|
|
||||||
import com.gitee.sop.sopauth.auth.AppIdManager;
|
|
||||||
import com.gitee.sop.sopauth.entity.IsvInfo;
|
|
||||||
import com.gitee.sop.sopauth.mapper.IsvInfoMapper;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class AppIdManagerImpl implements AppIdManager {
|
|
||||||
|
|
||||||
public static final int FORBIDDEN = 2;
|
|
||||||
@Autowired
|
|
||||||
private IsvInfoMapper isvInfoMapper;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValidAppId(String appId) {
|
|
||||||
IsvInfo isvInfo = isvInfoMapper.getByColumn("app_key", appId);
|
|
||||||
if (isvInfo == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (isvInfo.getStatus().intValue() == FORBIDDEN) {
|
|
||||||
log.error("appId已禁用:{}", appId);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,128 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth.impl;
|
|
||||||
|
|
||||||
import com.gitee.fastmybatis.core.query.Query;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Config;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Manager;
|
|
||||||
import com.gitee.sop.sopauth.auth.OpenUser;
|
|
||||||
import com.gitee.sop.sopauth.auth.RefreshToken;
|
|
||||||
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
|
|
||||||
import com.gitee.sop.sopauth.entity.UserInfo;
|
|
||||||
import com.gitee.sop.sopauth.mapper.UserInfoMapper;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* oauth2管理,默认谷歌缓存实现,跟redis实现只能用一个。
|
|
||||||
* 这里为了演示,使用本地缓存,正式环境请使用redis保存
|
|
||||||
* @see OAuth2ManagerRedis OAuth2ManagerRedis
|
|
||||||
* @author 六如
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class OAuth2ManagerCache implements OAuth2Manager {
|
|
||||||
/**
|
|
||||||
* 应用授权的app_auth_code唯一的;app_auth_code使用一次后失效,一天(从生成app_auth_code开始的24小时)未被使用自动过期;
|
|
||||||
* app_auth_token永久有效。
|
|
||||||
*/
|
|
||||||
private int codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds();
|
|
||||||
/**
|
|
||||||
* accessToken过期时间
|
|
||||||
* -1 永久有效
|
|
||||||
*/
|
|
||||||
private int accessTokenTimeoutSeconds = OAuth2Config.getInstance().getAccessTokenExpiresIn();
|
|
||||||
|
|
||||||
private int refreshTokenTimeoutSeconds = OAuth2Config.getInstance().getRefreshTokenExpiresIn();
|
|
||||||
|
|
||||||
private LoadingCache<String, OpenUser> codeCache = buildCache(codeTimeoutSeconds);
|
|
||||||
private LoadingCache<String, OpenUser> accessTokenCache = buildCache(accessTokenTimeoutSeconds);
|
|
||||||
private LoadingCache<String, RefreshToken> refreshTokenCache = buildCache(refreshTokenTimeoutSeconds);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserInfoMapper userInfoMapper;
|
|
||||||
|
|
||||||
|
|
||||||
private static <T> LoadingCache<String, T> buildCache(int timeout) {
|
|
||||||
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder();
|
|
||||||
if (timeout > 0) {
|
|
||||||
cacheBuilder.expireAfterAccess(timeout, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
return cacheBuilder
|
|
||||||
.build(new CacheLoader<String, T>() {
|
|
||||||
@Override
|
|
||||||
public T load(String key) throws Exception {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAuthCode(String authCode, OpenUser authUser) {
|
|
||||||
codeCache.put(authCode, authUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) {
|
|
||||||
accessTokenCache.put(accessToken, authUser);
|
|
||||||
refreshTokenCache.put(refreshToken, new RefreshToken(accessToken, authUser));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAccessToken(String accessToken) {
|
|
||||||
accessTokenCache.asMap().remove(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRefreshToken(String refreshToken) {
|
|
||||||
refreshTokenCache.asMap().remove(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RefreshToken getRefreshToken(String refreshToken) {
|
|
||||||
return refreshTokenCache.getIfPresent(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkAuthCode(String authCode) {
|
|
||||||
return codeCache.asMap().containsKey(authCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser getUserByAuthCode(String authCode) {
|
|
||||||
return codeCache.getIfPresent(authCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser getUserByAccessToken(String accessToken) {
|
|
||||||
return accessTokenCache.getIfPresent(accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser login(HttpServletRequest request) throws LoginErrorException {
|
|
||||||
// 这里应该先检查用户有没有登录,如果登录直接返回openUser
|
|
||||||
String username = request.getParameter("username");
|
|
||||||
String password = request.getParameter("password");
|
|
||||||
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
|
||||||
throw new LoginErrorException("用户名密码不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
Query query = new Query();
|
|
||||||
query.eq("username", username)
|
|
||||||
.eq("password", password);
|
|
||||||
UserInfo userInfo = userInfoMapper.getByQuery(query);
|
|
||||||
|
|
||||||
if(userInfo == null) {
|
|
||||||
throw new LoginErrorException("用户名密码不正确");
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,168 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth.impl;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.gitee.fastmybatis.core.query.Query;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Config;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Manager;
|
|
||||||
import com.gitee.sop.sopauth.auth.OpenUser;
|
|
||||||
import com.gitee.sop.sopauth.auth.RefreshToken;
|
|
||||||
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
|
|
||||||
import com.gitee.sop.sopauth.entity.UserInfo;
|
|
||||||
import com.gitee.sop.sopauth.mapper.UserInfoMapper;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* oauth2管理redis实现,这个类跟OAuth2ManagerCache类只能用一个,
|
|
||||||
* 如果要用这个类,
|
|
||||||
* 1、注释掉OAuth2ManagerCache的@Service。
|
|
||||||
* 2、在properties中配置redis
|
|
||||||
* 3、启用这个类的@Service
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
//@Service
|
|
||||||
public class OAuth2ManagerRedis implements OAuth2Manager {
|
|
||||||
|
|
||||||
private static String CODE_PREFIX = "com.gitee.sop.oauth2_code:";
|
|
||||||
private static String ACCESS_TOKEN_PREFIX = "com.gitee.sop.oauth2_access_token:";
|
|
||||||
private static String REFRESH_TOKEN_PREFIX = "com.gitee.sop.oauth2_refresh_token:";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private StringRedisTemplate redisTemplate;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserInfoMapper userInfoMapper;
|
|
||||||
|
|
||||||
public static String getCodeKey(String code) {
|
|
||||||
return CODE_PREFIX + code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getAccessTokenKey(String accessToken) {
|
|
||||||
return ACCESS_TOKEN_PREFIX + accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRefreshTokenKey(String refreshToken) {
|
|
||||||
return REFRESH_TOKEN_PREFIX + refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAuthCode(String authCode, OpenUser authUser) {
|
|
||||||
long codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds();
|
|
||||||
redisTemplate.opsForValue().set(getCodeKey(authCode),
|
|
||||||
JSON.toJSONString(authUser),
|
|
||||||
codeTimeoutSeconds,
|
|
||||||
TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) {
|
|
||||||
// 存accessToken
|
|
||||||
long expiresIn = OAuth2Config.getInstance().getAccessTokenExpiresIn();
|
|
||||||
long reExpiresIn = OAuth2Config.getInstance().getRefreshTokenExpiresIn();
|
|
||||||
if (expiresIn > 0) {
|
|
||||||
redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser), expiresIn, TimeUnit.SECONDS);
|
|
||||||
} else {
|
|
||||||
redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser));
|
|
||||||
}
|
|
||||||
// 存refreshToken
|
|
||||||
if (reExpiresIn > 0) {
|
|
||||||
redisTemplate.opsForValue().set(
|
|
||||||
getRefreshTokenKey(refreshToken),
|
|
||||||
JSON.toJSONString(new RefreshToken(accessToken, authUser)),
|
|
||||||
// refreshToken过期时间
|
|
||||||
reExpiresIn,
|
|
||||||
TimeUnit.SECONDS);
|
|
||||||
} else {
|
|
||||||
redisTemplate.opsForValue().set(
|
|
||||||
getRefreshTokenKey(refreshToken),
|
|
||||||
JSON.toJSONString(new RefreshToken(accessToken, authUser)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAccessToken(String accessToken) {
|
|
||||||
String accessTokenKey = getAccessTokenKey(accessToken);
|
|
||||||
int afterRefreshExpiresIn = OAuth2Config.getInstance().getAfterRefreshExpiresIn();
|
|
||||||
// 刷新令牌后,保证老的app_auth_token从刷新开始10分钟内可继续使用
|
|
||||||
redisTemplate.expire(accessTokenKey, afterRefreshExpiresIn, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeRefreshToken(String refreshToken) {
|
|
||||||
redisTemplate.delete(getRefreshTokenKey(refreshToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RefreshToken getRefreshToken(String refreshToken) {
|
|
||||||
String json = redisTemplate.opsForValue().get(getRefreshTokenKey(refreshToken));
|
|
||||||
if(StringUtils.isEmpty(json)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
JSONObject jsonObj = JSON.parseObject(json);
|
|
||||||
|
|
||||||
String userJson = jsonObj.getString("openUser");
|
|
||||||
UserInfo user = JSON.parseObject(userJson, UserInfo.class);
|
|
||||||
String accessToken = jsonObj.getString("accessToken");
|
|
||||||
|
|
||||||
return new RefreshToken(accessToken, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkAuthCode(String authCode) {
|
|
||||||
if(StringUtils.isEmpty(authCode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return redisTemplate.hasKey(getCodeKey(authCode));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser getUserByAuthCode(String authCode) {
|
|
||||||
String json = redisTemplate.opsForValue().get(getCodeKey(authCode));
|
|
||||||
if(StringUtils.isEmpty(json)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return JSON.parseObject(json, UserInfo.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser getUserByAccessToken(String accessToken) {
|
|
||||||
String json = redisTemplate.opsForValue().get(getAccessTokenKey(accessToken));
|
|
||||||
if(StringUtils.isEmpty(json)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return JSON.parseObject(json, UserInfo.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OpenUser login(HttpServletRequest request) throws LoginErrorException {
|
|
||||||
// 这里应该先检查用户有没有登录,如果登录直接返回openUser
|
|
||||||
|
|
||||||
String username = request.getParameter("username");
|
|
||||||
String password = request.getParameter("password");
|
|
||||||
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
|
|
||||||
throw new LoginErrorException("用户名密码不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
Query query = new Query();
|
|
||||||
query.eq("username", username)
|
|
||||||
.eq("password", password);
|
|
||||||
UserInfo userInfo = userInfoMapper.getByQuery(query);
|
|
||||||
|
|
||||||
if(userInfo == null) {
|
|
||||||
throw new LoginErrorException("用户名密码不正确");
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,262 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.auth.impl;
|
|
||||||
|
|
||||||
import com.gitee.sop.sopauth.auth.AppIdManager;
|
|
||||||
import com.gitee.sop.sopauth.auth.FetchTokenParam;
|
|
||||||
import com.gitee.sop.sopauth.auth.FetchTokenResult;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Config;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Manager;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Service;
|
|
||||||
import com.gitee.sop.sopauth.auth.OpenUser;
|
|
||||||
import com.gitee.sop.sopauth.auth.RefreshToken;
|
|
||||||
import com.gitee.sop.sopauth.auth.TokenPair;
|
|
||||||
import com.gitee.sop.sopauth.auth.exception.LoginErrorException;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
|
|
||||||
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
|
|
||||||
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
|
|
||||||
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
|
|
||||||
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
|
|
||||||
import org.apache.oltu.oauth2.common.OAuth;
|
|
||||||
import org.apache.oltu.oauth2.common.error.OAuthError;
|
|
||||||
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
|
|
||||||
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
|
|
||||||
import org.apache.oltu.oauth2.common.message.OAuthResponse;
|
|
||||||
import org.apache.oltu.oauth2.common.message.types.GrantType;
|
|
||||||
import org.apache.oltu.oauth2.common.message.types.ResponseType;
|
|
||||||
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* oauth2服务端默认实现
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class OAuth2ServiceImpl implements OAuth2Service {
|
|
||||||
|
|
||||||
|
|
||||||
private static final String TOKEN_TYPE = "Bearer";
|
|
||||||
public static final String APP_ID_NAME = "app_id";
|
|
||||||
|
|
||||||
private OAuthIssuer oauthIssuer = new OAuthIssuerImpl(new MD5Generator());
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private OAuth2Manager oauth2Manager;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AppIdManager appIdManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuthResponse authorize(HttpServletRequest request, HttpServletResponse resp, OAuth2Config authConfig)
|
|
||||||
throws URISyntaxException, OAuthSystemException {
|
|
||||||
try {
|
|
||||||
// 构建OAuth 授权请求
|
|
||||||
OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
|
|
||||||
String clientId = oauthRequest.getClientId();
|
|
||||||
// 检查传入的客户端id是否正确
|
|
||||||
if (!appIdManager.isValidAppId(clientId)) {
|
|
||||||
return OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
|
|
||||||
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
|
|
||||||
.setErrorDescription(OAuthError.TokenResponse.INVALID_CLIENT).buildJSONMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果用户没有登录,跳转到登陆页面
|
|
||||||
OpenUser user = null;
|
|
||||||
try {
|
|
||||||
user = oauth2Manager.login(request);
|
|
||||||
} catch (LoginErrorException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
request.setAttribute("error", e.getMessage());
|
|
||||||
try {
|
|
||||||
request.getRequestDispatcher(authConfig.getOauth2LoginUri()).forward(request, resp);
|
|
||||||
return null;
|
|
||||||
} catch (Exception e1) {
|
|
||||||
log.error(e1.getMessage(), e1);
|
|
||||||
throw new RuntimeException(e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成授权码
|
|
||||||
String authorizationCode = null;
|
|
||||||
// responseType目前仅支持CODE,另外还有TOKEN
|
|
||||||
String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
|
|
||||||
if (responseType.equals(ResponseType.CODE.toString())) {
|
|
||||||
OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
|
|
||||||
authorizationCode = oauthIssuerImpl.authorizationCode();
|
|
||||||
oauth2Manager.addAuthCode(authorizationCode, user);
|
|
||||||
}
|
|
||||||
// 进行OAuth响应构建
|
|
||||||
OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request,
|
|
||||||
HttpServletResponse.SC_FOUND);
|
|
||||||
// 设置授权码
|
|
||||||
builder.setCode(authorizationCode);
|
|
||||||
builder.setParam(APP_ID_NAME, clientId);
|
|
||||||
// 得到到客户端重定向地址
|
|
||||||
String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
|
|
||||||
|
|
||||||
// 构建响应
|
|
||||||
return builder.location(redirectURI).buildQueryMessage();
|
|
||||||
} catch (OAuthProblemException e) {
|
|
||||||
// 出错处理
|
|
||||||
String redirectUri = e.getRedirectUri();
|
|
||||||
if (OAuthUtils.isEmpty(redirectUri)) {
|
|
||||||
// 告诉客户端没有传入redirectUri直接报错
|
|
||||||
String error = "OAuth redirectUri needs to be provided by client!!!";
|
|
||||||
return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
|
|
||||||
.error(OAuthProblemException.error(error)).location(redirectUri).buildQueryMessage();
|
|
||||||
} else {
|
|
||||||
// 返回错误消息(如?error=)
|
|
||||||
return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND).error(e).location(redirectUri)
|
|
||||||
.buildQueryMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig) {
|
|
||||||
try {
|
|
||||||
// 这里需要检查client_id和client_secret,但是已经通过网关接口访问进来了
|
|
||||||
// 表示client_id和client_secret是合法的
|
|
||||||
|
|
||||||
// 检查验证类型,如果是第一次进来用code换取accessToken
|
|
||||||
String grant_type = fetchTokenParam.getGrant_type();
|
|
||||||
if (GrantType.AUTHORIZATION_CODE.toString().equals(grant_type)) {
|
|
||||||
String authCode = fetchTokenParam.getCode();
|
|
||||||
if (!oauth2Manager.checkAuthCode(authCode)) {
|
|
||||||
FetchTokenResult errorResult = new FetchTokenResult();
|
|
||||||
errorResult.setError(OAuthError.CodeResponse.INVALID_REQUEST);
|
|
||||||
errorResult.setError_description("invalid request");
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
// 生成Access Token
|
|
||||||
OpenUser user = oauth2Manager.getUserByAuthCode(authCode);
|
|
||||||
if (user == null) {
|
|
||||||
throw OAuthProblemException.error("Can not found user by code.");
|
|
||||||
}
|
|
||||||
|
|
||||||
TokenPair tokenPair = this.createNewToken(user);
|
|
||||||
|
|
||||||
oauth2Manager.addAccessToken(tokenPair.getAccessToken(), tokenPair.getRefreshToken(), user);
|
|
||||||
|
|
||||||
// 生成OAuth响应
|
|
||||||
return this.buildTokenResult(tokenPair, user);
|
|
||||||
} else if (GrantType.REFRESH_TOKEN.toString().equals(grant_type)) {
|
|
||||||
// 用refreshToken来刷新accessToken
|
|
||||||
String refreshToken = fetchTokenParam.getRefresh_token();
|
|
||||||
if (StringUtils.isEmpty(refreshToken)) {
|
|
||||||
FetchTokenResult errorResult = new FetchTokenResult();
|
|
||||||
errorResult.setError(OAuthError.ResourceResponse.EXPIRED_TOKEN);
|
|
||||||
errorResult.setError_description("expired token");
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshToken refreshTokenObj = oauth2Manager.getRefreshToken(refreshToken);
|
|
||||||
if (refreshTokenObj == null) {
|
|
||||||
FetchTokenResult errorResult = new FetchTokenResult();
|
|
||||||
errorResult.setError(OAuthError.ResourceResponse.INVALID_TOKEN);
|
|
||||||
errorResult.setError_description("invalid token");
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenUser user = refreshTokenObj.getOpenUser();
|
|
||||||
// 老的token对
|
|
||||||
TokenPair oldTokenPair = new TokenPair();
|
|
||||||
oldTokenPair.setAccessToken(refreshTokenObj.getAccessToken());
|
|
||||||
oldTokenPair.setRefreshToken(refreshToken);
|
|
||||||
// 创建一对新的accessToken和refreshToken
|
|
||||||
TokenPair newTokenPair = this.createRefreshToken(user, oldTokenPair);
|
|
||||||
|
|
||||||
this.afterRefreshToken(oldTokenPair, newTokenPair, user);
|
|
||||||
|
|
||||||
// 返回新的accessToken和refreshToken
|
|
||||||
return this.buildTokenResult(newTokenPair, user);
|
|
||||||
} else {
|
|
||||||
FetchTokenResult errorResult = new FetchTokenResult();
|
|
||||||
errorResult.setError(OAuthError.TokenResponse.INVALID_GRANT);
|
|
||||||
errorResult.setError_description("invalid grant");
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (OAuthProblemException e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
FetchTokenResult errorResult = new FetchTokenResult();
|
|
||||||
errorResult.setError(e.getMessage());
|
|
||||||
errorResult.setError_description("invalid grant");
|
|
||||||
return errorResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新token后续操作
|
|
||||||
*
|
|
||||||
* @param oldTokenPair 老的token
|
|
||||||
* @param newTokenPair 新的token
|
|
||||||
* @param user 用户
|
|
||||||
*/
|
|
||||||
protected void afterRefreshToken(TokenPair oldTokenPair, TokenPair newTokenPair, OpenUser user) {
|
|
||||||
// 保存token
|
|
||||||
oauth2Manager.addAccessToken(newTokenPair.getAccessToken(), newTokenPair.getRefreshToken(), user);
|
|
||||||
|
|
||||||
// 删除老的accessToken
|
|
||||||
oauth2Manager.removeAccessToken(oldTokenPair.getAccessToken());
|
|
||||||
// 删除老的refreshToken
|
|
||||||
oauth2Manager.removeRefreshToken(oldTokenPair.getRefreshToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建新的token
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* @return 返回新token
|
|
||||||
*/
|
|
||||||
protected TokenPair createNewToken(OpenUser user) {
|
|
||||||
return this.createDefaultTokenPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回刷新后token
|
|
||||||
*
|
|
||||||
* @param user 用户
|
|
||||||
* @param oldTokenPair 旧的token
|
|
||||||
* @return 返回新的token
|
|
||||||
*/
|
|
||||||
protected TokenPair createRefreshToken(OpenUser user, TokenPair oldTokenPair) {
|
|
||||||
return this.createDefaultTokenPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
private TokenPair createDefaultTokenPair() {
|
|
||||||
TokenPair tokenPair = new TokenPair();
|
|
||||||
try {
|
|
||||||
String accessToken = oauthIssuer.accessToken();
|
|
||||||
String refreshToken = oauthIssuer.refreshToken();
|
|
||||||
|
|
||||||
tokenPair.setAccessToken(accessToken);
|
|
||||||
tokenPair.setRefreshToken(refreshToken);
|
|
||||||
|
|
||||||
return tokenPair;
|
|
||||||
} catch (OAuthSystemException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FetchTokenResult buildTokenResult(TokenPair tokenPair, OpenUser user) {
|
|
||||||
OAuth2Config auth2Config = OAuth2Config.getInstance();
|
|
||||||
FetchTokenResult fetchTokenResult = new FetchTokenResult();
|
|
||||||
fetchTokenResult.setApp_auth_token(tokenPair.getAccessToken());
|
|
||||||
fetchTokenResult.setApp_refresh_token(tokenPair.getRefreshToken());
|
|
||||||
fetchTokenResult.setUser_id(user.getUserId());
|
|
||||||
fetchTokenResult.setExpires_in(auth2Config.getAccessTokenExpiresIn());
|
|
||||||
fetchTokenResult.setRe_expires_in(auth2Config.getRefreshTokenExpiresIn());
|
|
||||||
return fetchTokenResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.config;
|
|
||||||
|
|
||||||
import com.gitee.sop.servercommon.bean.ServiceConfig;
|
|
||||||
import com.gitee.sop.servercommon.configuration.AlipayServiceConfiguration;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用支付宝开放平台功能
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class OpenServiceConfig extends AlipayServiceConfiguration {
|
|
||||||
|
|
||||||
|
|
||||||
static {
|
|
||||||
ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,54 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.controller;
|
|
||||||
|
|
||||||
import com.gitee.sop.sdk.client.OpenClient;
|
|
||||||
import com.gitee.sop.sdk.model.OpenAuthTokenAppModel;
|
|
||||||
import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest;
|
|
||||||
import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
@Slf4j
|
|
||||||
public class CallbackController {
|
|
||||||
String url = "http://localhost:8081/api";
|
|
||||||
String appId = "2019032617262200001";
|
|
||||||
// 平台提供的私钥
|
|
||||||
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
|
|
||||||
|
|
||||||
OpenClient openClient = new OpenClient(url, appId, privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟开发者回调,这里需要开发者自己实现.通过code换取token
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@GetMapping("oauth2callback")
|
|
||||||
@ResponseBody
|
|
||||||
public OpenAuthTokenAppResponse callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
|
|
||||||
String app_id = servletRequest.getParameter("app_id");
|
|
||||||
String code = servletRequest.getParameter("code");
|
|
||||||
log.info("app_id:{}, code:{}", app_id, code);
|
|
||||||
|
|
||||||
OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
|
|
||||||
OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
|
|
||||||
model.setCode(code);
|
|
||||||
model.setGrant_type("authorization_code");
|
|
||||||
request.setBizModel(model);
|
|
||||||
|
|
||||||
// 根据code获取token
|
|
||||||
OpenAuthTokenAppResponse response = openClient.execute(request);
|
|
||||||
if (response.isSuccess()) {
|
|
||||||
// 成功拿到token,开发者在这里保存token信息
|
|
||||||
// 后续使用token进行接口访问
|
|
||||||
log.info("授权成功,body:{}", response.getBody());
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,88 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.controller;
|
|
||||||
|
|
||||||
import com.gitee.sop.servercommon.annotation.Open;
|
|
||||||
import com.gitee.sop.sopauth.auth.FetchTokenParam;
|
|
||||||
import com.gitee.sop.sopauth.auth.FetchTokenResult;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Config;
|
|
||||||
import com.gitee.sop.sopauth.auth.OAuth2Service;
|
|
||||||
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
|
|
||||||
import org.apache.oltu.oauth2.common.message.OAuthResponse;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.ModelMap;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权认证
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("oauth2")
|
|
||||||
public class OAuth2Controller {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private OAuth2Service oAuth2Service;
|
|
||||||
|
|
||||||
// 第一步:授权URL拼装
|
|
||||||
// http://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback
|
|
||||||
@GetMapping("appToAppAuth")
|
|
||||||
public String appToAppAuth(HttpServletRequest request, ModelMap modelMap) {
|
|
||||||
String app_id = request.getParameter("app_id");
|
|
||||||
String redirect_uri = request.getParameter("redirect_uri");
|
|
||||||
modelMap.put("response_type", "code");
|
|
||||||
modelMap.put("client_id", app_id);
|
|
||||||
modelMap.put("redirect_uri", redirect_uri);
|
|
||||||
return "oauth2login";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 第二步:点击登录,到这里拿code
|
|
||||||
* oauth2认证获取code
|
|
||||||
* @param request
|
|
||||||
* @param resp
|
|
||||||
* @return 返回code
|
|
||||||
* @throws URISyntaxException
|
|
||||||
* @throws OAuthSystemException
|
|
||||||
*/
|
|
||||||
@RequestMapping("authorize")
|
|
||||||
public Object authorize(HttpServletRequest request, HttpServletResponse resp) throws URISyntaxException, OAuthSystemException {
|
|
||||||
OAuthResponse response = oAuth2Service.authorize(request, resp, OAuth2Config.getInstance());
|
|
||||||
if(response == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setLocation(new URI(response.getLocationUri()));
|
|
||||||
return new ResponseEntity<String>(headers, HttpStatus.valueOf(response.getResponseStatus()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 第三步,通过code获取token
|
|
||||||
* 或者,通过refresh_token换取token
|
|
||||||
* @param param
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Open("open.auth.token.app")
|
|
||||||
@RequestMapping("fetchToken")
|
|
||||||
@ResponseBody
|
|
||||||
public FetchTokenResult fetchToken(FetchTokenParam param) {
|
|
||||||
FetchTokenResult fetchTokenResult = oAuth2Service.accessToken(param, OAuth2Config.getInstance());
|
|
||||||
return fetchTokenResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("login")
|
|
||||||
public String oauth2login() {
|
|
||||||
return "oauth2login";
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.entity;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import com.gitee.fastmybatis.annotation.Pk;
|
|
||||||
import com.gitee.fastmybatis.annotation.Table;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表名: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,44 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.entity;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.annotation.JSONField;
|
|
||||||
import com.gitee.sop.sopauth.auth.OpenUser;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import com.gitee.fastmybatis.annotation.Pk;
|
|
||||||
import com.gitee.fastmybatis.annotation.Table;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表名:user_info
|
|
||||||
* 备注:用户信息表
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Table(name = "user_info",pk = @Pk(name = "id"))
|
|
||||||
@Data
|
|
||||||
public class UserInfo implements OpenUser {
|
|
||||||
/** 数据库字段:id */
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/** 用户名, 数据库字段:username */
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
/** 密码, 数据库字段:password */
|
|
||||||
@JSONField(serialize = false)
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
/** 昵称, 数据库字段:nickname */
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
/** 数据库字段:gmt_create */
|
|
||||||
private Date gmtCreate;
|
|
||||||
|
|
||||||
/** 数据库字段:gmt_modified */
|
|
||||||
private Date gmtModified;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUserId() {
|
|
||||||
return String.valueOf(id);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.mapper;
|
|
||||||
|
|
||||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
|
||||||
import com.gitee.sop.sopauth.entity.IsvInfo;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface IsvInfoMapper extends CrudMapper<IsvInfo, Long> {
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth.mapper;
|
|
||||||
|
|
||||||
import com.gitee.fastmybatis.core.mapper.CrudMapper;
|
|
||||||
import com.gitee.sop.sopauth.entity.UserInfo;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface UserInfoMapper extends CrudMapper<UserInfo, Long> {
|
|
||||||
}
|
|
@@ -1,26 +0,0 @@
|
|||||||
server.port=8087
|
|
||||||
spring.application.name=sop-auth
|
|
||||||
|
|
||||||
# ------- 需要改的配置 -------
|
|
||||||
# mysql数据库账号
|
|
||||||
mysql.host=localhost:3306
|
|
||||||
mysql.username=root
|
|
||||||
mysql.password=root
|
|
||||||
|
|
||||||
# nacos地址
|
|
||||||
register.url=127.0.0.1:8848
|
|
||||||
|
|
||||||
# ------- 需要改的配置end -------
|
|
||||||
|
|
||||||
# nacos cloud配置
|
|
||||||
spring.cloud.nacos.discovery.server-addr=${register.url}
|
|
||||||
|
|
||||||
# 数据库配置
|
|
||||||
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
|
||||||
spring.datasource.url=jdbc:mysql://${mysql.host}/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
|
|
||||||
spring.datasource.username=${mysql.username}
|
|
||||||
spring.datasource.password=${mysql.password}
|
|
||||||
|
|
||||||
spring.thymeleaf.cache=false
|
|
||||||
|
|
||||||
logging.level.com.gitee=debug
|
|
@@ -1 +0,0 @@
|
|||||||
spring.profiles.active=dev
|
|
@@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
|
||||||
xmlns:th="http://www.thymeleaf.org"
|
|
||||||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
|
|
||||||
<head>
|
|
||||||
<title>用户登录</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div th:if="${error}" style="color: red;"><span th:text="${error}"></span></div>
|
|
||||||
<form th:action="@{/oauth2/authorize}" method="post">
|
|
||||||
<input name="response_type" type="hidden" th:value="${response_type}"/>
|
|
||||||
<input name="client_id" type="hidden" th:value="${client_id}"/>
|
|
||||||
<input name="redirect_uri" type="hidden" th:value="${redirect_uri}"/>
|
|
||||||
用户名: <input type="text" name="username" value="zhangsan"/> <br/>
|
|
||||||
密码: <input type="password" name="password" value="123456"/> <br/>
|
|
||||||
<button type="submit">登录并授权</button>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,40 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth;
|
|
||||||
|
|
||||||
import com.gitee.sop.sdk.client.OpenClient;
|
|
||||||
import com.gitee.sop.sdk.model.OpenAuthTokenAppModel;
|
|
||||||
import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest;
|
|
||||||
import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse;
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class RefreshTokenTest extends TestCase {
|
|
||||||
String url = "http://localhost:8081/api";
|
|
||||||
String appId = "2019032617262200001";
|
|
||||||
// 平台提供的私钥
|
|
||||||
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=";
|
|
||||||
|
|
||||||
OpenClient openClient = new OpenClient(url, appId, privateKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据refreshToken换取token
|
|
||||||
*/
|
|
||||||
public void testRefreshToken() {
|
|
||||||
OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
|
|
||||||
OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
|
|
||||||
model.setGrant_type("refresh_token");
|
|
||||||
model.setRefresh_token("c9e4003c06fe59066eed73d64ea074ca");
|
|
||||||
request.setBizModel(model);
|
|
||||||
|
|
||||||
OpenAuthTokenAppResponse response = openClient.execute(request);
|
|
||||||
if (response.isSuccess()) {
|
|
||||||
// 成功拿到token,开发者在这里保存token信息
|
|
||||||
// 后续使用token进行接口访问
|
|
||||||
log.info("换取token成功,body:{}", response.getBody());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
package com.gitee.sop.sopauth;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@SpringBootTest
|
|
||||||
public class SopAuthApplicationTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void contextLoads() {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<java.version>1.8</java.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<version>1.18.34</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-context</artifactId>
|
|
||||||
<version>6.1.11</version>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
@@ -1,12 +0,0 @@
|
|||||||
# sop-common
|
|
||||||
|
|
||||||
- sop-bridge-nacos:注册中心桥接器,接入nacos
|
|
||||||
- sop-bridge-eureka:注册中心桥接器,接入eureka
|
|
||||||
- sop-gateway-common:提供给网关使用
|
|
||||||
- sop-service-common:提供给微服务端使用,需要打成jar
|
|
||||||
|
|
||||||
正式开发请将这些模块上传的maven私服
|
|
||||||
|
|
||||||
- 打包成jar:`mvn clean package`
|
|
||||||
- 上传到本机仓库:`mvn clean install`
|
|
||||||
- 上传到maven私服:`mvn clean deploy`
|
|
25
sop-common/sop-bridge-eureka/.gitignore
vendored
25
sop-common/sop-bridge-eureka/.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,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>sop-bridge-eureka</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
|
||||||
<version>2.2.10.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
@@ -1,31 +0,0 @@
|
|||||||
package com.gitee.sop.bridge;
|
|
||||||
|
|
||||||
import com.gitee.sop.bridge.route.EurekaRegistryListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
|
||||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
|
||||||
import org.springframework.cloud.netflix.ribbon.eureka.EurekaServerIntrospector;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class SopRegisterAutoConfiguration {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 负责获取eureka实例的metadata
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
ServerIntrospector eurekaServerIntrospector() {
|
|
||||||
return new EurekaServerIntrospector();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
RegistryListener registryListenerEureka() {
|
|
||||||
return new EurekaRegistryListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@@ -1,134 +0,0 @@
|
|||||||
package com.gitee.sop.bridge.route;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.BaseRegistryListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryEvent;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceHolder;
|
|
||||||
import com.netflix.appinfo.InstanceInfo;
|
|
||||||
import com.netflix.discovery.shared.Application;
|
|
||||||
import com.netflix.discovery.shared.Applications;
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.netflix.eureka.CloudEurekaClient;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载服务路由,eureka实现
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class EurekaRegistryListener extends BaseRegistryListener {
|
|
||||||
|
|
||||||
private Set<ServiceHolder> cacheServices = new HashSet<>();
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private List<RegistryEvent> registryEventList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEvent(ApplicationEvent applicationEvent) {
|
|
||||||
Object source = applicationEvent.getSource();
|
|
||||||
CloudEurekaClient cloudEurekaClient = (CloudEurekaClient) source;
|
|
||||||
Applications applications = cloudEurekaClient.getApplications();
|
|
||||||
List<Application> registeredApplications = applications.getRegisteredApplications();
|
|
||||||
|
|
||||||
List<ServiceHolder> serviceList = registeredApplications
|
|
||||||
.stream()
|
|
||||||
.filter(application -> CollectionUtils.isNotEmpty(application.getInstances()))
|
|
||||||
.map(Application::getInstances)
|
|
||||||
.map(instanceInfos -> {
|
|
||||||
// 根据更新时间倒叙
|
|
||||||
instanceInfos.sort(Comparator.comparing(InstanceInfo::getLastUpdatedTimestamp).reversed());
|
|
||||||
// 获取最新的个服务实例,说明这个服务实例刚刚重启过
|
|
||||||
return instanceInfos.get(0);
|
|
||||||
})
|
|
||||||
.map(instanceInfo -> new ServiceHolder(instanceInfo.getAppName(), instanceInfo.getLastUpdatedTimestamp()))
|
|
||||||
.filter(this::canOperator)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
final Set<ServiceHolder> currentServices = new HashSet<>(serviceList);
|
|
||||||
// 删除现有的,剩下的就是新服务
|
|
||||||
currentServices.removeAll(cacheServices);
|
|
||||||
// 如果有新的服务注册进来
|
|
||||||
if (currentServices.size() > 0) {
|
|
||||||
List<Application> newApplications = registeredApplications.stream()
|
|
||||||
.filter(application ->
|
|
||||||
containsService(currentServices, application.getName()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
this.doRegister(newApplications);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> removedServiceIdList = getRemovedServiceId(serviceList);
|
|
||||||
// 如果有服务下线
|
|
||||||
this.doRemove(removedServiceIdList);
|
|
||||||
|
|
||||||
cacheServices = new HashSet<>(serviceList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已经下线的serviceId
|
|
||||||
*
|
|
||||||
* @param serviceList 最新的serviceId集合
|
|
||||||
* @return 返回已下线的serviceId
|
|
||||||
*/
|
|
||||||
private Set<String> getRemovedServiceId(List<ServiceHolder> serviceList) {
|
|
||||||
Set<String> cache = cacheServices.stream()
|
|
||||||
.map(ServiceHolder::getServiceId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
Set<String> newList = serviceList.stream()
|
|
||||||
.map(ServiceHolder::getServiceId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
cache.removeAll(newList);
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean containsService(Set<ServiceHolder> currentServices, String serviceId) {
|
|
||||||
for (ServiceHolder currentService : currentServices) {
|
|
||||||
if (currentService.getServiceId().equalsIgnoreCase(serviceId)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doRegister(List<Application> registeredApplications) {
|
|
||||||
registeredApplications.forEach(application -> {
|
|
||||||
List<InstanceInfo> instances = application.getInstances();
|
|
||||||
if (CollectionUtils.isNotEmpty(instances)) {
|
|
||||||
instances.sort(Comparator.comparing(InstanceInfo::getLastUpdatedTimestamp).reversed());
|
|
||||||
InstanceInfo instanceInfo = instances.get(0);
|
|
||||||
InstanceDefinition instanceDefinition = new InstanceDefinition();
|
|
||||||
instanceDefinition.setInstanceId(instanceInfo.getInstanceId());
|
|
||||||
instanceDefinition.setServiceId(instanceInfo.getAppName());
|
|
||||||
instanceDefinition.setIp(instanceInfo.getIPAddr());
|
|
||||||
instanceDefinition.setPort(instanceInfo.getPort());
|
|
||||||
instanceDefinition.setMetadata(instanceInfo.getMetadata());
|
|
||||||
pullRoutes(instanceDefinition);
|
|
||||||
if (registryEventList != null) {
|
|
||||||
registryEventList.forEach(registryEvent -> registryEvent.onRegistry(instanceDefinition));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doRemove(Set<String> deletedServices) {
|
|
||||||
if (deletedServices == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deletedServices.forEach(serviceId -> {
|
|
||||||
this.removeRoutes(serviceId);
|
|
||||||
if (registryEventList != null) {
|
|
||||||
registryEventList.forEach(registryEvent -> registryEvent.onRemove(serviceId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
25
sop-common/sop-bridge-nacos/.gitignore
vendored
25
sop-common/sop-bridge-nacos/.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,32 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>sop-bridge-nacos</artifactId>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.gitee.sop.bridge;
|
|
||||||
|
|
||||||
import com.gitee.sop.bridge.route.NacosRegistryListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class SopRegisterAutoConfiguration {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 微服务路由加载
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
RegistryListener registryListenerNacos() {
|
|
||||||
return new NacosRegistryListener();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,163 +0,0 @@
|
|||||||
package com.gitee.sop.bridge.route;
|
|
||||||
|
|
||||||
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
|
|
||||||
import com.alibaba.nacos.api.exception.NacosException;
|
|
||||||
import com.alibaba.nacos.api.naming.NamingService;
|
|
||||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
|
||||||
import com.alibaba.nacos.api.naming.pojo.ListView;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.InstanceDefinition;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.BaseRegistryListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryEvent;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceHolder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang.math.NumberUtils;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载服务路由,nacos实现
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class NacosRegistryListener extends BaseRegistryListener {
|
|
||||||
|
|
||||||
private volatile Set<NacosServiceHolder> cacheServices = new HashSet<>();
|
|
||||||
|
|
||||||
@Value("${nacos.discovery.group:${spring.cloud.nacos.discovery.group:DEFAULT_GROUP}}")
|
|
||||||
private String nacosGroup;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private NacosDiscoveryProperties nacosDiscoveryProperties;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ObjectProvider<RegistryEvent> registryEventList;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onEvent(ApplicationEvent applicationEvent) {
|
|
||||||
List<NacosServiceHolder> serviceList = this.getServiceList();
|
|
||||||
final Set<NacosServiceHolder> currentServices = new HashSet<>(serviceList);
|
|
||||||
|
|
||||||
// 删除现有的,剩下的就是新服务
|
|
||||||
currentServices.removeAll(cacheServices);
|
|
||||||
// 如果有新的服务注册进来
|
|
||||||
if (currentServices.size() > 0) {
|
|
||||||
currentServices.forEach(nacosServiceHolder -> {
|
|
||||||
Instance instance = nacosServiceHolder.getInstance();
|
|
||||||
InstanceDefinition instanceDefinition = new InstanceDefinition();
|
|
||||||
instanceDefinition.setInstanceId(instance.getInstanceId());
|
|
||||||
instanceDefinition.setServiceId(nacosServiceHolder.getServiceId());
|
|
||||||
instanceDefinition.setIp(instance.getIp());
|
|
||||||
instanceDefinition.setPort(instance.getPort());
|
|
||||||
instanceDefinition.setMetadata(instance.getMetadata());
|
|
||||||
pullRoutes(instanceDefinition);
|
|
||||||
if (registryEventList != null) {
|
|
||||||
registryEventList.forEach(registryEvent -> registryEvent.onRegistry(instanceDefinition));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有服务下线
|
|
||||||
Set<String> removedServiceIdList = getRemovedServiceId(serviceList);
|
|
||||||
// 移除
|
|
||||||
this.doRemove(removedServiceIdList);
|
|
||||||
|
|
||||||
cacheServices = new HashSet<>(serviceList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取建康的服务实例
|
|
||||||
*
|
|
||||||
* @return 没有返回空的list
|
|
||||||
*/
|
|
||||||
private List<NacosServiceHolder> getServiceList() {
|
|
||||||
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
|
|
||||||
ListView<String> servicesOfServer = null;
|
|
||||||
try {
|
|
||||||
servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE, nacosGroup);
|
|
||||||
} catch (NacosException e) {
|
|
||||||
log.error("namingService.getServicesOfServer()错误", e);
|
|
||||||
}
|
|
||||||
if (servicesOfServer == null || CollectionUtils.isEmpty(servicesOfServer.getData())) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return servicesOfServer
|
|
||||||
.getData()
|
|
||||||
.stream()
|
|
||||||
.map(serviceName -> {
|
|
||||||
List<Instance> allInstances;
|
|
||||||
try {
|
|
||||||
// 获取服务实例
|
|
||||||
allInstances = namingService.getAllInstances(serviceName, nacosGroup);
|
|
||||||
} catch (NacosException e) {
|
|
||||||
log.error("namingService.getAllInstances(serviceName)错误,serviceName:{}", serviceName, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (CollectionUtils.isEmpty(allInstances)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return allInstances.stream()
|
|
||||||
// 只获取建康实例
|
|
||||||
.filter(Instance::isHealthy)
|
|
||||||
.map(instance -> {
|
|
||||||
String startupTime = instance.getMetadata().get(SopConstants.METADATA_KEY_TIME_STARTUP);
|
|
||||||
if (startupTime == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
long time = NumberUtils.toLong(startupTime, 0);
|
|
||||||
return new NacosServiceHolder(serviceName, time, instance);
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.max(Comparator.comparing(ServiceHolder::getLastUpdatedTimestamp))
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.filter(this::canOperator)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已经下线的serviceId
|
|
||||||
*
|
|
||||||
* @param serviceList 最新的serviceId集合
|
|
||||||
* @return 返回已下线的serviceId
|
|
||||||
*/
|
|
||||||
private Set<String> getRemovedServiceId(List<NacosServiceHolder> serviceList) {
|
|
||||||
Set<String> cache = cacheServices.stream()
|
|
||||||
.map(NacosServiceHolder::getServiceId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
Set<String> newList = serviceList.stream()
|
|
||||||
.map(NacosServiceHolder::getServiceId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
cache.removeAll(newList);
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doRemove(Set<String> deletedServices) {
|
|
||||||
if (deletedServices == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
deletedServices.forEach(serviceId -> {
|
|
||||||
this.removeRoutes(serviceId);
|
|
||||||
if (registryEventList != null) {
|
|
||||||
registryEventList.forEach(registryEvent -> registryEvent.onRemove(serviceId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.gitee.sop.bridge.route;
|
|
||||||
|
|
||||||
import com.alibaba.nacos.api.naming.pojo.Instance;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceHolder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class NacosServiceHolder extends ServiceHolder {
|
|
||||||
|
|
||||||
private final Instance instance;
|
|
||||||
|
|
||||||
public NacosServiceHolder(String serviceId, long lastUpdatedTimestamp, Instance instance) {
|
|
||||||
super(serviceId, lastUpdatedTimestamp);
|
|
||||||
this.instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instance getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
}
|
|
25
sop-common/sop-gateway-common/.gitignore
vendored
25
sop-common/sop-gateway-common/.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,122 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.gitee.sop</groupId>
|
|
||||||
<artifactId>sop-common</artifactId>
|
|
||||||
<version>5.0.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../pom.xml</relativePath> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>sop-gateway-common</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<java.version>1.8</java.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.commons</groupId>
|
|
||||||
<artifactId>commons-lang3</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-fileupload</groupId>
|
|
||||||
<artifactId>commons-fileupload</artifactId>
|
|
||||||
<version>${commons-fileupload.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-codec</groupId>
|
|
||||||
<artifactId>commons-codec</artifactId>
|
|
||||||
<version>${commons-codec.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-collections</groupId>
|
|
||||||
<artifactId>commons-collections</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba</groupId>
|
|
||||||
<artifactId>fastjson</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-gateway-webflux</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- optional -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.data</groupId>
|
|
||||||
<artifactId>spring-data-redis</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- test -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<version>4.12</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.servlet</groupId>
|
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework</groupId>
|
|
||||||
<artifactId>spring-jdbc</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-commons</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
|
||||||
<version>2.2.10.RELEASE</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
@@ -1,3 +0,0 @@
|
|||||||
# sop-gateway-common
|
|
||||||
|
|
||||||
网关通用组件,主要封装网关的一些功能。
|
|
@@ -1,216 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.BizResultHandler;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.result.GatewayResultExecutor;
|
|
||||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
|
|
||||||
import com.gitee.sop.gatewaycommon.limit.DefaultLimitManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.AppIdGrayUserBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.GrayUserBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.loadbalancer.builder.IpGrayUserBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultEnvGrayManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultIPBlacklistManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultIsvRoutePermissionManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultLimitConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultRouteConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.DefaultServiceErrorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.monitor.MonitorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.DataNameBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.DefaultDataNameBuilder;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultAppender;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutorForGateway;
|
|
||||||
import com.gitee.sop.gatewaycommon.secret.CacheIsvManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.ApiEncrypter;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.ApiValidator;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Encrypter;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Signer;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.TokenValidator;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySigner;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ApiConfig {
|
|
||||||
|
|
||||||
private static ApiConfig instance = new ApiConfig();
|
|
||||||
|
|
||||||
private ApiConfig() {
|
|
||||||
grayUserBuilders = new ArrayList<>(4);
|
|
||||||
grayUserBuilders.add(new AppIdGrayUserBuilder());
|
|
||||||
grayUserBuilders.add(new IpGrayUserBuilder());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gateway合并结果处理
|
|
||||||
*/
|
|
||||||
private ResultExecutorForGateway gatewayResultExecutor = new GatewayResultExecutor();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* isv管理
|
|
||||||
*/
|
|
||||||
private IsvManager isvManager = new CacheIsvManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加密工具
|
|
||||||
*/
|
|
||||||
private Encrypter encrypter = new ApiEncrypter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 签名工具
|
|
||||||
*/
|
|
||||||
private Signer signer = new AlipaySigner();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证
|
|
||||||
*/
|
|
||||||
private Validator validator = new ApiValidator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* isv路由权限
|
|
||||||
*/
|
|
||||||
private IsvRoutePermissionManager isvRoutePermissionManager = new DefaultIsvRoutePermissionManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由配置管理
|
|
||||||
*/
|
|
||||||
private RouteConfigManager routeConfigManager = new DefaultRouteConfigManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 限流配置
|
|
||||||
*/
|
|
||||||
private LimitConfigManager limitConfigManager = new DefaultLimitConfigManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IP黑名单
|
|
||||||
*/
|
|
||||||
private IPBlacklistManager ipBlacklistManager = new DefaultIPBlacklistManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 限流管理
|
|
||||||
*/
|
|
||||||
private LimitManager limitManager = new DefaultLimitManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户key管理
|
|
||||||
*/
|
|
||||||
private EnvGrayManager userKeyManager = new DefaultEnvGrayManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建数据节点名称
|
|
||||||
*/
|
|
||||||
private DataNameBuilder dataNameBuilder = new DefaultDataNameBuilder();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 追加结果
|
|
||||||
*/
|
|
||||||
private ResultAppender resultAppender;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理错误信息
|
|
||||||
*/
|
|
||||||
private ServiceErrorManager serviceErrorManager = new DefaultServiceErrorManager();
|
|
||||||
|
|
||||||
private ParameterFormatter parameterFormatter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验token
|
|
||||||
*/
|
|
||||||
private TokenValidator tokenValidator = apiParam -> apiParam != null && StringUtils.isNotBlank(apiParam.fetchAccessToken());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由拦截器
|
|
||||||
*/
|
|
||||||
private List<RouteInterceptor> routeInterceptors = new ArrayList<>(4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 监控管理
|
|
||||||
*/
|
|
||||||
private MonitorManager monitorManager = new MonitorManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 业务返回参数处理
|
|
||||||
*/
|
|
||||||
private BizResultHandler bizResultHandler = (serviceData, serviceObj, apiParam, request) -> {
|
|
||||||
serviceData.putAll(serviceObj);
|
|
||||||
};
|
|
||||||
|
|
||||||
// -------- fields ---------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误模块
|
|
||||||
*/
|
|
||||||
private List<String> i18nModules = new ArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 忽略验证,设置true,则所有接口不会进行签名校验
|
|
||||||
*/
|
|
||||||
private boolean ignoreValidate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否对结果进行合并。<br>
|
|
||||||
* 默认情况下是否合并结果由微服务端决定,一旦指定该值,则由该值决定,不管微服务端如何设置。
|
|
||||||
*/
|
|
||||||
private Boolean mergeResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 超时时间
|
|
||||||
*/
|
|
||||||
private int timeoutSeconds = 60 * 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否开启限流功能
|
|
||||||
*/
|
|
||||||
private boolean openLimit = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示返回sign
|
|
||||||
*/
|
|
||||||
private boolean showReturnSign = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存错误信息容器的容量
|
|
||||||
*/
|
|
||||||
private int storeErrorCapacity = 20;
|
|
||||||
|
|
||||||
private boolean useGateway;
|
|
||||||
|
|
||||||
private List<GrayUserBuilder> grayUserBuilders;
|
|
||||||
|
|
||||||
public void addGrayUserBuilder(GrayUserBuilder grayUserBuilder) {
|
|
||||||
grayUserBuilders.add(grayUserBuilder);
|
|
||||||
grayUserBuilders.sort(Comparator.comparing(GrayUserBuilder::order));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAppSecret(Map<String, String> appSecretPair) {
|
|
||||||
for (Map.Entry<String, String> entry : appSecretPair.entrySet()) {
|
|
||||||
this.isvManager.update(new IsvDefinition(entry.getKey(), entry.getValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiConfig getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setInstance(ApiConfig apiConfig) {
|
|
||||||
instance = apiConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用上下文,方便获取信息
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class ApiContext {
|
|
||||||
|
|
||||||
|
|
||||||
public static ApiConfig getApiConfig() {
|
|
||||||
return ApiConfig.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setApiConfig(ApiConfig apiConfig) {
|
|
||||||
ApiConfig.setInstance(apiConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface ApiParamAware<T> {
|
|
||||||
ApiParam getApiParam(T t);
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ApiResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.JsonResult;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public abstract class BaseErrorLogController<T> {
|
|
||||||
|
|
||||||
TaobaoSigner signer = new TaobaoSigner();
|
|
||||||
|
|
||||||
@Value("${sop.secret}")
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
protected abstract ApiParam getApiParam(T t);
|
|
||||||
|
|
||||||
@GetMapping("/sop/listErrors")
|
|
||||||
public ApiResult listErrors(T request) {
|
|
||||||
try {
|
|
||||||
this.check(request);
|
|
||||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
|
||||||
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors();
|
|
||||||
JsonResult apiResult = new JsonResult();
|
|
||||||
apiResult.setData(allErrors);
|
|
||||||
return apiResult;
|
|
||||||
} catch (Exception e) {
|
|
||||||
ApiResult apiResult = new ApiResult();
|
|
||||||
apiResult.setCode("505050");
|
|
||||||
apiResult.setMsg(e.getMessage());
|
|
||||||
return apiResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/sop/clearErrors")
|
|
||||||
public ApiResult clearErrors(T request) {
|
|
||||||
try {
|
|
||||||
this.check(request);
|
|
||||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager();
|
|
||||||
serviceErrorManager.clear();
|
|
||||||
return new ApiResult();
|
|
||||||
} catch (Exception e) {
|
|
||||||
ApiResult apiResult = new ApiResult();
|
|
||||||
apiResult.setCode("505050");
|
|
||||||
apiResult.setMsg(e.getMessage());
|
|
||||||
return apiResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void check(T request) {
|
|
||||||
ApiParam apiParam = getApiParam(request);
|
|
||||||
boolean right = signer.checkSign(apiParam, secret);
|
|
||||||
if (!right) {
|
|
||||||
throw new RuntimeException("签名校验失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface BeanInitializer extends ChannelMsgProcessor {
|
|
||||||
/**
|
|
||||||
* 执行加载操作
|
|
||||||
*/
|
|
||||||
void load();
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ChannelMsg {
|
|
||||||
private String operation;
|
|
||||||
private JSONObject data;
|
|
||||||
|
|
||||||
public <T> T toObject(Class<T> clazz) {
|
|
||||||
return data.toJavaObject(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,169 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.google.common.util.concurrent.RateLimiter;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
public class ConfigLimitDto {
|
|
||||||
|
|
||||||
public static final byte LIMIT_STATUS_OPEN = 1;
|
|
||||||
public static final byte LIMIT_STATUS_CLOSE = 0;
|
|
||||||
|
|
||||||
/** 数据库字段: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;
|
|
||||||
|
|
||||||
/** 限流过期时间,默认1秒,即每durationSeconds秒允许多少请求(当limit_type=1时有效), 数据库字段:durationSeconds */
|
|
||||||
private Integer durationSeconds;
|
|
||||||
|
|
||||||
/** 返回的错误码, 数据库字段: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;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 窗口计数器
|
|
||||||
*/
|
|
||||||
private volatile LoadingCache<Long, AtomicLong> counter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取持续时间,1秒内限制请求,则duration设置2
|
|
||||||
*
|
|
||||||
* @return 返回缓存保存的值。
|
|
||||||
*/
|
|
||||||
public int fetchDuration() {
|
|
||||||
Integer durationSeconds = this.durationSeconds;
|
|
||||||
if (durationSeconds == null || durationSeconds < 1) {
|
|
||||||
durationSeconds = 1;
|
|
||||||
}
|
|
||||||
// 1秒内限制请求,则duration设置2
|
|
||||||
return durationSeconds + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoadingCache<Long, AtomicLong> getCounter() {
|
|
||||||
if (counter == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (counter == null) {
|
|
||||||
int duration = fetchDuration();
|
|
||||||
counter = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterWrite(duration, TimeUnit.SECONDS)
|
|
||||||
.build(new CacheLoader<Long, AtomicLong>() {
|
|
||||||
@Override
|
|
||||||
public AtomicLong load(Long seconds) throws Exception {
|
|
||||||
return new AtomicLong(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌桶
|
|
||||||
*/
|
|
||||||
@Getter(AccessLevel.PRIVATE)
|
|
||||||
@Setter(AccessLevel.PRIVATE)
|
|
||||||
private volatile RateLimiter rateLimiter;
|
|
||||||
|
|
||||||
public synchronized void initRateLimiter() {
|
|
||||||
rateLimiter = RateLimiter.create(tokenBucketCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取令牌桶
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public RateLimiter fetchRateLimiter() {
|
|
||||||
if (rateLimiter == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (rateLimiter == null) {
|
|
||||||
rateLimiter = RateLimiter.create(tokenBucketCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rateLimiter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ConfigLimitDto that = (ConfigLimitDto) o;
|
|
||||||
return id.equals(that.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ConfigLimitDto{" +
|
|
||||||
"id=" + id +
|
|
||||||
", routeId='" + routeId + '\'' +
|
|
||||||
", appKey='" + appKey + '\'' +
|
|
||||||
", limitIp='" + limitIp + '\'' +
|
|
||||||
", serviceId='" + serviceId + '\'' +
|
|
||||||
", limitType=" + limitType +
|
|
||||||
", execCountPerSecond=" + execCountPerSecond +
|
|
||||||
", durationSeconds=" + durationSeconds +
|
|
||||||
", limitCode='" + limitCode + '\'' +
|
|
||||||
", limitMsg='" + limitMsg + '\'' +
|
|
||||||
", tokenBucketCount=" + tokenBucketCount +
|
|
||||||
", limitStatus=" + limitStatus +
|
|
||||||
", orderIndex=" + orderIndex +
|
|
||||||
", gmtCreate=" + gmtCreate +
|
|
||||||
", gmtModified=" + gmtModified +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,126 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptorContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import org.springframework.cloud.client.ServiceInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class DefaultRouteInterceptorContext implements RouteInterceptorContext {
|
|
||||||
|
|
||||||
/** 请求参数 */
|
|
||||||
private ApiParam apiParam;
|
|
||||||
/** 错误信息 */
|
|
||||||
private String serviceErrorMsg;
|
|
||||||
/** 微服务返回状态 */
|
|
||||||
private int responseStatus;
|
|
||||||
/** 开始时间 */
|
|
||||||
private long beginTimeMillis;
|
|
||||||
/** 结束时间 */
|
|
||||||
private long finishTimeMillis;
|
|
||||||
/** 请求上下文 */
|
|
||||||
private Object requestContext;
|
|
||||||
/** 微服务返回结果 */
|
|
||||||
private String serviceResult;
|
|
||||||
/** 请求包大小 */
|
|
||||||
private long requestDataSize;
|
|
||||||
/** 返回内容大小 */
|
|
||||||
private long responseDataSize;
|
|
||||||
/** 负载均衡选中的微服务 */
|
|
||||||
private ServiceInstance serviceInstance;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ApiParam getApiParam() {
|
|
||||||
return apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServiceResult() {
|
|
||||||
return serviceResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getResponseStatus() {
|
|
||||||
return responseStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getBeginTimeMillis() {
|
|
||||||
return beginTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getFinishTimeMillis() {
|
|
||||||
return finishTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getRequestContext() {
|
|
||||||
return requestContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApiParam(ApiParam apiParam) {
|
|
||||||
this.apiParam = apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServiceResult(String serviceResult) {
|
|
||||||
this.serviceResult = serviceResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponseStatus(int responseStatus) {
|
|
||||||
this.responseStatus = responseStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBeginTimeMillis(long beginTimeMillis) {
|
|
||||||
this.beginTimeMillis = beginTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinishTimeMillis(long finishTimeMillis) {
|
|
||||||
this.finishTimeMillis = finishTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestContext(Object requestContext) {
|
|
||||||
this.requestContext = requestContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServiceErrorMsg() {
|
|
||||||
return serviceErrorMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServiceErrorMsg(String serviceErrorMsg) {
|
|
||||||
this.serviceErrorMsg = serviceErrorMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getRequestDataSize() {
|
|
||||||
return requestDataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestDataSize(long requestDataSize) {
|
|
||||||
// spring cloud gateway get请求contentLength返回-1
|
|
||||||
if (requestDataSize < 0) {
|
|
||||||
requestDataSize = 0;
|
|
||||||
}
|
|
||||||
this.requestDataSize = requestDataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getResponseDataSize() {
|
|
||||||
return responseDataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponseDataSize(long responseDataSize) {
|
|
||||||
this.responseDataSize = responseDataSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServiceInstance getServiceInstance() {
|
|
||||||
return serviceInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setServiceInstance(ServiceInstance serviceInstance) {
|
|
||||||
this.serviceInstance = serviceInstance;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ErrorDefinition {
|
|
||||||
/**
|
|
||||||
* 接口名
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
/**
|
|
||||||
* 版本号
|
|
||||||
*/
|
|
||||||
private String version;
|
|
||||||
/**
|
|
||||||
* 服务名
|
|
||||||
*/
|
|
||||||
private String serviceId;
|
|
||||||
/**
|
|
||||||
* 错误内容
|
|
||||||
*/
|
|
||||||
private String errorMsg;
|
|
||||||
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class ErrorEntity {
|
|
||||||
private String id;
|
|
||||||
private String name;
|
|
||||||
private String version;
|
|
||||||
private String serviceId;
|
|
||||||
private String errorMsg;
|
|
||||||
private long count;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ErrorEntity that = (ErrorEntity) o;
|
|
||||||
return id.equals(that.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(id);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class GatewayFilterDefinition {
|
|
||||||
/** Filter Name */
|
|
||||||
private String name;
|
|
||||||
/** 对应的路由规则 */
|
|
||||||
private Map<String, String> args = new LinkedHashMap<>();
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class GatewayPredicateDefinition {
|
|
||||||
public static final String GEN_KEY = "_genkey_";
|
|
||||||
/** 断言对应的Name */
|
|
||||||
private String name;
|
|
||||||
/** 配置的断言规则 */
|
|
||||||
private Map<String, String> args = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
public GatewayPredicateDefinition() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public GatewayPredicateDefinition(String text) {
|
|
||||||
int eqIdx = text.indexOf(61);
|
|
||||||
if (eqIdx <= 0) {
|
|
||||||
throw new RuntimeException("Unable to parse GatewayPredicateDefinition text '" + text + "', must be of the form name=value");
|
|
||||||
} else {
|
|
||||||
this.setName(text.substring(0, eqIdx));
|
|
||||||
String[] params = StringUtils.tokenizeToStringArray(text.substring(eqIdx + 1), ",");
|
|
||||||
|
|
||||||
for(int i = 0; i < params.length; ++i) {
|
|
||||||
this.args.put(generateName(i), params[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String generateName(int i) {
|
|
||||||
return GEN_KEY + i;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,22 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class GatewayPushDTO {
|
|
||||||
private String dataId;
|
|
||||||
private String groupId;
|
|
||||||
private ChannelMsg channelMsg;
|
|
||||||
|
|
||||||
public GatewayPushDTO() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public GatewayPushDTO(String dataId, String groupId, ChannelMsg channelMsg) {
|
|
||||||
this.dataId = dataId;
|
|
||||||
this.groupId = groupId;
|
|
||||||
this.channelMsg = channelMsg;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class InstanceDefinition {
|
|
||||||
private String instanceId;
|
|
||||||
private String serviceId;
|
|
||||||
private String ip;
|
|
||||||
private int port;
|
|
||||||
private Map<String, String> metadata;
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface Isv {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* appKey
|
|
||||||
* @return 返回appKey
|
|
||||||
*/
|
|
||||||
String getAppKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 秘钥
|
|
||||||
* @return 返回秘钥
|
|
||||||
*/
|
|
||||||
String getSecretInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取平台的私钥
|
|
||||||
* @return 返回私钥
|
|
||||||
*/
|
|
||||||
String getPrivateKeyPlatform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 0启用,1禁用
|
|
||||||
* @return 返回状态
|
|
||||||
*/
|
|
||||||
Byte getStatus();
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class IsvDefinition implements Isv {
|
|
||||||
|
|
||||||
public static final int SIGN_TYPE_RSA2 = 1;
|
|
||||||
|
|
||||||
public IsvDefinition() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public IsvDefinition(String appKey, String secret) {
|
|
||||||
this.appKey = appKey;
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String appKey;
|
|
||||||
|
|
||||||
/** 秘钥,签名方式为MD5时有用 */
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
/** 开发者生成的公钥, 数据库字段:public_key_isv */
|
|
||||||
private String publicKeyIsv;
|
|
||||||
|
|
||||||
/** 平台生成的私钥, 数据库字段:private_key_platform */
|
|
||||||
private String privateKeyPlatform;
|
|
||||||
|
|
||||||
/** 0启用,1禁用 */
|
|
||||||
private Byte status;
|
|
||||||
|
|
||||||
/** 签名类型:1:RSA2,2:MD5 */
|
|
||||||
private Byte signType = 1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSecretInfo() {
|
|
||||||
return signType == SIGN_TYPE_RSA2 ? publicKeyIsv : secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "IsvDefinition{" +
|
|
||||||
"appKey='" + appKey + '\'' +
|
|
||||||
", secret='" + secret + '\'' +
|
|
||||||
", publicKeyIsv='" + publicKeyIsv + '\'' +
|
|
||||||
", privateKeyPlatform='" + privateKeyPlatform + '\'' +
|
|
||||||
", status=" + status +
|
|
||||||
", signType=" + signType +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* isv授权过的路由
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class IsvRoutePermission {
|
|
||||||
private String appKey;
|
|
||||||
private List<String> routeIdList = Collections.emptyList();
|
|
||||||
private String routeIdListMd5;
|
|
||||||
private String listenPath;
|
|
||||||
|
|
||||||
}
|
|
@@ -1,43 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LRU缓存
|
|
||||||
* LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素)
|
|
||||||
* 由于最近最少使用元素是 LinkedHashMap 内部处理
|
|
||||||
* 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储
|
|
||||||
* created by Ethan-Walker on 2019/2/16
|
|
||||||
*/
|
|
||||||
public class LRUCache<K, V> {
|
|
||||||
private final Map<K, V> map;
|
|
||||||
|
|
||||||
public LRUCache(int capacity) {
|
|
||||||
map = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
|
|
||||||
@Override
|
|
||||||
protected boolean removeEldestEntry(Map.Entry eldest) {
|
|
||||||
// 容量大于capacity 时就删除
|
|
||||||
return size() > capacity;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
public V get(K key) {
|
|
||||||
return map.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public V computeIfAbsent(K key,
|
|
||||||
Function<? super K, ? extends V> mappingFunction) {
|
|
||||||
return map.computeIfAbsent(key, mappingFunction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public V put(K key, V value) {
|
|
||||||
return map.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<V> values() {
|
|
||||||
return map.values();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class NacosConfigs {
|
|
||||||
|
|
||||||
public static final String GROUP_CHANNEL = "sop:channel";
|
|
||||||
|
|
||||||
public static final String GROUP_ROUTE = "sop:route";
|
|
||||||
|
|
||||||
public static final String DATA_ID_GRAY = "com.gitee.sop.channel.gray";
|
|
||||||
|
|
||||||
public static final String DATA_ID_IP_BLACKLIST = "com.gitee.sop.channel.ipblacklist";
|
|
||||||
|
|
||||||
public static final String DATA_ID_ISV = "com.gitee.sop.channel.isv";
|
|
||||||
|
|
||||||
public static final String DATA_ID_ROUTE_PERMISSION = "com.gitee.sop.channel.routepermission";
|
|
||||||
|
|
||||||
public static final String DATA_ID_LIMIT_CONFIG = "com.gitee.sop.channel.limitconfig";
|
|
||||||
|
|
||||||
public static final String DATA_ID_ROUTE_CONFIG = "com.gitee.sop.channel.routeconfig";
|
|
||||||
|
|
||||||
private static final String DATA_ID_TPL = "com.gitee.sop.route.%s";
|
|
||||||
|
|
||||||
public static String getRouteDataId(String serviceId) {
|
|
||||||
return String.format(DATA_ID_TPL, serviceId);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,31 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class RouteConfig {
|
|
||||||
|
|
||||||
private static final byte STATUS_ENABLE = RouteStatus.ENABLE.getStatus();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由id
|
|
||||||
*/
|
|
||||||
private String routeId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态,0:待审核,1:启用,2:禁用。默认启用
|
|
||||||
*/
|
|
||||||
private Byte status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否启用
|
|
||||||
*
|
|
||||||
* @return true:启用
|
|
||||||
*/
|
|
||||||
public boolean enable() {
|
|
||||||
return status == STATUS_ENABLE;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,78 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class RouteDefinition {
|
|
||||||
/**
|
|
||||||
* 路由的Id(接口名+版本号),确保此id全局唯一
|
|
||||||
*/
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 接口名
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 版本号
|
|
||||||
*/
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由断言集合配置
|
|
||||||
*/
|
|
||||||
private List<GatewayPredicateDefinition> predicates = Collections.emptyList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由过滤器集合配置
|
|
||||||
*/
|
|
||||||
private List<GatewayFilterDefinition> filters = Collections.emptyList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由规则转发的目标uri
|
|
||||||
*/
|
|
||||||
private String uri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* uri后面跟的path
|
|
||||||
*/
|
|
||||||
private String path;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由执行的顺序
|
|
||||||
*/
|
|
||||||
private int order = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否忽略验证,业务参数验证除外
|
|
||||||
*/
|
|
||||||
private int ignoreValidate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态,0:待审核,1:启用,2:禁用
|
|
||||||
*/
|
|
||||||
private int status = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否合并结果
|
|
||||||
*/
|
|
||||||
private int mergeResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否需要授权才能访问
|
|
||||||
*/
|
|
||||||
private int permission;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否需要token
|
|
||||||
*/
|
|
||||||
private int needToken;
|
|
||||||
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 路由状态
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public enum RouteStatus {
|
|
||||||
/**
|
|
||||||
* 路由状态,0:待审核
|
|
||||||
*/
|
|
||||||
AUDIT(0, "待审核"),
|
|
||||||
/**
|
|
||||||
* 路由状态,1:已启用
|
|
||||||
*/
|
|
||||||
ENABLE(1, "已启用"),
|
|
||||||
/**
|
|
||||||
* 路由状态,2:已禁用
|
|
||||||
*/
|
|
||||||
DISABLE(2, "已禁用"),
|
|
||||||
;
|
|
||||||
private byte status;
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
RouteStatus(Integer status, String description) {
|
|
||||||
this.status = status.byteValue();
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}}
|
|
@@ -1,11 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface ServiceBeanInitializer extends ChannelMsgProcessor {
|
|
||||||
|
|
||||||
void load(String serviceId);
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ServiceDefinition {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务名称,对应spring.application.name
|
|
||||||
*/
|
|
||||||
private String serviceId;
|
|
||||||
|
|
||||||
public ServiceDefinition(String serviceId) {
|
|
||||||
this.serviceId = serviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String fetchServiceIdLowerCase() {
|
|
||||||
return serviceId.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ServiceGrayDefinition {
|
|
||||||
private String serviceId;
|
|
||||||
private String instanceId;
|
|
||||||
private String data;
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ServiceRouteInfo {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务名称,对应spring.application.name
|
|
||||||
*/
|
|
||||||
private String serviceId;
|
|
||||||
|
|
||||||
private Date createTime = new Date();
|
|
||||||
|
|
||||||
private Date updateTime = new Date();
|
|
||||||
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
private List<RouteDefinition> routeDefinitionList;
|
|
||||||
|
|
||||||
private String md5;
|
|
||||||
|
|
||||||
public String fetchServiceIdLowerCase() {
|
|
||||||
return serviceId.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
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";
|
|
||||||
public static final String FORMAT_JSON = "json";
|
|
||||||
public static final String DEFAULT_SIGN_METHOD = "md5";
|
|
||||||
public static final String EMPTY_JSON = "{}";
|
|
||||||
|
|
||||||
public static final String METADATA_SERVER_CONTEXT_PATH = "server.servlet.context-path";
|
|
||||||
|
|
||||||
public static final String METADATA_SERVER_CONTEXT_PATH_COMPATIBILITY = "context-path";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在拦截器中调用获取参数:
|
|
||||||
* String cachedBody = (String)exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_OBJECT_KEY);
|
|
||||||
*/
|
|
||||||
public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在拦截器中调用获取参数:
|
|
||||||
* Map<String, String> params = exchange.getAttribute(SopConstants.CACHE_REQUEST_BODY_FOR_MAP);
|
|
||||||
*/
|
|
||||||
public static final String CACHE_REQUEST_BODY_FOR_MAP = "cacheRequestBodyForMap";
|
|
||||||
|
|
||||||
public static final String CACHE_API_PARAM = "cacheApiParam";
|
|
||||||
|
|
||||||
public static final String CACHE_UPLOAD_REQUEST = "cacheUploadRequest";
|
|
||||||
|
|
||||||
public static final String X_SERVICE_ERROR_CODE = "x-service-error-code";
|
|
||||||
|
|
||||||
public static final String X_SERVICE_ERROR_MESSAGE = "x-service-error-message";
|
|
||||||
|
|
||||||
public static final String X_SERVICE_ERROR_RESPONSE = "x-service-error-response";
|
|
||||||
|
|
||||||
public static final int BIZ_ERROR_STATUS = 4000;
|
|
||||||
public static final int UNKNOWN_ERROR_STATUS = 5050;
|
|
||||||
|
|
||||||
public static final String UNKNOWN_SERVICE= "_sop_unknown_service_";
|
|
||||||
public static final String UNKNOWN_METHOD = "_sop_unknown_method_";
|
|
||||||
public static final String UNKNOWN_VERSION = "_sop_unknown_version_";
|
|
||||||
|
|
||||||
public static final String METADATA_ENV_KEY = "env";
|
|
||||||
public static final String METADATA_ENV_PRE_VALUE = "pre";
|
|
||||||
public static final String METADATA_ENV_GRAY_VALUE = "gray";
|
|
||||||
|
|
||||||
public static final String CACHE_ROUTE_INTERCEPTOR_CONTEXT = "cacheRouteInterceptorContext";
|
|
||||||
public static final String TARGET_SERVICE = "sop-target-service";
|
|
||||||
public static final String RESTFUL_REQUEST = "sop-restful-request";
|
|
||||||
|
|
||||||
public static final String METADATA_KEY_TIME_STARTUP = "server.startup-time";
|
|
||||||
|
|
||||||
public static final String CACHE_ROUTE_INFO = "cacheRouteInfo";
|
|
||||||
|
|
||||||
}
|
|
@@ -1,27 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class SpringContext {
|
|
||||||
|
|
||||||
private static ApplicationContext ctx;
|
|
||||||
|
|
||||||
public static <T> T getBean(Class<T> clazz) {
|
|
||||||
return ctx.getBean(clazz);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object getBean(String beanName) {
|
|
||||||
return ctx.getBean(beanName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setApplicationContext(ApplicationContext ctx) {
|
|
||||||
SpringContext.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApplicationContext getApplicationContext() {
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.bean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public interface TargetRoute {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回服务信息
|
|
||||||
*
|
|
||||||
* @return 返回服务信息
|
|
||||||
*/
|
|
||||||
ServiceDefinition getServiceDefinition();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回微服务路由对象
|
|
||||||
*
|
|
||||||
* @return 返回微服务路由对象
|
|
||||||
*/
|
|
||||||
RouteDefinition getRouteDefinition();
|
|
||||||
|
|
||||||
String getFullPath();
|
|
||||||
|
|
||||||
}
|
|
@@ -1,233 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.config;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.BeanInitializer;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.interceptor.RouteInterceptor;
|
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.*;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
|
||||||
import com.gitee.sop.gatewaycommon.monitor.MonitorManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.RegistryListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ServiceRouteListener;
|
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.sync.SopAsyncConfigurer;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.SignConfig;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.boot.ApplicationArguments;
|
|
||||||
import org.springframework.boot.ApplicationRunner;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.Condition;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class AbstractConfiguration implements ApplicationContextAware, ApplicationRunner {
|
|
||||||
|
|
||||||
private final Lock lock = new ReentrantLock();
|
|
||||||
private final Condition condition = lock.newCondition();
|
|
||||||
|
|
||||||
private volatile boolean isStartupCompleted;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected Environment environment;
|
|
||||||
|
|
||||||
protected ApplicationContext applicationContext;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
|
|
||||||
applicationContext = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* nacos事件监听
|
|
||||||
*
|
|
||||||
* @param heartbeatEvent
|
|
||||||
*/
|
|
||||||
@EventListener(classes = HeartbeatEvent.class)
|
|
||||||
public void listenEvent(ApplicationEvent heartbeatEvent) {
|
|
||||||
// 没有启动完毕先等待
|
|
||||||
if (!isStartupCompleted) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
condition.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.error("condition.await() error", e);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
applicationContext.getBean(RegistryListener.class).onEvent(heartbeatEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
ServiceListener serviceListener() {
|
|
||||||
return new ServiceRouteListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
Validator validator() {
|
|
||||||
return ApiConfig.getInstance().getValidator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
IsvManager isvManager() {
|
|
||||||
return ApiConfig.getInstance().getIsvManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
IsvRoutePermissionManager isvRoutePermissionManager() {
|
|
||||||
return ApiConfig.getInstance().getIsvRoutePermissionManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
RouteConfigManager routeConfigManager() {
|
|
||||||
return ApiConfig.getInstance().getRouteConfigManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
LimitConfigManager limitConfigManager() {
|
|
||||||
return ApiConfig.getInstance().getLimitConfigManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
LimitManager limitManager() {
|
|
||||||
return ApiConfig.getInstance().getLimitManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
IPBlacklistManager ipBlacklistManager() {
|
|
||||||
return ApiConfig.getInstance().getIpBlacklistManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
EnvGrayManager envGrayManager() {
|
|
||||||
return ApiConfig.getInstance().getUserKeyManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
ParameterFormatter parameterFormatter() {
|
|
||||||
return ApiConfig.getInstance().getParameterFormatter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SopAsyncConfigurer sopAsyncConfigurer(@Value("${sop.monitor-route-interceptor.thread-pool-size:4}")
|
|
||||||
int threadPoolSize) {
|
|
||||||
return new SopAsyncConfigurer("gatewayAsync", threadPoolSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public MonitorManager monitorManager() {
|
|
||||||
return new MonitorManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跨域过滤器,gateway采用react形式,需要使用reactive包下的UrlBasedCorsConfigurationSource
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
@ConditionalOnProperty("sop.gateway-index-path")
|
|
||||||
public CorsWebFilter corsWebFilter() {
|
|
||||||
org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource source = new org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource();
|
|
||||||
source.registerCorsConfiguration("/**", createCorsConfiguration());
|
|
||||||
return new CorsWebFilter(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CorsConfiguration createCorsConfiguration() {
|
|
||||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
|
||||||
corsConfiguration.addAllowedOrigin("*");
|
|
||||||
corsConfiguration.addAllowedHeader("*");
|
|
||||||
corsConfiguration.addAllowedMethod("*");
|
|
||||||
return corsConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run(ApplicationArguments args) throws Exception {
|
|
||||||
this.isStartupCompleted = true;
|
|
||||||
lock.lock();
|
|
||||||
condition.signalAll();
|
|
||||||
lock.unlock();
|
|
||||||
after();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
private void post() {
|
|
||||||
EnvironmentContext.setEnvironment(environment);
|
|
||||||
SpringContext.setApplicationContext(applicationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void after() {
|
|
||||||
if (RouteRepositoryContext.getRouteRepository() == null) {
|
|
||||||
throw new IllegalArgumentException("RouteRepositoryContext.setRouteRepository()方法未使用");
|
|
||||||
}
|
|
||||||
String serverName = EnvironmentKeys.SPRING_APPLICATION_NAME.getValue();
|
|
||||||
if (!"sop-gateway".equals(serverName)) {
|
|
||||||
throw new IllegalArgumentException("spring.application.name必须为sop-gateway");
|
|
||||||
}
|
|
||||||
String urlencode = EnvironmentKeys.SIGN_URLENCODE.getValue();
|
|
||||||
if ("true".equals(urlencode)) {
|
|
||||||
SignConfig.enableUrlencodeMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
initMessage();
|
|
||||||
initBeanInitializer();
|
|
||||||
initRouteInterceptor();
|
|
||||||
doAfter();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initBeanInitializer() {
|
|
||||||
Map<String, BeanInitializer> beanInitializerMap = applicationContext.getBeansOfType(BeanInitializer.class);
|
|
||||||
beanInitializerMap.values().forEach(BeanInitializer::load);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initRouteInterceptor() {
|
|
||||||
Map<String, RouteInterceptor> routeInterceptorMap = applicationContext.getBeansOfType(RouteInterceptor.class);
|
|
||||||
Collection<RouteInterceptor> routeInterceptors = new ArrayList<>(routeInterceptorMap.values());
|
|
||||||
RouteInterceptorUtil.addInterceptors(routeInterceptors);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doAfter() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initMessage() {
|
|
||||||
ErrorFactory.initMessageSource(ApiContext.getApiConfig().getI18nModules());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.config;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@ConfigurationProperties(prefix = "sop.api-config")
|
|
||||||
public class ApiConfigProperties {
|
|
||||||
|
|
||||||
private List<String> i18nModules = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 忽略验证,设置true,则所有接口不会进行签名校验
|
|
||||||
*/
|
|
||||||
private boolean ignoreValidate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否对结果进行合并。<br>
|
|
||||||
* 默认情况下是否合并结果由微服务端决定,一旦指定该值,则由该值决定,不管微服务端如何设置。
|
|
||||||
*/
|
|
||||||
private Boolean mergeResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求超时时间,默认5分钟,即允许在5分钟内重复请求
|
|
||||||
*/
|
|
||||||
private int timeoutSeconds = 300;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否开启限流功能
|
|
||||||
*/
|
|
||||||
private boolean openLimit = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示返回sign
|
|
||||||
*/
|
|
||||||
private boolean showReturnSign = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存错误信息容器的容量
|
|
||||||
*/
|
|
||||||
private int storeErrorCapacity = 20;
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.config;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.BeanUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@EnableConfigurationProperties(ApiConfigProperties.class)
|
|
||||||
public class BaseGatewayAutoConfiguration {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApiConfigProperties apiConfigProperties;
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void after() {
|
|
||||||
log.info("网关基本配置:{}", JSON.toJSONString(apiConfigProperties));
|
|
||||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
|
||||||
BeanUtils.copyProperties(apiConfigProperties, apiConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.config;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.configuration.AlipayGatewayConfiguration;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://blog.csdn.net/seashouwang/article/details/80299571
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@Import(AlipayGatewayConfiguration.class)
|
|
||||||
public class SopGatewayAutoConfiguration extends BaseGatewayAutoConfiguration {
|
|
||||||
}
|
|
@@ -1,40 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
|
||||||
import org.springframework.core.env.ConfigurableEnvironment;
|
|
||||||
import org.springframework.core.env.PropertiesPropertySource;
|
|
||||||
import org.springframework.core.env.PropertySource;
|
|
||||||
import org.springframework.core.io.ClassPathResource;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义环境处理,在运行SpringApplication之前加载任意配置文件到Environment环境中
|
|
||||||
*/
|
|
||||||
public class SopGatewayEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
|
||||||
|
|
||||||
private final Properties properties = new Properties();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
|
||||||
Resource resource = new ClassPathResource("META-INF/gateway.properties");
|
|
||||||
// 加载成PropertySource对象,并添加到Environment环境中
|
|
||||||
environment.getPropertySources().addLast(loadProfiles(resource));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PropertySource<?> loadProfiles(Resource resource) {
|
|
||||||
if (resource == null || !resource.exists()) {
|
|
||||||
throw new IllegalArgumentException("资源" + resource + "不存在");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
properties.load(resource.getInputStream());
|
|
||||||
return new PropertiesPropertySource(resource.getFilename(), properties);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new IllegalStateException("加载配置文件失败" + resource, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,44 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.exception;
|
|
||||||
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.message.Error;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorFactory;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class ApiException extends RuntimeException {
|
|
||||||
|
|
||||||
private transient Error error;
|
|
||||||
|
|
||||||
private transient ErrorMeta errorMeta;
|
|
||||||
private transient Object[] params;
|
|
||||||
|
|
||||||
public ApiException(ErrorMeta errorMeta, Object... params) {
|
|
||||||
this.errorMeta = errorMeta;
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiException(Throwable cause, ErrorMeta errorMeta, Object... params) {
|
|
||||||
super(cause);
|
|
||||||
this.errorMeta = errorMeta;
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Error getError(Locale locale) {
|
|
||||||
if (error == null) {
|
|
||||||
error = ErrorFactory.getError(this.errorMeta, locale, params);
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
String message = super.getMessage();
|
|
||||||
return message == null ? errorMeta.toString() : message;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,328 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.common.FileUploadHttpServletRequest;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.common.RequestContentDataExtractor;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.common.SopServerHttpRequestDecorator;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.FormHttpOutputMessage;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
|
||||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
|
||||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.server.WebFilterChain;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class ServerWebExchangeUtil {
|
|
||||||
|
|
||||||
private static final String THROWABLE_KEY = "sop.throwable";
|
|
||||||
private static final String UNKNOWN_PATH = "/sop/unknown";
|
|
||||||
|
|
||||||
private static final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重定向
|
|
||||||
*
|
|
||||||
* @param exchange exchange
|
|
||||||
* @param forwardInfo forwardInfo
|
|
||||||
* @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用
|
|
||||||
*/
|
|
||||||
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, ForwardInfo forwardInfo) {
|
|
||||||
ServerHttpRequest.Builder builder = exchange.getRequest()
|
|
||||||
.mutate();
|
|
||||||
ServerHttpRequest newRequest = builder
|
|
||||||
.header(ParamNames.HEADER_VERSION_NAME, forwardInfo.getVersion())
|
|
||||||
.path(forwardInfo.getFullPath()).build();
|
|
||||||
return exchange.mutate().request(newRequest).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建一个接受请求体的request
|
|
||||||
*
|
|
||||||
* @param exchange exchange
|
|
||||||
* @param codecConfigurer codecConfigurer
|
|
||||||
* @return 返回ServerRequest
|
|
||||||
*/
|
|
||||||
public static ServerRequest createReadBodyRequest(ServerWebExchange exchange, ServerCodecConfigurer codecConfigurer) {
|
|
||||||
return ServerRequest.create(exchange, codecConfigurer.getReaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getRestfulPath(String path, String prefix) {
|
|
||||||
int index = path.indexOf(prefix);
|
|
||||||
// 取"/rest"的后面部分
|
|
||||||
return path.substring(index + prefix.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重定向
|
|
||||||
*
|
|
||||||
* @param exchange exchange
|
|
||||||
* @param forwardPath 重定向path
|
|
||||||
* @return 返回新的ServerWebExchange,配合chain.filter(newExchange);使用
|
|
||||||
*/
|
|
||||||
public static ServerWebExchange getForwardExchange(ServerWebExchange exchange, String forwardPath) {
|
|
||||||
ServerHttpRequest newRequest = exchange.getRequest()
|
|
||||||
.mutate()
|
|
||||||
.path(forwardPath).build();
|
|
||||||
return exchange.mutate().request(newRequest).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Mono<Void> forwardUnknown(ServerWebExchange exchange, WebFilterChain chain) {
|
|
||||||
// 非法访问
|
|
||||||
ServerWebExchange newExchange = ServerWebExchangeUtil.getForwardExchange(exchange, UNKNOWN_PATH);
|
|
||||||
return chain.filter(newExchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiParam getApiParamByQuery(ServerWebExchange exchange, String query) {
|
|
||||||
ApiParam apiParam = new ApiParam();
|
|
||||||
String ip = RequestUtil.getIP(exchange.getRequest());
|
|
||||||
apiParam.setIp(ip);
|
|
||||||
Map<String, ?> params = RequestUtil.parseQueryToMap(query);
|
|
||||||
apiParam.putAll(params);
|
|
||||||
setApiParam(exchange, apiParam);
|
|
||||||
return apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiParam getApiParam(ServerWebExchange exchange, byte[] body) {
|
|
||||||
MediaType contentType = exchange.getRequest().getHeaders().getContentType();
|
|
||||||
if (contentType == null) {
|
|
||||||
contentType = MediaType.APPLICATION_FORM_URLENCODED;
|
|
||||||
}
|
|
||||||
ApiParam apiParam = new ApiParam();
|
|
||||||
String ip = RequestUtil.getIP(exchange.getRequest());
|
|
||||||
apiParam.setIp(ip);
|
|
||||||
Map<String, ?> params = null;
|
|
||||||
String contentTypeStr = contentType.toString().toLowerCase();
|
|
||||||
// 如果是json方式提交
|
|
||||||
if (StringUtils.containsAny(contentTypeStr, "json", "text")) {
|
|
||||||
JSONObject jsonObject = JSON.parseObject(new String(body, SopConstants.CHARSET_UTF8));
|
|
||||||
apiParam.putAll(jsonObject);
|
|
||||||
} else if (StringUtils.containsIgnoreCase(contentTypeStr, "multipart")) {
|
|
||||||
// 如果是文件上传请求
|
|
||||||
HttpServletRequest fileUploadRequest = getFileUploadRequest(exchange, body);
|
|
||||||
setFileUploadRequest(exchange, fileUploadRequest);
|
|
||||||
RequestUtil.UploadInfo uploadInfo = RequestUtil.getUploadInfo(fileUploadRequest);
|
|
||||||
params = uploadInfo.getUploadParams();
|
|
||||||
apiParam.setUploadContext(uploadInfo.getUploadContext());
|
|
||||||
} else {
|
|
||||||
// APPLICATION_FORM_URLENCODED请求
|
|
||||||
params = RequestUtil.parseQueryToMap(new String(body, SopConstants.CHARSET_UTF8));
|
|
||||||
}
|
|
||||||
if (params != null) {
|
|
||||||
apiParam.putAll(params);
|
|
||||||
}
|
|
||||||
setApiParam(exchange, apiParam);
|
|
||||||
return apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static ApiParam getApiParam(ServerWebExchange exchange, Map<String, String> params) {
|
|
||||||
ApiParam apiParam = new ApiParam();
|
|
||||||
apiParam.putAll(params);
|
|
||||||
setApiParam(exchange, apiParam);
|
|
||||||
return apiParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setThrowable(ServerWebExchange exchange, Throwable throwable) {
|
|
||||||
exchange.getAttributes().put(THROWABLE_KEY, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Throwable getThrowable(ServerWebExchange exchange) {
|
|
||||||
return (Throwable)exchange.getAttribute(THROWABLE_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取请求参数
|
|
||||||
*
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @return 返回请求参数
|
|
||||||
*/
|
|
||||||
public static ApiParam getApiParam(ServerWebExchange exchange) {
|
|
||||||
return exchange.getAttribute(SopConstants.CACHE_API_PARAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置请求参数
|
|
||||||
*
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @param apiParam 请求参数
|
|
||||||
*/
|
|
||||||
public static void setApiParam(ServerWebExchange exchange, ApiParam apiParam) {
|
|
||||||
exchange.getAttributes().put(SopConstants.CACHE_API_PARAM, apiParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加header
|
|
||||||
*
|
|
||||||
* @param exchange 当前ServerWebExchange
|
|
||||||
* @param headersConsumer headers
|
|
||||||
* @return 返回一个新的ServerWebExchange
|
|
||||||
*/
|
|
||||||
public static ServerWebExchange addHeaders(ServerWebExchange exchange, Consumer<HttpHeaders> headersConsumer) {
|
|
||||||
// 创建一个新的request
|
|
||||||
ServerHttpRequest serverHttpRequestNew = exchange.getRequest()
|
|
||||||
.mutate()
|
|
||||||
.headers(headersConsumer)
|
|
||||||
.build();
|
|
||||||
// 创建一个新的exchange对象
|
|
||||||
return exchange
|
|
||||||
.mutate()
|
|
||||||
.request(serverHttpRequestNew)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回header值
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @param name header名
|
|
||||||
* @return 返回值,没有返回null
|
|
||||||
*/
|
|
||||||
public static Optional<String> getHeaderValue(ServerWebExchange exchange, String name) {
|
|
||||||
List<String> values = exchange.getResponse().getHeaders().get(name);
|
|
||||||
if (CollectionUtils.isEmpty(values)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return values.stream().findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除header
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @param name header名
|
|
||||||
*/
|
|
||||||
public static void removeHeader(ServerWebExchange exchange, String name) {
|
|
||||||
exchange.getResponse().getHeaders().remove(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一个文件上传request
|
|
||||||
*
|
|
||||||
* @param exchange 当前ServerWebExchange
|
|
||||||
* @param data 上传文件请求体内容
|
|
||||||
* @return 返回文件上传request
|
|
||||||
*/
|
|
||||||
public static HttpServletRequest getFileUploadRequest(ServerWebExchange exchange, byte[] data) {
|
|
||||||
return new FileUploadHttpServletRequest(exchange.getRequest(), data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HttpServletRequest getFileUploadRequest(ServerWebExchange exchange) {
|
|
||||||
return exchange.getAttribute(SopConstants.CACHE_UPLOAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFileUploadRequest(ServerWebExchange exchange, HttpServletRequest request) {
|
|
||||||
exchange.getAttributes().put(SopConstants.CACHE_UPLOAD_REQUEST, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改请求参数。参考自:https://blog.csdn.net/fuck487/article/details/85166162
|
|
||||||
*
|
|
||||||
* @param exchange ServerWebExchange
|
|
||||||
* @param apiParam 原始请求参数
|
|
||||||
* @param paramsConsumer 执行参数更改
|
|
||||||
* @param headerConsumer header更改
|
|
||||||
* @param <T> 参数类型
|
|
||||||
* @return 返回新的ServerWebExchange
|
|
||||||
*/
|
|
||||||
public static <T extends Map<String, Object>> ServerWebExchange format(
|
|
||||||
ServerWebExchange exchange
|
|
||||||
, T apiParam
|
|
||||||
, Consumer<T> paramsConsumer
|
|
||||||
, Consumer<HttpHeaders> headerConsumer
|
|
||||||
) {
|
|
||||||
ServerHttpRequest serverHttpRequest = exchange.getRequest();
|
|
||||||
// 新的request
|
|
||||||
ServerHttpRequest newRequest;
|
|
||||||
paramsConsumer.accept(apiParam);
|
|
||||||
if (serverHttpRequest.getMethod() == HttpMethod.GET) {
|
|
||||||
// 新的查询参数
|
|
||||||
String queryString = RequestUtil.convertMapToQueryString(apiParam);
|
|
||||||
// 创建一个新的request,并使用新的uri
|
|
||||||
newRequest = new SopServerHttpRequestDecorator(serverHttpRequest, queryString);
|
|
||||||
} else {
|
|
||||||
MediaType mediaType = serverHttpRequest.getHeaders().getContentType();
|
|
||||||
if (mediaType == null) {
|
|
||||||
mediaType = MediaType.APPLICATION_FORM_URLENCODED;
|
|
||||||
}
|
|
||||||
String contentType = mediaType.toString().toLowerCase();
|
|
||||||
// 修改后的请求体
|
|
||||||
// 处理json请求(application/json)
|
|
||||||
if (StringUtils.containsAny(contentType, "json", "text")) {
|
|
||||||
String bodyStr = JSON.toJSONString(apiParam);
|
|
||||||
byte[] bodyBytes = bodyStr.getBytes(StandardCharsets.UTF_8);
|
|
||||||
newRequest = new SopServerHttpRequestDecorator(serverHttpRequest, bodyBytes);
|
|
||||||
} else if (StringUtils.contains(contentType, "multipart")) {
|
|
||||||
// 处理文件上传请求
|
|
||||||
FormHttpOutputMessage outputMessage = new FormHttpOutputMessage();
|
|
||||||
HttpServletRequest request = ServerWebExchangeUtil.getFileUploadRequest(exchange);
|
|
||||||
try {
|
|
||||||
// 转成MultipartRequest
|
|
||||||
if (!(request instanceof MultipartHttpServletRequest)) {
|
|
||||||
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
|
|
||||||
request = commonsMultipartResolver.resolveMultipart(request);
|
|
||||||
}
|
|
||||||
// 重写新的值
|
|
||||||
MultiValueMap<String, Object> builder = RequestContentDataExtractor.extract(request);
|
|
||||||
for (Map.Entry<String, Object> entry : apiParam.entrySet()) {
|
|
||||||
Object value = entry.getValue();
|
|
||||||
if (value instanceof List) {
|
|
||||||
builder.put(entry.getKey(), (List) value);
|
|
||||||
} else {
|
|
||||||
builder.put(entry.getKey(), Collections.singletonList(String.valueOf(value)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 将字段以及上传文件重写写入到流中
|
|
||||||
formHttpMessageConverter.write(builder, mediaType, outputMessage);
|
|
||||||
// 获取新的上传文件流
|
|
||||||
byte[] bodyBytes = outputMessage.getInput();
|
|
||||||
newRequest = new SopServerHttpRequestDecorator(serverHttpRequest, bodyBytes);
|
|
||||||
// 必须要重新指定content-type,因为此时的boundary已经发生改变
|
|
||||||
MediaType contentTypeMultipart = outputMessage.getHeaders().getContentType();
|
|
||||||
newRequest.getHeaders().setContentType(contentTypeMultipart);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("修改上传文件请求参数失败, apiParam:{}", apiParam, e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 否则一律按表单请求处理
|
|
||||||
String bodyStr = RequestUtil.convertMapToQueryString(apiParam);
|
|
||||||
byte[] bodyBytes = bodyStr.getBytes(StandardCharsets.UTF_8);
|
|
||||||
newRequest = new SopServerHttpRequestDecorator(serverHttpRequest, bodyBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HttpHeaders headers = newRequest.getHeaders();
|
|
||||||
// 自定义header
|
|
||||||
headerConsumer.accept(headers);
|
|
||||||
// 创建一个新的exchange
|
|
||||||
return exchange.mutate().request(newRequest).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,53 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.common;
|
|
||||||
|
|
||||||
import javax.servlet.ReadListener;
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* byte数组流包装
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class ByteArrayStreamWrapper extends ServletInputStream {
|
|
||||||
|
|
||||||
private byte[] data;
|
|
||||||
private int idx = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new <code>ByteArrayStreamWrapper</code> instance.
|
|
||||||
*
|
|
||||||
* @param data a <code>byte[]</code> value
|
|
||||||
*/
|
|
||||||
public ByteArrayStreamWrapper(byte[] data) {
|
|
||||||
if (data == null) {
|
|
||||||
data = new byte[0];
|
|
||||||
}
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (idx == data.length) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// I have to AND the byte with 0xff in order to ensure that it is returned as an unsigned integer
|
|
||||||
// the lack of this was causing a weird bug when manually unzipping gzipped request bodies
|
|
||||||
return data[idx++] & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReadListener(ReadListener readListener) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,391 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.common;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
|
|
||||||
import javax.servlet.AsyncContext;
|
|
||||||
import javax.servlet.DispatcherType;
|
|
||||||
import javax.servlet.RequestDispatcher;
|
|
||||||
import javax.servlet.ServletContext;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.Cookie;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import javax.servlet.http.HttpUpgradeHandler;
|
|
||||||
import javax.servlet.http.Part;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传文件的request
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class FileUploadHttpServletRequest implements HttpServletRequest {
|
|
||||||
private ServerHttpRequest serverHttpRequest;
|
|
||||||
private byte[] data;
|
|
||||||
|
|
||||||
public FileUploadHttpServletRequest(ServerHttpRequest serverHttpRequest, byte[] data) {
|
|
||||||
this.serverHttpRequest = serverHttpRequest;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
|
||||||
return new ByteArrayStreamWrapper(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getContentLength() {
|
|
||||||
return data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getContentLengthLong() {
|
|
||||||
return data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 最关键这个要返回正确
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getContentType() {
|
|
||||||
return serverHttpRequest.getHeaders().getContentType().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cookie[] getCookies() {
|
|
||||||
return new Cookie[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDateHeader(String s) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getHeaders(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getHeaderNames() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIntHeader(String s) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMethod() {
|
|
||||||
return HttpMethod.POST.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPathInfo() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPathTranslated() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getContextPath() {
|
|
||||||
return "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getQueryString() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRemoteUser() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isUserInRole(String s) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Principal getUserPrincipal() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRequestedSessionId() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRequestURI() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StringBuffer getRequestURL() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServletPath() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpSession getSession(boolean b) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpSession getSession() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String changeSessionId() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRequestedSessionIdValid() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRequestedSessionIdFromCookie() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRequestedSessionIdFromURL() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRequestedSessionIdFromUrl() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void login(String s, String s1) throws ServletException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void logout() throws ServletException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Part> getParts() throws IOException, ServletException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Part getPart(String s) throws IOException, ServletException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getAttribute(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getAttributeNames() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCharacterEncoding() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getParameterNames() {
|
|
||||||
return Collections.emptyEnumeration();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getParameterValues(String s) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String[]> getParameterMap() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProtocol() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getScheme() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getServerName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getServerPort() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BufferedReader getReader() throws IOException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRemoteAddr() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRemoteHost() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setAttribute(String s, Object o) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAttribute(String s) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Locale getLocale() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<Locale> getLocales() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSecure() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestDispatcher getRequestDispatcher(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRealPath(String s) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRemotePort() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLocalName() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getLocalAddr() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLocalPort() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletContext getServletContext() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncContext startAsync() throws IllegalStateException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAsyncStarted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAsyncSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncContext getAsyncContext() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DispatcherType getDispatcherType() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013-2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.gitee.sop.gatewaycommon.gateway.common;
|
|
||||||
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
|
||||||
import org.springframework.http.HttpEntity;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static org.springframework.util.StringUtils.isEmpty;
|
|
||||||
import static org.springframework.util.StringUtils.tokenizeToStringArray;
|
|
||||||
import static org.springframework.util.StringUtils.uriDecode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class RequestContentDataExtractor {
|
|
||||||
public static MultiValueMap<String, Object> extract(HttpServletRequest request) throws IOException {
|
|
||||||
return (request instanceof MultipartHttpServletRequest) ?
|
|
||||||
extractFromMultipartRequest((MultipartHttpServletRequest) request) :
|
|
||||||
extractFromRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MultiValueMap<String, Object> extractFromRequest(HttpServletRequest request) throws IOException {
|
|
||||||
MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>();
|
|
||||||
Set<String> queryParams = findQueryParams(request);
|
|
||||||
|
|
||||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
|
||||||
String key = entry.getKey();
|
|
||||||
|
|
||||||
if (!queryParams.contains(key) && entry.getValue() != null) {
|
|
||||||
for (String value : entry.getValue()) {
|
|
||||||
builder.add(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MultiValueMap<String, Object> extractFromMultipartRequest(MultipartHttpServletRequest request)
|
|
||||||
throws IOException {
|
|
||||||
MultiValueMap<String, Object> builder = new LinkedMultiValueMap<>();
|
|
||||||
Map<String, List<String>> queryParamsGroupedByName = findQueryParamsGroupedByName(
|
|
||||||
request);
|
|
||||||
Set<String> queryParams = findQueryParams(request);
|
|
||||||
|
|
||||||
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
|
|
||||||
String key = entry.getKey();
|
|
||||||
List<String> listOfAllParams = stream(request.getParameterMap().get(key))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<String> listOfOnlyQueryParams = queryParamsGroupedByName.get(key);
|
|
||||||
|
|
||||||
if(listOfOnlyQueryParams != null) {
|
|
||||||
listOfOnlyQueryParams = listOfOnlyQueryParams.stream()
|
|
||||||
.map(param -> uriDecode(param, Charset.defaultCharset()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (!listOfOnlyQueryParams.containsAll(listOfAllParams)) {
|
|
||||||
listOfAllParams.removeAll(listOfOnlyQueryParams);
|
|
||||||
for (String value : listOfAllParams) {
|
|
||||||
builder.add(key,
|
|
||||||
new HttpEntity<>(value, newHttpHeaders(request, key)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!queryParams.contains(key)) {
|
|
||||||
for (String value : entry.getValue()) {
|
|
||||||
builder.add(key,
|
|
||||||
new HttpEntity<>(value, newHttpHeaders(request, key)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Entry<String, List<MultipartFile>> parts : request.getMultiFileMap().entrySet()) {
|
|
||||||
for (MultipartFile file : parts.getValue()) {
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentDispositionFormData(file.getName(), file.getOriginalFilename());
|
|
||||||
if (file.getContentType() != null) {
|
|
||||||
headers.setContentType(MediaType.valueOf(file.getContentType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpEntity entity = new HttpEntity<>(new InputStreamResource(file.getInputStream()), headers);
|
|
||||||
builder.add(parts.getKey(), entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpHeaders newHttpHeaders(MultipartHttpServletRequest request,
|
|
||||||
String key) {
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
String type = request.getMultipartContentType(key);
|
|
||||||
|
|
||||||
if (type != null) {
|
|
||||||
headers.setContentType(MediaType.valueOf(type));
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> findQueryParams(HttpServletRequest request) {
|
|
||||||
Set<String> result = new HashSet<>();
|
|
||||||
String query = request.getQueryString();
|
|
||||||
|
|
||||||
if (query != null) {
|
|
||||||
for (String value : tokenizeToStringArray(query, "&")) {
|
|
||||||
if (value.contains("=")) {
|
|
||||||
value = value.substring(0, value.indexOf("="));
|
|
||||||
}
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, List<String>> findQueryParamsGroupedByName(
|
|
||||||
HttpServletRequest request) {
|
|
||||||
String query = request.getQueryString();
|
|
||||||
if (isEmpty(query)) {
|
|
||||||
return emptyMap();
|
|
||||||
}
|
|
||||||
return UriComponentsBuilder.fromUriString("?" + query).build().getQueryParams();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,97 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.common;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class SopServerHttpRequestDecorator extends ServerHttpRequestDecorator {
|
|
||||||
|
|
||||||
private Flux<DataBuffer> bodyDataBuffer;
|
|
||||||
private HttpHeaders httpHeaders;
|
|
||||||
private URI uri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServerHttpRequest包装,作用类似于HttpServletRequestWrapper
|
|
||||||
*
|
|
||||||
* @param delegate 老的request
|
|
||||||
* @param queryString get请求后面的参数
|
|
||||||
*/
|
|
||||||
public SopServerHttpRequestDecorator(ServerHttpRequest delegate, String queryString) {
|
|
||||||
super(delegate);
|
|
||||||
if (delegate.getMethod() != HttpMethod.GET) {
|
|
||||||
throw new IllegalArgumentException("this constructor must be used by GET request.");
|
|
||||||
}
|
|
||||||
if (queryString == null) {
|
|
||||||
throw new IllegalArgumentException("queryString can not be null.");
|
|
||||||
}
|
|
||||||
// 默认header是只读的,把它改成可写,方便后面的过滤器使用
|
|
||||||
this.httpHeaders = HttpHeaders.writableHttpHeaders(delegate.getHeaders());
|
|
||||||
this.uri = UriComponentsBuilder.fromUri(delegate.getURI())
|
|
||||||
.replaceQuery(queryString)
|
|
||||||
.build(true)
|
|
||||||
.toUri();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ServerHttpRequest包装,作用类似于HttpServletRequestWrapper
|
|
||||||
*
|
|
||||||
* @param delegate 老的request
|
|
||||||
* @param bodyData 请求体内容
|
|
||||||
*/
|
|
||||||
public SopServerHttpRequestDecorator(ServerHttpRequest delegate, byte[] bodyData) {
|
|
||||||
super(delegate);
|
|
||||||
if (bodyData == null) {
|
|
||||||
throw new IllegalArgumentException("bodyData can not be null.");
|
|
||||||
}
|
|
||||||
// 默认header是只读的,把它改成可写,方便后面的过滤器使用
|
|
||||||
this.httpHeaders = HttpHeaders.writableHttpHeaders(delegate.getHeaders());
|
|
||||||
// 由于请求体已改变,这里要重新设置contentLength
|
|
||||||
int contentLength = bodyData.length;
|
|
||||||
httpHeaders.setContentLength(contentLength);
|
|
||||||
if (contentLength <= 0) {
|
|
||||||
// TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
|
|
||||||
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
|
|
||||||
}
|
|
||||||
this.bodyDataBuffer = stringBuffer(bodyData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字符串转DataBuffer
|
|
||||||
*
|
|
||||||
* @param bytes 请求体
|
|
||||||
* @return 返回buffer
|
|
||||||
*/
|
|
||||||
private static Flux<DataBuffer> stringBuffer(byte[] bytes) {
|
|
||||||
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
|
|
||||||
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
|
|
||||||
buffer.write(bytes);
|
|
||||||
return Flux.just(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URI getURI() {
|
|
||||||
return uri == null ? super.getURI() : uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders getHeaders() {
|
|
||||||
return this.httpHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<DataBuffer> getBody() {
|
|
||||||
return bodyDataBuffer == null ? super.getBody() : bodyDataBuffer;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,10 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 具备支付宝开放平台能力配置 https://docs.open.alipay.com/api
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class AlipayGatewayConfiguration extends BaseGatewayConfiguration {
|
|
||||||
|
|
||||||
}
|
|
@@ -1,97 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
|
||||||
import com.gitee.sop.gatewaycommon.config.AbstractConfiguration;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.GatewayModifyResponseGatewayFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.IndexFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.LimitFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.filter.ParameterFormatterFilter;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.handler.GatewayExceptionHandler;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteCache;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayRouteRepository;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
|
||||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class BaseGatewayConfiguration extends AbstractConfiguration {
|
|
||||||
|
|
||||||
public BaseGatewayConfiguration() {
|
|
||||||
ApiConfig.getInstance().setUseGateway(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public IndexFilter indexFilter() {
|
|
||||||
return new IndexFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义异常处理[@@]注册Bean时依赖的Bean,会从容器中直接获取,所以直接注入即可
|
|
||||||
*
|
|
||||||
* @param viewResolversProvider viewResolversProvider
|
|
||||||
* @param serverCodecConfigurer serverCodecConfigurer
|
|
||||||
*/
|
|
||||||
@Primary
|
|
||||||
@Bean
|
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
||||||
public ErrorWebExceptionHandler sopErrorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
|
|
||||||
ServerCodecConfigurer serverCodecConfigurer) {
|
|
||||||
|
|
||||||
GatewayExceptionHandler jsonExceptionHandler = new GatewayExceptionHandler();
|
|
||||||
jsonExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
|
|
||||||
jsonExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
|
|
||||||
jsonExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
|
|
||||||
return jsonExceptionHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理返回结果
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
GatewayModifyResponseGatewayFilter gatewayModifyResponseGatewayFilter() {
|
|
||||||
return new GatewayModifyResponseGatewayFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
ParameterFormatterFilter parameterFormatterFilter() {
|
|
||||||
return new ParameterFormatterFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
LimitFilter limitFilter() {
|
|
||||||
return new LimitFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
GatewayRouteCache gatewayRouteCache(GatewayRouteRepository gatewayRouteRepository) {
|
|
||||||
return new GatewayRouteCache(gatewayRouteRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
GatewayRouteRepository gatewayRouteRepository() {
|
|
||||||
GatewayRouteRepository gatewayRouteRepository = new GatewayRouteRepository();
|
|
||||||
RouteRepositoryContext.setRouteRepository(gatewayRouteRepository);
|
|
||||||
return gatewayRouteRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
GatewayForwardChooser gatewayForwardChooser() {
|
|
||||||
return new GatewayForwardChooser();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.configuration;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 具备淘宝开放平台能力配置
|
|
||||||
* 淘宝开放平台:http://open.taobao.com/doc.htm
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class TaobaoGatewayConfiguration extends BaseGatewayConfiguration {
|
|
||||||
|
|
||||||
static {
|
|
||||||
ParamNames.APP_KEY_NAME = "app_key";
|
|
||||||
ParamNames.SIGN_TYPE_NAME = "sign_method";
|
|
||||||
ParamNames.VERSION_NAME = "v";
|
|
||||||
ParamNames.APP_AUTH_TOKEN_NAME = "session";
|
|
||||||
|
|
||||||
ApiContext.getApiConfig().setSigner(new TaobaoSigner());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,80 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.controller;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.GatewayPushDTO;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.NacosConfigs;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SpringContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.ChannelMsgProcessor;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.IPBlacklistManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RequestUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
|
||||||
public class ConfigChannelController {
|
|
||||||
|
|
||||||
private static Map<String, Class<? extends ChannelMsgProcessor>> processorMap = new HashMap<>(16);
|
|
||||||
|
|
||||||
static {
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_GRAY, EnvGrayManager.class);
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_IP_BLACKLIST, IPBlacklistManager.class);
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ISV, IsvManager.class);
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_PERMISSION, IsvRoutePermissionManager.class);
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_LIMIT_CONFIG, LimitConfigManager.class);
|
|
||||||
processorMap.put(NacosConfigs.GROUP_CHANNEL + NacosConfigs.DATA_ID_ROUTE_CONFIG, RouteConfigManager.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value("${sop.secret}")
|
|
||||||
private String secret;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ServerCodecConfigurer codecConfigurer;
|
|
||||||
|
|
||||||
@PostMapping("/sop/configChannelMsg")
|
|
||||||
public Mono<String> configChannel(ServerWebExchange exchange) {
|
|
||||||
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange, codecConfigurer);
|
|
||||||
// 读取请求体中的内容
|
|
||||||
return serverRequest.bodyToMono(String.class)
|
|
||||||
.flatMap(requestJson -> {
|
|
||||||
String sign = exchange.getRequest().getHeaders().getFirst("sign");
|
|
||||||
try {
|
|
||||||
// 签名验证
|
|
||||||
RequestUtil.checkResponseBody(requestJson, sign, secret);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("configChannelMsg错误", e);
|
|
||||||
return Mono.just(e.getMessage());
|
|
||||||
}
|
|
||||||
GatewayPushDTO gatewayPushDTO = JSON.parseObject(requestJson, GatewayPushDTO.class);
|
|
||||||
ChannelMsgProcessor channelMsgProcessor = getChannelMsgProcessor(gatewayPushDTO);
|
|
||||||
channelMsgProcessor.process(gatewayPushDTO.getChannelMsg());
|
|
||||||
return Mono.just("ok");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChannelMsgProcessor getChannelMsgProcessor(GatewayPushDTO gatewayPushDTO) {
|
|
||||||
String key = gatewayPushDTO.getGroupId() + gatewayPushDTO.getDataId();
|
|
||||||
Class<? extends ChannelMsgProcessor> aClass = processorMap.get(key);
|
|
||||||
return SpringContext.getBean(aClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,46 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.controller;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
public class GatewayController {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理签名错误返回
|
|
||||||
*
|
|
||||||
* @param exchange exchange
|
|
||||||
* @return 返回最终结果
|
|
||||||
*/
|
|
||||||
@RequestMapping("/sop/validateError")
|
|
||||||
public Mono<String> validateError(ServerWebExchange exchange) {
|
|
||||||
Throwable throwable = ServerWebExchangeUtil.getThrowable(exchange);
|
|
||||||
// 合并微服务传递过来的结果,变成最终结果
|
|
||||||
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
|
||||||
String gatewayResult = resultExecutor.buildErrorResult(exchange, throwable);
|
|
||||||
exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
return Mono.just(gatewayResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping("/sop/unknown")
|
|
||||||
public Mono<String> unknown(ServerWebExchange exchange) {
|
|
||||||
ApiException exception = ErrorEnum.ISV_INVALID_METHOD.getErrorMeta().getException();
|
|
||||||
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
|
||||||
String gatewayResult = resultExecutor.buildErrorResult(exchange, exception);
|
|
||||||
exchange.getResponse().getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
return Mono.just(gatewayResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,71 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.controller;
|
|
||||||
|
|
||||||
import org.apache.commons.collections.CollectionUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.cloud.client.ServiceInstance;
|
|
||||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
|
||||||
import org.springframework.cloud.gateway.webflux.ProxyExchange;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理restful请求
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Controller
|
|
||||||
public class RestfulController {
|
|
||||||
|
|
||||||
@Value("${sop.restful.path:/rest}")
|
|
||||||
private String prefix;
|
|
||||||
@Autowired
|
|
||||||
private DiscoveryClient discoveryClient;
|
|
||||||
|
|
||||||
@RequestMapping("${sop.restful.path:/rest}/**")
|
|
||||||
public Mono<ResponseEntity<byte[]>> proxy(ProxyExchange<byte[]> proxy, ServerWebExchange exchange) {
|
|
||||||
String path = proxy.path();
|
|
||||||
String serviceId = getServiceId(path);
|
|
||||||
String targetPath = getTargetPath(serviceId, path);
|
|
||||||
String rawQuery = exchange.getRequest().getURI().getRawQuery();
|
|
||||||
if (StringUtils.hasLength(rawQuery)) {
|
|
||||||
targetPath = targetPath + "?" + rawQuery;
|
|
||||||
}
|
|
||||||
// 负载均衡
|
|
||||||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
|
|
||||||
if (CollectionUtils.isEmpty(instances)) {
|
|
||||||
return Mono.error(new RuntimeException("serviceId: " + serviceId + " not found"));
|
|
||||||
}
|
|
||||||
ServiceInstance serviceInstance = instances.stream()
|
|
||||||
.skip(new Random().nextInt(instances.size()))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
String uri = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + targetPath;
|
|
||||||
return proxy.uri(uri).forward();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从path中解析出serviceId
|
|
||||||
* @param path 格式:/rest/<serviceId><real_path>
|
|
||||||
* @return 返回serviceId
|
|
||||||
*/
|
|
||||||
private String getServiceId(String path) {
|
|
||||||
int length = prefix.length() + 1;
|
|
||||||
path = path.substring(length);
|
|
||||||
int index = path.indexOf('/');
|
|
||||||
path = path.substring(0, index);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTargetPath(String serviceId, String path) {
|
|
||||||
int length = prefix.length() + 1;
|
|
||||||
int len = length + serviceId.length();
|
|
||||||
return path.substring(len);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvGrayManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
* @deprecated
|
|
||||||
* @see com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class EnvGrayFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private EnvGrayManager envGrayManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
|
||||||
String nameVersion = apiParam.fetchNameVersion();
|
|
||||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(nameVersion);
|
|
||||||
if (targetRoute == null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
String serviceId = targetRoute.getServiceDefinition().fetchServiceIdLowerCase();
|
|
||||||
// 如果服务在灰度阶段,返回一个灰度版本号
|
|
||||||
String version = envGrayManager.getVersion(serviceId, nameVersion);
|
|
||||||
if (version != null && envGrayManager.containsKey(serviceId, apiParam.fetchAppKey())) {
|
|
||||||
ServerWebExchange serverWebExchange = ServerWebExchangeUtil.addHeaders(exchange, httpHeaders -> httpHeaders.set(ParamNames.HEADER_VERSION_NAME, version));
|
|
||||||
return chain.filter(serverWebExchange);
|
|
||||||
}
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return Orders.ENV_GRAY_FILTER_ORDER;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,214 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.reactivestreams.Publisher;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
|
|
||||||
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
|
|
||||||
import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyEncoder;
|
|
||||||
import org.springframework.cloud.gateway.support.BodyInserterContext;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseCookie;
|
|
||||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserter;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import reactor.core.scheduler.Schedulers;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.util.function.Function.identity;
|
|
||||||
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改返回结果
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class GatewayModifyResponseGatewayFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ServerCodecConfigurer codecConfigurer;
|
|
||||||
@Autowired
|
|
||||||
private Set<MessageBodyEncoder> bodyEncoders;
|
|
||||||
|
|
||||||
private Map<String, MessageBodyEncoder> messageBodyEncoders;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
|
|
||||||
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
|
||||||
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
|
|
||||||
// 如果是下载文件,直接放行,不合并结果
|
|
||||||
if (StringUtils.containsIgnoreCase(originalResponseContentType, MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
// rest请求,直接放行
|
|
||||||
if (exchange.getAttribute(SopConstants.RESTFUL_REQUEST) != null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
Class inClass = String.class;
|
|
||||||
Class outClass = String.class;
|
|
||||||
|
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
// explicitly add it in this way instead of
|
|
||||||
// 'httpHeaders.setContentType(originalResponseContentType)'
|
|
||||||
// this will prevent exception in case of using non-standard media
|
|
||||||
// types like "Content-Type: image"
|
|
||||||
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
|
|
||||||
ClientResponse clientResponse = prepareClientResponse(exchange, body, httpHeaders);
|
|
||||||
|
|
||||||
//TODO: flux or mono
|
|
||||||
Mono modifiedBody = clientResponse.bodyToMono(inClass)
|
|
||||||
// 修复微服务接口返回void网关不会返回code和msg问题
|
|
||||||
.switchIfEmpty(Mono.just(""))
|
|
||||||
.flatMap(originalBody -> {
|
|
||||||
// 合并微服务传递过来的结果,变成最终结果
|
|
||||||
ResultExecutor resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
|
||||||
String ret = resultExecutor.mergeResult(exchange, String.valueOf(originalBody));
|
|
||||||
return Mono.just(ret);
|
|
||||||
});
|
|
||||||
|
|
||||||
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
|
|
||||||
outClass);
|
|
||||||
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
|
|
||||||
exchange.getResponse().getHeaders());
|
|
||||||
return bodyInserter.insert(outputMessage, new BodyInserterContext())
|
|
||||||
.then(Mono.defer(() -> {
|
|
||||||
Mono<DataBuffer> messageBody = writeBody(getDelegate(),
|
|
||||||
outputMessage, outClass);
|
|
||||||
HttpHeaders headers = getDelegate().getHeaders();
|
|
||||||
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
|
|
||||||
|| headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
|
|
||||||
messageBody = messageBody.doOnNext(data -> headers
|
|
||||||
.setContentLength(data.readableByteCount()));
|
|
||||||
}
|
|
||||||
// TODO: fail if isStreamingMediaType?
|
|
||||||
return getDelegate().writeWith(messageBody);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
|
||||||
return writeWith(Flux.from(body)
|
|
||||||
.flatMapSequential(p -> p));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return chain.filter(exchange.mutate().response(responseDecorator).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClientResponse prepareClientResponse(
|
|
||||||
ServerWebExchange exchange,
|
|
||||||
Publisher<? extends DataBuffer> body,
|
|
||||||
HttpHeaders httpHeaders
|
|
||||||
) {
|
|
||||||
ClientResponse.Builder builder;
|
|
||||||
builder = ClientResponse.create(exchange.getResponse().getStatusCode(), codecConfigurer.getReaders());
|
|
||||||
return builder.headers(headers -> headers.putAll(httpHeaders))
|
|
||||||
.body(Flux.from(body)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mono<DataBuffer> writeBody(ServerHttpResponse httpResponse,
|
|
||||||
CachedBodyOutputMessage message, Class<?> outClass) {
|
|
||||||
Mono<DataBuffer> response = DataBufferUtils.join(message.getBody());
|
|
||||||
if (byte[].class.isAssignableFrom(outClass)) {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> encodingHeaders = httpResponse.getHeaders()
|
|
||||||
.getOrEmpty(HttpHeaders.CONTENT_ENCODING);
|
|
||||||
for (String encoding : encodingHeaders) {
|
|
||||||
MessageBodyEncoder encoder = messageBodyEncoders.get(encoding);
|
|
||||||
if (encoder != null) {
|
|
||||||
DataBufferFactory dataBufferFactory = httpResponse.bufferFactory();
|
|
||||||
response = response.publishOn(Schedulers.parallel()).map(buffer -> {
|
|
||||||
byte[] encodedResponse = encoder.encode(buffer);
|
|
||||||
DataBufferUtils.release(buffer);
|
|
||||||
return encodedResponse;
|
|
||||||
}).map(dataBufferFactory::wrap);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ResponseAdapter implements ClientHttpResponse {
|
|
||||||
|
|
||||||
private final Flux<DataBuffer> flux;
|
|
||||||
private final HttpHeaders headers;
|
|
||||||
|
|
||||||
public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {
|
|
||||||
this.headers = headers;
|
|
||||||
if (body instanceof Flux) {
|
|
||||||
flux = (Flux) body;
|
|
||||||
} else {
|
|
||||||
flux = ((Mono) body).flux();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<DataBuffer> getBody() {
|
|
||||||
return flux;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpHeaders getHeaders() {
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpStatus getStatusCode() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRawStatusCode() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MultiValueMap<String, ResponseCookie> getCookies() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void after() {
|
|
||||||
this.messageBodyEncoders = bodyEncoders == null ? Collections.emptyMap() : bodyEncoders.stream()
|
|
||||||
.collect(Collectors.toMap(MessageBodyEncoder::encodingType, identity()));
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,188 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.DefaultRouteInterceptorContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.route.GatewayForwardChooser;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.EnvironmentKeys;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.route.ForwardInfo;
|
|
||||||
import com.gitee.sop.gatewaycommon.util.RouteInterceptorUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.validate.Validator;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
|
|
||||||
import org.springframework.cloud.gateway.support.BodyInserterContext;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserter;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import org.springframework.web.server.WebFilter;
|
|
||||||
import org.springframework.web.server.WebFilterChain;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 入口
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
|
||||||
public class IndexFilter implements WebFilter {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ServerCodecConfigurer codecConfigurer;
|
|
||||||
|
|
||||||
/** 路径白名单 */
|
|
||||||
private static final List<String> PATH_WHITE_LIST = Arrays.asList(
|
|
||||||
"/sop", "/actuator"
|
|
||||||
);
|
|
||||||
|
|
||||||
@Value("${sop.gateway-index-path:/}")
|
|
||||||
private String indexPath;
|
|
||||||
|
|
||||||
/** sop.restful.enable=true ,开启restful请求,默认开启 */
|
|
||||||
@Value("${sop.restful.enable:true}")
|
|
||||||
private boolean enableRestful;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private Validator validator;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private GatewayForwardChooser gatewayForwardChooser;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
|
||||||
ServerHttpRequest request = exchange.getRequest();
|
|
||||||
String path = request.getURI().getPath();
|
|
||||||
// 路径是否在白名单中,直接放行
|
|
||||||
if (this.isPathInWhiteList(path)) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
// 如果是restful请求,直接转发
|
|
||||||
// see:com.gitee.sop.gatewaycommon.gateway.controller.RestfulController
|
|
||||||
if (enableRestful && path.startsWith(EnvironmentKeys.SOP_RESTFUL_PATH.getValue())) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
if (Objects.equals(path, indexPath) || "".equals(path)) {
|
|
||||||
if (request.getMethod() == HttpMethod.POST) {
|
|
||||||
ServerRequest serverRequest = ServerWebExchangeUtil.createReadBodyRequest(exchange, codecConfigurer);
|
|
||||||
// 读取请求体中的内容
|
|
||||||
Mono<?> modifiedBody = serverRequest.bodyToMono(byte[].class)
|
|
||||||
.switchIfEmpty(Mono.just("".getBytes()))
|
|
||||||
.flatMap(data -> {
|
|
||||||
// 构建ApiParam
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange, data);
|
|
||||||
// 签名验证
|
|
||||||
doValidate(exchange, apiParam);
|
|
||||||
return Mono.just(data);
|
|
||||||
});
|
|
||||||
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, (Class)byte[].class);
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.putAll(exchange.getRequest().getHeaders());
|
|
||||||
|
|
||||||
// the new content type will be computed by bodyInserter
|
|
||||||
// and then set in the request decorator
|
|
||||||
headers.remove(HttpHeaders.CONTENT_LENGTH);
|
|
||||||
|
|
||||||
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
|
|
||||||
exchange, headers);
|
|
||||||
return bodyInserter.insert(outputMessage, new BodyInserterContext())
|
|
||||||
.then(Mono.defer(() -> {
|
|
||||||
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
|
|
||||||
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
|
|
||||||
ServerWebExchange newExchange = exchange.mutate().request(decorator).build();
|
|
||||||
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(newExchange, forwardInfo);
|
|
||||||
return chain.filter(forwardExchange);
|
|
||||||
}));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
URI uri = exchange.getRequest().getURI();
|
|
||||||
// 原始参数
|
|
||||||
String originalQuery = uri.getRawQuery();
|
|
||||||
// 构建ApiParam
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParamByQuery(exchange, originalQuery);
|
|
||||||
// 签名验证
|
|
||||||
doValidate(exchange, apiParam);
|
|
||||||
|
|
||||||
ForwardInfo forwardInfo = gatewayForwardChooser.getForwardInfo(exchange);
|
|
||||||
ServerWebExchange forwardExchange = ServerWebExchangeUtil.getForwardExchange(exchange, forwardInfo);
|
|
||||||
return chain.filter(forwardExchange);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ServerWebExchangeUtil.forwardUnknown(exchange, chain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPathInWhiteList(String path) {
|
|
||||||
for (String whitePath : PATH_WHITE_LIST) {
|
|
||||||
if (path.startsWith(whitePath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doValidate(ServerWebExchange exchange, ApiParam apiParam) {
|
|
||||||
try {
|
|
||||||
validator.validate(apiParam);
|
|
||||||
this.afterValidate(exchange, apiParam);
|
|
||||||
} catch (ApiException e) {
|
|
||||||
log.error("验证失败, errorMsg:{},url:{}, ip:{}, params:{}",
|
|
||||||
e.getMessage(),
|
|
||||||
exchange.getRequest().getURI().toString(),
|
|
||||||
apiParam.fetchIp(), apiParam.toJSONString());
|
|
||||||
ServerWebExchangeUtil.setThrowable(exchange, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void afterValidate(ServerWebExchange exchange, ApiParam param) {
|
|
||||||
RouteInterceptorUtil.runPreRoute(exchange, param, context -> {
|
|
||||||
DefaultRouteInterceptorContext defaultRouteInterceptorContext = (DefaultRouteInterceptorContext) context;
|
|
||||||
defaultRouteInterceptorContext.setRequestDataSize(exchange.getRequest().getHeaders().getContentLength());
|
|
||||||
exchange.getAttributes().put(SopConstants.CACHE_ROUTE_INTERCEPTOR_CONTEXT, context);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private ServerHttpRequestDecorator decorate(
|
|
||||||
ServerWebExchange exchange
|
|
||||||
, HttpHeaders headers
|
|
||||||
, CachedBodyOutputMessage outputMessage
|
|
||||||
) {
|
|
||||||
return new ServerHttpRequestDecorator(exchange.getRequest()) {
|
|
||||||
@Override
|
|
||||||
public HttpHeaders getHeaders() {
|
|
||||||
long contentLength = headers.getContentLength();
|
|
||||||
HttpHeaders httpHeaders = new HttpHeaders();
|
|
||||||
httpHeaders.putAll(super.getHeaders());
|
|
||||||
if (contentLength > 0) {
|
|
||||||
httpHeaders.setContentLength(contentLength);
|
|
||||||
} else {
|
|
||||||
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
|
|
||||||
}
|
|
||||||
return httpHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<DataBuffer> getBody() {
|
|
||||||
return outputMessage.getBody();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,108 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ConfigLimitDto;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.exception.ApiException;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.limit.LimitType;
|
|
||||||
import com.gitee.sop.gatewaycommon.manager.LimitConfigManager;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
|
||||||
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* spring cloud gateway限流过滤器
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class LimitFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
private static final ErrorMeta LIMIT_ERROR_META = ErrorEnum.ISV_REQUEST_LIMIT.getErrorMeta();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
if (exchange.getAttribute(SopConstants.RESTFUL_REQUEST) != null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
|
||||||
// 限流功能未开启,直接返回
|
|
||||||
if (!apiConfig.isOpenLimit()) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
|
||||||
if (apiParam == null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
ConfigLimitDto configLimitDto = this.findConfigLimitDto(apiConfig, apiParam, exchange);
|
|
||||||
if (configLimitDto == null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
// 单个限流功能未开启
|
|
||||||
if (configLimitDto.getLimitStatus() == ConfigLimitDto.LIMIT_STATUS_CLOSE) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
Byte limitType = configLimitDto.getLimitType();
|
|
||||||
LimitManager limitManager = ApiConfig.getInstance().getLimitManager();
|
|
||||||
// 如果是窗口策略
|
|
||||||
if (limitType == LimitType.LEAKY_BUCKET.getType()) {
|
|
||||||
boolean acquire = limitManager.acquire(configLimitDto);
|
|
||||||
// 被限流,返回错误信息
|
|
||||||
if (!acquire) {
|
|
||||||
throw new ApiException(LIMIT_ERROR_META);
|
|
||||||
}
|
|
||||||
} else if (limitType == LimitType.TOKEN_BUCKET.getType()) {
|
|
||||||
limitManager.acquireToken(configLimitDto);
|
|
||||||
}
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return Orders.LIMIT_FILTER_ORDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ConfigLimitDto findConfigLimitDto(ApiConfig apiConfig, ApiParam apiParam, ServerWebExchange exchange) {
|
|
||||||
LimitConfigManager limitConfigManager = apiConfig.getLimitConfigManager();
|
|
||||||
|
|
||||||
String routeId = apiParam.fetchNameVersion();
|
|
||||||
String appKey = apiParam.fetchAppKey();
|
|
||||||
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
|
|
||||||
|
|
||||||
String[] limitKeys = new String[]{
|
|
||||||
routeId,
|
|
||||||
appKey,
|
|
||||||
routeId + appKey,
|
|
||||||
|
|
||||||
ip,
|
|
||||||
ip + routeId,
|
|
||||||
ip + appKey,
|
|
||||||
ip + routeId + appKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
List<ConfigLimitDto> limitConfigList = new ArrayList<>();
|
|
||||||
for (String limitKey : limitKeys) {
|
|
||||||
ConfigLimitDto configLimitDto = limitConfigManager.get(limitKey);
|
|
||||||
if (configLimitDto == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
limitConfigList.add(configLimitDto);
|
|
||||||
}
|
|
||||||
if (limitConfigList.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
limitConfigList.sort(Comparator.comparing(ConfigLimitDto::getOrderIndex));
|
|
||||||
return limitConfigList.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class Orders {
|
|
||||||
/** 验证拦截器order */
|
|
||||||
public static final int VALIDATE_FILTER_ORDER = Ordered.HIGHEST_PRECEDENCE + 1000;
|
|
||||||
|
|
||||||
/** 参数格式化过滤器 */
|
|
||||||
public static final int PARAMETER_FORMATTER_FILTER_ORDER = VALIDATE_FILTER_ORDER + 1;
|
|
||||||
|
|
||||||
/** 权限验证过滤 */
|
|
||||||
public static final int PRE_ROUTE_PERMISSION_FILTER_ORDER = PARAMETER_FORMATTER_FILTER_ORDER + 100;
|
|
||||||
|
|
||||||
/** 验证拦截器order */
|
|
||||||
public static final int LIMIT_FILTER_ORDER = PRE_ROUTE_PERMISSION_FILTER_ORDER + 100;
|
|
||||||
|
|
||||||
/** 灰度发布过滤器 */
|
|
||||||
public static final int ENV_GRAY_FILTER_ORDER = LIMIT_FILTER_ORDER + 100;
|
|
||||||
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.filter;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ParameterFormatter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
|
||||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import static com.gitee.sop.gatewaycommon.gateway.filter.Orders.PARAMETER_FORMATTER_FILTER_ORDER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class ParameterFormatterFilter implements GlobalFilter, Ordered {
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private ParameterFormatter parameterFormatter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
|
||||||
if (apiParam == null) {
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
// 校验成功后进行参数转换
|
|
||||||
if (parameterFormatter != null) {
|
|
||||||
ServerWebExchange formatExchange = ServerWebExchangeUtil.format(
|
|
||||||
exchange
|
|
||||||
, apiParam
|
|
||||||
, parameterFormatter::format
|
|
||||||
, httpHeaders -> httpHeaders.set(ParamNames.HEADER_VERSION_NAME, apiParam.fetchVersion()));
|
|
||||||
return chain.filter(formatExchange);
|
|
||||||
}
|
|
||||||
return chain.filter(exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOrder() {
|
|
||||||
return PARAMETER_FORMATTER_FILTER_ORDER;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,148 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.handler;
|
|
||||||
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
|
||||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
|
||||||
import com.gitee.sop.gatewaycommon.gateway.ServerWebExchangeUtil;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import com.gitee.sop.gatewaycommon.result.ResultExecutor;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
|
||||||
import org.springframework.http.codec.HttpMessageWriter;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
|
||||||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
|
||||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
|
||||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一异常处理
|
|
||||||
*
|
|
||||||
* @author 六如
|
|
||||||
*/
|
|
||||||
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MessageReader
|
|
||||||
*/
|
|
||||||
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MessageWriter
|
|
||||||
*/
|
|
||||||
private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ViewResolvers
|
|
||||||
*/
|
|
||||||
private List<ViewResolver> viewResolvers = Collections.emptyList();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
|
||||||
ResultExecutor<ServerWebExchange, String> resultExecutor = ApiContext.getApiConfig().getGatewayResultExecutor();
|
|
||||||
String errorResult = resultExecutor.buildErrorResult(exchange, ex);
|
|
||||||
ApiParam apiParam = ServerWebExchangeUtil.getApiParam(exchange);
|
|
||||||
// 错误记录
|
|
||||||
Object routeDefinition = exchange.getAttribute(SopConstants.CACHE_ROUTE_INFO);
|
|
||||||
log.error("网关报错,参数:{}, 错误信息:{}, 路由信息:{}", apiParam, ex.getMessage(), routeDefinition, ex);
|
|
||||||
// 参考AbstractErrorWebExceptionHandler
|
|
||||||
if (exchange.getResponse().isCommitted()) {
|
|
||||||
return Mono.error(ex);
|
|
||||||
}
|
|
||||||
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
|
|
||||||
return RouterFunctions.route(RequestPredicates.all(), (serverRequest) -> this.renderErrorResponse(errorResult)).route(newRequest)
|
|
||||||
.switchIfEmpty(Mono.error(ex))
|
|
||||||
.flatMap((handler) -> handler.handle(newRequest))
|
|
||||||
.flatMap((response) -> write(exchange, response));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
|
||||||
*
|
|
||||||
* @param messageReaders messageReaders
|
|
||||||
*/
|
|
||||||
public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
|
|
||||||
Assert.notNull(messageReaders, "'messageReaders' must not be null");
|
|
||||||
this.messageReaders = messageReaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
|
||||||
*
|
|
||||||
* @param viewResolvers viewResolvers
|
|
||||||
*/
|
|
||||||
public void setViewResolvers(List<ViewResolver> viewResolvers) {
|
|
||||||
this.viewResolvers = viewResolvers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
|
||||||
*
|
|
||||||
* @param messageWriters messageWriters
|
|
||||||
*/
|
|
||||||
public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
|
|
||||||
Assert.notNull(messageWriters, "'messageWriters' must not be null");
|
|
||||||
this.messageWriters = messageWriters;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考DefaultErrorWebExceptionHandler
|
|
||||||
*
|
|
||||||
* @param result 返回结果
|
|
||||||
* @return 返回mono
|
|
||||||
*/
|
|
||||||
protected Mono<ServerResponse> renderErrorResponse(String result) {
|
|
||||||
return ServerResponse
|
|
||||||
.status(HttpStatus.OK)
|
|
||||||
.contentType(MediaType.APPLICATION_JSON_UTF8)
|
|
||||||
.body(BodyInserters.fromObject(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
|
||||||
*
|
|
||||||
* @param exchange exchange
|
|
||||||
* @param response response
|
|
||||||
* @return 返回Mono
|
|
||||||
*/
|
|
||||||
private Mono<? extends Void> write(ServerWebExchange exchange,
|
|
||||||
ServerResponse response) {
|
|
||||||
exchange.getResponse().getHeaders()
|
|
||||||
.setContentType(response.headers().getContentType());
|
|
||||||
return response.writeTo(exchange, new ResponseContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 参考AbstractErrorWebExceptionHandler
|
|
||||||
*/
|
|
||||||
private class ResponseContext implements ServerResponse.Context {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<HttpMessageWriter<?>> messageWriters() {
|
|
||||||
return GatewayExceptionHandler.this.messageWriters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ViewResolver> viewResolvers() {
|
|
||||||
return GatewayExceptionHandler.this.viewResolvers;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package com.gitee.sop.gatewaycommon.gateway.result;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.gitee.sop.gatewaycommon.param.ApiParam;
|
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理业务返回数据
|
|
||||||
* @author thc
|
|
||||||
*/
|
|
||||||
public interface BizResultHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理业务参数
|
|
||||||
* @param serviceData 外层service数据
|
|
||||||
* @param serviceObj 业务数据
|
|
||||||
* @param apiParam apiParam
|
|
||||||
* @param request ServerWebExchange
|
|
||||||
* @author thc
|
|
||||||
* @date 2021/7/7 10:41
|
|
||||||
*/
|
|
||||||
void handle(Map<String, Object> serviceData, JSONObject serviceObj, ApiParam apiParam, ServerWebExchange request);
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user