diff --git a/mall4v/src/views/common/home.vue b/mall4v/src/views/common/home.vue index b494a2f..453fc59 100644 --- a/mall4v/src/views/common/home.vue +++ b/mall4v/src/views/common/home.vue @@ -24,14 +24,9 @@ Spring Boot - 2.1.6.RELEASE + 3.0.4 MVC核心框架 - - Spring Security oauth2 - 2.1.5.RELEASE - 认证和授权框架 - MyBatis 3.5.0 @@ -39,22 +34,17 @@ MyBatisPlus - 3.1.0 + 3.5.3.1 基于mybatis,使用lambda表达式的 Swagger-UI - 2.9.2 + 4.0.0 文档生产工具 - - Hibernator-Validator - 6.0.17.Final - 验证框架 - redisson - 3.10.6 + 3.19.3 对redis进行封装、集成分布式锁等 @@ -64,19 +54,9 @@ log4j2 - 2.11.2 + 2.17.2 更快的log日志工具 - - fst - 2.57 - 更快的序列化和反序列化工具 - - - orika - 1.5.4 - 更快的bean复制工具 - lombok 1.18.8 @@ -84,13 +64,13 @@ hutool - 4.5.0 + 5.8.15 更适合国人的java工具集 - swagger-bootstrap - 1.9.3 - 基于swagger,更便于国人使用的swagger ui + xxl-job + 2.3.1 + 定时任务 @@ -112,7 +92,7 @@ jdk - 1.8+ + 17 mysql diff --git a/pom.xml b/pom.xml index dd7d2a6..798d54c 100644 --- a/pom.xml +++ b/pom.xml @@ -33,11 +33,13 @@ 1.1.0 3.5.3.1 3.19.3 - 2.12.1 + 2.14.2 2.19.0 4.0.0 2.3.1 4.0.1 + 1.34.0 + 1.2.83 diff --git a/yami-shop-admin/src/main/resources/application.yml b/yami-shop-admin/src/main/resources/application.yml index b70d5c0..0e51f9c 100644 --- a/yami-shop-admin/src/main/resources/application.yml +++ b/yami-shop-admin/src/main/resources/application.yml @@ -25,3 +25,14 @@ mybatis-plus: field-strategy: NOT_NULL # 默认数据库表下划线命名 table-underline: true +sa-token: + # token名称 (同时也是cookie名称) + token-name: Authorization + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token(不共用,避免登出时导致其他用户也登出) + is-share: false + # token风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: uuid + # 是否输出操作日志 + is-log: false diff --git a/yami-shop-api/src/main/resources/application.yml b/yami-shop-api/src/main/resources/application.yml index 5412477..da39db5 100644 --- a/yami-shop-api/src/main/resources/application.yml +++ b/yami-shop-api/src/main/resources/application.yml @@ -29,3 +29,14 @@ mybatis-plus: management: server: add-application-context-header: false +sa-token: + # token名称 (同时也是cookie名称) + token-name: Authorization + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token(不共用,避免登出时导致其他用户也登出) + is-share: false + # token风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik) + token-style: uuid + # 是否输出操作日志 + is-log: false diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java index dac1954..0429557 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/constants/OauthCacheNames.java @@ -30,4 +30,9 @@ public interface OauthCacheNames { * 根据uid获取保存的token key缓存使用的key */ String UID_TO_ACCESS = OAUTH_TOKEN_PREFIX + "uid_to_access:"; + + /** + * 保存token的用户信息使用的key + */ + String USER_INFO = OAUTH_TOKEN_PREFIX + "user_info:"; } diff --git a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java index 29956c9..e3d1b35 100644 --- a/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java +++ b/yami-shop-common/src/main/java/com/yami/shop/common/response/ServerResponseEntity.java @@ -106,7 +106,7 @@ public class ServerResponseEntity implements Serializable { public ServerResponseEntity() { // 版本号 - this.version = "mall4j.v230410"; + this.version = "mall4j.v230424"; } public static ServerResponseEntity success(T data) { diff --git a/yami-shop-security/yami-shop-security-common/pom.xml b/yami-shop-security/yami-shop-security-common/pom.xml index ebbe526..9c1e3ee 100644 --- a/yami-shop-security/yami-shop-security-common/pom.xml +++ b/yami-shop-security/yami-shop-security-common/pom.xml @@ -28,6 +28,23 @@ captcha 1.3.0 + + + cn.dev33 + sa-token-spring-boot3-starter + ${satoken.version} + + + + cn.dev33 + sa-token-dao-redis-jackson + ${satoken.version} + + + com.alibaba + fastjson + ${fastjson.version} + \ No newline at end of file diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java index aa4f11c..ebcef4f 100644 --- a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/filter/AuthFilter.java @@ -9,6 +9,7 @@ */ package com.yami.shop.security.common.filter; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.yami.shop.common.exception.YamiShopBindException; @@ -22,6 +23,7 @@ import com.yami.shop.security.common.util.AuthUserContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; @@ -51,6 +53,9 @@ public class AuthFilter implements Filter { @Autowired private TokenStore tokenStore; + @Value("${sa-token.token-name}") + private String tokenName; + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -72,7 +77,7 @@ public class AuthFilter implements Filter { } } - String accessToken = req.getHeader("Authorization"); + String accessToken = req.getHeader(tokenName); // 也许需要登录,不登陆也能用的uri boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri); @@ -82,6 +87,13 @@ public class AuthFilter implements Filter { try { // 如果有token,就要获取token if (StrUtil.isNotBlank(accessToken)) { + // 校验登录,并从缓存中取出用户信息 + try { + StpUtil.checkLogin(); + } catch (Exception e) { + httpHandler.printServerResponseToWeb(ServerResponseEntity.fail(ResponseEnum.UNAUTHORIZED)); + return; + } userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true); } else if (!mayAuth) { diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java index e73f98e..c72e8b8 100644 --- a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/PasswordManager.java @@ -33,6 +33,10 @@ public class PasswordManager { public String passwordSignKey; public String decryptPassword(String data) { + // 在使用oracle的JDK时,JAR包必须签署特殊的证书才能使用。 + // 解决方案 1.使用openJDK或者非oracle的JDK(建议) 2.添加证书 + // hutool的aes报错可以打开下面那段代码 + // SecureUtil.disableBouncyCastle(); AES aes = new AES(passwordSignKey.getBytes(StandardCharsets.UTF_8)); String decryptStr; String decryptPassword; diff --git a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java index 3e02136..6d280ce 100644 --- a/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java +++ b/yami-shop-security/yami-shop-security-common/src/main/java/com/yami/shop/security/common/manager/TokenStore.java @@ -9,34 +9,26 @@ */ package com.yami.shop.security.common.manager; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.IdUtil; +import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.symmetric.AES; +import com.alibaba.fastjson.JSON; import com.yami.shop.common.constants.OauthCacheNames; -import com.yami.shop.common.response.ResponseEnum; import com.yami.shop.common.exception.YamiShopBindException; -import com.yami.shop.common.util.PrincipalUtil; +import com.yami.shop.common.response.ResponseEnum; import com.yami.shop.security.common.bo.TokenInfoBO; import com.yami.shop.security.common.bo.UserInfoInTokenBO; import com.yami.shop.security.common.enums.SysTypeEnum; import com.yami.shop.security.common.vo.TokenInfoVO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; /** * token管理 1. 登陆返回token 2. 刷新token 3. 清除用户过去token 4. 校验token @@ -49,96 +41,49 @@ public class TokenStore { private static final Logger logger = LoggerFactory.getLogger(TokenStore.class); - /** - * 用于aes签名的key,16位 - */ - @Value("${auth.token.signKey:-mall4j--mall4j-}") - public String tokenSignKey; - private final RedisTemplate redisTemplate; - private final RedisSerializer redisSerializer; - - private final StringRedisTemplate stringRedisTemplate; - - public TokenStore(RedisTemplate redisTemplate, - StringRedisTemplate stringRedisTemplate, GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer) { + public TokenStore(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; - this.redisSerializer = genericJackson2JsonRedisSerializer; - this.stringRedisTemplate = stringRedisTemplate; } - /** - * 将用户的部分信息存储在token中,并返回token信息 - * @param userInfoInToken 用户在token中的信息 - * @return token信息 + * 以Sa-Token技术生成token,并返回token信息 + * @param userInfoInToken + * @return */ - public TokenInfoBO storeAccessToken(UserInfoInTokenBO userInfoInToken) { + public TokenInfoBO storeAccessSaToken(UserInfoInTokenBO userInfoInToken) { + // token生成 + int timeoutSecond = getExpiresIn(userInfoInToken.getSysType()); + String uid = this.getUid(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId()); + StpUtil.login(uid, timeoutSecond); + String token = StpUtil.getTokenValue(); + // 用户信息存入缓存 + String keyName = OauthCacheNames.USER_INFO + token; + redisTemplate.delete(keyName); + redisTemplate.opsForValue().set( + keyName, + JSON.toJSONString(userInfoInToken), + timeoutSecond, + TimeUnit.SECONDS + ); + // 数据封装返回(token不用加密) TokenInfoBO tokenInfoBO = new TokenInfoBO(); - String accessToken = IdUtil.simpleUUID(); - String refreshToken = IdUtil.simpleUUID(); - tokenInfoBO.setUserInfoInToken(userInfoInToken); - tokenInfoBO.setExpiresIn(getExpiresIn(userInfoInToken.getSysType())); - - String uidToAccessKeyStr = getUserIdToAccessKey(getApprovalKey(userInfoInToken)); - String accessKeyStr = getAccessKey(accessToken); - String refreshToAccessKeyStr = getRefreshToAccessKey(refreshToken); - - // 一个用户会登陆很多次,每次登陆的token都会存在 uid_to_access里面 - // 但是每次保存都会更新这个key的时间,而key里面的token有可能会过期,过期就要移除掉 - List existsAccessTokensBytes = new ArrayList<>(); - // 新的token数据 - existsAccessTokensBytes.add((accessToken + StrUtil.COLON + refreshToken).getBytes(StandardCharsets.UTF_8)); - - Long size = redisTemplate.opsForSet().size(uidToAccessKeyStr); - if (size != null && size != 0) { - List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidToAccessKeyStr, size); - if (tokenInfoBoList != null) { - for (String accessTokenWithRefreshToken : tokenInfoBoList) { - String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); - String accessTokenData = accessTokenWithRefreshTokenArr[0]; - if (BooleanUtil.isTrue(stringRedisTemplate.hasKey(getAccessKey(accessTokenData)))) { - existsAccessTokensBytes.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); - } - } - } - } - - redisTemplate.executePipelined((RedisCallback) connection -> { - - long expiresIn = tokenInfoBO.getExpiresIn(); - - byte[] uidKey = uidToAccessKeyStr.getBytes(StandardCharsets.UTF_8); - byte[] refreshKey = refreshToAccessKeyStr.getBytes(StandardCharsets.UTF_8); - byte[] accessKey = accessKeyStr.getBytes(StandardCharsets.UTF_8); - - connection.sAdd(uidKey, ArrayUtil.toArray(existsAccessTokensBytes, byte[].class)); - - // 通过uid + sysType 保存access_token,当需要禁用用户的时候,可以根据uid + sysType 禁用用户 - connection.expire(uidKey, expiresIn); - - // 通过refresh_token获取用户的access_token从而刷新token - connection.setEx(refreshKey, expiresIn, accessToken.getBytes(StandardCharsets.UTF_8)); - - // 通过access_token保存用户的租户id,用户id,uid - connection.setEx(accessKey, expiresIn, Objects.requireNonNull(redisSerializer.serialize(userInfoInToken))); - - return null; - }); - - // 返回给前端是加密的token - tokenInfoBO.setAccessToken(encryptToken(accessToken,userInfoInToken.getSysType())); - tokenInfoBO.setRefreshToken(encryptToken(refreshToken,userInfoInToken.getSysType())); - + tokenInfoBO.setExpiresIn(timeoutSecond); + tokenInfoBO.setAccessToken(token); + tokenInfoBO.setRefreshToken(token); return tokenInfoBO; } + /** + * 计算过期时间(单位:秒) + * @param sysType + * @return + */ private int getExpiresIn(int sysType) { // 3600秒 int expiresIn = 3600; - // 普通用户token过期时间 1小时 if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) { expiresIn = expiresIn * 24 * 30; @@ -160,20 +105,12 @@ public class TokenStore { if (StrUtil.isBlank(accessToken)) { throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"accessToken is blank"); } - String realAccessToken; - if (needDecrypt) { - realAccessToken = decryptToken(accessToken); + String keyName = OauthCacheNames.USER_INFO + accessToken; + Object redisCache = redisTemplate.opsForValue().get(keyName); + if (redisCache == null) { + throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"登录过期,请重新登录"); } - else { - realAccessToken = accessToken; - } - UserInfoInTokenBO userInfoInTokenBO = (UserInfoInTokenBO) redisTemplate.opsForValue() - .get(getAccessKey(realAccessToken)); - - if (userInfoInTokenBO == null) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"accessToken 已过期"); - } - return userInfoInTokenBO; + return JSON.parseObject(redisCache.toString(), UserInfoInTokenBO.class); } /** @@ -185,106 +122,43 @@ public class TokenStore { if (StrUtil.isBlank(refreshToken)) { throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"refreshToken is blank"); } - String realRefreshToken = decryptToken(refreshToken); - String accessToken = stringRedisTemplate.opsForValue().get(getRefreshToAccessKey(realRefreshToken)); - - if (StrUtil.isBlank(accessToken)) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"refreshToken 已过期"); - } - UserInfoInTokenBO userInfoInTokenBO = getUserInfoByAccessToken(accessToken, - false); - - // 删除旧的refresh_token - stringRedisTemplate.delete(getRefreshToAccessKey(realRefreshToken)); - // 删除旧的access_token - stringRedisTemplate.delete(getAccessKey(accessToken)); + // 删除旧token + UserInfoInTokenBO userInfoInTokenBO = getUserInfoByAccessToken(refreshToken, false); + this.deleteCurrentToken(refreshToken); // 保存一份新的token - return storeAccessToken(userInfoInTokenBO); + return storeAccessSaToken(userInfoInTokenBO); } /** - * 删除全部的token + * 删除指定用户的全部的token */ public void deleteAllToken(String sysType, String userId) { - String uidKey = getUserIdToAccessKey(getApprovalKey(sysType, userId)); - Long size = redisTemplate.opsForSet().size(uidKey); - if (size == null || size == 0) { - return; - } - List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); - - if (CollUtil.isEmpty(tokenInfoBoList)) { - return; - } - - for (String accessTokenWithRefreshToken : tokenInfoBoList) { - String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); - String accessToken = accessTokenWithRefreshTokenArr[0]; - String refreshToken = accessTokenWithRefreshTokenArr[1]; - redisTemplate.delete(getRefreshToAccessKey(refreshToken)); - redisTemplate.delete(getAccessKey(accessToken)); - } - redisTemplate.delete(uidKey); - - } - - private static String getApprovalKey(UserInfoInTokenBO userInfoInToken) { - return getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId()); - } - - private static String getApprovalKey(String sysType, String userId) { - return userId == null? sysType : sysType + StrUtil.COLON + userId; - } - - private String encryptToken(String accessToken,Integer sysType) { - AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); - return aes.encryptBase64(accessToken + System.currentTimeMillis() + sysType); - } - - private String decryptToken(String data) { - AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); - String decryptStr; - String decryptToken; - try { - decryptStr = aes.decryptStr(data); - decryptToken = decryptStr.substring(0,32); - // 创建token的时间,token使用时效性,防止攻击者通过一堆的尝试找到aes的密码,虽然aes是目前几乎最好的加密算法 - long createTokenTime = Long.parseLong(decryptStr.substring(32,45)); - // 系统类型 - int sysType = Integer.parseInt(decryptStr.substring(45)); - // token的过期时间 - int expiresIn = getExpiresIn(sysType); - long second = 1000L; - if (System.currentTimeMillis() - createTokenTime > expiresIn * second) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"token error"); + // 删除用户缓存 + String uid = this.getUid(sysType, userId); + List tokens = StpUtil.getTokenValueListByLoginId(uid); + if (!CollectionUtils.isEmpty(tokens)) { + List keyNames = new ArrayList<>(); + for (String token : tokens) { + keyNames.add(OauthCacheNames.USER_INFO + token); } + redisTemplate.delete(keyNames); } - catch (Exception e) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"token error"); - } - - // 防止解密后的token是脚本,从而对redis进行攻击,uuid只能是数字和小写字母 - if (!PrincipalUtil.isSimpleChar(decryptToken)) { - throw new YamiShopBindException(ResponseEnum.UNAUTHORIZED,"token error"); - } - return decryptToken; - } - - public String getAccessKey(String accessToken) { - return OauthCacheNames.ACCESS + accessToken; - } - - public String getUserIdToAccessKey(String approvalKey) { - return OauthCacheNames.UID_TO_ACCESS + approvalKey; - } - - public String getRefreshToAccessKey(String refreshToken) { - return OauthCacheNames.REFRESH_TO_ACCESS + refreshToken; + // 移除token + StpUtil.logout(userId); } + /** + * 生成token,并返回token展示信息 + * @param userInfoInToken + * @return + */ public TokenInfoVO storeAndGetVo(UserInfoInTokenBO userInfoInToken) { - TokenInfoBO tokenInfoBO = storeAccessToken(userInfoInToken); - + if (!userInfoInToken.getEnabled()){ + // 用户已禁用,请联系客服 + throw new YamiShopBindException("yami.user.disabled"); + } + TokenInfoBO tokenInfoBO = storeAccessSaToken(userInfoInToken); + // 数据封装返回 TokenInfoVO tokenInfoVO = new TokenInfoVO(); tokenInfoVO.setAccessToken(tokenInfoBO.getAccessToken()); tokenInfoVO.setRefreshToken(tokenInfoBO.getRefreshToken()); @@ -292,41 +166,25 @@ public class TokenStore { return tokenInfoVO; } + /** + * 删除当前登录的token + * @param accessToken 令牌 + */ public void deleteCurrentToken(String accessToken) { - String decryptToken = decryptToken(accessToken); + // 删除用户缓存 + String keyName = OauthCacheNames.USER_INFO + accessToken; + redisTemplate.delete(keyName); + // 移除token + StpUtil.logoutByTokenValue(accessToken); + } - UserInfoInTokenBO userInfoInToken = getUserInfoByAccessToken(accessToken, true); - - String uidKey = getUserIdToAccessKey(getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId())); - Long size = redisTemplate.opsForSet().size(uidKey); - if (size == null || size == 0) { - return; - } - List tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); - - if (CollUtil.isEmpty(tokenInfoBoList)) { - return; - } - String dbAccessToken = null; - String dbRefreshToken = null; - List list = new ArrayList<>(); - for (String accessTokenWithRefreshToken : tokenInfoBoList) { - String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); - dbAccessToken = accessTokenWithRefreshTokenArr[0]; - if (decryptToken.equals(dbAccessToken)) { - dbRefreshToken = accessTokenWithRefreshTokenArr[1]; - redisTemplate.delete(getRefreshToAccessKey(dbRefreshToken)); - redisTemplate.delete(getAccessKey(dbAccessToken)); - continue; - } - list.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); - } - - if (CollUtil.isNotEmpty(list)) { - redisTemplate.executePipelined((RedisCallback) connection -> { - connection.sAdd(uidKey.getBytes(StandardCharsets.UTF_8), ArrayUtil.toArray(list, byte[].class)); - return null; - }); - } + /** + * 生成各系统唯一uid + * @param sysType 系统类型 + * @param userId 用户id + * @return + */ + private String getUid(String sysType, String userId) { + return sysType + ":" + userId; } }