mirror of
https://codeup.aliyun.com/64f7d6b8ce01efaafef1e678/coal/coal.git
synced 2026-01-25 15:55:18 +08:00
性能优化
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -45,4 +45,5 @@ public interface ResourceMapper
|
||||
x -> ObjectUtils.defaultIfNull(x.getSortKey(), 99999)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user