feat:(REQ-2720) 增加角色权限、产品权限事件和消费缓存

This commit is contained in:
lilong 2024-07-25 10:09:56 +08:00
parent eb73fc41b4
commit e7c6dd2f92
13 changed files with 839 additions and 46 deletions

View File

@ -9,6 +9,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Set;
@Data
@Builder
@ -30,4 +31,13 @@ public class PageProductFeatureRelationReq implements IPageReq {
@CriteriaField(field = "id", operator = Operator.IN)
private List<Long> ids;
/**
* 产品 ID
*/
@CriteriaField(field = "productModuleId", operator = Operator.IN)
private Set<Long> productModuleIds;
@CriteriaField(field = "type", operator = Operator.EQ)
private Integer type;
}

View File

@ -0,0 +1,40 @@
package cn.axzo.tyr.server.config;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventProducer;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author yanglin
*/
@Slf4j
@Component
@RefreshScope
public class MqProducer {
@Autowired
private EventProducer<?> eventProducer;
@Value("${sendMq}")
private Boolean sendMq;
public void send(Event event){
log.info(JSON.toJSONString(event));
if(sendMq != null && !sendMq){
return;
}
//生产消息
eventProducer.send(event);
}
public void sendBatch(List<Event> events){
events.forEach(this::send);
}
}

View File

@ -70,7 +70,25 @@ public class RocketMQEventConfiguration {
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class DefaultListener extends BaseListener implements RocketMQListener<MessageExt> {
public static class ThronesListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;
@Override
public void onMessage(MessageExt message) {
super.onEvent(message, eventConsumer);
}
}
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic_tyr_${spring.profiles.active}",
consumerGroup = "GID_topic_tyr_${spring.application.name}_${spring.profiles.active}",
consumeMode = ConsumeMode.ORDERLY,
nameServer = "${rocketmq.name-server}"
)
public static class TyrListener extends BaseListener implements RocketMQListener<MessageExt> {
@Autowired
private EventConsumer eventConsumer;

View File

@ -0,0 +1,193 @@
package cn.axzo.tyr.server.event.inner;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.tyr.client.model.req.PageProductFeatureRelationReq;
import cn.axzo.tyr.client.model.req.PageSaasFeatureResourceReq;
import cn.axzo.tyr.client.model.res.SaasFeatureResourceResp;
import cn.axzo.tyr.server.event.payload.ProductPermissionCreatedPayload;
import cn.axzo.tyr.server.repository.dao.SaasFeatureDao;
import cn.axzo.tyr.server.repository.entity.SaasFeature;
import cn.axzo.tyr.server.repository.entity.SaasProductModuleFeatureRelation;
import cn.axzo.tyr.server.service.ProductFeatureRelationService;
import cn.axzo.tyr.server.service.ProductPermissionCacheService;
import cn.axzo.tyr.server.service.SaasFeatureResourceService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.repository.entity.SaasPgroupPermissionRelation.NEW_FEATURE;
import static cn.axzo.tyr.server.repository.entity.SaasPgroupPermissionRelation.OLD_FEATURE;
/**
* 缓存产品的权限
*/
@Slf4j
@Component
public class CacheProductPermissionHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private ProductPermissionCacheService productPermissionCacheService;
@Autowired
private ProductFeatureRelationService productFeatureRelationService;
@Autowired
private SaasFeatureResourceService saasFeatureResourceService;
@Autowired
private SaasFeatureDao saasFeatureDao;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
log.info("begin cached product permission handler rocketmq event: {}", event);
ProductPermissionCreatedPayload payload = event.normalizedData(ProductPermissionCreatedPayload.class);
if (CollectionUtils.isEmpty(payload.getProductModuleIds())) {
return;
}
PageProductFeatureRelationReq pageProductFeatureRelationReq = PageProductFeatureRelationReq.builder()
.productModuleIds(payload.getProductModuleIds())
.build();
List<SaasProductModuleFeatureRelation> productFeatures = productFeatureRelationService.list(pageProductFeatureRelationReq);
if (CollectionUtils.isEmpty(productFeatures)) {
return;
}
List<ProductPermissionCacheService.ProductPermission> productPermissions = resolveProductPermissions(productFeatures);
if (CollectionUtils.isEmpty(productPermissions)) {
return;
}
ProductPermissionCacheService.StoreProductPermissionParam storeProductPermissionParam = ProductPermissionCacheService.StoreProductPermissionParam.builder()
.productPermissions(productPermissions)
.build();
productPermissionCacheService.store(storeProductPermissionParam);
log.info("end cached product permission handler rocketmq event: {}", event);
}
@Override
public void afterPropertiesSet() throws Exception {
eventConsumer.registerHandler(EventTypeEnum.PRODUCT_PERMISSION_CREATED.getEventCode(), this);
}
public List<ProductPermissionCacheService.ProductPermission> resolveProductPermissions(List<SaasProductModuleFeatureRelation> productPermissions) {
if (CollectionUtils.isEmpty(productPermissions)) {
return Collections.emptyList();
}
Map<Long, SaasFeatureResourceResp> featureResources = listSaasFeatureResource(productPermissions);
Map<Long, SaasFeature> saasFeatures = listSaasFeature(productPermissions);
return productPermissions.stream()
.collect(Collectors.groupingBy(SaasProductModuleFeatureRelation::getProductModuleId))
.entrySet()
.stream()
.map(e -> {
List<SaasProductModuleFeatureRelation> productFeatureRelations = e.getValue();
if (CollectionUtils.isEmpty(productFeatureRelations)) {
return null;
}
List<ProductPermissionCacheService.PermissionDTO> permissions = productFeatureRelations.stream()
.map(relation -> {
if (Objects.equals(relation.getType(), NEW_FEATURE)) {
SaasFeatureResourceResp featureResource = featureResources.get(relation.getFeatureId());
if (Objects.isNull(featureResource) || CollectionUtils.isEmpty(featureResource.getFeatureCodes())) {
return null;
}
return featureResource.getFeatureCodes().stream()
.map(featureCode -> ProductPermissionCacheService.PermissionDTO.builder()
.featureId(featureResource.getId())
.featureCode(featureCode)
.featureType(featureResource.getFeatureType())
.terminal(featureResource.getTerminal())
.cooperateType(relation.getDictCode())
.build())
.collect(Collectors.toList());
}
SaasFeature saasFeature = saasFeatures.get(relation.getFeatureId());
if (Objects.isNull(saasFeature)) {
return null;
}
return Lists.newArrayList(ProductPermissionCacheService.PermissionDTO.builder()
.featureId(saasFeature.getId())
.featureCode(saasFeature.getFeatureCode())
.featureType(saasFeature.getFeatureType())
.terminal(saasFeature.getTerminal())
.cooperateType(relation.getDictCode())
.build());
})
.flatMap(Collection::stream)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(permissions)) {
return null;
}
return ProductPermissionCacheService.ProductPermission.builder()
.productId(e.getKey())
.permissions(permissions)
.build();
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private Map<Long, SaasFeatureResourceResp> listSaasFeatureResource(List<SaasProductModuleFeatureRelation> productPermissions) {
List<Long> featureIds = productPermissions.stream()
.filter(e -> Objects.equals(e.getType(), NEW_FEATURE))
.map(SaasProductModuleFeatureRelation::getFeatureId)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(featureIds)) {
return Collections.emptyMap();
}
PageSaasFeatureResourceReq pageSaasFeatureResourceReq = PageSaasFeatureResourceReq.builder()
.ids(featureIds)
.needFeatureCodes(true)
.build();
return saasFeatureResourceService.list(pageSaasFeatureResourceReq).stream()
.collect(Collectors.toMap(SaasFeatureResourceResp::getId, Function.identity()));
}
private Map<Long, SaasFeature> listSaasFeature(List<SaasProductModuleFeatureRelation> productPermissions) {
List<Long> featureIds = productPermissions.stream()
.filter(e -> Objects.equals(e.getType(), OLD_FEATURE))
.map(SaasProductModuleFeatureRelation::getFeatureId)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(featureIds)) {
return Collections.emptyMap();
}
return saasFeatureDao.listByIds(featureIds).stream()
.collect(Collectors.toMap(SaasFeature::getId, Function.identity()));
}
}

View File

@ -0,0 +1,189 @@
package cn.axzo.tyr.server.event.inner;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.tyr.client.model.req.PageSaasFeatureResourceReq;
import cn.axzo.tyr.client.model.res.SaasFeatureResourceResp;
import cn.axzo.tyr.client.model.res.SaasPermissionRelationRes;
import cn.axzo.tyr.client.model.res.SaasRoleRes;
import cn.axzo.tyr.server.event.payload.RolePermissionCreatedPayload;
import cn.axzo.tyr.server.repository.dao.SaasFeatureDao;
import cn.axzo.tyr.server.repository.entity.SaasFeature;
import cn.axzo.tyr.server.service.RolePermissionCacheService;
import cn.axzo.tyr.server.service.RoleService;
import cn.axzo.tyr.server.service.SaasFeatureResourceService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.repository.entity.SaasPgroupPermissionRelation.NEW_FEATURE;
import static cn.axzo.tyr.server.repository.entity.SaasPgroupPermissionRelation.OLD_FEATURE;
/**
* 缓存角色的权限
*/
@Slf4j
@Component
public class CacheRolePermissionHandler implements EventHandler, InitializingBean {
@Autowired
private EventConsumer eventConsumer;
@Autowired
private RolePermissionCacheService rolePermissionCacheService;
@Autowired
private RoleService roleService;
@Autowired
private SaasFeatureResourceService saasFeatureResourceService;
@Autowired
private SaasFeatureDao saasFeatureDao;
@Override
public void onEvent(Event event, EventConsumer.Context context) {
log.info("begin cached role permission handler rocketmq event: {}", event);
RolePermissionCreatedPayload payload = event.normalizedData(RolePermissionCreatedPayload.class);
if (CollectionUtils.isEmpty(payload.getRoleIds())) {
return;
}
RoleService.ListSaasRoleParam listSaasRoleParam = RoleService.ListSaasRoleParam.builder()
.roleIds(Lists.newArrayList(payload.getRoleIds()))
.needPermissionRelation(true)
.build();
List<SaasRoleRes> roles = roleService.list(listSaasRoleParam);
if (CollectionUtils.isEmpty(roles)) {
return;
}
List<RolePermissionCacheService.RolePermission> rolePermissions = resolveRolePermission(roles);
if (CollectionUtils.isEmpty(rolePermissions)) {
return;
}
RolePermissionCacheService.StoreRolePermissionParam storeRolePermissionParam = RolePermissionCacheService.StoreRolePermissionParam.builder()
.rolePermissions(rolePermissions)
.build();
rolePermissionCacheService.store(storeRolePermissionParam);
log.info("end cached role permission handler rocketmq event: {}", event);
}
@Override
public void afterPropertiesSet() throws Exception {
eventConsumer.registerHandler(EventTypeEnum.ROLE_PERMISSION_CREATED.getEventCode(), this);
}
public List<RolePermissionCacheService.RolePermission> resolveRolePermission(List<SaasRoleRes> roles) {
Map<Long, SaasFeatureResourceResp> featureResources = listSaasFeatureResource(roles);
Map<Long, SaasFeature> saasFeatures = listSaasFeature(roles);
return roles.stream()
.map(e -> {
if (CollectionUtils.isEmpty(e.getPermissionRelations())) {
return null;
}
List<RolePermissionCacheService.PermissionDTO> permissions = e.getPermissionRelations().stream()
.map(permissionRelation -> {
if (Objects.equals(permissionRelation.getType(), NEW_FEATURE)) {
SaasFeatureResourceResp featureResource = featureResources.get(permissionRelation.getFeatureId());
if (Objects.isNull(featureResource) || CollectionUtils.isEmpty(featureResource.getFeatureCodes())) {
return null;
}
return featureResource.getFeatureCodes().stream()
.map(featureCode -> RolePermissionCacheService.PermissionDTO.builder()
.featureId(featureResource.getId())
.featureCode(featureCode)
.featureType(featureResource.getFeatureType())
.terminal(featureResource.getTerminal())
.build())
.collect(Collectors.toList());
}
SaasFeature saasFeature = saasFeatures.get(permissionRelation.getFeatureId());
if (Objects.isNull(saasFeature)) {
return null;
}
return Lists.newArrayList(RolePermissionCacheService.PermissionDTO.builder()
.featureId(saasFeature.getId())
.featureCode(saasFeature.getFeatureCode())
.featureType(saasFeature.getFeatureType())
.terminal(saasFeature.getTerminal())
.build());
})
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(permissions)) {
return null;
}
return RolePermissionCacheService.RolePermission.builder()
.roleId(e.getId())
.permissions(permissions)
.build();
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private Map<Long, SaasFeatureResourceResp> listSaasFeatureResource(List<SaasRoleRes> roles) {
List<Long> featureIds = roles.stream()
.filter(e -> !CollectionUtils.isEmpty(e.getPermissionRelations()))
.map(SaasRoleRes::getPermissionRelations)
.flatMap(Collection::stream)
.filter(e -> Objects.equals(e.getType(), NEW_FEATURE))
.map(SaasPermissionRelationRes::getFeatureId)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(featureIds)) {
return Collections.emptyMap();
}
PageSaasFeatureResourceReq pageSaasFeatureResourceReq = PageSaasFeatureResourceReq.builder()
.ids(featureIds)
.needFeatureCodes(true)
.build();
return saasFeatureResourceService.list(pageSaasFeatureResourceReq).stream()
.collect(Collectors.toMap(SaasFeatureResourceResp::getId, Function.identity()));
}
private Map<Long, SaasFeature> listSaasFeature(List<SaasRoleRes> roles) {
List<Long> featureIds = roles.stream()
.filter(e -> !CollectionUtils.isEmpty(e.getPermissionRelations()))
.map(SaasRoleRes::getPermissionRelations)
.flatMap(Collection::stream)
.filter(e -> Objects.equals(e.getType(), OLD_FEATURE))
.map(SaasPermissionRelationRes::getFeatureId)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(featureIds)) {
return Collections.emptyMap();
}
return saasFeatureDao.listByIds(featureIds).stream()
.collect(Collectors.toMap(SaasFeature::getId, Function.identity()));
}
}

View File

@ -0,0 +1,27 @@
package cn.axzo.tyr.server.event.inner;
import cn.axzo.framework.rocketmq.Event;
import lombok.Getter;
@Getter
public enum EventTypeEnum {
PRODUCT_PERMISSION_CREATED("product-permission", "product-permission-created", "产品权限添加"),
ROLE_PERMISSION_CREATED("role-permission", "role-permission-created", "角色权限添加"),
;
EventTypeEnum(String model, String name, String desc) {
this.eventCode = Event.EventCode.builder()
.module(model)
.name(name)
.build();
this.model = model;
this.name = name;
this.desc = desc;
}
private String model;
private String name;
private String desc;
private Event.EventCode eventCode;
}

View File

@ -0,0 +1,21 @@
package cn.axzo.tyr.server.event.payload;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Set;
/**
* 产品权限点做了更新没有发送相关权限点消息是因为日志太大只能消费的时候实时查询
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProductPermissionCreatedPayload implements Serializable {
private Set<Long> productModuleIds;
}

View File

@ -0,0 +1,23 @@
package cn.axzo.tyr.server.event.payload;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Set;
/**
* 角色权限点做了更新没有发送相关权限点消息是因为日志太大只能消费的时候实时查询
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RolePermissionCreatedPayload implements Serializable {
private Set<Long> roleIds;
}

View File

@ -6,14 +6,29 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ProductPermissionCacheService {
List<PermissionDTO> list(ListProductPermissionParam param);
/**
* 查询产品的权限信息
* @param param
* @return
*/
Map<Long, List<PermissionDTO>> list(ListProductPermissionParam param);
/**
* 存储产品的权限信息
* @param param
*/
void store(StoreProductPermissionParam param);
/**
* 产品是否有缓存权限
* @param param
* @return
*/
List<Long> hasProductIds(HasProductPermissionParam param);
@Data
@ -47,7 +62,9 @@ public interface ProductPermissionCacheService {
@NoArgsConstructor
@AllArgsConstructor
class ListProductPermissionParam {
private List<Long> productIds;
private Set<Long> productIds;
private Set<String> featureCodes;
}
@Data
@ -57,12 +74,17 @@ public interface ProductPermissionCacheService {
class PermissionDTO {
/**
* 产品关联的字典 Code 原值
* 协同关系类型
* 原saas_product_module_feature_relation.dictCode
*/
private String dictCode;
private String cooperateType;
private Long featureId;
private String featureCode;
private String terminal;
private Integer featureType;
}
}

View File

@ -6,12 +6,13 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface RolePermissionCacheService {
List<PermissionDTO> list(ListRolePermissionParam param);
Map<Long, List<PermissionDTO>> list(ListRolePermissionParam param);
/**
* redisKey:roleId
@ -53,7 +54,9 @@ public interface RolePermissionCacheService {
@NoArgsConstructor
@AllArgsConstructor
class ListRolePermissionParam {
private List<Long> roleIds;
private Set<Long> roleIds;
private Set<String> featureCodes;
}
@Data
@ -80,5 +83,7 @@ public interface RolePermissionCacheService {
* 资源所属端
*/
private String terminal;
private Integer featureType;
}
}

View File

@ -5,6 +5,7 @@ import cn.axzo.foundation.dao.support.converter.PageConverter;
import cn.axzo.foundation.dao.support.mysql.QueryWrapperHelper;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.framework.rocketmq.Event;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import cn.axzo.thrones.client.saas.ServicePkgClient;
import cn.axzo.thrones.client.saas.entity.serivicepgkproduct.ServicePkgProduct;
@ -15,6 +16,8 @@ import cn.axzo.tyr.client.model.product.ProductFeatureRelationUpdateReq;
import cn.axzo.tyr.client.model.product.ProductFeatureRelationVO;
import cn.axzo.tyr.client.model.req.FeatureIdPair;
import cn.axzo.tyr.client.model.req.PageProductFeatureRelationReq;
import cn.axzo.tyr.server.config.MqProducer;
import cn.axzo.tyr.server.event.payload.ProductPermissionCreatedPayload;
import cn.axzo.tyr.server.repository.dao.ProductModuleDao;
import cn.axzo.tyr.server.repository.dao.SaasFeatureDao;
import cn.axzo.tyr.server.repository.dao.SaasFeatureResourceDao;
@ -50,6 +53,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.axzo.tyr.server.event.inner.EventTypeEnum.PRODUCT_PERMISSION_CREATED;
import static cn.axzo.tyr.server.util.RpcInternalUtil.checkAndGetData;
/**
@ -68,6 +72,9 @@ public class ProductFeatureRelationServiceImpl extends ServiceImpl<SaasProductMo
private final SaasFeatureDao saasFeatureDao;
private final ProductModuleDao productModuleDao;
private final SaasFeatureResourceDao saasFeatureResourceDao;
private final MqProducer mqProducer;
private static final String TARGET_TYPE = "productModuleId";
@Override
public ApiResult<List<ProductFeatureRelationVO>> featureList(ProductFeatureRelationSearchReq req) {
@ -115,6 +122,8 @@ public class ProductFeatureRelationServiceImpl extends ServiceImpl<SaasProductMo
.filter(CollectionUtil::isNotEmpty).flatMap(List::stream).collect(Collectors.toSet());
if (CollectionUtil.isEmpty(allFeatureResourceIds)) {
log.warn("product has no featureResourceIds to bind.");
// 产品的权限点可能会被清空也需要发送mq
sendProductPermissionCreatedMsg(req);
return;
}
Map<Long, SaasFeatureResource> resourceMap = saasFeatureResourceDao.lambdaQuery()
@ -137,6 +146,27 @@ public class ProductFeatureRelationServiceImpl extends ServiceImpl<SaasProductMo
saveList.add(relation);
}));
saasProductModuleFeatureRelationDao.saveBatch(saveList);
sendProductPermissionCreatedMsg(req);
}
private void sendProductPermissionCreatedMsg(List<ProductFeatureRelationUpdateReq> req) {
if (CollectionUtils.isEmpty(req)) {
return;
}
Set<Long> productModuleIds = req.stream()
.map(ProductFeatureRelationUpdateReq::getProductModuleId)
.collect(Collectors.toSet());
Event event = Event.builder()
.targetType(TARGET_TYPE)
.eventCode(PRODUCT_PERMISSION_CREATED.getEventCode())
.data(ProductPermissionCreatedPayload.builder()
.productModuleIds(productModuleIds)
.build())
.build();
mqProducer.send(event);
}
@Override
@ -326,7 +356,7 @@ public class ProductFeatureRelationServiceImpl extends ServiceImpl<SaasProductMo
public List<SaasProductModuleFeatureRelation> list(PageProductFeatureRelationReq param) {
return PageConverter.drainAll(pageNumber -> {
param.setPage(pageNumber);
param.setPageSize(500);
param.setPageSize(1000);
return page(param);
});
}

View File

@ -2,25 +2,36 @@ package cn.axzo.tyr.server.service.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.client.model.req.PageProductFeatureRelationReq;
import cn.axzo.tyr.server.event.inner.CacheProductPermissionHandler;
import cn.axzo.tyr.server.repository.entity.SaasProductModuleFeatureRelation;
import cn.axzo.tyr.server.service.ProductFeatureRelationService;
import cn.axzo.tyr.server.service.ProductPermissionCacheService;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -33,24 +44,75 @@ public class ProductPermissionCacheServiceImpl implements ProductPermissionCache
private static final String PRODUCT_PERMISSION_KEY = "product:permission:%s";
@Autowired
protected StringRedisTemplate redisTemplate;
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ProductFeatureRelationService productFeatureRelationService;
@Autowired
private CacheProductPermissionHandler cacheProductPermissionHandler;
/** 产品权限缓存过期时间 **/
@Value("${product.permission.expire:180}")
private Long expireInDays;
@Override
public List<PermissionDTO> list(ListProductPermissionParam param) {
public Map<Long, List<PermissionDTO>> list(ListProductPermissionParam param) {
if (CollectionUtils.isEmpty(param.getProductIds())) {
return Collections.emptyList();
return Collections.emptyMap();
}
Map<Long, List<PermissionDTO>> permissionCached = listProductPermissionCached(param);
Set<String> redisKeys = param.getProductIds().stream()
.map(this::getKey)
.collect(Collectors.toSet());
fillCacheProductPermissions(param, permissionCached);
Set<String> redisValues = redisTemplate.opsForSet().union(redisKeys);
return permissionCached;
}
return redisValues.stream()
.filter(StringUtils::isNotBlank)
.map(value -> JSONObject.parseObject(value, PermissionDTO.class))
.collect(Collectors.toList());
private Map<Long, List<PermissionDTO>> listProductPermissionCached(ListProductPermissionParam param) {
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Long productId : param.getProductIds()) {
String redisKey = getKey(productId);
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
RedisClient.HashOps.hGetAll(redisKey);
} else {
RedisClient.HashOps.hMultiGet(redisKey, Lists.newArrayList(param.getFeatureCodes()));
}
}
return null;
}
});
return Streams.zip(param.getProductIds().stream(),
redisValues.stream(),
(productId, redisValue) -> {
if (Objects.isNull(redisValue)) {
return null;
}
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
List<PermissionDTO> permissions = (List<PermissionDTO>) ((Map) redisValue).values().stream()
.flatMap(e -> JSONArray.parseArray(JSONArray.toJSONString(e), PermissionDTO.class).stream())
.collect(Collectors.toList());
return Pair.of(productId, permissions);
}
List<PermissionDTO> permissions = (List<PermissionDTO>) ((List) redisValue).stream()
.filter(Objects::nonNull)
.flatMap(e -> JSONArray.parseArray((String) e, PermissionDTO.class).stream())
.collect(Collectors.toList());
return Pair.of(productId, permissions);
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}
@Override
@ -65,12 +127,14 @@ public class ProductPermissionCacheServiceImpl implements ProductPermissionCache
for (ProductPermission productPermission : param.getProductPermissions()) {
String redisKey = getKey(productPermission.getProductId());
String[] redisValues = productPermission.getPermissions().stream()
.map(JSONObject::toJSONString)
.toArray(String[]::new);
Map<String, String> redisValues = productPermission.getPermissions().stream()
.collect(Collectors.groupingBy(PermissionDTO::getFeatureCode))
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> JSONObject.toJSONString(e.getValue())));
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
RedisClient.HashOps.hPutAll(redisKey, redisValues);
redisTemplate.expire(redisKey, expireInDays, TimeUnit.DAYS);
log.info("succeed to store product permission: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
@ -100,7 +164,61 @@ public class ProductPermissionCacheServiceImpl implements ProductPermissionCache
.collect(Collectors.toList());
}
/**
* 组装在缓存中没有查询到权限的产品
* @param param
* @param permissionCached
*/
private void fillCacheProductPermissions(ListProductPermissionParam param,
Map<Long, List<PermissionDTO>> permissionCached) {
Sets.SetView<Long> difference = Sets.difference(param.getProductIds(), permissionCached.keySet());
if (difference.isEmpty()) {
return;
}
PageProductFeatureRelationReq pageProductFeatureRelationReq = PageProductFeatureRelationReq.builder()
.productModuleIds(difference)
.build();
List<SaasProductModuleFeatureRelation> productPermissions = productFeatureRelationService.list(pageProductFeatureRelationReq);
List<ProductPermission> productPermissionsCache = cacheProductPermissionHandler.resolveProductPermissions(productPermissions);
if (CollectionUtils.isEmpty(productPermissionsCache)) return;
StoreProductPermissionParam storeWorkspaceProductParam = StoreProductPermissionParam.builder()
.productPermissions(productPermissionsCache)
.build();
store(storeWorkspaceProductParam);
Map<Long, List<PermissionDTO>> productPermissionMap = productPermissionsCache.stream()
.collect(Collectors.toMap(ProductPermission::getProductId, ProductPermission::getPermissions));
permissionCached.putAll(productPermissionMap);
}
private String getKey(Object... params) {
return String.format(PRODUCT_PERMISSION_KEY, params);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
static class PermissionWrapper {
/**
* 协同关系类型
* 原saas_product_module_feature_relation.dictCode
*/
private String cooperateType;
private Long featureId;
private String featureCode;
private String terminal;
private Integer featureType;
private Long productId;
}
}

View File

@ -2,14 +2,20 @@ package cn.axzo.tyr.server.service.impl;
import cn.axzo.foundation.exception.Axssert;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.client.model.res.SaasRoleRes;
import cn.axzo.tyr.server.event.inner.CacheRolePermissionHandler;
import cn.axzo.tyr.server.service.RolePermissionCacheService;
import cn.axzo.tyr.server.service.RoleService;
import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
@ -20,8 +26,8 @@ import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -35,23 +41,73 @@ public class RolePermissionCacheServiceImpl implements RolePermissionCacheServic
@Autowired
protected StringRedisTemplate redisTemplate;
@Autowired
private RoleService roleService;
@Autowired
private CacheRolePermissionHandler cacheRolePermissionHandler;
/** 角色权限缓存过期时间 **/
@Value("${role.permission.expire:180}")
private Long expireInDays;
@Override
public List<PermissionDTO> list(ListRolePermissionParam param) {
public Map<Long, List<PermissionDTO>> list(ListRolePermissionParam param) {
if (CollectionUtils.isEmpty(param.getRoleIds())) {
return Collections.emptyList();
return Collections.emptyMap();
}
Set<String> redisKeys = param.getRoleIds().stream()
.map(this::getKey)
.collect(Collectors.toSet());
Map<Long, List<PermissionDTO>> permissions = listRolePermissionCached(param);
// 聚合是因为角色的权限点有重复网络io压力大
Set<String> redisValues = redisTemplate.opsForSet().union(redisKeys);
return redisValues.stream()
.filter(StringUtils::isNotBlank)
.map(value -> JSONObject.parseObject(value, PermissionDTO.class))
.collect(Collectors.toList());
fillCacheRolePermissions(param, permissions);
return permissions;
}
private Map<Long, List<PermissionDTO>> listRolePermissionCached(ListRolePermissionParam param) {
List<Object> redisValues = redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
for (Long roleId : param.getRoleIds()) {
String redisKey = getKey(roleId);
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
RedisClient.HashOps.hGetAll(redisKey);
} else {
RedisClient.HashOps.hMultiGet(redisKey, Lists.newArrayList(param.getFeatureCodes()));
}
}
return null;
}
});
return Streams.zip(param.getRoleIds().stream(),
redisValues.stream(),
(roleId, redisValue) -> {
if (Objects.isNull(redisValue)) {
return null;
}
if (CollectionUtils.isEmpty(param.getFeatureCodes())) {
List<PermissionDTO> permissions = (List<PermissionDTO>) ((Map) redisValue).values().stream()
.flatMap(e -> JSONArray.parseArray(JSONArray.toJSONString(e), PermissionDTO.class).stream())
.collect(Collectors.toList());
return Pair.of(roleId, permissions);
}
List<PermissionDTO> permissions = (List<PermissionDTO>) ((List) redisValue).stream()
.filter(Objects::nonNull)
.flatMap(e -> JSONArray.parseArray((String) e, PermissionDTO.class).stream())
.collect(Collectors.toList());
return Pair.of(roleId, permissions);
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}
@Override
@ -66,12 +122,14 @@ public class RolePermissionCacheServiceImpl implements RolePermissionCacheServic
for (RolePermission rolePermission : param.getRolePermissions()) {
String redisKey = getKey(rolePermission.getRoleId());
String[] redisValues = rolePermission.getPermissions().stream()
.map(JSONObject::toJSONString)
.toArray(String[]::new);
Map<String, String> redisValues = rolePermission.getPermissions().stream()
.collect(Collectors.groupingBy(PermissionDTO::getFeatureCode))
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> JSONObject.toJSONString(e.getValue())));
RedisClient.SetOps.sAdd(redisKey, redisValues);
redisTemplate.expire(redisKey, 4, TimeUnit.DAYS);
RedisClient.HashOps.hPutAll(redisKey, redisValues);
redisTemplate.expire(redisKey, expireInDays, TimeUnit.DAYS);
log.info("succeed to store role permission: redisKey:{} value:{}", redisKey, redisValues);
}
return null;
@ -100,6 +158,45 @@ public class RolePermissionCacheServiceImpl implements RolePermissionCacheServic
.collect(Collectors.toList());
}
/**
* 组装在缓存中没有查询到权限的角色权限
* @param param
* @param permissionCached
*/
private void fillCacheRolePermissions(ListRolePermissionParam param,
Map<Long, List<PermissionDTO>> permissionCached) {
Sets.SetView<Long> difference = Sets.difference(param.getRoleIds(), permissionCached.keySet());
if (difference.isEmpty()) {
return;
}
RoleService.ListSaasRoleParam listSaasRoleParam = RoleService.ListSaasRoleParam.builder()
.roleIds(Lists.newArrayList(difference))
.needPermissionRelation(true)
.build();
List<SaasRoleRes> roles = roleService.list(listSaasRoleParam);
if (CollectionUtils.isEmpty(roles)) {
return;
}
List<RolePermission> rolePermissions = cacheRolePermissionHandler.resolveRolePermission(roles);
if (CollectionUtils.isEmpty(rolePermissions)) {
return;
}
StoreRolePermissionParam storeRolePermissionParam = StoreRolePermissionParam.builder()
.rolePermissions(rolePermissions)
.build();
store(storeRolePermissionParam);
Map<Long, List<PermissionDTO>> rolePermissionMap = rolePermissions.stream()
.collect(Collectors.toMap(RolePermission::getRoleId, RolePermission::getPermissions));
permissionCached.putAll(rolePermissionMap);
}
private String getKey(Object... params) {
return String.format(ROLE_PERMISSION_KEY, params);
}