性能优化

This commit is contained in:
2024-01-13 16:45:00 +08:00
parent 9648d72d0a
commit 529c26e8cc
11 changed files with 209 additions and 68 deletions

View File

@@ -36,6 +36,10 @@ public class Constants {
public static final String CACHE_CLIENT_RANDOM_PREFIX = "clientRandom::";
public static final String HTTP_HEADER_RESPONSE_TIME = "X-Response-Time";
public static final String SYSCONFIG_REQUEST_SIGN_IGNORE_URLS = "request_sign_ignore_urls";
public static final String HTTP_ATTR_RESOURCE = "http_attr_resource";
public static final String CACHE_RESOURCE_BY_URL_2 = "resourceByUrl2";
public static final String CACHE_IS_ANONYMOUS_BY_RESOURCE_ID = "isAnonymousByResourceId";
public static final String CACHE_ORG_ADMIN_HAS_PERMISSION = "orgAdminHasPermission";
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

@@ -85,4 +85,14 @@ public class RequestUtils {
response.getOutputStream().write(Ctx.getContext().getBean(ObjectMapper.class).writeValueAsBytes(fail));
response.getOutputStream().flush();
}
public static String getRequestURI(HttpServletRequest request, String contextPath1) {
if (StringUtils.equalsIgnoreCase(contextPath1, "/")) {
return request.getRequestURI();
} else {
return request.getRequestURI().replace(contextPath1, "/");
}
}
}

View File

@@ -4,7 +4,6 @@ import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.permission.dto.PermissionDto;
import cn.lihongjie.coal.permission.service.PermissionService;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.resource.service.ResourceService;
@@ -16,16 +15,13 @@ import cn.lihongjie.coal.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vavr.collection.Stream;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -33,15 +29,11 @@ import org.springframework.core.annotation.Order;
import org.springframework.http.server.PathContainer;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StopWatch;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.pattern.PathPatternParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@Component
@Order(100)
@@ -63,23 +55,22 @@ public class AuthFilter extends OncePerRequestFilter {
@Autowired PermissionService permissionService;
@Override
public void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
getTransactionStatusConsumer(request, response, filterChain).accept(null);
} catch (Exception e) {
logger.info("系统异常", e);
RequestUtils.writeResponse(new BizException("系统异常"), response);
}
}
@Autowired UserService userService;
@Nullable
private static Optional<ResourceDto> getResourceDto(
HttpServletRequest request, HttpServletResponse response) {
Optional<ResourceDto> resource =
Optional.ofNullable(
(ResourceDto) request.getAttribute(Constants.HTTP_ATTR_RESOURCE));
if (resource.isEmpty()) {
RequestUtils.writeResponse(new BizException("invalidUrl", "资源未找到"), response);
return null;
}
return resource;
}
private static void doFilter(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
try {
@@ -89,10 +80,13 @@ public class AuthFilter extends OncePerRequestFilter {
}
}
private Consumer<TransactionStatus> getTransactionStatusConsumer(
@Override
public void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
StopWatch stopWatch = new StopWatch();
try {
return tx -> {
MDC.remove("user");
if (isMatches(request)) {
@@ -100,34 +94,24 @@ public class AuthFilter extends OncePerRequestFilter {
return;
}
String sessionId = StringUtils.defaultIfEmpty(request.getHeader(Constants.HTTP_HEADER_TOKEN), request.getParameter(Constants.HTTP_HEADER_TOKEN));
stopWatch.start("get resource");
String sessionId =
StringUtils.defaultIfEmpty(
request.getHeader(Constants.HTTP_HEADER_TOKEN),
request.getParameter(Constants.HTTP_HEADER_TOKEN));
Optional<ResourceDto> resource =
resourceService.findUrlFromCache(getRequestURI(request));
Optional<ResourceDto> resource = getResourceDto(request, response);
if (resource.isEmpty()) {
RequestUtils.writeResponse(new BizException("invalidUrl", "资源未找到"), response);
return;
}
stopWatch.stop();
// 未登录用户访问
if (StringUtils.isEmpty(sessionId)) {
// 找到匿名权限
List<PermissionDto> permissions = permissionService.getByTypeFromCache("0");
stopWatch.start("找到匿名权限");
// 找到匿名权限
boolean isAnonymous =
permissions.stream()
.flatMap(
x ->
ObjectUtils.defaultIfNull(
x.getResources(),
new ArrayList<PermissionDto>())
.stream())
.anyMatch(
x ->
StringUtils.equals(
x.getId(), resource.get().getId()))
|| BooleanUtils.isTrue(resource.get().getAnonymous());
permissionService.isAnonymousByResourceId(resource.get().getId());
stopWatch.stop();
if (isAnonymous) {
sessionService.anonymousSession();
@@ -146,8 +130,11 @@ public class AuthFilter extends OncePerRequestFilter {
//
try {
stopWatch.start("重建session");
sessionService.rebuildSession(sessionId);
stopWatch.stop();
} catch (BizException ex) {
RequestUtils.writeResponse(ex, response);
@@ -158,20 +145,38 @@ public class AuthFilter extends OncePerRequestFilter {
var user = Ctx.currentUser();
MDC.put("user", user.getUsername());
Optional<ResourceDto> userResource =
Stream.ofAll(userService.resources(user.getId()))
.filter(x -> StringUtils.equals(x.getId(), resource.get().getId()))
.headOption()
.toJavaOptional();
stopWatch.start("检查权限");
if (userResource.isEmpty() && BooleanUtils.isFalse(user.getSysAdmin())) {
boolean hasPermission = false;
if (BooleanUtils.isTrue(user.getSysAdmin())) {
hasPermission = true;
} else if (BooleanUtils.isTrue(user.getOrgAdmin())) {
RequestUtils.writeResponse(new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response);
hasPermission = permissionService.orgAdminHasPermission(resource.get().getId());
} else {
hasPermission = userService.hasResource(user.getId(), resource.get().getId());
}
stopWatch.stop();
if (!hasPermission) {
RequestUtils.writeResponse(
new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response);
} else {
doFilter(request, response, filterChain);
}
};
} catch (Exception e) {
logger.info("系统异常", e);
RequestUtils.writeResponse(new BizException("系统异常"), response);
} finally {
if (stopWatch.isRunning()) {
stopWatch.stop();
}
logger.info(stopWatch.prettyPrint());
}
}
private boolean isMatches(HttpServletRequest request) {
@@ -181,22 +186,13 @@ public class AuthFilter extends OncePerRequestFilter {
matches =
PathPatternParser.defaultInstance
.parse(url)
.matches(PathContainer.parsePath(getRequestURI(request)));
.matches(
PathContainer.parsePath(
RequestUtils.getRequestURI(request, contextPath)));
if (matches) {
break;
}
}
return matches;
}
private String getRequestURI(HttpServletRequest request) {
if (StringUtils.equalsIgnoreCase(contextPath, "/")) {
return request.getRequestURI();
} else {
return request.getRequestURI().replace(contextPath, "/");
}
}
}

View File

@@ -0,0 +1,57 @@
package cn.lihongjie.coal.filter;
import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.RequestUtils;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.resource.service.ResourceService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Optional;
/**
*
*/
@Component
@Order(-1)
@Slf4j
public class ResourceFilter extends OncePerRequestFilter {
@Autowired ResourceService resourceService;
@Value("${server.servlet.context-path}")
private String contextPath;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Optional<ResourceDto> resource =
resourceService.findUrlFromCache2(RequestUtils.getRequestURI(request, contextPath));
if (resource.isEmpty()) {
RequestUtils.writeResponse(new BizException("invalidUrl", "资源未找到"), response);
return;
}
request.setAttribute(Constants.HTTP_ATTR_RESOURCE, resource.get());
doFilter(request, response, filterChain);
}
}

View File

@@ -3,6 +3,7 @@ package cn.lihongjie.coal.permission.repository;
import cn.lihongjie.coal.base.dao.BaseRepository;
import cn.lihongjie.coal.permission.entity.PermissionEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@@ -12,4 +13,10 @@ public interface PermissionRepository extends BaseRepository<PermissionEntity> {
List<PermissionEntity> findAllByPermissionType(String type);
List<PermissionEntity> findAllByPermissionTypeIn(String[] types);
@Query("select count(p) > 0 from PermissionEntity p inner join p.resources r on r.id = ?1 where p.permissionType = '0' ")
boolean isAnonymousByResourceId(String resourceId);
@Query("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);
}

View File

@@ -36,6 +36,7 @@ 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.stream.Collectors;
@@ -230,5 +231,26 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
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();
}
@Cacheable(cacheNames = Constants.CACHE_IS_ANONYMOUS_BY_RESOURCE_ID, key = "#resourceId")
public boolean isAnonymousByResourceId(String resourceId) {
return this.repository.isAnonymousByResourceId(resourceId);
}
@Cacheable(cacheNames = Constants.CACHE_ORG_ADMIN_HAS_PERMISSION, key = "#resourceId")
public boolean orgAdminHasPermission( String resourceId) {
return this.repository.hasPermission(resourceId, Arrays.asList("0", "1", "2"));
}
}

View File

@@ -36,4 +36,20 @@ public class ResourceDto extends CommonDto {
private String metadata;
private String parent;
public ResourceDto() {
}
public ResourceDto(String id, String code, String name, String type, Boolean anonymous, Boolean orgAdmin, Boolean sysAdmin) {
this.setId(id);
this.setCode(code);
this.setName(name);
this.type = type;
this.orgAdmin = orgAdmin;
this.sysAdmin = sysAdmin;
this.anonymous = anonymous;
}
}

View File

@@ -1,8 +1,10 @@
package cn.lihongjie.coal.resource.repository;
import cn.lihongjie.coal.base.dao.BaseRepository;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.resource.entity.ResourceEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@@ -12,4 +14,9 @@ public interface ResourceRepository extends BaseRepository<ResourceEntity> {
// @EntityGraph(attributePaths = {"children"})
List<ResourceEntity> findAllByTypeAndParentIsNullOrderBySortKey(String type);
ResourceEntity findByUrlAndType(String url, String type);
@Query("select new cn.lihongjie.coal.resource.dto.ResourceDto(r.id, r.code, r.name, r.type, r.anonymous, r.orgAdmin, r.sysAdmin) from ResourceEntity r where r.code = ?1 and r.type = ?2 ")
ResourceDto findByCodeAndType(String code, String type);
}

View File

@@ -240,10 +240,23 @@ public class ResourceService extends BaseService<ResourceEntity, ResourceReposit
.findAny();
}
@Cacheable(cacheNames = Constants.CACHE_RESOURCE_BY_URL_2, key = "#url")
public Optional<ResourceDto> findUrlFromCache2(String url) {
return Optional.ofNullable(repository.findByCodeAndType(url, "3"));
}
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();

View File

@@ -15,4 +15,8 @@ public interface UserRepository extends BaseRepository<UserEntity> {
@Query("select count(u) from UserEntity u where u.organizationId = ?1")
Integer countByOrganizationId(String organizationId);
@Query("select count(u) > 0 from UserEntity u join u.roles r join r.permissions p join p.resources pr where u.id = ?1 and pr.id = ?2")
boolean hasResource(String userId, String resourceId);
}

View File

@@ -390,4 +390,9 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
UserEntity user = users.get(0);
return user;
}
public boolean hasResource(String userId, String resourceId) {
return this.repository.hasResource(userId, resourceId);
}
}