From 54db38763911835b7d8321c576758fb830fc0cdf Mon Sep 17 00:00:00 2001 From: lihongjie0209 Date: Sat, 27 Apr 2024 10:14:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 +- .../cacheCtl/service/CacheCtlService.java | 80 +++++-- .../cn/lihongjie/coal/common/Constants.java | 9 + .../cn/lihongjie/coal/filter/AuthFilter.java | 22 +- .../cn/lihongjie/coal/filter/CacheFilter.java | 2 +- .../loginUser/service/LoginUserService.java | 13 +- .../entity/OrganizationEntity.java | 2 + .../entity/OrganizationEntityDto.java | 28 +++ .../service/OrganizationService.java | 80 +++++-- .../coal/permission/dto/PermissionDto.java | 15 +- .../permission/dto/PermissionExportDto.java | 2 +- .../repository/PermissionRepository.java | 2 +- .../permission/service/PermissionService.java | 63 +++--- .../coal/rabbitmq/RabbitMQService.java | 63 +++++- .../coal/resource/mapper/ResourceMapper.java | 1 + .../resource/service/ResourceService.java | 95 +++++---- .../coal/session/service/SessionService.java | 14 ++ .../coal/spring/config/CacheConfig.java | 62 +++--- .../coal/spring/config/MultiLevelCache.java | 200 ++++++++++++++++++ .../spring/config/MultiLevelCacheManager.java | 106 ++++++++++ .../coal/user/service/UserService.java | 146 +++++++++++-- 21 files changed, 805 insertions(+), 205 deletions(-) create mode 100644 src/main/java/cn/lihongjie/coal/organization/entity/OrganizationEntityDto.java create mode 100644 src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCache.java create mode 100644 src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCacheManager.java diff --git a/pom.xml b/pom.xml index f3245c83..729fc5a1 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,10 @@ - + + com.github.ben-manes.caffeine + caffeine + org.apache.poi poi diff --git a/src/main/java/cn/lihongjie/coal/cacheCtl/service/CacheCtlService.java b/src/main/java/cn/lihongjie/coal/cacheCtl/service/CacheCtlService.java index 1cfffa5f..facb0e30 100644 --- a/src/main/java/cn/lihongjie/coal/cacheCtl/service/CacheCtlService.java +++ b/src/main/java/cn/lihongjie/coal/cacheCtl/service/CacheCtlService.java @@ -10,23 +10,28 @@ import cn.lihongjie.coal.cacheCtl.entity.CacheCtlEntity; import cn.lihongjie.coal.cacheCtl.mapper.CacheCtlMapper; import cn.lihongjie.coal.cacheCtl.repository.CacheCtlRepository; import cn.lihongjie.coal.exception.BizException; +import cn.lihongjie.coal.spring.config.MultiLevelCache; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Splitter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.data.redis.cache.RedisCache; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; @Service @@ -80,32 +85,67 @@ public class CacheCtlService extends BaseService keys(CacheCtlDto request) { - Set keys = redisTemplate.keys(request.getCacheName() + "::*"); + Cache cache = cacheManager.getCache(request.getCacheName()); + + List keys = new ArrayList<>(); + if (cache instanceof MultiLevelCache c){ + + + keys = c.getKeys(); + return keys.stream() + .map( + x -> { + CacheCtlDto dto = new CacheCtlDto(request.getId()); + dto.setId(request.getId()); + dto.setCacheKey(x); + return dto; + }) + .collect(Collectors.toList()); + + }else if (cache instanceof RedisCache ) { + + + keys = redisTemplate.keys(request.getCacheName() + "::*").stream().toList(); + + return keys.stream() + .map( + x -> { + CacheCtlDto dto = new CacheCtlDto(request.getId()); + dto.setId(request.getId()); + dto.setCacheKey( + Splitter.on("::") + .trimResults() + .omitEmptyStrings() + .splitToList(x) + .get(1)); + return dto; + }) + .collect(Collectors.toList()); + }else { + throw new BizException("不支持的缓存类型"); + + } - return keys.stream() - .map( - x -> { - CacheCtlDto dto = new CacheCtlDto(request.getId()); - dto.setId(request.getId()); - dto.setCacheKey( - Splitter.on("::") - .trimResults() - .omitEmptyStrings() - .splitToList(x) - .get(1)); - return dto; - }) - .collect(Collectors.toList()); } + @SneakyThrows public CacheCtlDto value(CacheCtlDto request) { - request.setCacheValue( - redisTemplate - .opsForValue() - .get(request.getCacheName() + "::" + request.getCacheKey())); + Cache cache = cacheManager.getCache(request.getCacheName()); + Cache.ValueWrapper valueWrapper = cache.get(request.getCacheKey()); + + if (valueWrapper == null){ + + request.setCacheValue("null"); + }else { + request.setCacheValue(objectMapper.writeValueAsString(valueWrapper.get())); + } + + return request; } diff --git a/src/main/java/cn/lihongjie/coal/common/Constants.java b/src/main/java/cn/lihongjie/coal/common/Constants.java index 7e43c5af..7aae4556 100644 --- a/src/main/java/cn/lihongjie/coal/common/Constants.java +++ b/src/main/java/cn/lihongjie/coal/common/Constants.java @@ -1,6 +1,11 @@ package cn.lihongjie.coal.common; +import java.util.UUID; + public class Constants { + + public static final String RUN_ID = UUID.randomUUID().toString(); + public static final String CACHE_RESOURCE = "resource"; public static final String CACHE_USER_PERMISSIONS = "userPermissions"; public static final String CACHE_USER_RESOURCES = "userResources"; @@ -10,6 +15,7 @@ public class Constants { public static final String CACHE_SYSCONFIG = "sysconfig"; public static final String CACHE_RESOURCE_API_TREE = "resourceApiTree"; public static final String CACHE_RESOURCE_MENU_TREE = "resourceMenuTree"; + public static final String CACHE_RESOURCE_ALL = "resourceAll"; public static final String HTTP_HEADER_TOKEN = "X-Token"; public static final String HTTP_HEADER_CLIENT_SIGN = "X-Client-Sign"; public static final String HTTP_HEADER_CLIENT_RANDOM = "X-Client-Random"; @@ -43,6 +49,9 @@ public class Constants { public static final String SYSCONFIG_ENABLE_REQUEST_SUBMIT_TOKEN = "enable_request_submit_token"; public static final String HTTP_HEADER_SUBMIT_TOKEN = "X-Submit-Token"; public static final String CACHE_ERROR_MSG = "errorMsg"; + public static final String CACHE_ORGANIZATION_PERMISSION_IDS = "organizationPermissionIds"; + public static final String CACHE_USER_BY_ID = "userById"; + public static final String CACHE_USER_RESOURCE_IDS = "userResourceIds"; public static String SYSCONFIG_ENABLE_CAPTCHA = "enable_captcha"; public static String SYSCONFIG_ENABLE_REQUEST_SIGN = "enable_request_sign"; public static String SYSCONFIG_SESSION_TIMEOUT = "session_timeout"; diff --git a/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java b/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java index be266cd7..59a9fd0f 100644 --- a/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java +++ b/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java @@ -19,7 +19,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; import org.slf4j.MDC; @@ -145,27 +144,12 @@ public class AuthFilter extends OncePerRequestFilter { var user = Ctx.currentUser(); MDC.put("user", user.getUsername()); - stopWatch.start("检查权限"); + stopWatch.start("hasResource"); - boolean hasPermission = false; - if (BooleanUtils.isTrue(user.getSysAdmin())) { - hasPermission = true; - } else if (BooleanUtils.isTrue(user.getOrgAdmin())) { - // 机构管理员 - - hasPermission = permissionService.orgAdminHasPermission(resource.get().getId()); - } else { - // 普通用户默认权限 - hasPermission = permissionService.orgUserHasPermission(resource.get().getId()); - - if (!hasPermission) { - - // 普通用户指定权限 - hasPermission = userService.hasResource(user.getId(), resource.get().getId()); - } - } + boolean hasPermission = userService.hasResource(user, resource.get().getId()); stopWatch.stop(); + if (!hasPermission) { RequestUtils.writeResponse( diff --git a/src/main/java/cn/lihongjie/coal/filter/CacheFilter.java b/src/main/java/cn/lihongjie/coal/filter/CacheFilter.java index d8d5110f..b98767f5 100644 --- a/src/main/java/cn/lihongjie/coal/filter/CacheFilter.java +++ b/src/main/java/cn/lihongjie/coal/filter/CacheFilter.java @@ -30,7 +30,7 @@ public class CacheFilter extends OncePerRequestFilter { var req = new CacheBodyRequestWrapper(request); filterChain.doFilter(req, response); long end = System.currentTimeMillis(); - response.addHeader(Constants.HTTP_HEADER_RESPONSE_TIME, String.valueOf(end - start)); + response.setHeader(Constants.HTTP_HEADER_RESPONSE_TIME, String.valueOf(end - start)); } } diff --git a/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java index 2e0e6dfe..d7d0d0dc 100644 --- a/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java +++ b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java @@ -4,7 +4,6 @@ import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; import cn.lihongjie.coal.common.Constants; -import cn.lihongjie.coal.common.Ctx; import cn.lihongjie.coal.loginUser.dto.CreateLoginUserDto; import cn.lihongjie.coal.loginUser.dto.LoginUserDto; import cn.lihongjie.coal.loginUser.dto.UpdateLoginUserDto; @@ -296,12 +295,12 @@ public class LoginUserService extends BaseService> fileIds; + private String name; + private String code; + private String remarks; + private Integer sortKey; + private Integer status; + private Integer maxUser; + private LocalDateTime expireTime; + private String namex; +} diff --git a/src/main/java/cn/lihongjie/coal/organization/service/OrganizationService.java b/src/main/java/cn/lihongjie/coal/organization/service/OrganizationService.java index e65533a3..7331469a 100644 --- a/src/main/java/cn/lihongjie/coal/organization/service/OrganizationService.java +++ b/src/main/java/cn/lihongjie/coal/organization/service/OrganizationService.java @@ -4,6 +4,7 @@ import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; import cn.lihongjie.coal.coalParameterDef.service.CoalParameterDefService; +import cn.lihongjie.coal.common.Constants; import cn.lihongjie.coal.common.JpaUtils; import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.organization.dto.CreateOrganizationDto; @@ -28,9 +29,12 @@ import jakarta.persistence.Tuple; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -39,10 +43,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; @Service @Slf4j @@ -60,8 +61,38 @@ public class OrganizationService extends BaseService { + log.info("organization.cache.invalid {}", m.getBody()); + byte[] body = m.getBody(); + + if (ArrayUtils.isEmpty(body)) { + + cacheManager.getCache(Constants.CACHE_ORGANIZATION_PERMISSION_IDS).clear(); + } else { + + cacheManager + .getCache(Constants.CACHE_ORGANIZATION_PERMISSION_IDS) + .evict(new String(body)); + } + }); + } + + private void reloadCache() { + reloadCache(null); + } + + private void reloadCache(String organizationId) { + rabbitMQService.sendToSysExchange( + "organization.cache.invalid", StringUtils.defaultIfBlank(organizationId, "")); + } @Autowired RabbitTemplate rabbitTemplate; @@ -74,6 +105,7 @@ public class OrganizationService extends BaseService()); + reloadCache(request.getId()); return getById(entity.getId()); } @@ -89,10 +121,8 @@ public class OrganizationService extends BaseService list(CommonQuery query) { @@ -134,17 +165,18 @@ public class OrganizationService extends BaseService map = page.map(this.mapper::toDto); - List counts = em.createQuery( - "select u.organizationId as id , count(1) as userCount from UserEntity u where u.organizationId in :ids group by u.organizationId", - Tuple.class) - - .setParameter("ids", page.getContent().stream().map(OrganizationEntity::getId).toList()) - .getResultList(); + List counts = + em.createQuery( + "select u.organizationId as id , count(1) as userCount from UserEntity u where u.organizationId in :ids group by u.organizationId", + Tuple.class) + .setParameter( + "ids", + page.getContent().stream().map(OrganizationEntity::getId).toList()) + .getResultList(); var maps = JpaUtils.convertTuplesToRawMap(counts); - JpaUtils.mergeMapToPojo( - map.getContent(), maps, conversionService); + JpaUtils.mergeMapToPojo(map.getContent(), maps, conversionService); return map; } @@ -189,4 +221,16 @@ public class OrganizationService extends BaseService getDefaultPermissionIds(String organizationId) { + + List id = em.createQuery( + "select p.id from OrganizationEntity o join o.permissions p where o.id = :id", + String.class) + .setParameter("id", organizationId) + .getResultList(); + return new HashSet<>( + id); + } } diff --git a/src/main/java/cn/lihongjie/coal/permission/dto/PermissionDto.java b/src/main/java/cn/lihongjie/coal/permission/dto/PermissionDto.java index 6a32de17..f3fc3347 100644 --- a/src/main/java/cn/lihongjie/coal/permission/dto/PermissionDto.java +++ b/src/main/java/cn/lihongjie/coal/permission/dto/PermissionDto.java @@ -3,11 +3,10 @@ package cn.lihongjie.coal.permission.dto; import cn.lihongjie.coal.base.dto.OrgCommonDto; import cn.lihongjie.coal.common.DictCode; import cn.lihongjie.coal.pojoProcessor.DictTranslate; +import cn.lihongjie.coal.resource.dto.ResourceDto; import lombok.Data; -import org.hibernate.annotations.Comment; - import java.util.List; @Data @@ -21,16 +20,6 @@ public class PermissionDto extends OrgCommonDto { @DictTranslate(dictKey = DictCode.PERMISSION_TYPE) private String permissionTypeName; - @Data - public static class ResourceDto extends OrgCommonDto { - // @OneToMany(mappedBy = "parent") - // private List children; - - @Comment("资源类型") - private String type; - - @Comment("资源地址") - private String url; - } + } diff --git a/src/main/java/cn/lihongjie/coal/permission/dto/PermissionExportDto.java b/src/main/java/cn/lihongjie/coal/permission/dto/PermissionExportDto.java index c6779d29..b9a873d2 100644 --- a/src/main/java/cn/lihongjie/coal/permission/dto/PermissionExportDto.java +++ b/src/main/java/cn/lihongjie/coal/permission/dto/PermissionExportDto.java @@ -12,7 +12,7 @@ import java.util.*; public class PermissionExportDto extends CommonDto { private String parentName; - private List resources; + private List resources; private String permissionType; private String permissionTypeName; diff --git a/src/main/java/cn/lihongjie/coal/permission/repository/PermissionRepository.java b/src/main/java/cn/lihongjie/coal/permission/repository/PermissionRepository.java index 1afd3c56..3c051b7e 100644 --- a/src/main/java/cn/lihongjie/coal/permission/repository/PermissionRepository.java +++ b/src/main/java/cn/lihongjie/coal/permission/repository/PermissionRepository.java @@ -23,6 +23,6 @@ public interface PermissionRepository extends BaseRepository { "select count(p) > 0 from PermissionEntity p inner join p.resources r on r.id = ?1 where p.permissionType in ?2 ") boolean hasPermission(String resourceId, List permissionType); - @Query("select exists(select 1 from RoleEntity e join PermissionEntity p where p.id in :ids)") + @Query("select exists(select 1 from RoleEntity e join e.permissions p where p.id in :ids)") boolean isLinked(@Param("ids") List ids); } diff --git a/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java b/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java index 405a47e4..8ad067d3 100644 --- a/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java +++ b/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java @@ -5,6 +5,7 @@ import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; import cn.lihongjie.coal.common.Constants; import cn.lihongjie.coal.exception.BizException; +import cn.lihongjie.coal.organization.service.OrganizationService; import cn.lihongjie.coal.permission.dto.CreatePermissionDto; import cn.lihongjie.coal.permission.dto.PermissionDto; import cn.lihongjie.coal.permission.dto.PermissionExportDto; @@ -15,6 +16,7 @@ import cn.lihongjie.coal.permission.mapper.PermissionMapper; import cn.lihongjie.coal.permission.repository.PermissionRepository; import cn.lihongjie.coal.resource.entity.ResourceEntity; import cn.lihongjie.coal.resource.service.ResourceService; +import cn.lihongjie.coal.user.service.UserService; import io.vavr.Tuple; import io.vavr.Tuple3; @@ -27,6 +29,7 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.ConversionService; @@ -36,15 +39,13 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @Slf4j @Transactional +@CacheConfig(cacheManager = "caffeineCacheManager") public class PermissionService extends BaseService { @Autowired PermissionRepository repository; @@ -56,16 +57,13 @@ public class PermissionService extends BaseService getByTypeFromCache(String type) { + public void delete(IdRequest request) { - return applicationContext.getBean(this.getClass()).getAllFromCache().stream() - .filter(x -> StringUtils.equals(x.getPermissionType(), type)) - .collect(Collectors.toList()); - } + if (this.repository.isLinked(request.getIds())) { + throw new BizException("部分数据已被关联,无法删除"); + } - public void clearCache() { - cacheManager.getCache(Constants.CACHE_PERMISSION).clear(); - cacheManager.getCache(Constants.CACHE_PERMISSION_BY_TYPE).clear(); - cacheManager.getCache(Constants.CACHE_IS_ANONYMOUS_BY_RESOURCE_ID).clear(); - cacheManager.getCache(Constants.CACHE_ORG_ADMIN_HAS_PERMISSION).clear(); + this.repository.deleteAllById(request.getIds()); + clearCache(); } @Cacheable(cacheNames = Constants.CACHE_IS_ANONYMOUS_BY_RESOURCE_ID, key = "#resourceId") @@ -249,16 +243,33 @@ public class PermissionService extends BaseService(organizationService.getDefaultPermissionIds(organizationId)); + + return that.getAllFromCache().stream() + .filter(x -> defaultPermissionIds.contains(x.getId()) || StringUtils.equalsAny(x.getPermissionType(), "0", "1")) + .flatMap(x -> x.getResources().stream()) + .anyMatch(x -> StringUtils.equals(x.getId(), resourceId)); + } + @Cacheable(cacheNames = Constants.CACHE_ORG_ADMIN_HAS_PERMISSION, key = "#resourceId") public boolean orgUserHasPermission(String resourceId) { return this.repository.hasPermission(resourceId, Arrays.asList("0", "1")); } diff --git a/src/main/java/cn/lihongjie/coal/rabbitmq/RabbitMQService.java b/src/main/java/cn/lihongjie/coal/rabbitmq/RabbitMQService.java index 501f487b..f7c4dbd7 100644 --- a/src/main/java/cn/lihongjie/coal/rabbitmq/RabbitMQService.java +++ b/src/main/java/cn/lihongjie/coal/rabbitmq/RabbitMQService.java @@ -1,13 +1,19 @@ package cn.lihongjie.coal.rabbitmq; +import cn.lihongjie.coal.common.Constants; + import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.MapUtils; +import org.springframework.amqp.core.MessageListener; +import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; @@ -23,6 +29,8 @@ public class RabbitMQService { @PersistenceContext EntityManager em; + @Autowired GenericApplicationContext applicationContext; + private static void execCommand(Runnable command) { if (TransactionSynchronizationManager.isActualTransactionActive()) { @@ -31,12 +39,11 @@ public class RabbitMQService { @Override public void afterCompletion(int status) { - if (status == STATUS_COMMITTED){ + if (status == STATUS_COMMITTED) { command.run(); - }else { + } else { log.info("transaction is not committed, skip send message"); - } } }); @@ -45,16 +52,54 @@ public class RabbitMQService { } } + public void createInstanceQueue(String routingKey, MessageListener messageListener) { + createQueue( + new Queue(routingKey + "." + Constants.RUN_ID, false, true, true), + routingKey, + messageListener); + } + + public void createQueue(Queue queue, String routingKey, MessageListener messageListener) { + createQueue(queue, RabbitMQConfiguration.SYS_EXCHANGE, routingKey, messageListener); + } + + public void createQueue( + Queue queue, String exchange, String routingKey, MessageListener messageListener) { + rabbitTemplate.execute( + channel -> { + channel.queueDeclare( + queue.getName(), + queue.isDurable(), + queue.isExclusive(), + queue.isAutoDelete(), + queue.getArguments()); + channel.queueBind(queue.getName(), exchange, routingKey); + return null; + }); + + SimpleMessageListenerContainer container = + new SimpleMessageListenerContainer(rabbitTemplate.getConnectionFactory()); + container.addQueues(queue); + + container.setConcurrency("1"); + container.setMessageListener(messageListener); + + applicationContext.registerBean("container." + queue.getName(), + SimpleMessageListenerContainer.class, + () -> container, + bd -> { + bd.setInitMethodName("start"); + bd.setDestroyMethodName("stop"); + bd.setLazyInit(false); + + }); + } + public void send(String exchange, String routingKey, Object data, Map headers) { Runnable command = () -> { - log.info("send to exchange: {} {}", exchange, routingKey); -// try { -// Thread.sleep(1000); -// } catch (InterruptedException e) { -// throw new RuntimeException(e); -// } + log.info("send to exchange: {} {}", exchange, routingKey); rabbitTemplate.convertAndSend( diff --git a/src/main/java/cn/lihongjie/coal/resource/mapper/ResourceMapper.java b/src/main/java/cn/lihongjie/coal/resource/mapper/ResourceMapper.java index adf6c4ce..e660ff6b 100644 --- a/src/main/java/cn/lihongjie/coal/resource/mapper/ResourceMapper.java +++ b/src/main/java/cn/lihongjie/coal/resource/mapper/ResourceMapper.java @@ -45,4 +45,5 @@ public interface ResourceMapper x -> ObjectUtils.defaultIfNull(x.getSortKey(), 99999))); } } + } diff --git a/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java b/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java index c3f66035..d6e06206 100644 --- a/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java +++ b/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java @@ -1,5 +1,7 @@ package cn.lihongjie.coal.resource.service; +import static cn.lihongjie.coal.common.Constants.*; + import cn.lihongjie.coal.annotation.Anonymous; import cn.lihongjie.coal.annotation.RateLimit; import cn.lihongjie.coal.annotation.SignCheck; @@ -7,9 +9,9 @@ import cn.lihongjie.coal.annotation.SubmitToken; import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; -import cn.lihongjie.coal.common.Constants; import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.permission.service.PermissionService; +import cn.lihongjie.coal.rabbitmq.RabbitMQService; import cn.lihongjie.coal.resource.dto.*; import cn.lihongjie.coal.resource.entity.ResourceEntity; import cn.lihongjie.coal.resource.mapper.ResourceMapper; @@ -17,6 +19,7 @@ import cn.lihongjie.coal.resource.repository.ResourceRepository; import cn.lihongjie.coal.user.service.UserService; import io.vavr.Tuple2; +import io.vavr.control.Try; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; @@ -29,10 +32,9 @@ import jakarta.persistence.criteria.Root; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.hibernate.Cache; -import org.hibernate.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.ConversionService; @@ -47,15 +49,13 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.util.pattern.PathPattern; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; @Service @Slf4j @Transactional +@CacheConfig(cacheManager = "caffeineCacheManager") public class ResourceService extends BaseService { @Autowired ResourceRepository repository; @@ -64,24 +64,37 @@ public class ResourceService extends BaseService allResources(){ + + return this.repository.findAll().stream().map(mapper::toDto).collect(Collectors.toList()); + } public void delete(IdRequest request) { - if (this.repository.isLinked(request.getIds())) { throw new BizException("部分数据已被关联,无法删除"); } this.repository.deleteAllById(request.getIds()); - Cache cache = entityManager.unwrap(Session.class).getSessionFactory().getCache(); - cache.evictCollectionData("cn.lihongjie.coal.resource.entity.ResourceEntity.children"); } public ResourceDto getById(String id) { @@ -156,6 +169,7 @@ public class ResourceService extends BaseService menuTree() { return this.mapper.toTreeDto( this.repository.findAllByTypeAndParentIsNullOrderBySortKey("0")); } - @Cacheable(cacheNames = Constants.CACHE_RESOURCE_API_TREE) + @Cacheable(cacheNames = CACHE_RESOURCE_API_TREE) public List apiTree() { return this.mapper.toTreeDto( this.repository.findAllByTypeAndParentIsNullOrderBySortKey("3")); } - @Cacheable(cacheNames = Constants.CACHE_RESOURCE) - public List resourcesFromCache() { - return this.repository.findAll().stream() - .map(re -> mapper.toDto(re)) - .collect(Collectors.toList()); - } - - @Cacheable(cacheNames = Constants.CACHE_RESOURCE_BY_URL, key = "#url") - public Optional findUrlFromCache(String url) { - - return applicationContext.getBean(this.getClass()).resourcesFromCache().stream() - .filter( - x -> { - return StringUtils.equals(x.getCode(), url) - && StringUtils.equals(x.getType(), "3"); - }) - .findAny(); - } - - @Cacheable(cacheNames = Constants.CACHE_RESOURCE_BY_URL_2, key = "#url") + @Cacheable(cacheNames = CACHE_RESOURCE_BY_URL_2, key = "#url") public Optional findUrlFromCache2(String url) { - return Optional.ofNullable(repository.findByCodeAndType(url, "3")); + return applicationContext.getBean(this.getClass()).allResources().stream() + .filter( + x -> + StringUtils.equals(x.getType(), "3") + && StringUtils.equals(x.getCode(), url)).findAny(); + } public void clearCache() { - cacheManager.getCache(Constants.CACHE_RESOURCE).clear(); - cacheManager.getCache(Constants.CACHE_RESOURCE_BY_URL).clear(); - cacheManager.getCache(Constants.CACHE_RESOURCE_BY_URL_2).clear(); - cacheManager.getCache(Constants.CACHE_RESOURCE_MENU_TREE).clear(); - cacheManager.getCache(Constants.CACHE_RESOURCE_API_TREE).clear(); - permissionService.clearCache(); - cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).clear(); - cacheManager.getCache(Constants.CACHE_USER_RESOURCES).clear(); + Try.run( () -> cacheManager.getCache(CACHE_RESOURCE_ALL).clear()).andThen(() -> log.info("clear cache {} ", CACHE_RESOURCE_ALL)); + Try.run( () -> cacheManager.getCache(CACHE_RESOURCE_BY_URL_2).clear()).andThen(() -> log.info("clear cache {} ", CACHE_RESOURCE_BY_URL_2)); + Try.run( () -> cacheManager.getCache(CACHE_RESOURCE_MENU_TREE).clear()).andThen(() -> log.info("clear cache {} ", CACHE_RESOURCE_MENU_TREE)); + Try.run(() -> cacheManager.getCache(CACHE_RESOURCE_API_TREE).clear()).andThen(() -> log.info("clear cache {} ", CACHE_RESOURCE_API_TREE)); + + + + + + permissionService.clearCache(); } + + + + + } diff --git a/src/main/java/cn/lihongjie/coal/session/service/SessionService.java b/src/main/java/cn/lihongjie/coal/session/service/SessionService.java index 7f3eeb99..fa4acd29 100644 --- a/src/main/java/cn/lihongjie/coal/session/service/SessionService.java +++ b/src/main/java/cn/lihongjie/coal/session/service/SessionService.java @@ -49,6 +49,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StopWatch; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -120,10 +121,14 @@ public class SessionService { return; } + StopWatch stopWatch = new StopWatch(); + LoginUserDto loginDto; try { + stopWatch.start("get from cache"); loginDto = loginUserService.getFromCache(sessionId); + stopWatch.stop(); } catch (Exception e) { log.warn("会话已过期 {}", sessionId); logout(sessionId); @@ -132,6 +137,7 @@ public class SessionService { + stopWatch.start("check ip"); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); @@ -153,8 +159,14 @@ public class SessionService { throw new BizException("invalidToken", "检测到浏览器发生变化,请重新登录"); } + stopWatch.stop(); + + stopWatch.start("touch"); loginUserService.touch(loginDto); + stopWatch.stop(); + + stopWatch.start("init context"); UserDto user = loginDto.getUser(); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -162,6 +174,8 @@ public class SessionService { context.setAuthentication(new MyAuthentication(loginDto, user, sessionId)); SecurityContextHolder.setContext(context); + stopWatch.stop(); + log.debug(stopWatch.prettyPrint()); } public void logout(String sessionId) { diff --git a/src/main/java/cn/lihongjie/coal/spring/config/CacheConfig.java b/src/main/java/cn/lihongjie/coal/spring/config/CacheConfig.java index 11b12e10..dc3f63c4 100644 --- a/src/main/java/cn/lihongjie/coal/spring/config/CacheConfig.java +++ b/src/main/java/cn/lihongjie/coal/spring/config/CacheConfig.java @@ -1,45 +1,59 @@ package cn.lihongjie.coal.spring.config; -import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.benmanes.caffeine.cache.CaffeineSpec; +import org.apache.commons.collections.Factory; +import org.apache.commons.collections.map.DefaultedMap; +import org.redisson.api.RedissonClient; +import org.redisson.spring.cache.RedissonSpringCacheManager; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; @EnableCaching @Configuration public class CacheConfig { @Autowired ObjectMapper objectMapper; - @Bean - public RedisCacheConfiguration cacheConfiguration() { - objectMapper = objectMapper.copy(); - objectMapper = - objectMapper.enableDefaultTyping( - ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); - return RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofMinutes(10)) + @Autowired RedissonClient redissonClient; - .serializeValuesWith( - RedisSerializationContext.SerializationPair.fromSerializer( - new GenericJackson2JsonRedisSerializer(objectMapper))); + @Bean + @Qualifier("redisCacheManager" ) + public RedissonSpringCacheManager redisCacheManager() { + + Map cacheConfigs = new HashMap<>(); + return new RedissonSpringCacheManager( + redissonClient, + DefaultedMap.decorate( + cacheConfigs, + new Factory() { + @Override + public Object create() { + return new org.redisson.spring.cache.CacheConfig( + TimeUnit.MINUTES.toMillis(120), + TimeUnit.MINUTES.toMillis(120)); + } + })); } @Bean - public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() { - return new RedisCacheManagerBuilderCustomizer() { - @Override - public void customize( - org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder - builder) {} - }; + @Qualifier("caffeineCacheManager") + public CaffeineCacheManager caffeineCacheManager() { + CaffeineCacheManager manager = new CaffeineCacheManager(); + manager.setCaffeineSpec(CaffeineSpec.parse("expireAfterWrite=60m")); + return manager; } + + + + + } diff --git a/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCache.java b/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCache.java new file mode 100644 index 00000000..cc3c92e4 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCache.java @@ -0,0 +1,200 @@ +package cn.lihongjie.coal.spring.config; + +import cn.lihongjie.coal.rabbitmq.RabbitMQService; + +import io.vavr.collection.Stream; + +import lombok.extern.slf4j.Slf4j; + +import org.redisson.RedissonMapCache; +import org.redisson.api.map.event.EntryEvent; +import org.redisson.api.map.event.EntryExpiredListener; +import org.redisson.spring.cache.RedissonCache; +import org.springframework.cache.Cache; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; + +@Slf4j +public class MultiLevelCache implements Cache { + private final RedissonCache redisCache; + private final CaffeineCache caffeineCache; + private final GenericJackson2JsonRedisSerializer serializer; + + private final RabbitMQService rabbitMQService; + + public MultiLevelCache(Cache redisCache, Cache caffeineCache, RabbitMQService rabbitMQService, GenericJackson2JsonRedisSerializer serializer) { + this.redisCache = (RedissonCache) redisCache; + this.caffeineCache = (CaffeineCache) caffeineCache; + this.serializer = serializer; + + RedissonMapCache redisCacheNativeCache = (RedissonMapCache) redisCache.getNativeCache(); + + redisCacheNativeCache.addListener( + new EntryExpiredListener() { + + @Override + public void onExpired(EntryEvent event) { + + log.info( + "redis cached expired key: {} , broadcast event to all instance", + event.getKey()); + + rabbitMQService.sendToSysExchange( + "multiLevelCache.event", + new MultiLevelCacheManager.MultiLevelCacheEvent( + MultiLevelCache.this.getName(), + "redisExpired", + serializerKey(event.getKey()))); + } + }); + this.rabbitMQService = rabbitMQService; + } + + private String serializerKey(Object key) { + return new String(serializer.serialize(key), StandardCharsets.UTF_8); + } + + @Override + public String getName() { + return caffeineCache.getName(); + } + + @Override + public Object getNativeCache() { + return this; + } + + @Override + public ValueWrapper get(Object key) { + + var start = System.nanoTime(); + var stop = System.nanoTime(); + var sb = new StringBuilder(); + + sb.append("cache name [").append(getName()).append("] get key [").append(key).append("] "); + + ValueWrapper valueWrapper = caffeineCache.get(key); + + stop = System.nanoTime(); + + sb.append(valueWrapper == null ? " null " : valueWrapper.getClass().getSimpleName()); + sb.append(" " ).append(stop - start).append(" ns"); + + if (valueWrapper == null) { + + sb.append("\n caffeine cache miss, try to get from redis cache"); + + start = System.nanoTime(); + valueWrapper = redisCache.get(key); + + stop = System.nanoTime(); + sb.append("\n get from redis cache [") + .append(valueWrapper == null ? "null" : valueWrapper.getClass()) + .append("]").append(" ").append(stop - start).append(" ns"); + + Object value = valueWrapper == null ? null : valueWrapper.get(); + + if (valueWrapper != null) { + + if (value != null || caffeineCache.isAllowNullValues()) { + + sb.append("\n put to caffeine cache "); + // log.info("put to caffeine {}", key); + + start = System.nanoTime(); + caffeineCache.put(key, value); + stop = System.nanoTime(); + sb.append(" ").append(stop - start).append(" ns"); + } + }else { + + sb.append("\n redis cache miss too, return null"); + } + } + + log.info(sb.toString()); + return valueWrapper; + } + + @Override + public T get(Object key, Class type) { + + return ((T)get(key)); + + } + + @Override + public T get(Object key, Callable valueLoader) { + + Object o = + caffeineCache.get( + key, + new Callable() { + @Override + public Object call() throws Exception { + + T called = valueLoader == null ? null : valueLoader.call(); + + log.info("cache name {} put to redis cache from value loader {}", getName(), key); + redisCache.put(key, called); + return called; + } + }); + + return o == null ? null : (T) o; + } + + @Override + public void put(Object key, Object value) { + + log.info("cache name [{}] put key [{}] ", getName(), key); + + caffeineCache.put(key, value); + redisCache.put(key, value); + + rabbitMQService.sendToSysExchange( + "multiLevelCache.event", + new MultiLevelCacheManager.MultiLevelCacheEvent( + MultiLevelCache.this.getName(), "put", serializerKey(key))); + } + + @Override + public void evict(Object key) { + + log.info("cache name [{}] evict key [{}] ", getName(), key); + caffeineCache.evict(key); + redisCache.evict(key); + + rabbitMQService.sendToSysExchange( + "multiLevelCache.event", + new MultiLevelCacheManager.MultiLevelCacheEvent( + MultiLevelCache.this.getName(), "evict", serializerKey(key))); + } + + @Override + public void clear() { + + log.info("cache name [{}] clear", getName()); + caffeineCache.clear(); + redisCache.clear(); + + rabbitMQService.sendToSysExchange( + "multiLevelCache.event", + new MultiLevelCacheManager.MultiLevelCacheEvent( + MultiLevelCache.this.getName(), "clear", null)); + } + + public List getKeys() { + + + List k1 = caffeineCache.getNativeCache().asMap().keySet().stream().map(x -> x.toString()).toList(); + + List k2 = redisCache.getNativeCache().keySet().stream().map(x -> x.toString()).toList(); + + return Stream.ofAll(k1).appendAll(k2).distinct().toJavaList(); + } +} diff --git a/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCacheManager.java b/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCacheManager.java new file mode 100644 index 00000000..f84b2383 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/spring/config/MultiLevelCacheManager.java @@ -0,0 +1,106 @@ +package cn.lihongjie.coal.spring.config; + +import cn.lihongjie.coal.rabbitmq.RabbitMQService; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.vavr.collection.Stream; + +import jakarta.annotation.PostConstruct; + +import lombok.extern.slf4j.Slf4j; + +import org.redisson.spring.cache.RedissonSpringCacheManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +@Component +@Primary +@Slf4j +public class MultiLevelCacheManager implements CacheManager { + + @Autowired private RedissonSpringCacheManager redissonSpringCacheManager; + @Autowired private CaffeineCacheManager caffeineCacheManager; + @Autowired private RabbitMQService rabbitMQService; + + @Autowired private ObjectMapper objectMapper; + private GenericJackson2JsonRedisSerializer serializer; + + @PostConstruct + public void init() { + + serializer = new GenericJackson2JsonRedisSerializer(objectMapper); + + rabbitMQService.createInstanceQueue( + "multiLevelCache.event", + msg -> { + byte[] body = msg.getBody(); + + log.info("receive multiLevelCache message: {}", new String(body, StandardCharsets.UTF_8)); + try { + MultiLevelCacheEvent cacheEvent = + objectMapper.readValue(body, MultiLevelCacheEvent.class); + + switch (cacheEvent.eventType) { + case "redisExpired": + Cache caffeineCache = + caffeineCacheManager.getCache(cacheEvent.cacheName); + caffeineCache.evict(serializer.deserialize(cacheEvent.key.getBytes(StandardCharsets.UTF_8))); + break; + case "put": + case "evict": + Cache caffeineCache1 = + caffeineCacheManager.getCache(cacheEvent.cacheName); + caffeineCache1.evict(serializer.deserialize(cacheEvent.key.getBytes(StandardCharsets.UTF_8))); + break; + + case "clear": + Cache redisCache2 = + redissonSpringCacheManager.getCache(cacheEvent.cacheName); + Cache caffeineCache3 = + caffeineCacheManager.getCache(cacheEvent.cacheName); + redisCache2.clear(); + caffeineCache3.clear(); + break; + + default: + throw new IllegalArgumentException( + "unknown event type: " + cacheEvent.eventType); + } + + } catch (IOException e) { + + log.info("handle multiLevelCache message error", e); + } + }); + } + + @Override + public Cache getCache(String name) { + + Cache caffeineCache = caffeineCacheManager.getCache(name); + + Cache redisCache = redissonSpringCacheManager.getCache(name); + + return new MultiLevelCache(redisCache, caffeineCache, rabbitMQService, serializer); + } + + @Override + public Collection getCacheNames() { + return Stream.ofAll(caffeineCacheManager.getCacheNames()) + .appendAll(redissonSpringCacheManager.getCacheNames()) + .distinct() + .toJavaList(); + } + + public record MultiLevelCacheEvent(String cacheName, String eventType, String key) {} +} diff --git a/src/main/java/cn/lihongjie/coal/user/service/UserService.java b/src/main/java/cn/lihongjie/coal/user/service/UserService.java index d5b47dbb..96febce7 100644 --- a/src/main/java/cn/lihongjie/coal/user/service/UserService.java +++ b/src/main/java/cn/lihongjie/coal/user/service/UserService.java @@ -1,5 +1,6 @@ package cn.lihongjie.coal.user.service; +import cn.lihongjie.coal.base.dto.BaseDto; import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.entity.BaseEntity; @@ -10,6 +11,7 @@ import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.organization.entity.OrganizationEntity; import cn.lihongjie.coal.organization.service.OrganizationService; import cn.lihongjie.coal.passwordDict.service.PasswordDictService; +import cn.lihongjie.coal.permission.dto.PermissionDto; import cn.lihongjie.coal.permission.dto.PermissionSimpleDto; import cn.lihongjie.coal.permission.entity.PermissionEntity; import cn.lihongjie.coal.permission.mapper.PermissionMapper; @@ -29,6 +31,7 @@ import com.nulabinc.zxcvbn.Strength; import com.nulabinc.zxcvbn.Zxcvbn; import io.vavr.collection.Stream; +import io.vavr.control.Try; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; @@ -78,6 +81,9 @@ public class UserService extends BaseService { @Autowired PasswordDictService passwordDictService; private Pbkdf2PasswordEncoder passwordEncoder; + @Autowired + UserService that; + @PostConstruct public void init() { @@ -116,6 +122,8 @@ public class UserService extends BaseService { stopWatch.stop(); stopWatch.start("getById"); + that.userResourceIds(entity.getId()); + return getById(entity.getId()); } finally { stopWatch.stop(); @@ -172,6 +180,7 @@ public class UserService extends BaseService { stopWatch.stop(); stopWatch.start("getById"); + that.userResourceIds(entity.getId()); return getById(entity.getId()); } finally { stopWatch.stop(); @@ -184,12 +193,14 @@ public class UserService extends BaseService { UserEntity user = this.repository.get(request.getId()); this.mapper.updateEntity(user, request); this.repository.save(user); + clearUserCache(request.getId()); return getById(user.getId()); } public void delete(IdRequest request) { this.repository.deleteAllById(request.getIds()); + request.getIds().forEach(this::clearUserCache); } public UserDto getById(String id) { @@ -310,11 +321,32 @@ public class UserService extends BaseService { UserEntity user = get(id); - if (user.getSysAdmin() != null && user.getSysAdmin()) { - return resourceService.findAll().stream() - .map(x -> resourceMapper.toDto(x)) - .collect(Collectors.toList()); + if (BooleanUtils.isTrue(user.getSysAdmin())) { + + return resourceService.allResources(); } + + if (BooleanUtils.isTrue(user.getOrgAdmin())) { + + var defaultPermissionIds = + organizationService.getDefaultPermissionIds(user.getOrganizationId()); + + List allPermissions = permissionService.getAllFromCache(); + + return Stream.ofAll(allPermissions) + .filter( + x -> + defaultPermissionIds.isEmpty() + ? StringUtils.equalsAny( + x.getPermissionType(), "0", "1", "2") + : (StringUtils.equalsAny( + x.getPermissionType(), "0", "1") + || defaultPermissionIds.contains(x.getId()))) + .flatMap(PermissionDto::getResources) + .distinctBy(BaseDto::getId) + .toJavaList(); + } + OrganizationEntity organization = organizationService.get(user.getOrganizationId()); return Stream.ofAll(user.allRoles()) @@ -327,7 +359,10 @@ public class UserService extends BaseService { Stream.ofAll( BooleanUtils.isTrue(user.getOrgAdmin()) ? (CollectionUtils.isEmpty( - organization.getPermissions()) ? permissionService.getByTypes(new String[] {"0", "1", "2"}) : organization.getPermissions()) + organization.getPermissions()) + ? permissionService.getByTypes( + new String[] {"0", "1", "2"}) + : organization.getPermissions()) : Collections.emptyList()) .flatMap(PermissionEntity::getResources)) .distinctBy(BaseEntity::getId) @@ -365,18 +400,11 @@ public class UserService extends BaseService { public void clearUserCache(String id) { if (StringUtils.isEmpty(id)) return; - try { - cacheManager.getCache(Constants.CACHE_USER_RESOURCES).evict(id); - } catch (Exception e) { - log.error("清除用户资源缓存失败", e); - } - try { - - cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).evict(id); - } catch (Exception e) { - log.error("清除用户权限缓存失败", e); - } + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_BY_ID).evict(id)); + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_RESOURCES).evict(id)); + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).evict(id)); + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_RESOURCE_IDS).evict(id)); } public UserEntity findUniqUserByPhone(final String phone) { @@ -403,11 +431,6 @@ public class UserService extends BaseService { return user; } - public boolean hasResource(String userId, String resourceId) { - - return this.repository.hasResource(userId, resourceId); - } - public Map getIdNameMap(Set strings) { strings = Stream.ofAll(strings).filter(StringUtils::isNotBlank).toJavaSet(); @@ -448,4 +471,85 @@ public class UserService extends BaseService { repository.save(x); }); } + + @Cacheable(cacheNames = Constants.CACHE_USER_BY_ID, key = "#userId") + public UserDto getFromCache(String userId) { + + return getById(userId); + } + + @Cacheable(cacheNames = Constants.CACHE_USER_RESOURCE_IDS, key = "#userId") + public Set userResourceIds(String userId) { + UserDto user = getFromCache(userId); + + List allPermissions = permissionService.getAllFromCache(); + if (BooleanUtils.isTrue(user.getOrgAdmin())) { + + var defaultPermissionIds = + organizationService.getDefaultPermissionIds(user.getOrganizationId()); + + return Stream.ofAll(allPermissions) + .filter( + x -> + defaultPermissionIds.isEmpty() + ? StringUtils.equalsAny( + x.getPermissionType(), "0", "1", "2") + : (StringUtils.equalsAny( + x.getPermissionType(), "0", "1") + || defaultPermissionIds.contains(x.getId()))) + .flatMap(PermissionDto::getResources) + .distinctBy(BaseDto::getId) + .map(BaseDto::getId) + .toJavaSet(); + } else { + + Set ids = + em + .createQuery( + "select rr.id from UserEntity u join u.roles r join r.permissions p join p.resources rr where u.id = :userId", + String.class) + .setParameter("userId", userId) + .getResultList() + .stream() + .map(x -> x) + .collect(Collectors.toSet()); + + ids.addAll( + allPermissions.stream() + .filter(x -> StringUtils.equalsAny(x.getPermissionType(), "0", "1")) + .flatMap( + x -> + x.getResources() == null + ? java.util.stream.Stream.empty() + : x.getResources().stream()) + .map(BaseDto::getId) + .collect(Collectors.toSet())); + return ids; + } + } + + public boolean hasResource(UserDto user, String resourceId) { + + if (BooleanUtils.isTrue(user.getSysAdmin())) { + return true; + } + StopWatch stopWatch = new StopWatch(); + + stopWatch.start("userResourceIds"); + Set ids = that.userResourceIds(user.getId()); + stopWatch.stop(); + stopWatch.start("contains"); + boolean contains = ids.contains(resourceId); + stopWatch.stop(); +// log.info(stopWatch.prettyPrint()); + return contains; + } + + public void clearUserPermissionCache() { + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_RESOURCES).clear()); + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).clear()); + Try.run(() -> cacheManager.getCache(Constants.CACHE_USER_RESOURCE_IDS).clear()); + + + } }