fix(登录问题): 修复登录用户缓存为空的问题

This commit is contained in:
zhuangpeng.li
2025-04-23 11:39:44 +08:00
parent 201bbfeefb
commit 0215feb4b0
18 changed files with 979 additions and 111 deletions

View File

@@ -1,14 +1,11 @@
package com.fastbee.framework.config;
import java.nio.charset.Charset;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.filter.Filter;
import com.fastbee.common.constant.Constants;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* Redis使用FastJson序列化
@@ -19,8 +16,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz)
@@ -48,6 +43,6 @@ public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}

View File

@@ -1,7 +1,6 @@
package com.fastbee.framework.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -11,11 +10,10 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*
*
* @author ruoyi
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean

View File

@@ -3,7 +3,7 @@ package com.fastbee.framework.mybatis.helper;
import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.mabatis.enums.DataBaseType;
import com.fastbee.common.mybatis.enums.DataBaseType;
import com.fastbee.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

View File

@@ -1,112 +0,0 @@
package com.fastbee.framework.mybatis.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.fastbee.common.core.domain.PageParam;
import com.fastbee.common.core.domain.PageResult;
import com.fastbee.framework.mybatis.utils.MyBatisUtils;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
import java.util.List;
/**
* 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力
* <p>
* 为什么继承 MPJBaseMapper 接口?支持 MyBatis Plus 多表 Join 的能力。
*/
public interface BaseMapperX<T> extends BaseMapper<T> {
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
// MyBatis Plus 查询
IPage<T> mpPage = MyBatisUtils.buildPage(pageParam);
selectPage(mpPage, queryWrapper);
// 转换返回
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
default T selectOne(String field, Object value) {
return selectOne(new QueryWrapper<T>().eq(field, value));
}
default T selectOne(SFunction<T, ?> field, Object value) {
return selectOne(new LambdaQueryWrapper<T>().eq(field, value));
}
default T selectOne(String field1, Object value1, String field2, Object value2) {
return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));
}
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
}
default Long selectCount() {
return selectCount(new QueryWrapper<T>());
}
default Long selectCount(String field, Object value) {
return selectCount(new QueryWrapper<T>().eq(field, value));
}
default Long selectCount(SFunction<T, ?> field, Object value) {
return selectCount(new LambdaQueryWrapper<T>().eq(field, value));
}
default List<T> selectList() {
return selectList(new QueryWrapper<>());
}
default List<T> selectList(String field, Object value) {
return selectList(new QueryWrapper<T>().eq(field, value));
}
default List<T> selectList(SFunction<T, ?> field, Object value) {
return selectList(new LambdaQueryWrapper<T>().eq(field, value));
}
default List<T> selectList(String field, Collection<?> values) {
return selectList(new QueryWrapper<T>().in(field, values));
}
default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {
return selectList(new LambdaQueryWrapper<T>().in(field, values));
}
default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
}
/**
* 批量插入,适合大量数据插入
*
* @param entities 实体们
*/
default void insertBatch(Collection<T> entities) {
Db.saveBatch(entities);
}
/**
* 批量插入,适合大量数据插入
*
* @param entities 实体们
* @param size 插入数量 Db.saveBatch 默认为 1000
*/
default void insertBatch(Collection<T> entities, int size) {
Db.saveBatch(entities, size);
}
default void updateBatch(T update) {
update(update, new QueryWrapper<>());
}
default void updateBatch(Collection<T> entities, int size) {
Db.updateBatchById(entities, size);
}
}

View File

@@ -1,88 +0,0 @@
package com.fastbee.framework.mybatis.utils;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fastbee.common.core.domain.PageParam;
import com.fastbee.common.core.domain.SortingField;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* MyBatis 工具类
*/
public class MyBatisUtils {
private static final String MYSQL_ESCAPE_CHARACTER = "`";
public static <T> Page<T> buildPage(PageParam pageParam) {
return buildPage(pageParam, null);
}
public static <T> Page<T> buildPage(PageParam pageParam, Collection<SortingField> sortingFields) {
// 页码 + 数量
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
// 排序字段
if (!CollectionUtil.isEmpty(sortingFields)) {
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
.collect(Collectors.toList()));
}
return page;
}
/**
* 将拦截器添加到链中
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
*
* @param interceptor 链
* @param inner 拦截器
* @param index 位置
*/
public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {
List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());
inners.add(index, inner);
interceptor.setInterceptors(inners);
}
/**
* 获得 Table 对应的表名
*
* 兼容 MySQL 转义表名 `t_xxx`
*
* @param table 表
* @return 去除转移字符后的表名
*/
public static String getTableName(Table table) {
String tableName = table.getName();
if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) {
tableName = tableName.substring(1, tableName.length() - 1);
}
return tableName;
}
/**
* 构建 Column 对象
*
* @param tableName 表名
* @param tableAlias 别名
* @param column 字段名
* @return Column 对象
*/
public static Column buildColumn(String tableName, Alias tableAlias, String column) {
if (tableAlias != null) {
tableName = tableAlias.getName();
}
return new Column(tableName + StringPool.DOT + column);
}
}

View File

@@ -2,6 +2,7 @@ package com.fastbee.framework.web.service;
import com.fastbee.common.constant.CacheConstants;
import com.fastbee.common.constant.Constants;
import com.fastbee.common.core.domain.entity.SysUser;
import com.fastbee.common.core.domain.model.LoginUser;
import com.fastbee.common.core.redis.RedisCache;
import com.fastbee.common.utils.ServletUtils;
@@ -9,27 +10,33 @@ import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.ip.AddressUtils;
import com.fastbee.common.utils.ip.IpUtils;
import com.fastbee.common.utils.uuid.IdUtils;
import com.fastbee.system.domain.SysClient;
import com.fastbee.system.service.ISysClientService;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* token验证处理
*
* @author ruoyi
*/
@Slf4j
@Component
public class TokenService
{
public class TokenService {
// 令牌自定义标识
@Value("${token.header}")
private String header;
@@ -51,34 +58,37 @@ public class TokenService
@Autowired
private RedisCache redisCache;
@Autowired
private ISysClientService sysClientService;
@Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
if (StringUtils.isNotEmpty(token)) {
try {
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
user.setRequestToken(token);
return user;
}
catch (Exception e)
{
} catch (Exception e) {
log.info("获取缓存报错:{}", e.getMessage());
}
}
return null;
}
/**
/**
* 获取用户身份信息
*
* @return 用户信息
@@ -93,6 +103,7 @@ public class TokenService
LoginUser user = redisCache.getCacheObject(userKey);
return user;
} catch (Exception e) {
log.info("获取缓存报错:{}", e.getMessage());
}
}
return null;
@@ -101,6 +112,7 @@ public class TokenService
/**
* 根据用户id获取用户身份信息
* 由于多端登录根据token获取的用户信息不一样所以增加一个根据用户id获取用户信息的缓存key以后多端需要获取用户最新信息就用这个方法吧
*
* @return 用户信息
*/
public LoginUser getLoginUserByUserId(Long userId) {
@@ -109,6 +121,7 @@ public class TokenService
String userKey = getUserIdKey(userId);
return redisCache.getCacheObject(userKey);
} catch (Exception e) {
log.info("获取缓存报错:{}", e.getMessage());
}
}
return null;
@@ -117,10 +130,8 @@ public class TokenService
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
public void setLoginUser(LoginUser loginUser) {
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser);
}
}
@@ -128,10 +139,8 @@ public class TokenService
/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
@@ -143,8 +152,7 @@ public class TokenService
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser)
{
public String createToken(LoginUser loginUser) {
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
@@ -155,18 +163,61 @@ public class TokenService
return createToken(claims);
}
public int addToken(SysUser user, SysClient sysClient) {
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(user);
LoginUser loginUser = (LoginUser) userDetails;
sysClient.setToken(Constants.TOKEN_PREFIX + createToken(loginUser, sysClient.getClientSecret(), Math.toIntExact(sysClient.getTimeout())));
return sysClientService.insertSysClient(sysClient);
}
public int updateToken(SysUser loginuser, SysClient sysClient) {
LoginUser user;
Claims claims = parseToken(sysClient);
if (claims == null) {
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(loginuser);
user = (LoginUser) userDetails;
sysClient.setToken(Constants.TOKEN_PREFIX + createToken(user, sysClient.getClientSecret(), Math.toIntExact(sysClient.getTimeout())));
} else {
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
user = redisCache.getCacheObject(userKey);
if (user == null) {
UserDetails userDetails = userDetailsServiceImpl.createLoginUser(loginuser);
user = (LoginUser) userDetails;
}
log.debug("loginUser:{}", user);
if (sysClient.getEnable() != null && "1".equals(sysClient.getEnable())) {
delLoginUser(uuid);
sysClient.setToken(Constants.TOKEN_PREFIX + createToken(user, sysClient.getClientSecret(), Math.toIntExact(sysClient.getTimeout())));
} else if ("0".equals(sysClient.getEnable())) {
delLoginUser(uuid);
}
}
return sysClientService.updateSysClient(sysClient);
}
public String createToken(LoginUser loginUser, String secret, int expireTime) {
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser, expireTime);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return createToken(claims, secret);
}
/**
* 验证令牌有效期相差不足20分钟自动刷新缓存
*
* @param loginUser
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
refreshToken(loginUser);
}
}
@@ -176,8 +227,7 @@ public class TokenService
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
public void refreshToken(LoginUser loginUser, int expireTime) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
@@ -188,13 +238,29 @@ public class TokenService
redisCache.setCacheObject(userIdKey, loginUser, expireTime, TimeUnit.MINUTES);
}
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
String userIdKey = getUserIdKey(loginUser.getUserId());
if (Boolean.TRUE.equals(loginUser.getNeverExpire())) {
redisCache.setCacheObject(userKey, loginUser);
// 使用token作为用户信息缓存key多端不能同步最新信息需要重新登录因此添加一个使用用户id作为缓存key
redisCache.setCacheObject(userIdKey, loginUser);
} else {
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
// 使用token作为用户信息缓存key多端不能同步最新信息需要重新登录因此添加一个使用用户id作为缓存key
redisCache.setCacheObject(userIdKey, loginUser, expireTime, TimeUnit.MINUTES);
}
}
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
public void setUserAgent(LoginUser loginUser) {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
loginUser.setIpaddr(ip);
@@ -209,8 +275,14 @@ public class TokenService
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map<String, Object> claims)
{
private String createToken(Map<String, Object> claims) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
private String createToken(Map<String, Object> claims, String secret) {
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
@@ -223,12 +295,23 @@ public class TokenService
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
private Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Claims parseToken(SysClient sysClient) {
if (sysClient.getClientSecret() != null && !Objects.equals(sysClient.getToken(), "")) {
return Jwts.parser()
.setSigningKey(sysClient.getClientSecret())
.parseClaimsJws(sysClient.getToken().substring(Constants.TOKEN_PREFIX.length()))
.getBody();
} else {
return null;
}
}
/**
@@ -237,8 +320,7 @@ public class TokenService
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
public String getUsernameFromToken(String token) {
Claims claims = parseToken(token);
return claims.getSubject();
}
@@ -249,18 +331,15 @@ public class TokenService
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
private String getToken(HttpServletRequest request) {
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
private String getTokenKey(String uuid) {
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
}