禅道问题修复:1、组织套餐允许超管操作;2、角色权限修改触发角色下用户的token缓存删除;3、网关checktoken移除本地缓存,直接查feignapi;4、order_goods的goods_id转为字符串类型的相关代码适配;

This commit is contained in:
gaoqr
2024-09-10 18:28:34 +08:00
parent 433e6825e5
commit e7192ff6cd
8 changed files with 182 additions and 98 deletions
@@ -1,17 +1,13 @@
package com.cf.imes.gateway.filter.security; package com.cf.imes.gateway.filter.security;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.text.CharSequenceUtil;
import com.cf.imes.framework.common.core.KeyValue;
import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.util.cache.CacheUtils;
import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.gateway.util.SecurityFrameworkUtils; import com.cf.imes.gateway.util.SecurityFrameworkUtils;
import com.cf.imes.gateway.util.WebFrameworkUtils; import com.cf.imes.gateway.util.WebFrameworkUtils;
import com.cf.imes.module.system.api.oauth2.OAuth2TokenApi; import com.cf.imes.module.system.api.oauth2.OAuth2TokenApi;
import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.GlobalFilter;
@@ -22,7 +18,6 @@ import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
@@ -53,23 +48,6 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
private final WebClient webClient; private final WebClient webClient;
/**
* 登录用户的本地缓存
*
* key1:多组织的编号
* key2:访问令牌
*/
private final LoadingCache<KeyValue<Long, String>, LoginUser> loginUserCache = CacheUtils.buildAsyncReloadingCache(Duration.ofMinutes(1),
new CacheLoader<KeyValue<Long, String>, LoginUser>() {
@Override
public LoginUser load(KeyValue<Long, String> token) {
String body = checkAccessToken(token.getKey(), token.getValue()).block();
return buildUser(body);
}
});
public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) { public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
// Q:为什么不使用 OAuth2TokenApi 进行调用? // Q:为什么不使用 OAuth2TokenApi 进行调用?
// A1Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support // A1Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
@@ -85,7 +63,7 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
// 情况一,如果没有 Token 令牌,则直接继续 filter // 情况一,如果没有 Token 令牌,则直接继续 filter
String token = SecurityFrameworkUtils.obtainAuthorization(exchange); String token = SecurityFrameworkUtils.obtainAuthorization(exchange);
if (StrUtil.isEmpty(token)) { if (CharSequenceUtil.isEmpty(token)) {
return chain.filter(exchange); return chain.filter(exchange);
} }
@@ -108,21 +86,11 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
} }
private Mono<LoginUser> getLoginUser(ServerWebExchange exchange, String token) { private Mono<LoginUser> getLoginUser(ServerWebExchange exchange, String token) {
//Long organId = organIdCache.getIfPresent(token); // 用token从checktoken api中获取当前用户
// 从缓存中,获取 LoginUser
Long organId = WebFrameworkUtils.getOrganId(exchange); Long organId = WebFrameworkUtils.getOrganId(exchange);
KeyValue<Long, String> cacheKey = new KeyValue<Long, String>().setKey(organId).setValue(token);
LoginUser localUser = loginUserCache.getIfPresent(cacheKey);
if (localUser != null) {
return Mono.just(localUser);
}
// 缓存不存在,则请求远程服务
return checkAccessToken(organId, token).flatMap((Function<String, Mono<LoginUser>>) body -> { return checkAccessToken(organId, token).flatMap((Function<String, Mono<LoginUser>>) body -> {
LoginUser remoteUser = buildUser(body); LoginUser remoteUser = buildUser(body);
if (remoteUser != null) { if (remoteUser != null) {
// 非空,则进行缓存
loginUserCache.put(cacheKey, remoteUser);
return Mono.just(remoteUser); return Mono.just(remoteUser);
} }
return Mono.empty(); return Mono.empty();
@@ -15,7 +15,7 @@ import java.math.BigDecimal;
public class OrderGoodsPlateRespVO implements Comparable<OrderGoodsPlateRespVO> { public class OrderGoodsPlateRespVO implements Comparable<OrderGoodsPlateRespVO> {
@Schema(description = "商品 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "243") @Schema(description = "商品 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "243")
private Long goodsId; private String goodsId;
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰") @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰")
private String goodsName; private String goodsName;
@@ -32,7 +32,7 @@ public interface OrderPlateStatisticsMapper extends BaseMapperX<PlateDO> {
* @param reqVO * @param reqVO
* @return * @return
*/ */
List<OrderGoodsPlateRespVO> selectTopTenPlateCountListGroupByOrderDate(@Param("goodsIds") List<Long> goodsIdList, @Param("req") OrderStatisticsReqVO reqVO); List<OrderGoodsPlateRespVO> selectTopTenPlateCountListGroupByOrderDate(@Param("goodsIds") List<String> goodsIdList, @Param("req") OrderStatisticsReqVO reqVO);
/** /**
@@ -49,6 +49,6 @@ public interface OrderPlateStatisticsMapper extends BaseMapperX<PlateDO> {
* @param reqVO * @param reqVO
* @return * @return
*/ */
List<OrderGoodsPlateRespVO> selectTopTenPlateAreaListGroupByOrderDate(@Param("goodsIds") List<Long> goodsIdList, @Param("req") OrderStatisticsReqVO reqVO); List<OrderGoodsPlateRespVO> selectTopTenPlateAreaListGroupByOrderDate(@Param("goodsIds") List<String> goodsIdList, @Param("req") OrderStatisticsReqVO reqVO);
} }
@@ -240,7 +240,7 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
List<String> dateList = getDateList(reqVO); List<String> dateList = getDateList(reqVO);
// 查询时间段内数量最多的小板,列出数量前10的goods_id // 查询时间段内数量最多的小板,列出数量前10的goods_id
List<Long> topTenCountGoodsId = orderPlateStatisticsMapper.selectPlateCountList(reqVO).stream() List<String> topTenCountGoodsId = orderPlateStatisticsMapper.selectPlateCountList(reqVO).stream()
.map(OrderGoodsPlateRespVO::getGoodsId) .map(OrderGoodsPlateRespVO::getGoodsId)
.toList(); .toList();
@@ -254,8 +254,8 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
// 获取所有商品{goodsId:respvo},根据top10排序 // 获取所有商品{goodsId:respvo},根据top10排序
Map<Long, OrderGoodsPlateRespVO> goodsMap = new LinkedHashMap<>(); Map<String, OrderGoodsPlateRespVO> goodsMap = new LinkedHashMap<>();
for (Long id : topTenCountGoodsId) { for (String id : topTenCountGoodsId) {
List<OrderGoodsPlateRespVO> list = orderGoodsPlateRespMap.values().stream() List<OrderGoodsPlateRespVO> list = orderGoodsPlateRespMap.values().stream()
.flatMap(List::stream) .flatMap(List::stream)
.filter(vo -> vo.getGoodsId().equals(id)) .filter(vo -> vo.getGoodsId().equals(id))
@@ -268,7 +268,7 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
// 遍历所有goods_id // 遍历所有goods_id
List<Map<String, Object>> series = new ArrayList<>(); List<Map<String, Object>> series = new ArrayList<>();
for (Map.Entry<Long, OrderGoodsPlateRespVO> entry : goodsMap.entrySet()) { for (Map.Entry<String, OrderGoodsPlateRespVO> entry : goodsMap.entrySet()) {
Map<String, Object> seriesItem = new HashMap<>(); Map<String, Object> seriesItem = new HashMap<>();
int[] sumArr = new int[dateList.size()]; int[] sumArr = new int[dateList.size()];
@@ -310,7 +310,7 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
List<String> dateList = getDateList(reqVO); List<String> dateList = getDateList(reqVO);
// 查询时间段内面积最多的小板,列出前10面积的goods_id // 查询时间段内面积最多的小板,列出前10面积的goods_id
List<Long> topTenCountGoodsId = orderPlateStatisticsMapper.selectPlateAreaList(reqVO).stream() List<String> topTenCountGoodsId = orderPlateStatisticsMapper.selectPlateAreaList(reqVO).stream()
.map(OrderGoodsPlateRespVO::getGoodsId) .map(OrderGoodsPlateRespVO::getGoodsId)
.toList(); .toList();
@@ -324,8 +324,8 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
// 获取所有商品{goodsId:respvo},根据top10排序 // 获取所有商品{goodsId:respvo},根据top10排序
Map<Long, OrderGoodsPlateRespVO> goodsMap = new LinkedHashMap<>(); Map<String, OrderGoodsPlateRespVO> goodsMap = new LinkedHashMap<>();
for (Long id : topTenCountGoodsId) { for (String id : topTenCountGoodsId) {
List<OrderGoodsPlateRespVO> list = orderGoodsPlateRespMap.values().stream() List<OrderGoodsPlateRespVO> list = orderGoodsPlateRespMap.values().stream()
.flatMap(List::stream) .flatMap(List::stream)
.filter(vo -> vo.getGoodsId().equals(id)) .filter(vo -> vo.getGoodsId().equals(id))
@@ -338,7 +338,7 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
// 遍历所有goods_id // 遍历所有goods_id
List<Map<String, Object>> series = new ArrayList<>(); List<Map<String, Object>> series = new ArrayList<>();
for (Map.Entry<Long, OrderGoodsPlateRespVO> entry : goodsMap.entrySet()) { for (Map.Entry<String, OrderGoodsPlateRespVO> entry : goodsMap.entrySet()) {
Map<String, Object> seriesItem = new HashMap<>(); Map<String, Object> seriesItem = new HashMap<>();
BigDecimal[] areaArr = new BigDecimal[dateList.size()]; BigDecimal[] areaArr = new BigDecimal[dateList.size()];
@@ -402,13 +402,13 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
Map<String, Aggregate> goodsCountAggMap = response.aggregations(); Map<String, Aggregate> goodsCountAggMap = response.aggregations();
Aggregate goodsCountAgg = goodsCountAggMap.get(ORDER_REMAIN_PLATE_MODEL_GOODS_GROUP_NAME); Aggregate goodsCountAgg = goodsCountAggMap.get(ORDER_REMAIN_PLATE_MODEL_GOODS_GROUP_NAME);
List<Long> goodsIds = new ArrayList<>(); List<String> goodsIds = new ArrayList<>();
// 构建map{日期:{goodsId、count}} // 构建map{日期:{goodsId、count}}
Map<String, List<OrderGoodsPlateRespVO>> goodsDateGroupMap = new HashMap<>(); Map<String, List<OrderGoodsPlateRespVO>> goodsDateGroupMap = new HashMap<>();
/** /**
* 获取数量最大前十的goodsId列表 * 获取数量最大前十的goodsId列表
*/ */
goodsCountAgg.lterms().buckets().array().forEach(e -> goodsIds.add(e.key())); goodsCountAgg.sterms().buckets().array().forEach(e -> goodsIds.add(e.key().stringValue()));
if (CollUtil.isNotEmpty(goodsIds)) { if (CollUtil.isNotEmpty(goodsIds)) {
List<FieldValue> goodsIdFieldValueList = goodsIds.stream().map(FieldValue::of).toList(); List<FieldValue> goodsIdFieldValueList = goodsIds.stream().map(FieldValue::of).toList();
@@ -433,12 +433,11 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
Aggregate goodsDateAgg = goodsDateCountAggMap.get(ORDER_REMAIN_PLATE_MODEL_GOODS_GROUP_NAME); Aggregate goodsDateAgg = goodsDateCountAggMap.get(ORDER_REMAIN_PLATE_MODEL_GOODS_GROUP_NAME);
goodsDateAgg.lterms().buckets().array().forEach(goodsDateAggBucket -> { goodsDateAgg.sterms().buckets().array().forEach(goodsDateAggBucket -> {
Long goodsId = goodsDateAggBucket.key();
// todo 目前没有发现好的es统计月周的方式,先把年月日查出来转周,后续优化 // todo 目前没有发现好的es统计月周的方式,先把年月日查出来转周,后续优化
String dateStr = dateToWeekMonth(goodsDateAggBucket.aggregations().get("date_group").dateHistogram().buckets().array().get(0).keyAsString(), unit); String dateStr = dateToWeekMonth(goodsDateAggBucket.aggregations().get("date_group").dateHistogram().buckets().array().get(0).keyAsString(), unit);
double goodsCount = goodsDateAggBucket.aggregations().get(ORDER_REMAIN_PLATE_MODEL_GOODS_COUNT_NAME).sum().value(); double goodsCount = goodsDateAggBucket.aggregations().get(ORDER_REMAIN_PLATE_MODEL_GOODS_COUNT_NAME).sum().value();
OrderGoodsPlateRespVO vo = OrderGoodsPlateRespVO.builder().goodsId(goodsId).orderDate(dateStr).orderCount((int) goodsCount).build(); OrderGoodsPlateRespVO vo = OrderGoodsPlateRespVO.builder().goodsId(goodsDateAggBucket.key().stringValue()).orderDate(dateStr).orderCount((int) goodsCount).build();
List<OrderGoodsPlateRespVO> orderGoodsPlateRespVOS = goodsDateGroupMap.get(dateStr); List<OrderGoodsPlateRespVO> orderGoodsPlateRespVOS = goodsDateGroupMap.get(dateStr);
if (ObjectUtil.isNotNull(orderGoodsPlateRespVOS)) { if (ObjectUtil.isNotNull(orderGoodsPlateRespVOS)) {
orderGoodsPlateRespVOS.add(vo); orderGoodsPlateRespVOS.add(vo);
@@ -452,7 +451,7 @@ public class OrderStatisticsServiceImpl implements OrderStatisticsService {
// 遍历所有goods_id // 遍历所有goods_id
List<Map<String, Object>> series = new ArrayList<>(); List<Map<String, Object>> series = new ArrayList<>();
for (Long goodsId : goodsIds) { for (String goodsId : goodsIds) {
Map<String, Object> seriesItem = new HashMap<>(); Map<String, Object> seriesItem = new HashMap<>();
int[] countArr = new int[dateList.size()]; int[] countArr = new int[dateList.size()];
@@ -1,6 +1,8 @@
package com.cf.imes.module.system.dal.mysql.permission; package com.cf.imes.module.system.dal.mysql.permission;
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.core.aop.OrganIgnore;
import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO; import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
@@ -45,4 +47,24 @@ public interface UserRoleMapper extends BaseMapperX<UserRoleDO> {
return selectList(UserRoleDO::getRoleId, roleIds); return selectList(UserRoleDO::getRoleId, roleIds);
} }
/**
* 忽视多组织查询角色下的用户id列表
*
* @param roleId
* @return
*/
@OrganIgnore
default List<UserRoleDO> selectRoleUserIds(Long roleId) {
return selectList(new LambdaQueryWrapperX<UserRoleDO>().eq(UserRoleDO::getRoleId, roleId).select(UserRoleDO::getUserId));
}
/**
* 查询角色下的用户id列表
*
* @param roleId
* @return
*/
default List<UserRoleDO> selectOrgRoleUserIds(Long roleId) {
return selectList(new LambdaQueryWrapperX<UserRoleDO>().eq(UserRoleDO::getRoleId, roleId).select(UserRoleDO::getUserId));
}
} }
@@ -7,6 +7,7 @@ import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils; import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.controller.admin.organ.vo.packages.TenantPackagePageReqVO; import com.cf.imes.module.system.controller.admin.organ.vo.packages.TenantPackagePageReqVO;
import com.cf.imes.module.system.controller.admin.organ.vo.packages.TenantPackageSaveReqVO; import com.cf.imes.module.system.controller.admin.organ.vo.packages.TenantPackageSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
@@ -58,7 +59,8 @@ public class TenantPackageServiceImpl implements TenantPackageService {
validateTenantPackageUnique(updateReqVO); validateTenantPackageUnique(updateReqVO);
// 校验存在 // 校验存在
TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId()); TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
if(Objects.equals(tenantPackage.getId(), SYSTEM_ORGAN_PACKAGE_ID) || Objects.equals(tenantPackage.getId(), SYSTEM_SUPER_PACKAGE_ID)) { if (Boolean.FALSE.equals(SecurityFrameworkUtils.isSuperAdmin()) &&
(Objects.equals(tenantPackage.getId(), SYSTEM_ORGAN_PACKAGE_ID) || Objects.equals(tenantPackage.getId(), SYSTEM_SUPER_PACKAGE_ID))) {
throw exception(TENANT_PACKAGE_DEPT_EXIT); throw exception(TENANT_PACKAGE_DEPT_EXIT);
} }
@@ -1,13 +1,13 @@
package com.cf.imes.module.system.service.permission; package com.cf.imes.module.system.service.permission;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil; import cn.hutool.extra.spring.SpringUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.util.collection.CollectionUtils; import com.cf.imes.framework.common.util.collection.CollectionUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.datapermission.core.annotation.DataPermission; import com.cf.imes.framework.datapermission.core.annotation.DataPermission;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.organ.core.aop.OrganIgnore; import com.cf.imes.framework.organ.core.aop.OrganIgnore;
@@ -18,7 +18,7 @@ import com.cf.imes.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import com.cf.imes.module.system.constants.permission.InternalRoleConstants; import com.cf.imes.module.system.constants.permission.InternalRoleConstants;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleUserReqVO; import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleUserReqVO;
import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;
import com.cf.imes.module.system.convert.auth.AuthConvert; import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleDO; import com.cf.imes.module.system.dal.dataobject.permission.RoleDO;
import com.cf.imes.module.system.dal.dataobject.permission.RoleMenuDO; import com.cf.imes.module.system.dal.dataobject.permission.RoleMenuDO;
@@ -32,17 +32,17 @@ import com.cf.imes.module.system.enums.permission.DataScopeEnum;
import com.cf.imes.module.system.service.dept.DeptService; import com.cf.imes.module.system.service.dept.DeptService;
import com.cf.imes.module.system.service.user.AdminUserService; import com.cf.imes.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.cf.imes.module.system.util.organ.OrganUtils;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions; import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -54,10 +54,10 @@ import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet; import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet;
import static com.cf.imes.framework.common.util.json.JsonUtils.toJsonString; import static com.cf.imes.framework.common.util.json.JsonUtils.toJsonString;
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static com.cf.imes.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ROLE_ME_ERROR; import static com.cf.imes.module.system.enums.ErrorCodeConstants.ROLE_ME_ERROR;
/** /**
@@ -83,8 +83,8 @@ public class PermissionServiceImpl implements PermissionService {
@Resource @Resource
private AdminUserService userService; private AdminUserService userService;
@Autowired @Resource
private RedisTemplate redisTemplate; private StringRedisTemplate stringRedisTemplate;
@Override @Override
public boolean hasAnyPermissions(Long userId, String... permissions) { public boolean hasAnyPermissions(Long userId, String... permissions) {
@@ -164,18 +164,15 @@ public class PermissionServiceImpl implements PermissionService {
allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
public void assignRoleMenu(Long roleId, Set<Long> menuIds, Long organId) { public void assignRoleMenu(Long roleId, Set<Long> menuIds, Long organId) {
// 获得角色拥有菜单编号 // 获得角色拥有菜单编号
Set<Long> dbMenuIds = null; Set<Long> dbMenuIds;
boolean isOrganRole = Objects.equals(roleId, InternalRoleConstants.ORGAN_ADMIN_ROLE_ID) || Objects.equals(roleId, InternalRoleConstants.ORGAN_STAFF_ROLE_ID); boolean isOrganRole = OrganUtils.isOrgRole(roleId);
if (isOrganRole) { if (isOrganRole) {
dbMenuIds = convertSet(roleMenuMapper.selectListByRoleIdWithOrganAdmin(List.of(roleId)), RoleMenuDO::getMenuId); dbMenuIds = convertSet(roleMenuMapper.selectListByRoleIdWithOrganAdmin(List.of(roleId)), RoleMenuDO::getMenuId);
}else { }else {
dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
} }
// 查询角色获取角色的组织id,如果没有传入组织id就跟角色的组织统一 // 查询角色获取角色的组织id,如果没有传入组织id就跟角色的组织统一
RoleDO role = roleService.getRole(roleId); RoleDO role = validateRole(roleId);
if (Objects.isNull(role)) {
throw new ServiceException(ErrorCodeConstants.ROLE_NOT_EXISTS);
}
// 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织 // 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织
Long roleOrganId = role.getOrganId(); Long roleOrganId = role.getOrganId();
if (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId)) { if (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId)) {
@@ -206,12 +203,26 @@ public class PermissionServiceImpl implements PermissionService {
roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
} }
// 角色菜单权限发生修改,通知刷新本地缓存
flushRoleMenuCache(roleId, isOrganRole, createMenuIds, deleteMenuIds, finalOrganId);
}
/**
* 跨机构清理角色菜单缓存 + 通知刷新本地缓存
*
* @param roleId 操作的角色id
* @param isOrganRole 是否内置角色
* @param createMenuIds 新增角色-菜单关联id
* @param deleteMenuIds 删除角色-菜单关联id
* @param organId 角色所属机构id
*/
private void flushRoleMenuCache(Long roleId, boolean isOrganRole, Collection<Long> createMenuIds, Collection<Long> deleteMenuIds, Long organId) {
Long currentOrganId = SecurityFrameworkUtils.getUserOrganId(); Long currentOrganId = SecurityFrameworkUtils.getUserOrganId();
// 角色菜单权限发生修改,通知刷新本地缓存 // 角色菜单权限发生修改,通知刷新本地缓存
if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) { if (CollUtil.isNotEmpty(createMenuIds) || CollUtil.isNotEmpty(deleteMenuIds)) {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
// 当前操作人和所操作的目标菜单机构不同时,清空MENU_ROLE_ID_LIST:*:菜单id的缓存 // 当前操作人和所操作的目标菜单机构不同时,清空MENU_ROLE_ID_LIST:*:菜单id的缓存
if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) { if (ObjectUtil.notEqual(organId, currentOrganId)) {
// 获取新增和删除菜单id的并集 // 获取新增和删除菜单id的并集
Collection<Long> delMenuIds = CollUtil.union(createMenuIds, deleteMenuIds); Collection<Long> delMenuIds = CollUtil.union(createMenuIds, deleteMenuIds);
delMenuIds.forEach(delMenuId -> { delMenuIds.forEach(delMenuId -> {
@@ -220,14 +231,50 @@ public class PermissionServiceImpl implements PermissionService {
log.info("[assignRoleMenu] 批量删除redis[{}]数量:{}", patternKey, batchDelNum); log.info("[assignRoleMenu] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
}); });
} }
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); stringRedisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { })
log.error("[assignRoleMenu] redis 清空MENU_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); .thenRunAsync(() -> {
// 清空角色下用户的token缓存
List<Long> userIds;
if(isOrganRole) {
userIds = userRoleMapper.selectRoleUserIds(roleId).stream().map(UserRoleDO::getUserId).toList();
} else {
userIds = userRoleMapper.selectOrgRoleUserIds(roleId).stream().map(UserRoleDO::getUserId).toList();
}
scanAndCompareUserAndDelKeys(String.format(OAUTH2_ACCESS_TOKEN, "*"), userIds);
})
.exceptionally(e -> {
log.error("[assignRoleMenu][flushRoleMenuCache]失败, 异常:{}", e);
return null; return null;
}); });
} }
} }
/**
* scan匹配oauth2_access_token:下缓存并删除缓存
* @param keyPattern
* @param userIds
*/
private void scanAndCompareUserAndDelKeys(String keyPattern, List<Long> userIds) {
// 根据keyPattern scan匹配的redis key
List<String> matchKeys = new ArrayList<>();
Cursor<String> cursor = stringRedisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
while (cursor.hasNext()) {
matchKeys.add(cursor.next());
}
cursor.close();
if (CollUtil.isNotEmpty(matchKeys)) {
for (String key : matchKeys) {
// 获取key下的用户信息
OAuth2AccessTokenDO oAuth2AccessTokenDO = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(key), OAuth2AccessTokenDO.class);
if (CollUtil.contains(userIds, oAuth2AccessTokenDO.getUserId())) {
// 角色下的用户id匹配上了删除redis中的token缓存
stringRedisTemplate.delete(key);
}
}
}
}
/** /**
* scan所有匹配的redis key并删除 * scan所有匹配的redis key并删除
* *
@@ -236,18 +283,32 @@ public class PermissionServiceImpl implements PermissionService {
private Long scanAndDelKeys(String keyPattern) { private Long scanAndDelKeys(String keyPattern) {
// 根据keyPattern scan匹配的redis key // 根据keyPattern scan匹配的redis key
List<String> delKeys = new ArrayList<>(); List<String> delKeys = new ArrayList<>();
Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build()); Cursor<String> cursor = stringRedisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
while (cursor.hasNext()) { while (cursor.hasNext()) {
delKeys.add(cursor.next()); delKeys.add(cursor.next());
} }
cursor.close(); cursor.close();
// 删除匹配到的key // 删除匹配到的key
if (CollectionUtil.isNotEmpty(delKeys)) { if (CollUtil.isNotEmpty(delKeys)) {
return redisTemplate.delete(delKeys); return stringRedisTemplate.delete(delKeys);
} }
return 0L; return 0L;
} }
/**
* 校验角色
*
* @param roleId
*/
private RoleDO validateRole(Long roleId) {
RoleDO role = roleService.getRole(roleId);
if (Objects.isNull(role)) {
throw new ServiceException(ErrorCodeConstants.ROLE_NOT_EXISTS);
}
return role;
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Caching(evict = { @Caching(evict = {
@@ -343,13 +404,10 @@ public class PermissionServiceImpl implements PermissionService {
} }
} }
// 查询角色获取角色的组织id,如果没有传入组织id就跟角色的组织统一 // 查询角色获取角色的组织id,如果没有传入组织id就跟角色的组织统一
RoleDO role = roleService.getRole(roleId); RoleDO role = validateRole(roleId);
if (Objects.isNull(role)) {
throw new ServiceException(ErrorCodeConstants.ROLE_NOT_EXISTS);
}
// 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织 // 前端没传或者查一次后切换了组织导致跟角色组织不一致的统一为角色的组织
Long roleOrganId = role.getOrganId(); Long roleOrganId = role.getOrganId();
boolean isOrganRole = Objects.equals(roleId, InternalRoleConstants.ORGAN_ADMIN_ROLE_ID) || Objects.equals(roleId, InternalRoleConstants.ORGAN_STAFF_ROLE_ID); boolean isOrganRole = OrganUtils.isOrgRole(roleId);
// 内置角色还是按照机构来存 // 内置角色还是按照机构来存
if (!isOrganRole && (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId))) { if (!isOrganRole && (Objects.isNull(organId) || ObjectUtil.notEqual(roleOrganId, organId))) {
organId = roleOrganId; organId = roleOrganId;
@@ -364,7 +422,7 @@ public class PermissionServiceImpl implements PermissionService {
// 筛选要新增的userRole:前端有,数据库没有 // 筛选要新增的userRole:前端有,数据库没有
Collection<Long> createRoleUserIds = CollUtil.subtract(userIds, roleUserIds); Collection<Long> createRoleUserIds = CollUtil.subtract(userIds, roleUserIds);
if (!CollectionUtil.isEmpty(createRoleUserIds)) { if (CollUtil.isNotEmpty(createRoleUserIds)) {
userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleUserIds, userId -> { userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleUserIds, userId -> {
UserRoleDO entity = new UserRoleDO(); UserRoleDO entity = new UserRoleDO();
entity.setUserId(userId).setRoleId(roleId).setOrganId(finalOrganId); entity.setUserId(userId).setRoleId(roleId).setOrganId(finalOrganId);
@@ -374,7 +432,7 @@ public class PermissionServiceImpl implements PermissionService {
// 筛选要删除的userRole:前端没有,数据库有 // 筛选要删除的userRole:前端没有,数据库有
Collection<Long> deleteRoleUserIds = CollUtil.subtract(roleUserIds, userIds); Collection<Long> deleteRoleUserIds = CollUtil.subtract(roleUserIds, userIds);
if (!CollectionUtil.isEmpty(deleteRoleUserIds)) { if (CollUtil.isNotEmpty(deleteRoleUserIds)) {
// 当前用户不能移除自身的角色 // 当前用户不能移除自身的角色
Long incloudSelf = deleteRoleUserIds.stream().filter(userId -> userId.equals(loginUser.getId())).findAny().orElse(null); Long incloudSelf = deleteRoleUserIds.stream().filter(userId -> userId.equals(loginUser.getId())).findAny().orElse(null);
if (incloudSelf != null) { if (incloudSelf != null) {
@@ -382,13 +440,24 @@ public class PermissionServiceImpl implements PermissionService {
} }
userRoleMapper.deleteListByRoleIdAndUserIds(roleId, deleteRoleUserIds); userRoleMapper.deleteListByRoleIdAndUserIds(roleId, deleteRoleUserIds);
} }
// 角色菜单权限发生修改,通知刷新本地缓存
flushRoleUserCache(createRoleUserIds, deleteRoleUserIds, finalOrganId);
}
/**
* 跨机构清理角色用户缓存 + 通知刷新本地缓存
*
* @param createRoleUserIds 新增角色-用户关联id
* @param deleteRoleUserIds 删除角色-用户关联id
* @param organId 角色所属机构
*/
private void flushRoleUserCache(Collection<Long> createRoleUserIds, Collection<Long> deleteRoleUserIds, Long organId) {
Long currentOrganId = SecurityFrameworkUtils.getUserOrganId(); Long currentOrganId = SecurityFrameworkUtils.getUserOrganId();
// 角色菜单权限发生修改,通知刷新本地缓存 // 角色菜单权限发生修改,通知刷新本地缓存
if (CollectionUtil.isNotEmpty(createRoleUserIds) || CollectionUtil.isNotEmpty(deleteRoleUserIds)) { if (CollUtil.isNotEmpty(createRoleUserIds) || CollUtil.isNotEmpty(deleteRoleUserIds)) {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
// 当前操作人和所操作的目标菜单机构不同时,清空USER_ROLE_ID_LIST:*:用户id的缓存 // 当前操作人和所操作的目标菜单机构不同时,清空USER_ROLE_ID_LIST:*:用户id的缓存
if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) { if (ObjectUtil.notEqual(organId, currentOrganId)) {
// 获取新增和删除用户id的并集 // 获取新增和删除用户id的并集
Collection<Long> delRoleUserIds = CollUtil.union(createRoleUserIds, deleteRoleUserIds); Collection<Long> delRoleUserIds = CollUtil.union(createRoleUserIds, deleteRoleUserIds);
delRoleUserIds.forEach(delRoleUserId -> { delRoleUserIds.forEach(delRoleUserId -> {
@@ -397,7 +466,7 @@ public class PermissionServiceImpl implements PermissionService {
log.info("[batchAssignRoleUsers] 批量删除redis[{}]数量:{}", patternKey, batchDelNum); log.info("[batchAssignRoleUsers] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
}); });
} }
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); stringRedisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { }).exceptionally(e -> {
log.error("[batchAssignRoleUsers] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); log.error("[batchAssignRoleUsers] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null; return null;
@@ -461,7 +530,7 @@ public class PermissionServiceImpl implements PermissionService {
Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds); Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);
Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList); Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);
// 执行新增和删除。对于已经授权的角色,不用做任何处理 // 执行新增和删除。对于已经授权的角色,不用做任何处理
if (!CollectionUtil.isEmpty(createRoleIds)) { if (CollUtil.isNotEmpty(createRoleIds)) {
userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> { userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {
UserRoleDO entity = new UserRoleDO(); UserRoleDO entity = new UserRoleDO();
entity.setUserId(userId); entity.setUserId(userId);
@@ -470,16 +539,16 @@ public class PermissionServiceImpl implements PermissionService {
return entity; return entity;
})); }));
} }
if (!CollectionUtil.isEmpty(deleteMenuIds)) { if (CollUtil.isNotEmpty(deleteMenuIds)) {
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds); userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
} }
// 角色菜单权限发生修改,通知刷新本地缓存 // 角色菜单权限发生修改,通知刷新本地缓存
if (CollectionUtil.isNotEmpty(createRoleIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) { if (CollUtil.isNotEmpty(createRoleIds) || CollUtil.isNotEmpty(deleteMenuIds)) {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
// 移除redis中的缓存 // 移除redis中的缓存
redisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId)); stringRedisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId));
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); stringRedisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { }).exceptionally(e -> {
log.error("[assignUserRole] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); log.error("[assignUserRole] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null; return null;
@@ -0,0 +1,24 @@
package com.cf.imes.module.system.util.organ;
import com.cf.imes.module.system.constants.permission.InternalRoleConstants;
import java.util.Objects;
/**
* 组织相关工具类
*
* @author Gqr
* @since 2024/9/10 15:59
*/
public class OrganUtils {
/**
* 是否内置角色
*
* @param roleId
* @return
*/
public static boolean isOrgRole(Long roleId) {
return Objects.equals(roleId, InternalRoleConstants.ORGAN_ADMIN_ROLE_ID) || Objects.equals(roleId, InternalRoleConstants.ORGAN_STAFF_ROLE_ID);
}
}