【功能优化】SYSTEM:支持通过 refreshToken 认证,解决部分场景不方便刷新访问令牌场景

pull/143/MERGE
YunaiV 2024-10-02 14:54:06 +08:00
parent a4ff54c148
commit 2344332b03
11 changed files with 84 additions and 19 deletions

View File

@ -34,7 +34,7 @@ public interface ErrorCodeConstants {
// ========== 商品 SPU 1-008-005-000 ==========
ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
ErrorCode SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_005_002, "商品 SPU 保存失败,原因:优惠不存在");
ErrorCode SPU_SAVE_FAIL_COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_008_005_002, "商品 SPU 保存失败,原因:优惠不存在");
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_003, "商品 SPU【{}】不处于上架状态");
ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_004, "商品 SPU 不处于回收站状态");

View File

@ -142,7 +142,7 @@ public class AppCouponTemplateController {
ProductSpuRespDTO spu = productSpuApi.getSpu(spuId).getCheckedData();
return spu != null ? spu.getCategoryId() : null;
}
// 商品:直接返回
// 商品:直接返回
return spuId;
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.system.dal.dataobject.oauth2;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
@ -24,7 +24,7 @@ import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class OAuth2RefreshTokenDO extends BaseDO {
public class OAuth2RefreshTokenDO extends TenantBaseDO {
/**
*

View File

@ -30,4 +30,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
return selectList(DeptDO::getParentId, parentIds);
}
default List<DeptDO> selectListByLeaderUserId(Long id) {
return selectList(DeptDO::getLeaderUserId, id);
}
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.oauth2;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
import org.apache.ibatis.annotations.Mapper;
@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshToken
.eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));
}
@TenantIgnore // 获取 token 的时候,需要忽略租户编号。原因是:一些场景下,可能不会传递 tenant-id 请求头,例如说文件上传、积木报表等等
default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {
return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);
}

View File

@ -5,10 +5,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Service
@ -80,7 +77,25 @@ public interface DeptService {
* @param id
* @return
*/
List<DeptDO> getChildDeptList(Long id);
default List<DeptDO> getChildDeptList(Long id) {
return getChildDeptList(Collections.singleton(id));
}
/**
*
*
* @param ids
* @return
*/
List<DeptDO> getChildDeptList(Collection<Long> ids);
/**
*
*
* @param id
* @return
*/
List<DeptDO> getDeptListByLeaderUserId(Long id);
/**
*

View File

@ -11,13 +11,13 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import com.google.common.annotations.VisibleForTesting;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -170,10 +170,10 @@ public class DeptServiceImpl implements DeptService {
}
@Override
public List<DeptDO> getChildDeptList(Long id) {
public List<DeptDO> getChildDeptList(Collection<Long> ids) {
List<DeptDO> children = new LinkedList<>();
// 遍历每一层
Collection<Long> parentIds = Collections.singleton(id);
Collection<Long> parentIds = ids;
for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
// 查询当前层,所有的子部门
List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
@ -188,6 +188,11 @@ public class DeptServiceImpl implements DeptService {
return children;
}
@Override
public List<DeptDO> getDeptListByLeaderUserId(Long id) {
return deptMapper.selectListByLeaderUserId(id);
}
@Override
@DataPermission(enable = false) // 禁用数据权限,避免建立不正确的缓存
@Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")

View File

@ -9,8 +9,10 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
@ -105,8 +107,18 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return accessTokenDO;
}
// 获取不到,从 MySQL 中获取
// 获取不到,从 MySQL 中获取访问令牌
accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
if (accessTokenDO == null) {
// 特殊:从 MySQL 中获取刷新令牌。原因:解决部分场景不方便刷新访问令牌场景
// 例如说,积木报表只允许传递 token不允许传递 refresh_token导致无法刷新访问令牌
// 再例如说,前端 WebSocket 的 token 直接跟在 url 上,无法传递 refresh_token
OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(accessToken);
if (refreshTokenDO != null && !DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
accessTokenDO = convertToAccessToken(refreshTokenDO);
}
}
// 如果在 MySQL 存在,则往 Redis 中写入
if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
oauth2AccessTokenRedisDAO.set(accessTokenDO);
@ -169,8 +181,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
return refreshToken;
}
private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) {
OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class)
.setAccessToken(refreshTokenDO.getRefreshToken());
TenantUtils.execute(refreshTokenDO.getTenantId(),
() -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())));
return accessTokenDO;
}
/**
* 便 {@link cn.iocoder.yudao.framework.security.core.LoginUser}
* 便 {@link LoginUser}
*
* @param userId
* @param userType

View File

@ -335,6 +335,7 @@ public class AdminUserServiceImpl implements AdminUserService {
/**
*
*
* @param deptId
* @return
*/

View File

@ -158,10 +158,11 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
.setAccessTokenValiditySeconds(30);
when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);
// mock 数据(访问令牌)
OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
.setRefreshToken(refreshToken).setClientId(clientId)
.setExpiresTime(LocalDateTime.now().plusDays(1))
.setUserType(UserTypeEnum.ADMIN.getValue());
OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class, o ->
o.setRefreshToken(refreshToken).setClientId(clientId)
.setExpiresTime(LocalDateTime.now().plusDays(1))
.setUserType(UserTypeEnum.ADMIN.getValue())
.setTenantId(TenantContextHolder.getTenantId()));
oauth2RefreshTokenMapper.insert(refreshTokenDO);
// mock 数据(访问令牌)
OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setRefreshToken(refreshToken)
@ -231,6 +232,22 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
new ErrorCode(401, "访问令牌已过期"));
}
@Test
public void testCheckAccessToken_refreshToken() {
// mock 数据(访问令牌)
OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)
.setExpiresTime(LocalDateTime.now().plusDays(1));
oauth2RefreshTokenMapper.insert(refreshTokenDO);
// 准备参数
String accessToken = refreshTokenDO.getRefreshToken();
// 调研,并断言
OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);
// 断言
assertPojoEquals(refreshTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted",
"creator", "updater");
}
@Test
public void testCheckAccessToken_success() {
// mock 数据(访问令牌)

View File

@ -473,7 +473,7 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_access_token" (
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint NOT NULL,
"tenant_id" bigint not null,
PRIMARY KEY ("id")
) COMMENT 'OAuth2 访问令牌';
@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_refresh_token" (
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT 'OAuth2 刷新令牌';