性能优化

This commit is contained in:
2024-04-27 10:14:07 +08:00
parent 25f2a14680
commit 54db387639
21 changed files with 805 additions and 205 deletions

View File

@@ -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<CacheCtlEntity, CacheCtlReposit
collect, PageRequest.of(query.getPageNo(), query.getPageSize()), collect.size());
}
@Autowired ObjectMapper objectMapper;
public List<CacheCtlDto> keys(CacheCtlDto request) {
Set<String> keys = redisTemplate.keys(request.getCacheName() + "::*");
Cache cache = cacheManager.getCache(request.getCacheName());
List<String> 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;
}

View File

@@ -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";

View File

@@ -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(

View File

@@ -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));
}
}

View File

@@ -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<LoginUserEntity, LoginUserRepo
if (sessionId != null) {
this.deleteLogin(sessionId);
try {
userService.clearUserCache(Ctx.currentUser().getId());
} catch (Exception e) {
log.warn("清除用户缓存失败", e);
}
// try {
//
// userService.clearUserCache(Ctx.currentUser().getId());
// } catch (Exception e) {
// log.warn("清除用户缓存失败", e);
// }
}
}

View File

@@ -37,6 +37,7 @@ public class OrganizationEntity extends CommonEntity {
private Integer maxUser;
private String namex;
@Comment("过期时间")
private LocalDateTime expireTime;
@@ -51,4 +52,5 @@ public class OrganizationEntity extends CommonEntity {
}

View File

@@ -0,0 +1,28 @@
package cn.lihongjie.coal.organization.entity;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
/** DTO for {@link OrganizationEntity} */
@Getter
@Setter
public class OrganizationEntityDto implements Serializable {
private String id;
private String createUserId;
private LocalDateTime createTime;
private String updateUserId;
private LocalDateTime updateTime;
private List<List<String>> 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;
}

View File

@@ -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<OrganizationEntity, Organiz
@Autowired RabbitMQService rabbitMQService;
@Autowired
CacheManager cacheManager;
@PostConstruct
public void init() {}
public void init() {
rabbitMQService.createInstanceQueue(
"organization.cache.invalid",
m -> {
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<OrganizationEntity, Organiz
this.repository.save(entity);
rabbitMQService.sendToSysExchange(
"organization.update", entity.getId(), new HashMap<String, String>());
reloadCache(request.getId());
return getById(entity.getId());
}
@@ -89,10 +121,8 @@ public class OrganizationService extends BaseService<OrganizationEntity, Organiz
dto.setPassword(request.getOrgAdminPassword());
UserDto orgAdmin = userService.createOrgAdmin(dto);
entity.setAdminUser(em.getReference(UserEntity.class, orgAdmin.getId()));
this.repository.save(entity);
rabbitMQService.sendToSysExchange(
@@ -103,6 +133,8 @@ public class OrganizationService extends BaseService<OrganizationEntity, Organiz
public void delete(IdRequest request) {
this.repository.deleteAllById(request.getIds());
reloadCache();
}
public OrganizationDto getById(String id) {
@@ -114,12 +146,11 @@ public class OrganizationService extends BaseService<OrganizationEntity, Organiz
public void resetOrgAdminPassword(ResetAdminPasswordRequest request) {
if (!StringUtils.equals(request.getNewPassword(), request.getNewPassword2())){
if (!StringUtils.equals(request.getNewPassword(), request.getNewPassword2())) {
throw new BizException("两次密码不一致");
}
userService.resetOrgAdminPassword(
request.getId(), request.getNewPassword());
userService.resetOrgAdminPassword(request.getId(), request.getNewPassword());
}
public Page<OrganizationDto> list(CommonQuery query) {
@@ -134,17 +165,18 @@ public class OrganizationService extends BaseService<OrganizationEntity, Organiz
Page<OrganizationDto> map = page.map(this.mapper::toDto);
List<Tuple> 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<Tuple> 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<OrganizationEntity, Organiz
return userService.list(dto);
}
@Cacheable(cacheNames = Constants.CACHE_ORGANIZATION_PERMISSION_IDS, key = "#organizationId")
public Set<String> getDefaultPermissionIds(String organizationId) {
List<String> 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);
}
}

View File

@@ -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<ResourceDto> children;
@Comment("资源类型")
private String type;
@Comment("资源地址")
private String url;
}
}

View File

@@ -12,7 +12,7 @@ import java.util.*;
public class PermissionExportDto extends CommonDto {
private String parentName;
private List<PermissionDto.ResourceDto> resources;
private List<ResourceDto> resources;
private String permissionType;
private String permissionTypeName;

View File

@@ -23,6 +23,6 @@ public interface PermissionRepository extends BaseRepository<PermissionEntity> {
"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<String> 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<String> ids);
}

View File

@@ -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<PermissionEntity, PermissionRepository> {
@Autowired PermissionRepository repository;
@@ -56,16 +57,13 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
public void init() {}
@Autowired ApplicationContext applicationContext;
@Autowired CacheManager cacheManager;
@Autowired
CacheManager cacheManager;
public void delete(IdRequest request) {
if (this.repository.isLinked(request.getIds())) {
throw new BizException("部分数据已被关联,无法删除");
}
this.repository.deleteAllById(request.getIds());
}
@Autowired
PermissionService that;
@Autowired UserService userService;
public PermissionDto getById(String id) {
@@ -224,20 +222,16 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
return all.stream().map(this.mapper::toDto).collect(Collectors.toList());
}
@Autowired OrganizationService organizationService;
@Cacheable(cacheNames = Constants.CACHE_PERMISSION_BY_TYPE, key = "#type")
public List<PermissionDto> 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<PermissionEntity, PermissionR
}
public void clearCache() {
cacheManager.getCache(Constants.CACHE_PERMISSION).clear();
cacheManager.getCache(Constants.CACHE_IS_ANONYMOUS_BY_RESOURCE_ID).clear();
cacheManager.getCache(Constants.CACHE_ORG_ADMIN_HAS_PERMISSION).clear();
userService.clearUserPermissionCache();
}
@Cacheable(cacheNames = Constants.CACHE_ORG_ADMIN_HAS_PERMISSION, key = "#resourceId")
public boolean orgAdminHasPermission( String resourceId) {
public boolean orgAdminHasPermission(String resourceId, String organizationId) {
return this.repository.hasPermission(resourceId, Arrays.asList("0", "1", "2"));
var defaultPermissionIds =
new HashSet<>(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"));
}

View File

@@ -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<String, String> 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(

View File

@@ -45,4 +45,5 @@ public interface ResourceMapper
x -> ObjectUtils.defaultIfNull(x.getSortKey(), 99999)));
}
}
}

View File

@@ -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<ResourceEntity, ResourceRepository> {
@Autowired ResourceRepository repository;
@@ -64,24 +64,37 @@ public class ResourceService extends BaseService<ResourceEntity, ResourceReposit
@Autowired ConversionService conversionService;
@Autowired RequestMappingHandlerMapping requestMappingHandlerMapping;
@PersistenceContext EntityManager entityManager;
@Autowired
CacheManager cacheManager;
@Autowired private RabbitMQService rabbitMQService;
@PostConstruct
public void init() {}
public void init() {
}
@Autowired ApplicationContext applicationContext;
@Autowired CacheManager cacheManager;
@Cacheable(cacheNames = CACHE_RESOURCE_ALL)
public List<ResourceDto> 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<ResourceEntity, ResourceReposit
this.repository.save(entity);
this.clearCache();
return getById(entity.getId());
}
@@ -164,8 +178,8 @@ public class ResourceService extends BaseService<ResourceEntity, ResourceReposit
this.mapper.updateEntity(entity, request);
this.repository.save(entity);
this.clearCache();
return getById(entity.getId());
}
@@ -234,54 +248,47 @@ public class ResourceService extends BaseService<ResourceEntity, ResourceReposit
this.clearCache();
}
@Cacheable(cacheNames = Constants.CACHE_RESOURCE_MENU_TREE)
@Cacheable(cacheNames = CACHE_RESOURCE_MENU_TREE)
public List<ResourceTreeDto> menuTree() {
return this.mapper.toTreeDto(
this.repository.findAllByTypeAndParentIsNullOrderBySortKey("0"));
}
@Cacheable(cacheNames = Constants.CACHE_RESOURCE_API_TREE)
@Cacheable(cacheNames = CACHE_RESOURCE_API_TREE)
public List<ResourceTreeDto> apiTree() {
return this.mapper.toTreeDto(
this.repository.findAllByTypeAndParentIsNullOrderBySortKey("3"));
}
@Cacheable(cacheNames = Constants.CACHE_RESOURCE)
public List<ResourceDto> 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<ResourceDto> 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<ResourceDto> 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();
}
}

View File

@@ -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) {

View File

@@ -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<String, org.redisson.spring.cache.CacheConfig> 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;
}
}

View File

@@ -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> T get(Object key, Class<T> type) {
return ((T)get(key));
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
Object o =
caffeineCache.get(
key,
new Callable<Object>() {
@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<String> getKeys() {
List<String> k1 = caffeineCache.getNativeCache().asMap().keySet().stream().map(x -> x.toString()).toList();
List<String> k2 = redisCache.getNativeCache().keySet().stream().map(x -> x.toString()).toList();
return Stream.ofAll(k1).appendAll(k2).distinct().toJavaList();
}
}

View File

@@ -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<String> getCacheNames() {
return Stream.ofAll(caffeineCacheManager.getCacheNames())
.appendAll(redissonSpringCacheManager.getCacheNames())
.distinct()
.toJavaList();
}
public record MultiLevelCacheEvent(String cacheName, String eventType, String key) {}
}

View File

@@ -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<UserEntity, UserRepository> {
@Autowired PasswordDictService passwordDictService;
private Pbkdf2PasswordEncoder passwordEncoder;
@Autowired
UserService that;
@PostConstruct
public void init() {
@@ -116,6 +122,8 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
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, UserRepository> {
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, UserRepository> {
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<PermissionDto> 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<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
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<UserEntity, UserRepository> {
return user;
}
public boolean hasResource(String userId, String resourceId) {
return this.repository.hasResource(userId, resourceId);
}
public Map<String, String> getIdNameMap(Set<String> strings) {
strings = Stream.ofAll(strings).filter(StringUtils::isNotBlank).toJavaSet();
@@ -448,4 +471,85 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
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<String> userResourceIds(String userId) {
UserDto user = getFromCache(userId);
List<PermissionDto> 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<String> 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<String> 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());
}
}