本文是建立在微服务中已经整合redis 的基础之上进行的处理,如果没有项目没有整合redis 可以参考:https://blog.csdn.net/l123lgx/article/details/121521855
本文是建立在spring-aop基础只上进行的处理,spring-aop切面参考文档:https://blog.csdn.net/l123lgx/article/details/106399377
本文使用了nacos 作为微服务的注册与发现,nacos 阿里云服务器的安装可以参考:https://blog.csdn.net/l123lgx/article/details/121421431
nacos 服务端的配置和使用可以参考:
https://blog.csdn.net/l123lgx/article/details/121491529
1 gateway 介绍:
2 gateway -nacos 配置文件增加路由:
spring:
cloud:
gateway:
routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
- id: bluegrass-coree # 当前路由的标识, 要求唯一
uri: lb://bluegrass-coree # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
filters:
# StripPrefix GatewayFilter Factory 包括一个parts参数。 parts参数指示在将请求发送到下游之前,要从请求中去除的路径中的节数
- StripPrefix=1
# 当通过网关发出/bar/foo请求时,向bluegrass-core 服务发出的请求将是http://bluegrass-core/foo
predicates: # 断言(就是路由转发要满足的条件)
- Path=/bluegrass-coree/**
# 与服务注册与发现组件结合,设置为 true 后;id默认为微服务名称xxx-server;gatewayapplication有feign自动做负载均衡;
#不需要定制化显式配置;已生成默认的id=xservice,uri: lb://xserver , path=/serviceId/**
# discovery:
# locator:
# enabled: true
# lowerCaseServiceId: true
# redis 配置
redis:
host: localhost
port: 6379
timeout: 120000
database: 1
password: 123456
lettuce:
pool:
max-active: 50
max-idle: 8
max-wait: -1
min-idle: 1
# 这里配置token 验证白名单
token:
enable: true
skip-urls-pattern: /notoken,
3 增加全局过滤器:
在gateway 服务中新增一个config 文件夹,并增加一个全局过滤器配置文件 CorsConfig
增加一个filter 全局过滤器文件夹:
3.1 增加全局跨域过滤器:
(3.1.1)CorsResponseHeaderFilter.java
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
/**
* @Description TODO
* @Date 2021/11/26 15:01
* @Author lgx
* @Version 1.0
*/
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 5;
}
@Override
@SuppressWarnings("serial")
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.defer(() -> {
exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
.filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))
.forEach(kv ->
{
kv.setValue(new ArrayList<String>() {{add(kv.getValue().get(0));}});
});
return chain.filter(exchange);
}));
}
}
(3.1.1)CorsConfig .java
import org.lgx.bluegrass.bluegrassgateway.filter.CorsResponseHeaderFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* @Description TODO
* @Date 2021/11/26 15:00
* @Author lgx
* @Version 1.0
*/
@Configuration
public class CorsConfig {
private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client";
private static final String ALLOWED_METHODS = "*";
private static final String ALLOWED_ORIGIN = "*";
private static final String ALLOWED_Expose = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client";
private static final String MAX_AGE = "18000L";
// 跨域配置
@Bean
public CorsResponseHeaderFilter corsResponseHeaderFilter() {
//解决geteway重复添加跨域请求头的问题:https://blog.csdn.net/xht555/article/details/89484091
return new CorsResponseHeaderFilter();
}
// 跨域配置
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.set("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
headers.set("Access-Control-Allow-Methods", ALLOWED_METHODS);
headers.set("Access-Control-Max-Age", MAX_AGE);
headers.set("Access-Control-Allow-Headers", ALLOWED_HEADERS);
headers.set("Access-Control-Expose-Headers", ALLOWED_Expose);
headers.set("Access-Control-Allow-Credentials", "fasle");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
3.2 增加全局登录状态验证过滤器:
(3.2.1) 增加依赖:
(3.2.1.1)gateway 网关服务pom 增加servlet jar 依赖:
(3.2.1.2)Api 模块pom增加 jwt jar 加密jar依赖
(3.2.1.3)Api模块增加工具类:
定义常量类ConstantApi,RedisKeyConstant.java
package org.lgx.bluegrass.api.constant;
public class RedisKeyConstant {
public static String USER_DATA_PREFIX = "user_current_authority:";
public static String USER_DATA_NO_ORG_PREFIX = "user_current_no_org_authority:";
}
ConstantApi.java
/**
* @Description TODO
* @Date 2021/11/26 15:35
* @Author lgx
* @Version 1.0
*/
public class ConstantApi {
public static final String X_AUTHORIZATION = "X-Authorization";
}
JwtUtil.java
package org.lgx.bluegrass.api.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author liumeng
*/
public class JwtUtil {
private JwtUtil() {
throw new IllegalStateException("Utility class");
}
/**
* token秘钥,请勿泄露,请勿随便修改
*/
public static final String SECRET = "xhuser.135435234";
/**
* JWT生成Token.
* JWT构成: header, payload, signature
*
* @param uid 登录成功后用户uid, 参数uid不可传空
*/
public static String createToken(String uid) {
// sign time
Date iatDate = new Date();
// header Map
Map<String, Object> map = new HashMap<>();
map.put("alg", "HS256");
map.put("typ", "JWT");
return JWT.create().withHeader(map)
.withClaim("iss", "xuhui")
.withClaim("aud", "xuhui")
.withClaim("uid", null == uid ? null : uid)
.withIssuedAt(iatDate)
.sign(Algorithm.HMAC256(SECRET));
}
/**
* 解密Token
*
* @param token
* @return
* @throws Exception
*/
public static Map<String, Claim> verifyToken(String token) {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getClaims();
}
/**
* 根据Token获取user_id
*
* @param token
* @return user_id
*/
public static String getUID(String token) {
String uid = null;
try {
Map<String, Claim> claims = verifyToken(token);
Claim uidClaim = claims.get("uid");
if (null != uidClaim && !"".equals(uidClaim.asString())) {
uid = uidClaim.asString();
}
} catch (Exception e) {
uid = "";
}
return uid;
}
public static boolean isNumeric(String s) {
if (s != null && !"".equals(s.trim())) {
return s.matches("^[0-9]*$");
} else {
return false;
}
}
}
UrlPathUtil.java
package org.lgx.bluegrass.api.util;
/**
* @Description TODO
* @Date 2021/11/26 15:46
* @Author lgx
* @Version 1.0
*/
public class UrlPathUtil {
/**
* @描述 url正则匹配,匹配则返回true, 跳过的相反
* @参数[path, urlsPatterns, isSkip]
* @返回值 boolean
* @修改人和其他信息
*/
public static boolean isUrlPathMatch(String path, String urlsPatterns, boolean isSkip) {
String[] urlPattern = urlsPatterns.split(",");
for (String pattern : urlPattern) {
if (pattern != null && !pattern.isEmpty() && (path.contains(pattern) || path.matches(pattern))) {
return isSkip ? false : true;
}
}
return isSkip ? true : false;
}
}
(3.2.1.4) gateway 网关服务增加过滤器及工具类
GatewayUtil.java
package org.lgx.bluegrass.bluegrassgateway.util;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
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.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @创建人 lgx
* @描述
*/
public class GatewayUtil {
public static Mono<Void> responseErrorStatus(HttpStatus status, ServerHttpResponse serverHttpResponse, Integer code, String message) {
serverHttpResponse.setStatusCode(status);
Map<String, Object> mapData = new HashMap<>();
mapData.put("code", code);
mapData.put("message", message);
DataBuffer bodyDataBuffer = GatewayUtil.stringBuffer(JSONObject.toJSONString(mapData, SerializerFeature.WriteNullBooleanAsFalse,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteEnumUsingToString,
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteDateUseDateFormat));
Flux<DataBuffer> bodyFlux = Flux.create((t) -> t.next(bodyDataBuffer).complete());
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=utf-8");
serverHttpResponse.getHeaders().add("Date", new Date().toString());
return serverHttpResponse.writeWith(bodyFlux);
}
public static DataBuffer stringBuffer(String value) {
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
if (value == null) {
return nettyDataBufferFactory.allocateBuffer(0);
}
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
/**
* @描述 nginx已使用X-Real-IP,使用上面的方法getIpAddress
* @参数[request]
* @返回值 java.lang.String
* @创建人 孙中泉
* @创建时间 2019/12/10
* @修改人和其他信息
*/
public static String getIpAddressAll(ServerHttpRequest request) {
try {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("X-Original-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Forwarded-For");
}
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip == null ? "" : ip;
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
RedisUtil.java
package org.lgx.bluegrass.bluegrassgateway.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
/**
* Redis工具类 copy by liuhuan
* modified
*
* @author
* @version 1.1 (GitHub文档: https://github.com/whvcse/RedisUtil )
* @date
*/
@Component
public class RedisUtil {
@Autowired
@Qualifier("stringRedisTemplate")
private StringRedisTemplate redisTemplate;
public void setRedisTemplate(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public StringRedisTemplate getRedisTemplate() {
return this.redisTemplate;
}
/** -------------------key相关操作--------------------- */
/**
* 删除key
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量删除key
*
* @param keys
*/
public void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 序列化key
*
* @param key
* @return
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
*
* @param key
* @return
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*
* @param key
* @param timeout
* @param unit
* @return
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 多少秒失效
*
* @param key
* @param timeout 秒为单位
* @return
*/
public Boolean expire(String key, long timeout) {
return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置过期时间
*
* @param key
* @param date
* @return
*/
public Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
/**
* 查找匹配的key
*
* @param pattern
* @return
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 将当前数据库的 key 移动到给定的数据库 db 当中
*
* @param key
* @param dbIndex
* @return
*/
public Boolean move(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
/**
* 移除 key 的过期时间,key 将持久保持
*
* @param key
* @return
*/
public Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的过期时间
*
* @param key
* @param unit
* @return
*/
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 返回 key 的剩余的过期时间
*
* @param key
* @return
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
/**
* 从当前数据库中随机返回一个 key
*
* @return
*/
public String randomKey() {
return redisTemplate.randomKey();
}
/**
* 修改 key 的名称
*
* @param oldKey
* @param newKey
*/
public void rename(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
/**
* 仅当 newkey 不存在时,将 oldKey 改名为 newkey
*
* @param oldKey
* @param newKey
* @return
*/
public Boolean renameIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
/**
* 返回 key 所储存的值的类型
*
* @param key
* @return
*/
public DataType type(String key) {
return redisTemplate.type(key);
}
/** -------------------string相关操作--------------------- */
/**
* 设置指定 key 的值
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 指定秒
*
* @param key
* @param value
* @param seconds 多少秒失效
*/
public void set(String key, String value, long seconds) {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
}
/**
* 获取指定 key 的值
*
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 返回 key 中字符串值的子字符
*
* @param key
* @param start
* @param end
* @return
*/
public String getRange(String key, long start, long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
/**
* 将给定 key 的值设为 value ,并返回 key 的旧值(old value)
*
* @param key
* @param value
* @return
*/
public String getAndSet(String key, String value) {
return redisTemplate.opsForValue().getAndSet(key, value);
}
/**
* 对 key 所储存的字符串值,获取指定偏移量上的位(bit)
*
* @param key
* @param offset
* @return
*/
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 批量获取
*
* @param keys
* @return
*/
public List<String> multiGet(Collection<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 设置ASCII码, 字符串'a'的ASCII码是97, 转为二进制是'01100001', 此方法是将二进制第offset位值变为value
*
* @param key
* @param value 值,true为1, false为0
* @return
*/
public boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
*
* @param key
* @param value
* @param timeout 过期时间
* @param unit 时间单位, 天:TimeUnit.DAYS 小时:TimeUnit.HOURS 分钟:TimeUnit.MINUTES
* 秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
*/
public void setEx(String key, String value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 设置并失效
*
* @param key
* @param value
* @param secconds 多少秒失效
*/
public void setEx(String key, String value, long secconds) {
redisTemplate.opsForValue().set(key, value, secconds, TimeUnit.SECONDS);
}
/**
* 只有在 key 不存在时设置 key 的值
*
* @param key
* @param value
* @return 之前已经存在返回false, 不存在返回true
*/
public boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
*
* @param key
* @param value
* @param offset 从指定位置开始覆写
*/
public void setRange(String key, String value, long offset) {
redisTemplate.opsForValue().set(key, value, offset);
}
/**
* 获取字符串的长度
*
* @param key
* @return
*/
public Long size(String key) {
return redisTemplate.opsForValue().size(key);
}
/**
* 批量添加
*
* @param maps
*/
public void multiSet(Map<String, String> maps) {
redisTemplate.opsForValue().multiSet(maps);
}
/**
* 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
*
* @param maps
* @return 之前已经存在返回false, 不存在返回true
*/
public boolean multiSetIfAbsent(Map<String, String> maps) {
return redisTemplate.opsForValue().multiSetIfAbsent(maps);
}
/**
* 增加(自增长), 负数则为自减
*
* @param key
* @return
*/
public Long incrBy(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* @param key
* @return
*/
public Double incrByFloat(String key, double increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
/**
* 追加到末尾
*
* @param key
* @param value
* @return
*/
public Integer append(String key, String value) {
return redisTemplate.opsForValue().append(key, value);
}
/** -------------------hash相关操作------------------------- */
/**
* 获取存储在哈希表中指定字段的值
*
* @param key
* @param field
* @return
*/
public Object hGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
/**
* 获取所有给定字段的值
*
* @param key
* @return
*/
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 获取所有给定字段的值
*
* @param key
* @param fields
* @return
*/
public List<Object> hMultiGet(String key, Collection<Object> fields) {
return redisTemplate.opsForHash().multiGet(key, fields);
}
public void hPut(String key, String hashKey, String value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public void hPutAll(String key, Map<String, String> maps) {
redisTemplate.opsForHash().putAll(key, maps);
}
/**
* 仅当hashKey不存在时才设置
*
* @param key
* @param hashKey
* @param value
* @return
*/
public Boolean hPutIfAbsent(String key, String hashKey, String value) {
return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
}
/**
* 删除一个或多个哈希表字段
*
* @param key
* @param fields
* @return
*/
public Long hDelete(String key, Object... fields) {
return redisTemplate.opsForHash().delete(key, fields);
}
/**
* 查看哈希表 key 中,指定的字段是否存在
*
* @param key
* @param field
* @return
*/
public boolean hExists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param increment
* @return
*/
public Long hIncrBy(String key, Object field, long increment) {
return redisTemplate.opsForHash().increment(key, field, increment);
}
/**
* 为哈希表 key 中的指定字段的整数值加上增量 increment
*
* @param key
* @param field
* @param delta
* @return
*/
public Double hIncrByFloat(String key, Object field, double delta) {
return redisTemplate.opsForHash().increment(key, field, delta);
}
/**
* 获取所有哈希表中的字段
*
* @param key
* @return
*/
public Set<Object> hKeys(String key) {
return redisTemplate.opsForHash().keys(key);
}
/**
* 获取哈希表中字段的数量
*
* @param key
* @return
*/
public Long hSize(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 获取哈希表中所有值
*
* @param key
* @return
*/
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 迭代哈希表中的键值对
*
* @param key
* @param options
* @return
*/
public Cursor<Entry<Object, Object>> hScan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key, options);
}
/** ------------------------list相关操作---------------------------- */
/**
* 通过索引获取列表中的元素
*
* @param key
* @param index
* @return
*/
public String lIndex(String key, long index) {
return redisTemplate.opsForList().index(key, index);
}
/**
* 获取列表指定范围内的元素
*
* @param key
* @param start 开始位置, 0是开始位置
* @param end 结束位置, -1返回所有
* @return
*/
public List<String> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 存储在list头部
*
* @param key
* @param value
* @return
*/
public Long lLeftPush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, String... value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lLeftPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().leftPushAll(key, value);
}
/**
* 当list存在的时候才加入
*
* @param key
* @param value
* @return
*/
public Long lLeftPushIfPresent(String key, String value) {
return redisTemplate.opsForList().leftPushIfPresent(key, value);
}
/**
* 如果pivot存在,再pivot前面添加
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lLeftPush(String key, String pivot, String value) {
return redisTemplate.opsForList().leftPush(key, pivot, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, String... value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* @param key
* @param value
* @return
*/
public Long lRightPushAll(String key, Collection<String> value) {
return redisTemplate.opsForList().rightPushAll(key, value);
}
/**
* 为已存在的列表添加值
*
* @param key
* @param value
* @return
*/
public Long lRightPushIfPresent(String key, String value) {
return redisTemplate.opsForList().rightPushIfPresent(key, value);
}
/**
* 在pivot元素的右边添加值
*
* @param key
* @param pivot
* @param value
* @return
*/
public Long lRightPush(String key, String pivot, String value) {
return redisTemplate.opsForList().rightPush(key, pivot, value);
}
/**
* 通过索引设置列表元素的值
*
* @param key
* @param index 位置
* @param value
*/
public void lSet(String key, long index, String value) {
redisTemplate.opsForList().set(key, index, value);
}
/**
* 移出并获取列表的第一个元素
*
* @param key
* @return 删除的元素
*/
public String lLeftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param key
* @param timeout 等待时间
* @param unit 时间单位
* @return
*/
public String lBLeftPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().leftPop(key, timeout, unit);
}
/**
* 移除并获取列表最后一个元素
*
* @param key
* @return 删除的元素
*/
public String lRightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}
/**
* 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param key
* @param timeout 等待时间
* @param unit 时间单位
* @return
*/
public String lBRightPop(String key, long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPop(key, timeout, unit);
}
/**
* 移除列表的最后一个元素,并将该元素添加到另一个列表并返回
*
* @param sourceKey
* @param destinationKey
* @return
*/
public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey);
}
/**
* 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
*
* @param sourceKey
* @param destinationKey
* @param timeout
* @param unit
* @return
*/
public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
long timeout, TimeUnit unit) {
return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
destinationKey, timeout, unit);
}
/**
* 删除集合中值等于value得元素
*
* @param key
* @param index index=0, 删除所有值等于value的元素; index>0, 从头部开始删除第一个值等于value的元素;
* index<0, 从尾部开始删除第一个值等于value的元素;
* @param value
* @return
*/
public Long lRemove(String key, long index, String value) {
return redisTemplate.opsForList().remove(key, index, value);
}
/**
* 裁剪list
*
* @param key
* @param start
* @param end
*/
public void lTrim(String key, long start, long end) {
redisTemplate.opsForList().trim(key, start, end);
}
/**
* 获取列表长度
*
* @param key
* @return
*/
public Long lLen(String key) {
return redisTemplate.opsForList().size(key);
}
/** --------------------set相关操作-------------------------- */
/**
* set添加元素
*
* @param key
* @param values
* @return
*/
public Long sAdd(String key, String... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* set移除元素
*
* @param key
* @param values
* @return
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
/**
* 移除并返回集合的一个随机元素
*
* @param key
* @return
*/
public String sPop(String key) {
return redisTemplate.opsForSet().pop(key);
}
/**
* 将元素value从一个集合移到另一个集合
*
* @param key
* @param value
* @param destKey
* @return
*/
public Boolean sMove(String key, String value, String destKey) {
return redisTemplate.opsForSet().move(key, value, destKey);
}
/**
* 获取集合的大小
*
* @param key
* @return
*/
public Long sSize(String key) {
return redisTemplate.opsForSet().size(key);
}
/**
* 判断集合是否包含value
*
* @param key
* @param value
* @return
*/
public Boolean sIsMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
/**
* 获取两个集合的交集
*
* @param key
* @param otherKey
* @return
*/
public Set<String> sIntersect(String key, String otherKey) {
return redisTemplate.opsForSet().intersect(key, otherKey);
}
/**
* 获取key集合与多个集合的交集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sIntersect(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().intersect(key, otherKeys);
}
/**
* key集合与otherKey集合的交集存储到destKey集合中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* key集合与多个集合的交集存储到destKey集合中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
* 获取两个集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, String otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* 获取key集合与多个集合的并集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sUnion(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().union(key, otherKeys);
}
/**
* key集合与otherKey集合的并集存储到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
}
/**
* key集合与多个集合的并集存储到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取两个集合的差集
*
* @param key
* @param otherKey
* @return
*/
public Set<String> sDifference(String key, String otherKey) {
return redisTemplate.opsForSet().difference(key, otherKey);
}
/**
* 获取key集合与多个集合的差集
*
* @param key
* @param otherKeys
* @return
*/
public Set<String> sDifference(String key, Collection<String> otherKeys) {
return redisTemplate.opsForSet().difference(key, otherKeys);
}
/**
* key集合与otherKey集合的差集存储到destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long sDifference(String key, String otherKey, String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
destKey);
}
/**
* key集合与多个集合的差集存储到destKey中
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long sDifference(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
destKey);
}
/**
* 获取集合所有元素
*
* @param key
* @return
*/
public Set<String> setMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 随机获取集合中的一个元素
*
* @param key
* @return
*/
public String sRandomMember(String key) {
return redisTemplate.opsForSet().randomMember(key);
}
/**
* 随机获取集合中count个元素
*
* @param key
* @param count
* @return
*/
public List<String> sRandomMembers(String key, long count) {
return redisTemplate.opsForSet().randomMembers(key, count);
}
/**
* 随机获取集合中count个元素并且去除重复的
*
* @param key
* @param count
* @return
*/
public Set<String> sDistinctRandomMembers(String key, long count) {
return redisTemplate.opsForSet().distinctRandomMembers(key, count);
}
/**
* @param key
* @param options
* @return
*/
public Cursor<String> sScan(String key, ScanOptions options) {
return redisTemplate.opsForSet().scan(key, options);
}
/**------------------zSet相关操作--------------------------------*/
/**
* 添加元素,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @param score
* @return
*/
public Boolean zAdd(String key, String value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
/**
* 元素值按维度自增
*
* @param key
* @param value
* @param score
* @return
*/
public Double zAddForIncre(String key, String value, double score) {
return redisTemplate.opsForZSet().incrementScore(key, value, score);
}
/**
* @param key
* @param values
* @return
*/
public Long zAdd(String key, Set<TypedTuple<String>> values) {
return redisTemplate.opsForZSet().add(key, values);
}
/**
* @param key
* @param values
* @return
*/
public Long zRemove(String key, Object... values) {
return redisTemplate.opsForZSet().remove(key, values);
}
/**
* 增加元素的score值,并返回增加后的值
*
* @param key
* @param value
* @param delta
* @return
*/
public Double zIncrementScore(String key, String value, double delta) {
return redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
/**
* 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
*
* @param key
* @param value
* @return 0表示第一位
*/
public Long zRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
/**
* 返回元素在集合的排名,按元素的score值由大到小排列
*
* @param key
* @param value
* @return
*/
public Long zReverseRank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
/**
* 获取集合的元素, 从小到大排序
*
* @param key
* @param start 开始位置
* @param end 结束位置, -1查询所有
* @return
*/
public Set<String> zRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
/**
* 获取集合元素, 并且把score值也获取
*
* @param key
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
long end) {
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
/**
* 根据Score值查询集合元素
*
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<String> zRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
// public Set hSet(String key, String valueName, Objects value) {
// return redisTemplate.opsForHash().put(key,valueName,value);
// }
/**
* 根据Score值查询集合元素, 从小到大排序
*
* @param key
* @param min 最小值
* @param max 最大值
* @return
*/
public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
}
/**
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
double min, double max, long start, long end) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
start, end);
}
/**
* 获取集合的元素, 从大到小排序
*
* @param key
* @param start
* @param end
* @return
*/
public Set<String> zReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
/**
* 获取集合的元素, 从大到小排序, 并返回score值
*
* @param key
* @param start
* @param end
* @return
*/
public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
end);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
/**
* 根据Score值查询集合元素, 从大到小排序
*
* @param key
* @param min
* @param max
* @return
*/
public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
min, max);
}
/**
* @param key
* @param min
* @param max
* @param start
* @param end
* @return
*/
public Set<String> zReverseRangeByScore(String key, double min,
double max, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
start, end);
}
/**
* 根据score值获取集合元素数量
*
* @param key
* @param min
* @param max
* @return
*/
public Long zCount(String key, double min, double max) {
return redisTemplate.opsForZSet().count(key, min, max);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zSize(String key) {
return redisTemplate.opsForZSet().size(key);
}
/**
* 获取集合大小
*
* @param key
* @return
*/
public Long zZCard(String key) {
return redisTemplate.opsForZSet().zCard(key);
}
/**
* 获取集合中value元素的score值
*
* @param key
* @param value
* @return
*/
public Double zScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
/**
* 移除指定索引位置的成员
*
* @param key
* @param start
* @param end
* @return
*/
public Long zRemoveRange(String key, long start, long end) {
return redisTemplate.opsForZSet().removeRange(key, start, end);
}
/**
* 根据指定的score值的范围来移除成员
*
* @param key
* @param min
* @param max
* @return
*/
public Long zRemoveRangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
}
/**
* 获取key和otherKey的并集并存储在destKey中
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, String otherKey, String destKey) {
return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
}
/**
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zUnionAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet()
.unionAndStore(key, otherKeys, destKey);
}
/**
* 交集
*
* @param key
* @param otherKey
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, String otherKey,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
destKey);
}
/**
* 交集
*
* @param key
* @param otherKeys
* @param destKey
* @return
*/
public Long zIntersectAndStore(String key, Collection<String> otherKeys,
String destKey) {
return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
destKey);
}
/**
* @param key
* @param options
* @return
*/
public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
return redisTemplate.opsForZSet().scan(key, options);
}
/** -------------------锁相关操作--------------------- */
/**
* 尝试获取锁
*
* @param key
* @param timeout 秒为单位
* @return
*/
public boolean tryLock(String key, long timeout) {
if (setIfAbsent(key, "1")) {
expire(key, timeout, TimeUnit.SECONDS);
return true;
} else {
return false;
}
}
/**
* 释放锁
* @param key
* @return
*/
public boolean unlock(String key) {
delete(key);
return true;
}
}
UserUtil.java
package org.lgx.bluegrass.bluegrassgateway.util;
import lombok.extern.slf4j.Slf4j;
import org.lgx.bluegrass.api.constant.ConstantApi;
import org.lgx.bluegrass.api.constant.RedisKeyConstant;
import org.lgx.bluegrass.api.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
/**
* @Description TODO
* @Date 2021/11/26 15:14
* @Author lgx
* @Version 1.0
*/
@Slf4j
@Component
public class UserUtil {
public static RedisUtil redisUtilStatic;
@Autowired
private RedisUtil redisUtil;
@PostConstruct
public void init() {
redisUtilStatic = redisUtil;
}
/**
* 单位秒
*/
public static final long EXPIRE_TIME = 3600 * 12;
public static final String CODE_SUFFIX = "-code";
/**
* 获取当前登录用户id
*
* @param request
* @return
*/
public static String getUserId(ServerHttpRequest request) {
String token = request.getHeaders().getFirst(ConstantApi.X_AUTHORIZATION);
return getUid(token);
}
/**
* 根据token 获取用户id
*
* @param token
* @return
*/
private static String getUid(String token) {
String uid = JwtUtil.getUID(token);
if (StringUtils.isEmpty(uid)) {
return null;
}
String token_set = redisUtilStatic.get(uid);
if (StringUtils.isEmpty(token_set) || !token_set.equals(token)) {
return null;
}
return uid;
}
/**
* 设置当前用户id
*
* @param userId
*/
public static String setUser(String userId, String code) {
String sessionToken = setUserId(userId, code);
return sessionToken;
}
/**
* 设置useid
*
* @param userId
* @param code
* @return
*/
private static String setUserId(String userId, String code) {
if (StringUtils.isEmpty(userId)) {
return "";
}
String sessionToken = redisUtilStatic.get(userId);
if (StringUtils.isEmpty(sessionToken)) {
// 生成token
sessionToken = JwtUtil.createToken(userId);
}
redisUtilStatic.set(userId, sessionToken, EXPIRE_TIME);
redisUtilStatic.set(userId + CODE_SUFFIX, code, EXPIRE_TIME);
//清空数据权限缓存
String redisKey = RedisKeyConstant.USER_DATA_PREFIX + userId;
if (redisUtilStatic.hasKey(redisKey)) {
redisUtilStatic.delete(redisKey);
}
return sessionToken;
}
}
(3.2.2) 增加 TokenCheckFilter.java 过滤器
package org.lgx.bluegrass.bluegrassgateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.lgx.bluegrass.api.util.UrlPathUtil;
import org.lgx.bluegrass.bluegrassgateway.util.GatewayUtil;
import org.lgx.bluegrass.bluegrassgateway.util.UserUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
/**
* @Description TODO
* @Date 2021/11/26 15:10
* @Author lgx
* @Version 1.0
*/
@Slf4j
@RefreshScope
public class TokenCheckFilter implements GlobalFilter, Ordered {
// 需要跳过的路径
@Value("${token.skip-urls-pattern:abcdefg}")
private String skipUrlsPattern;
// 是否启用
@Value("${token.enable:true}")
private Boolean tokenEnable;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info(" start in gateway TokenCheckFilter header Authorization {}", request.getHeaders().getFirst("X-Authorization"));
URI uri = request.getURI();
log.info("in gateway,token skip url pattern is {}", skipUrlsPattern);
if (UrlPathUtil.isUrlPathMatch(uri.getPath(), skipUrlsPattern, false)) {
log.info("in gateway TokenCheckFilter,skip token auth");
return chain.filter(exchange);
}
if (tokenEnable) {
log.info("in gateway TokenCheckFilter,open token auth");
ServerHttpResponse serverHttpResponse = exchange.getResponse();
String userId = UserUtil.getUserId(request);
if (userId == null) {
log.info("in gateway TokenCheckFilter,token error");
return GatewayUtil.responseErrorStatus(HttpStatus.UNAUTHORIZED, serverHttpResponse, 401, "token验证失败,token不存在");
}
} else {
log.info("in gateway TokenCheckFilter,change token for test");
//TODO 测试代码 需要删除 如果没传递token ---默认用户
String userId = UserUtil.getUserId(request);
if (null == userId) {
String sessionToken = UserUtil.setUser("000015113","test_code");
ServerHttpRequest host = request.mutate().header("X-Authorization", sessionToken).build();
ServerWebExchange build = exchange.mutate().request(host).build();
log.info("in gateway TokenCheckFilter,Authorization has been change {}", sessionToken);
return chain.filter(build);
}
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
在另外的微服务中增加用户的登录并且保存用户登录态到redis
CorsConfig.java 增加token验证过滤器:
/**
* token 验证
* @return
*/
@Bean
@RefreshScope
public TokenCheckFilter tokenCheckFilter() {
return new TokenCheckFilter();
}
(3.2.3) token 测试:
(3.2.3.1)返回的accessToken后续的访问在header 中增加X-Authorization参数即可
3.3 增加全局接口访问记录过滤器,并将访问记录表按月分表:
(3.3.1)网关增加请求参数追加过滤器:RequestFilter.java
package org.lgx.bluegrass.bluegrassgateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.lgx.bluegrass.api.constant.ConstantApi;
import org.lgx.bluegrass.bluegrassgateway.util.GatewayUtil;
import org.lgx.bluegrass.bluegrassgateway.util.UserUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
/**
* @Description TODO
* @Date 2021/4/21 14:17
* @Author lgx
* @Version 1.0
*/
@Slf4j
public class RequestFilter implements GlobalFilter, Ordered {
@Value("${log_domain.address:RequestFilter/}")
private String serverAddress;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
log.info(" start in gateway RequestFilter header Authorization {}", serverHttpRequest.getHeaders().getFirst(ConstantApi.X_AUTHORIZATION));
URI uri = exchange.getRequest().getURI();
StringBuilder query = new StringBuilder();
String originalQuery = uri.getRawQuery();
if (StringUtils.hasText(originalQuery)) {
query.append(originalQuery);
if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
query.append('&');
}
}
String ip = GatewayUtil.getIpAddressAll(serverHttpRequest);
String url = serverHttpRequest.getURI().getPath();
if (url.startsWith("/")) {
url = serverAddress + url.substring(1);
} else {
url = serverAddress + url;
}
//TODO urlencode?
query.append("operationClientIp");
query.append('=');
query.append((ip == null || "0:0:0:0:0:0:0:1".equals(ip)) ? "110.110.110.110" : ip);
query.append("&operationRequestUrl");
query.append('=');
query.append(url);
query.append("&operationUserId");
query.append('=');
String userId = UserUtil.getUserId(serverHttpRequest);
query.append(userId != null ? userId : "");
try {
URI newUri = UriComponentsBuilder.fromUri(uri)
.replaceQuery(query.toString())
.build(true)
.toUri();
ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
log.info(" end in gateway RequestFilter header Authorization {}", request.getHeaders().getFirst(ConstantApi.X_AUTHORIZATION));
return chain.filter(exchange.mutate().request(request).build());
} catch (RuntimeException ex) {
log.error("Invalid URI query:{}", query.toString());
throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
}
}
@Override
public int getOrder() {
return 0;
}
}
(3.3.2)增加日志处理aop 切面
(3.3.2.1)微服务中增加aop jar 依赖:
<!-- -boot-starter依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(3.3.2.2)api 公共模块定义切点,及增加公用枚举类:
切面定义类:OperationLogAnnotation.java
package org.lgx.bluegrass.api.annotation;
import org.lgx.bluegrass.api.enums.ModuleEnum;
import org.lgx.bluegrass.api.enums.OperationEnum;
import java.lang.annotation.*;
/**
* @Description TODO
* @Date 2021/3/17 15:45
* @Author lgx
* @Version 1.0
*/
//注解放置的目标位置即方法级别
@Target(ElementType.METHOD)
//注解在哪个阶段执行
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnnotation {
String operTitle() default ""; // 操作标题
String operContent() default ""; // 操作内容
String moduleId() default ""; // 操作模块id
OperationEnum operationType() default OperationEnum.OTHER; // 操作code
ModuleEnum moduleCode() default ModuleEnum.其他;
}
定义枚举公共类:LogFiledEnum.java
package org.lgx.bluegrass.api.enums;
/**
* @Description TODO
* @Date 2021/3/18 14:49
* @Author lgx
* @Version 1.0
*/
public enum LogFiledEnum {
用户ID("operationUserId", "用户ID"),
用户名("operationUserName", "用户名"),
模块ID("operationModuleId", "模块ID"),
模块CODE("operationModuleCode", "模块CODE"),
客户端IP("operationClientIp", "IP"),
接口地址("operationRequestUrl", "URL"),
操作("operationType", "CODE"),
标题("operationTitle", "标题"),
内容("operationContent", "内容"),
;
private String fileName;
private String fileId;
public String getFileName() {
return fileName;
}
public String getFileId() {
return fileId;
}
LogFiledEnum(String fileId, String fileName){
this.fileId = fileId;
this.fileName = fileName;
}
public static LogFiledEnum getByFiledName(String fileName) {
LogFiledEnum result = null;
for (LogFiledEnum filed:LogFiledEnum.values()) {
if (fileName.equals(filed.getFileName()) ) {
result = filed;
break;
}
}
return result;
}
}
模块枚举类:ModuleEnum.java
package org.lgx.bluegrass.api.enums;
/**
* @Description TODO
* @Date 2021/6/17 10:50
* @Author lgx
* @Version 1.0
*/
public enum ModuleEnum {
其他("其他","other"),
首页("首页","home"),
;
private String moduleName;
private String moduleCode;
ModuleEnum(String moduleName, String moduleCode) {
this.moduleName = moduleName;
this.moduleCode = moduleCode;
}
public String getModuleCode() {
return moduleCode;
}
public String getModuleName() {
return moduleName;
}
}
操作枚举类OperationCodeEnum.java
package org.lgx.bluegrass.api.enums;
/**
* @Description TODO
* @Date 2021/3/30 19:46
* @Author lgx
* @Version 1.0
*/
public enum OperationCodeEnum {
新增("ADD", 1),
删除("DELETE", -1),
查询("QUERY", 0),
修改("EDIT", 2),
导入("IMPORT", 3),
导出("EXPORT", 4),
下载("DOWNLOAD", 5),
上传("UPLOAD", 6),
数据传输("SENDDATA", 7),
其它("OTHER", -2),
;
private String operationType;
private Integer operationTypeCode;
OperationCodeEnum(String operationType, Integer operationTypeCode) {
this.operationType = operationType;
this.operationTypeCode = operationTypeCode;
}
public String getOperationType() {
return operationType;
}
public Integer getOperationTypeCode() {
return operationTypeCode;
}
public static OperationCodeEnum getByoperationType(String operationType) {
OperationCodeEnum result = null;
for (OperationCodeEnum type :OperationCodeEnum.values()) {
if (operationType.equals(type.getOperationType())) {
result = type;
break;
}
}
return result;
}
}
OperationEnum.java
package org.lgx.bluegrass.api.enums;
/**
* @Description TODO
* @Date 2021/3/30 19:23
* @Author lgx
* @Version 1.0
*/
public enum OperationEnum {
/**
* 添加操作
*/
ADD
/** 修改 */
, EDIT
/** 删除 */
, DELETE
/** 查询 */
, QUERY
/** 导入 */
, IMPORT
/** 导出 */
, EXPORT
/** 下载 */
, UPLOAD
/** 远程数据传输 */
, SENDDATA
/** 下载 */
, DOWNLOAD
/** 页面 */
, PAGE
/** 移动端H5页面 */
, MOBILEPAGE
/** 其它 */
, OTHER
}
(3.3.2.3)微服务中定义切面日志处理类:OperationLogAspect.java
package org.lgx.bluegrass.bluegrasscoree.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.lgx.bluegrass.api.annotation.OperationLogAnnotation;
import org.lgx.bluegrass.api.dto.UserOperationLogDto;
import org.lgx.bluegrass.api.enums.LogFiledEnum;
import org.lgx.bluegrass.api.enums.OperationCodeEnum;
import org.lgx.bluegrass.api.util.ExceptionFormatUtil;
import org.lgx.bluegrass.bluegrasscoree.service.userlog.ITUserOpLogService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* @Description TODO
* @Date 2021/3/17 15:50
* @Author lgx
* @Version 1.0
*/
@Aspect
@Component
@Slf4j
public class OperationLogAspect implements Ordered {
private static ExecutorService executors = Executors.newFixedThreadPool(10);
@Autowired
private ITUserOpLogService userLogService;
private static StringBuffer stringBuffer = new StringBuffer();
/**
* 设置操作日志切入点 在注解的位置切入代码
*/
@Pointcut("@annotation(org.lgx.bluegrass.api.annotation.OperationLogAnnotation)")
public void operLogPoinCut() {
}
@Around("operLogPoinCut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
Map<String, Object> data = new HashMap<>();
data.put(LogFiledEnum.用户ID.getFileId(), request.getParameter(LogFiledEnum.用户ID.getFileId()));
data.put(LogFiledEnum.用户名.getFileId(), request.getParameter(LogFiledEnum.用户名.getFileId()));
data.put(LogFiledEnum.客户端IP.getFileId(), request.getParameter(LogFiledEnum.客户端IP.getFileId()));
data.put(LogFiledEnum.接口地址.getFileId(), request.getParameter(LogFiledEnum.接口地址.getFileId()));
int isSuccess = 0;
Exception exception = null;
try {
result = joinPoint.proceed();
} catch (Exception e) {
e.printStackTrace();
exception =e;
log.error("OperationLogAspect is get method error,cause:{}", ExceptionFormatUtil.buildErrorMessage(e));
isSuccess = 1;
} finally {
//create log object
OperationLogAnnotation annotation;
Class<?> aClass = joinPoint.getTarget().getClass();
String name = joinPoint.getSignature().getName();
List<Method> methods = Arrays.stream(aClass.getMethods()).filter(m -> m.getName().equals(name)).collect(Collectors.toList());
//If the method name is unique, take the annotation of the current instance
if (1 == methods.size()) {
annotation = methods.stream().findFirst().get().getAnnotation(OperationLogAnnotation.class);
}else {
//Otherwise, take the default setting annotation of the entry point
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
annotation = signature.getMethod().getAnnotation(OperationLogAnnotation.class);
}
data.put(LogFiledEnum.模块ID.getFileId(), annotation.moduleId());
data.put(LogFiledEnum.模块CODE.getFileId(), annotation.moduleCode().getModuleCode());
data.put(LogFiledEnum.内容.getFileId(), annotation.operContent());
data.put(LogFiledEnum.标题.getFileId(), annotation.operTitle());
data.put(LogFiledEnum.操作.getFileId(), OperationCodeEnum.getByoperationType(annotation.operationType().name()).getOperationTypeCode());
UserOperationLogDto logDto = null;
try {
logDto = returnLog(data);
} catch (Exception e) {
log.error("日志记录异常{}", ExceptionFormatUtil.buildErrorMessage(e));
}
if (logDto != null) {
logDto.setOperationIsSuccess(isSuccess);
// feign 调用存储日志
UserOperationLogDto finalLogDto = logDto;
CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
UserOperationLogDto log = new UserOperationLogDto();
BeanUtils.copyProperties(finalLogDto, log);
userLogService.saveLog(log);
}, executors);
}
if (exception!= null){
throw exception;
}
}
return result;
}
public UserOperationLogDto returnLog(Map<String, Object> data) {
UserOperationLogDto logDto = new UserOperationLogDto();
logDto.setOperationSource(1);
logDto.setOperationLogType(0);
logDto.setOperationUserId(data.get(LogFiledEnum.用户ID.getFileId()) + "");
logDto.setOperationUserName(data.get(LogFiledEnum.用户名.getFileId()) + "");
logDto.setOperationModuleId(data.get(LogFiledEnum.模块ID.getFileId()) + "");
logDto.setOperationModuleCode(data.get(LogFiledEnum.模块CODE.getFileId()) + "");
logDto.setOperationClientIp(data.get(LogFiledEnum.客户端IP.getFileId()) + "");
logDto.setOperationRequestUrl(data.get(LogFiledEnum.接口地址.getFileId()) + "");
logDto.setOperationModuleId(data.get(LogFiledEnum.模块ID.getFileId()) + "");
logDto.setOperationTitle(data.get(LogFiledEnum.标题.getFileId()) + "");
logDto.setOperationContent(data.get(LogFiledEnum.内容.getFileId()) + "");
logDto.setOperationType(Integer.parseInt(data.get(LogFiledEnum.操作.getFileId()) + ""));
return logDto;
}
@Override
public int getOrder() {
return 0;
}
}
(3.3.2.4)增加日志保存业务类:TUserOpLogEntity.java
package org.lgx.bluegrass.bluegrasscoree.entity.userlog;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
*
* 操作日志
*
*
* @author lgx
* @since 2021-03-18
*/
@Data
@TableName("t_user_op_log")
public class TUserOpLogEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户id
*/
private String userId;
/**
* 用户名
*/
private String userName;
/**
* 标题
*/
private String title;
/**
* 模块id
*/
private String moduleId;
/**
* 模块名称
*/
private String moduleName;
/**
* 模块code
*/
private String moduleCode;
/**
* 0.操作日志 1.系统日志
*/
private Integer logType=0;
/**
* 客户端ip
*/
private String clientIp;
/**
* 请求URL
*/
private String requestUrl;
/**
* 查询:0 增加:1 删除:-1 修改:2
*/
private Integer operationType;
/**
* 操作内容
*/
private String operationContent;
/**
* 1 PC端 ,2移动端, 3系统
*/
private Integer source =1;
/**
* 0:成功 1:失败
*/
private Integer success =0;
/**
* 是否删除
*/
private Integer isDelete =0;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/**
* 最后更新人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
}
(3.3.2.5)日志保存业务类:
ITUserOpLogService.java
package org.lgx.bluegrass.bluegrasscoree.service.userlog;
import org.lgx.bluegrass.api.dto.UserOperationLogDto;
import java.util.Map;
/**
* @Description TODO
* @Date 2021/11/29 09:53
* @Author lgx
* @Version 1.0
*/
public interface ITUserOpLogService {
Map<String,Object> saveLog(UserOperationLogDto userSubArticleDto);
}
ITUserOpLogServiceImpl.java
package org.lgx.bluegrass.bluegrasscoree.serviceimp.userlog;
import lombok.extern.slf4j.Slf4j;
import org.lgx.bluegrass.api.dto.UserOperationLogDto;
import org.lgx.bluegrass.api.util.ExceptionFormatUtil;
import org.lgx.bluegrass.bluegrasscoree.entity.userlog.TUserOpLogEntity;
import org.lgx.bluegrass.bluegrasscoree.mapper.userlog.TUserOpLogMapper;
import org.lgx.bluegrass.bluegrasscoree.service.common.CommonService;
import org.lgx.bluegrass.bluegrasscoree.service.userlog.ITUserOpLogService;
import org.lgx.bluegrass.bluegrasscoree.util.RedisUtil;
import org.lgx.bluegrass.bluegrasscoree.util.UserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* @Description TODO
* @Date 2021/11/29 09:54
* @Author lgx
* @Version 1.0
*/
@Slf4j
@Service
public class ITUserOpLogServiceImpl implements ITUserOpLogService {
@Resource
private RedisUtil redisUtil;
@Autowired
private TUserOpLogMapper tUserOpLogMapper;
@Resource
private CommonService commonService;
@Override
public Map<String, Object> saveLog(UserOperationLogDto userOperationLogDto) {
Map<String, Object> result = commonService.initSuccessMap();
try {
TUserOpLogEntity logEntity = new TUserOpLogEntity();
logEntity.setUserId(userOperationLogDto.getOperationUserId());
logEntity.setTitle(userOperationLogDto.getOperationTitle());
logEntity.setModuleId(userOperationLogDto.getOperationModuleId());
logEntity.setModuleCode(userOperationLogDto.getOperationModuleCode());
logEntity.setLogType(userOperationLogDto.getOperationLogType());
logEntity.setClientIp(userOperationLogDto.getOperationClientIp());
logEntity.setRequestUrl(userOperationLogDto.getOperationRequestUrl());
logEntity.setOperationType(userOperationLogDto.getOperationType());
logEntity.setOperationContent(userOperationLogDto.getOperationContent());
logEntity.setSource(userOperationLogDto.getOperationSource());
logEntity.setSuccess(userOperationLogDto.getOperationIsSuccess());
logEntity.setCreatedBy(UserUtil.getUserId());
logEntity.setUpdatedBy(UserUtil.getUserId());
saveOneLog(logEntity);
} catch (Exception ex) {
log.error("save log error,cause:{}", ExceptionFormatUtil.buildErrorMessage(ex));
result.put("success", false);
result.put("code", 500);
result.put("errorMsg", ExceptionFormatUtil.buildErrorMessage(ex));
return result;
}
return result;
}
private boolean saveOneLog(TUserOpLogEntity logEntity) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
String nowMonth = dateFormat.format(new Date());
String key = String.format("cs:system:tuserlog:%s:string", nowMonth);
log.info("接口调用日志表Redis Key:" + key);
String tableName = redisUtil.get(key);
log.info("接口调用日志表表名:" + tableName);
if (tableName != null) {
return tUserOpLogMapper.saveOneLog(tableName, logEntity) == 1;
}
String newTableName = String.format("t_user_op_log_%s", nowMonth);
try {
if (redisUtil.tryLock(newTableName, 10)) {
log.info("获得Redis锁");
// 第二层判断,防范作用
tableName = redisUtil.get(key);
log.info("再次获取接口调用日志表表名:" + tableName);
if (tableName != null) {
return tUserOpLogMapper.saveOneLog(tableName, logEntity) == 1;
}
Map<String, String> list = tUserOpLogMapper.checkTableExistsWithShow(newTableName);
if (CollectionUtils.isEmpty(list)) {
log.info("创建接口调用日志表,并将表名保存到Redis");
tUserOpLogMapper.createTable(newTableName);
redisUtil.set(key, newTableName);
}
return tUserOpLogMapper.saveOneLog(newTableName, logEntity) == 1;
}
} catch (Exception e) {
log.error("新增调用接口日志时获取Redis锁失败_" + e.getMessage(), e);
} finally {
redisUtil.unlock(newTableName);
log.info("释放Redis锁");
}
return false;
}
}
TUserOpLogMapper.java
package org.lgx.bluegrass.bluegrasscoree.mapper.userlog;
import org.apache.ibatis.annotations.Param;
import org.lgx.bluegrass.bluegrasscoree.entity.userlog.TUserOpLogEntity;
import org.springframework.stereotype.Repository;
import java.util.Map;
/**
* @Description TODO
* @Date 2021/11/29 09:57
* @Author lgx
* @Version 1.0
*/
@Repository
public interface TUserOpLogMapper {
/**
* 使用show tables检查表是否存在
* @param tableName
* @return
*/
Map<String, String> checkTableExistsWithShow(@Param("tableName")String tableName);
/**
* 创建表
*
* @param tableName 表名
*/
void createTable(@Param("tableName") String tableName);
/**
* 新增
*
* @param tableName 表名
* @param userOpLogEntity 调用接口日志数据对象
* @return 新增成功的记录数
*/
int saveOneLog(@Param("tableName") String tableName,
@Param("userOpLogEntity") TUserOpLogEntity userOpLogEntity);
}
TUserOpLogMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.lgx.bluegrass.bluegrasscoree.mapper.userlog.TUserOpLogMapper">
<select id="checkTableExistsWithShow" resultType="java.util.Map">
show tables like #{tableName}
</select>
<insert id="saveOneLog" parameterType="org.lgx.bluegrass.bluegrasscoree.entity.userlog.TUserOpLogEntity">
INSERT INTO ${tableName} (
`user_id`,
`user_name`,
`title`,
`module_id`,
`module_name`,
`module_code`,
`log_type`,
`client_ip`,
`request_url`,
`operation_type`,
`operation_content`,
`source`,
`success`,
`is_delete`,
`created_by`,
`updated_by`
)
VALUES
(
#{userOpLogEntity.userId},
#{userOpLogEntity.userName},
#{userOpLogEntity.title},
#{userOpLogEntity.moduleId},
#{userOpLogEntity.moduleName},
#{userOpLogEntity.moduleCode},
#{userOpLogEntity.logType},
#{userOpLogEntity.clientIp},
#{userOpLogEntity.requestUrl},
#{userOpLogEntity.operationType},
#{userOpLogEntity.operationContent},
#{userOpLogEntity.source},
#{userOpLogEntity.success},
#{userOpLogEntity.isDelete},
#{userOpLogEntity.createdBy},
#{userOpLogEntity.updatedBy}
)
</insert>
<update id="createTable" parameterType="string" >
CREATE TABLE ${tableName} (
`id` bigint(18) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` varchar(35) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户id',
`user_name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`title` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '标题',
`module_id` varchar(35) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '模块id',
`module_name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '模块名称',
`module_code` varchar(200) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '模块code',
`log_type` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '0.操作日志 1.系统日志',
`client_ip` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '客户端ip',
`request_url` varchar(300) COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '请求URL',
`operation_type` tinyint(4) NOT NULL DEFAULT '-2' COMMENT '查询:0 增加:1 删除:-1 修改:2',
`operation_content` longtext COLLATE utf8mb4_bin COMMENT '操作内容',
`source` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '1 PC端 ,2移动端, 3系统',
`success` tinyint(1) NOT NULL DEFAULT '-1' COMMENT '0:成功 1:失败 ',
`is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 ',
`created_by` varchar(40) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人员',
`updated_by` varchar(40) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人员',
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='操作日志';
</update>
</mapper>
CorsConfig.java 增加全局日志参数过滤器:
/**
* 请求记录
* @return
*/
@Bean
@RefreshScope
public RequestFilter requestFilter() {
return new RequestFilter();
}
本文参考:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#configuring-route-predicate-factories-and-gateway-filter-factories