feat: update gateway module

This commit is contained in:
landaiqing
2024-05-07 19:53:11 +08:00
parent 6160d98ddd
commit b3dd3101f3
14 changed files with 738 additions and 1 deletions

View File

@@ -9,10 +9,125 @@
<name>schisandra-cloud-storage-gateway</name> <name>schisandra-cloud-storage-gateway</name>
<properties> <properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.4.2</spring-boot.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<spring-cloud.version>2020.0.6</spring-cloud.version>
</properties> </properties>
<dependencies>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!-- Sa-Token 权限认证Reactor响应式集成, 在线文档https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies> </dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<!--打包成jar包时的名字-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>central</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project> </project>

View File

@@ -0,0 +1,19 @@
package com.schisandra.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* 网关启动类
*
* @author: landaiqing
* @date: 2024/2/7
*/
@SpringBootApplication
@ComponentScan("com.schisandra")
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class);
}
}

View File

@@ -0,0 +1,32 @@
package com.schisandra.gateway.auth;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 权限认证的配置器
*
* @author: landaiqing
*/
@Configuration
public class SaTokenConfigure {
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
System.out.println("-------- 前端访问path" + SaHolder.getRequest().getRequestPath());
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
//SaRouter.match("/auth/**", "/auth/user/doLogin", r -> StpUtil.checkRole("admin"));
// SaRouter.match("/oss/**", r -> StpUtil.checkLogin());
// SaRouter.match("/subject/subject/add", r -> StpUtil.checkPermission("subject:add"));
// SaRouter.match("/subject/**", r -> StpUtil.checkLogin());
})
;
}
}

View File

@@ -0,0 +1,62 @@
package com.schisandra.gateway.auth;
import cn.dev33.satoken.stp.StpInterface;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.schisandra.gateway.entity.AuthPermission;
import com.schisandra.gateway.entity.AuthRole;
import com.schisandra.gateway.redis.RedisUtil;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 自定义权限验证接口扩展
*
* @author: landaiqing
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private RedisUtil redisUtil;
private String authPermissionPrefix = "auth.permission";
private String authRolePrefix = "auth.role";
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return getAuth(loginId.toString(), authPermissionPrefix);
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return getAuth(loginId.toString(), authRolePrefix);
}
private List<String> getAuth(String loginId, String prefix) {
String authKey = redisUtil.buildKey(prefix, loginId.toString());
String authValue = redisUtil.get(authKey);
if (StringUtils.isBlank(authValue)) {
return Collections.emptyList();
}
List<String> authList = new LinkedList<>();
if (authRolePrefix.equals(prefix)) {
List<AuthRole> roleList = new Gson().fromJson(authValue, new TypeToken<List<AuthRole>>() {
}.getType());
authList = roleList.stream().map(AuthRole::getRoleKey).collect(Collectors.toList());
} else if (authPermissionPrefix.equals(prefix)) {
List<AuthPermission> permissionList = new Gson().fromJson(authValue, new TypeToken<List<AuthPermission>>() {
}.getType());
authList = permissionList.stream().map(AuthPermission::getPermissionKey).collect(Collectors.toList());
}
return authList;
}
}

View File

@@ -0,0 +1,53 @@
package com.schisandra.gateway.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* (AuthPermission)实体类
*
* @author landaiqing
*/
@Data
public class AuthPermission implements Serializable {
private Long id;
private String name;
private Long parentId;
private Integer type;
private String menuUrl;
private Integer status;
private Integer show;
private String icon;
private String permissionKey;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
private Integer isDeleted;
}

View File

@@ -0,0 +1,42 @@
package com.schisandra.gateway.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* (AuthRole)实体类
*
* @author landaiqing
* @since 2024-2-18 18:55:50
*/
@Data
public class AuthRole implements Serializable {
private Long id;
private String roleName;
private String roleKey;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新人
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
private Integer isDeleted;
}

View File

@@ -0,0 +1,59 @@
package com.schisandra.gateway.entity;
import com.schisandra.gateway.enums.ResultCodeEnum;
import lombok.Data;
@Data
public class Result<T> {
private Boolean success;
private Integer code;
private String message;
private T data;
public static Result ok(){
Result result = new Result();
result.setSuccess(true);
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
return result;
}
public static <T> Result ok(T data){
Result result = new Result();
result.setSuccess(true);
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setMessage(ResultCodeEnum.SUCCESS.getDesc());
result.setData(data);
return result;
}
public static Result fail(){
Result result = new Result();
result.setSuccess(false);
result.setCode(ResultCodeEnum.FAIL.getCode());
result.setMessage(ResultCodeEnum.FAIL.getDesc());
return result;
}
public static <T> Result fail(T data){
Result result = new Result();
result.setSuccess(false);
result.setCode(ResultCodeEnum.FAIL.getCode());
result.setMessage(ResultCodeEnum.FAIL.getDesc());
result.setData(data);
return result;
}
public static Result fail(Integer code,String message){
Result result = new Result();
result.setSuccess(false);
result.setCode(code);
result.setMessage(message);
return result;
}
}

View File

@@ -0,0 +1,24 @@
package com.schisandra.gateway.enums;
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(200,"成功"),
FAIL(500,"失败");
private int code;
private String desc;
ResultCodeEnum(int code,String desc){
this.code=code;
this.desc=desc;
}
public static ResultCodeEnum getByCode(int codeVal){
for(ResultCodeEnum resultCodeEnum:ResultCodeEnum.values()){
if(resultCodeEnum.code==codeVal){
return resultCodeEnum;
}
}
return null;
}
}

View File

@@ -0,0 +1,55 @@
package com.schisandra.gateway.exception;
import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.schisandra.gateway.entity.Result;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Classname GatewayExceptionHandler
* @BelongsProject: qing-yu-club
* @BelongsPackage: com.landaiqing.club.gateway.exception
* @Author: landaiqing
* @CreateTime: 2024-05-18 17:52
* @Description: 网关全局异常处理
* @Version: 1.0
*/
@Component
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
private ObjectMapper objectMapper=new ObjectMapper();
@Override
public Mono<Void> handle(ServerWebExchange serverWebExchange, Throwable throwable) {
ServerHttpRequest request = serverWebExchange.getRequest();
ServerHttpResponse response = serverWebExchange.getResponse();
Integer code=200;
String message="";
if(throwable instanceof SaTokenException){
code=401;
message="用户无权限";
}else {
code=500;
message="系统繁忙";
}
Result result = Result.fail(code, message);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Mono.fromSupplier(()->{
DataBufferFactory dataBufferFactory=response.bufferFactory();
byte[] bytes=null;
try {
bytes = objectMapper.writeValueAsBytes(result);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return dataBufferFactory.wrap(bytes);
}));
}
}

View File

@@ -0,0 +1,46 @@
package com.schisandra.gateway.filter;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Classname LoginFilter
* @BelongsProject: qing-yu-club
* @BelongsPackage: com.landaiqing.club.gateway.filter
* @Author: landaiqing
* @CreateTime: 2024-03-03 17:41
* @Description: 登录拦截器
* @Version: 1.0
*/
@Component
@Slf4j
public class LoginFilter implements GlobalFilter {
@Override
@SneakyThrows
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest.Builder mutate = request.mutate();
String url = request.getURI().getPath();
log.info("LoginFilter.filter.url:{}", url);
if (url.equals("/user/doLogin") || url.equals("/user/getUserInfo")) {
return chain.filter(exchange);
}
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
String loginId = (String) tokenInfo.getLoginId();
if (StringUtils.isEmpty(loginId)) {
throw new Exception("未获取到用户信息");
}
mutate.header("loginId", loginId);
return chain.filter(exchange.mutate().request(mutate.build()).build());
}
}

View File

@@ -0,0 +1,49 @@
package com.schisandra.gateway.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @Classname RedisConfig
* @BelongsProject: qing-yu-club
* @BelongsPackage: com.landaiqing.club.gateway.redis
* @Author: landaiqing
* @CreateTime: 2024-02-18 18:10
* @Description: redis的config处理
* @Version: 1.0
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
return redisTemplate;
}
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jsonRedisSerializer.setObjectMapper(objectMapper);
return jsonRedisSerializer;
}
}

View File

@@ -0,0 +1,107 @@
package com.schisandra.gateway.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* RedisUtil工具类
*
* @author: landaiqing
* @date: 2024/2/19
*/
@Component
@Slf4j
public class RedisUtil {
@Resource
private RedisTemplate redisTemplate;
private static final String CACHE_KEY_SEPARATOR = ".";
/**
* 构建缓存key
*/
public String buildKey(String... strObjs) {
return Stream.of(strObjs).collect(Collectors.joining(CACHE_KEY_SEPARATOR));
}
/**
* 是否存在key
*/
public boolean exist(String key) {
return redisTemplate.hasKey(key);
}
/**
* 删除key
*/
public boolean del(String key) {
return redisTemplate.delete(key);
}
/**
* set(不带过期)
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* set(带过期)
*/
public boolean setNx(String key, String value, Long time, TimeUnit timeUnit) {
return redisTemplate.opsForValue().setIfAbsent(key, value, time, timeUnit);
}
/**
* 获取string类型缓存
*/
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
public Boolean zAdd(String key, String value, Long score) {
return redisTemplate.opsForZSet().add(key, value, Double.valueOf(String.valueOf(score)));
}
public Long countZset(String key) {
return redisTemplate.opsForZSet().size(key);
}
public Set<String> rangeZset(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Long removeZset(String key, Object value) {
return redisTemplate.opsForZSet().remove(key, value);
}
public void removeZsetList(String key, Set<String> value) {
value.stream().forEach((val) -> redisTemplate.opsForZSet().remove(key, val));
}
public Double score(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
public Set<String> rangeByScore(String key, long start, long end) {
return redisTemplate.opsForZSet().rangeByScore(key, Double.valueOf(String.valueOf(start)), Double.valueOf(String.valueOf(end)));
}
public Object addScore(String key, Object obj, double score) {
return redisTemplate.opsForZSet().incrementScore(key, obj, score);
}
public Object rank(String key, Object obj) {
return redisTemplate.opsForZSet().rank(key, obj);
}
}

View File

@@ -0,0 +1,57 @@
server:
port: 5000
spring:
cloud:
gateway:
routes:
- id: oss
uri: lb://schisandra-cloud-storage-oss-dev
predicates:
- Path=/oss/**
filters:
- StripPrefix=1
- id: auth
uri: lb://schisandra-cloud-storage-auth-dev
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 116.196.80.239
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password: LDQ20020618xxx
# 连接超时时间
timeout: 2s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: token
# token 有效期(单位:秒) 默认30天-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token
is-share: true
# token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: random-32
# 是否输出操作日志
is-log: true
token-prefix: schisandra

View File

@@ -0,0 +1,17 @@
spring:
application:
name: schisandra-cloud-storage-gateway-dev
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 116.196.80.239:8848
prefix: ${spring.application.name}
group: DEFAULT_GROUP
namespace:
file-extension: yaml
discovery:
enabled: true
server-addr: 116.196.80.239:8848