禅道bug#1588修复

This commit is contained in:
gaoqr
2026-06-05 16:06:56 +08:00
parent 05c6eea837
commit e1369e7302
21 changed files with 158 additions and 15 deletions
@@ -4,6 +4,7 @@ import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2KickOutAccessTokenRespDTO;
import com.cf.imes.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
@@ -64,4 +65,7 @@ public interface OAuth2TokenApi {
@Operation(summary = "请求忽略令牌数据源")
CommonResult<String> getIgnoreTokenSource();
@GetMapping(PREFIX + "/kickout/{token}")
@Operation(summary = "获取当前token对应的被踢除token")
CommonResult<OAuth2KickOutAccessTokenRespDTO> getKickoutToken(@PathVariable("token") String token);
}
@@ -0,0 +1,20 @@
package com.cf.imes.module.system.api.oauth2.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Schema(description = "RPC 服务 - OAuth2 被踢除令牌的信息 Response DTO")
@Data
@Accessors(chain = true)
public class OAuth2KickOutAccessTokenRespDTO implements Serializable {
@Schema(description = "登录ip")
private String ip;
@Schema(description = "踢除时间")
private String kickTime;
}
@@ -5,6 +5,7 @@ import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2KickOutAccessTokenRespDTO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.service.oauth2.OAuth2TokenService;
import com.cf.imes.module.system.service.organ.OrganService;
@@ -68,4 +69,10 @@ public class OAuth2TokenApiImpl implements OAuth2TokenApi {
public CommonResult<String> getIgnoreTokenSource() {
return success(oauth2TokenService.getIgnoreTokenSource());
}
@Override
public CommonResult<OAuth2KickOutAccessTokenRespDTO> getKickoutToken(String token) {
OAuth2AccessTokenDO kickOutAccessToken = oauth2TokenService.getKickOutAccessToken(token);
return success(BeanUtils.toBean(kickOutAccessToken, OAuth2KickOutAccessTokenRespDTO.class));
}
}
@@ -117,4 +117,16 @@ public class OAuth2AccessTokenDO extends BaseDO {
*/
@TableField(exist = false)
private Long productId;
/**
* ip地址
*/
@TableField(exist = false)
private String ip;
/**
* 踢出时间
*/
@TableField(exist = false)
private String kickTime;
}
@@ -69,6 +69,11 @@ public class RedisKeyConstants {
*/
public static final String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
/**
* 踢出令牌的缓存,记录当前登录ip
*/
public static final String OAUTH2_KICKOUT_TOKEN = "oauth2_kickout_token:%s";
/**
* 站内信模版的缓存
* <p>
@@ -1,5 +1,7 @@
package com.cf.imes.module.system.dal.redis.oauth2;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.cf.imes.framework.common.util.collection.CollectionUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
@@ -11,10 +13,14 @@ import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static com.cf.imes.framework.common.util.servlet.ServletUtils.getClientIP;
import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
/**
@@ -39,6 +45,17 @@ public class OAuth2AccessTokenRedisDAO {
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
}
/**
* key获取被踢除token
*
* @param accessToken
* @return
*/
public OAuth2AccessTokenDO getKickOut(String accessToken) {
String redisKey = formatKickOutKey(accessToken);
return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);
}
/**
* 带key获取,oauth2_access_token:xxxx
*
@@ -98,6 +115,10 @@ public class OAuth2AccessTokenRedisDAO {
return String.format(RedisKeyConstants.OAUTH2_ACCESS_TOKEN, accessToken);
}
private static String formatKickOutKey(String accessToken) {
return String.format(RedisKeyConstants.OAUTH2_KICKOUT_TOKEN, accessToken);
}
/**
* 构建token游标,使用时轮训一次获取两百个token
*
@@ -106,4 +127,17 @@ public class OAuth2AccessTokenRedisDAO {
public Cursor<String> scan(){
return stringRedisTemplate.scan(ScanOptions.scanOptions().match(String.format(OAUTH2_ACCESS_TOKEN, "*")).count(200).build());
}
/**
* 创建被踢除的token
*
* @param accessToken
*/
public void createKickOutToken(String accessToken) {
String redisKey = formatKickOutKey(accessToken);
OAuth2AccessTokenDO kickOutToken = new OAuth2AccessTokenDO();
kickOutToken.setIp(getClientIP()).setKickTime(DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN));
// 创建被踢除的token,设置有效期为5分钟
stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(kickOutToken), Duration.ofMinutes(5));
}
}
@@ -104,4 +104,12 @@ public interface OAuth2TokenService {
* @param userId
*/
void kickOut(Long userId);
/**
* 获取被剔除的token信息
*
* @param token
* @return
*/
OAuth2AccessTokenDO getKickOutAccessToken(String token);
}
@@ -25,12 +25,10 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.Cursor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception0;
@@ -78,7 +76,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
@Override
@Transactional
public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes,Boolean large, Integer dbNo, Integer tableNo, String dataCode, Long organId, String nickname) {
OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
// 填入访问令牌包含的字段
@@ -243,11 +240,8 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
accessTokenDO.setAccessToken(currentToken);
// 记录到 Redis 中
oauth2AccessTokenRedisDAO.set(accessTokenDO);
// 异步踢出其他token
CompletableFuture.runAsync(() -> kickOut(userId, currentToken)).exceptionally(e -> {
log.error("[OAuth2TokenServiceImpl] redis 清空token失败, 用户id:{}, 异常:{}", userId, e);
return null;
});
// 踢出其他token
kickOut(userId, currentToken);
return accessTokenDO;
}
@@ -266,6 +260,8 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
if (ObjectUtil.equal(userId, oAuth2AccessTokenDO.getUserId()) && !StringUtils.equals(currentToken, oAuth2AccessTokenDO.getAccessToken()) && Boolean.FALSE.equals(oAuth2AccessTokenDO.getIsSupAdmin())) {
// 用户id匹配、非当次登录token、非超管 -> 删除redis中的token缓存
oauth2AccessTokenRedisDAO.deleteWithKey(key);
// 创建踢除的token
oauth2AccessTokenRedisDAO.createKickOutToken(oAuth2AccessTokenDO.getAccessToken());
}
}
cursor.close();
@@ -295,4 +291,9 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
}
cursor.close();
}
@Override
public OAuth2AccessTokenDO getKickOutAccessToken(String token) {
return oauth2AccessTokenRedisDAO.getKickOut(token);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long