From d305a7ec717ba21c2310e03b7b390d59abc7663e Mon Sep 17 00:00:00 2001 From: lihongjie0209 Date: Tue, 14 Nov 2023 20:23:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=94=A8=E6=88=B7=E7=99=BB?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/lihongjie/coal/common/Constants.java | 11 + .../java/cn/lihongjie/coal/common/Ctx.java | 4 +- .../cn/lihongjie/coal/filter/AuthFilter.java | 129 ++++++----- .../controller/LoginUserController.java | 52 +++++ .../loginUser/dto/CreateLoginUserDto.java | 21 ++ .../coal/loginUser/dto/LoginUserDto.java | 23 ++ .../loginUser/dto/UpdateLoginUserDto.java | 22 ++ .../loginUser/entity/LoginUserEntity.java | 27 +++ .../loginUser/mapper/LoginUserMapper.java | 18 ++ .../repository/LoginUserRepository.java | 27 +++ .../loginUser/service/LoginUserService.java | 202 ++++++++++++++++++ .../permission/service/PermissionService.java | 85 +++++--- .../resource/service/ResourceService.java | 145 ++++++++----- .../lihongjie/coal/runner/InitDataRunner.java | 6 +- .../coal/session/SessionService.java | 101 +++++---- .../sysconfig/service/SysConfigService.java | 27 +++ .../cn/lihongjie/coal/user/dto/UserDto.java | 5 + .../coal/user/service/UserService.java | 11 +- 18 files changed, 705 insertions(+), 211 deletions(-) create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/controller/LoginUserController.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/dto/CreateLoginUserDto.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/dto/LoginUserDto.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/dto/UpdateLoginUserDto.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/entity/LoginUserEntity.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/mapper/LoginUserMapper.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/repository/LoginUserRepository.java create mode 100644 src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java diff --git a/src/main/java/cn/lihongjie/coal/common/Constants.java b/src/main/java/cn/lihongjie/coal/common/Constants.java index 812ec134..458eae3c 100644 --- a/src/main/java/cn/lihongjie/coal/common/Constants.java +++ b/src/main/java/cn/lihongjie/coal/common/Constants.java @@ -3,5 +3,16 @@ package cn.lihongjie.coal.common; import java.util.*; public class Constants { + public static final String CACHE_RESOURCE = "resource"; + public static final String CACHE_USER_PERMISSIONS = "userPermissions"; + public static final String CACHE_USER_RESOURCES = "userResources"; + public static final String CACHE_RESOURCE_BY_URL = "resourceByUrl"; + public static final String CACHE_PERMISSION = "permission"; + public static final String CACHE_PERMISSION_BY_TYPE = "permissionByType"; + 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 String SYSCONFIG_ENABLE_CAPTCHA = "enable_captcha"; + public static String SYSCONFIG_SESSION_TIMEOUT = "session_timeout"; + public static String SYSCONFIG_ACCOUNT_MAX_ONLINE = "account_max_online"; } diff --git a/src/main/java/cn/lihongjie/coal/common/Ctx.java b/src/main/java/cn/lihongjie/coal/common/Ctx.java index fa374598..1c9b58ec 100644 --- a/src/main/java/cn/lihongjie/coal/common/Ctx.java +++ b/src/main/java/cn/lihongjie/coal/common/Ctx.java @@ -1,7 +1,7 @@ package cn.lihongjie.coal.common; import cn.lihongjie.coal.session.SessionService; -import cn.lihongjie.coal.user.entity.UserEntity; +import cn.lihongjie.coal.user.dto.UserDto; import lombok.experimental.UtilityClass; @@ -38,7 +38,7 @@ public class Ctx { return getAuthentication().getUser().getSysAdmin(); } - public static UserEntity currentUser() { + public static UserDto currentUser() { return getAuthentication().getUser(); } } diff --git a/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java b/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java index 38cdc739..d5e1894f 100644 --- a/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java +++ b/src/main/java/cn/lihongjie/coal/filter/AuthFilter.java @@ -3,15 +3,14 @@ package cn.lihongjie.coal.filter; import cn.lihongjie.coal.base.dto.R; import cn.lihongjie.coal.common.Ctx; import cn.lihongjie.coal.exception.BizException; -import cn.lihongjie.coal.permission.entity.PermissionEntity; +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.entity.ResourceEntity; import cn.lihongjie.coal.resource.service.ResourceService; import cn.lihongjie.coal.role.service.RoleService; import cn.lihongjie.coal.session.SessionService; import cn.lihongjie.coal.spring.config.SystemConfig; -import cn.lihongjie.coal.user.entity.UserEntity; +import cn.lihongjie.coal.user.dto.UserDto; import cn.lihongjie.coal.user.service.UserService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -92,6 +91,15 @@ public class AuthFilter extends OncePerRequestFilter { @Autowired UserService userService; + private static void doFilter( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { + try { + filterChain.doFilter(request, response); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private Consumer getTransactionStatusConsumer( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { @@ -99,97 +107,80 @@ public class AuthFilter extends OncePerRequestFilter { MDC.remove("user"); if (isMatches(request)) { - try { - filterChain.doFilter(request, response); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } + doFilter(request, response, filterChain); return; } String sessionId = request.getHeader("X-Token"); - Optional resource = resourceService.findUrl(getRequestURI(request)); + Optional resource = + resourceService.findUrlFromCache(getRequestURI(request)); if (resource.isEmpty()) { writeResponse(new BizException("invalidUrl", "资源未找到"), response); return; } - request.setAttribute("__resourceEntity", resource.get()); - + // 未登录用户访问 if (StringUtils.isEmpty(sessionId)) { // 找到匿名权限 - List permissions = permissionService.getByType("0"); + List permissions = permissionService.getByTypeFromCache("0"); - if (permissions.stream() - .flatMap( - x -> - ObjectUtils.defaultIfNull( - x.getResources(), - new ArrayList()) - .stream()) - .anyMatch( - x -> StringUtils.equals(x.getId(), resource.get().getId())) - || BooleanUtils.isTrue(resource.get().getAnonymous())) { + boolean isAnonymous = + permissions.stream() + .flatMap( + x -> + ObjectUtils.defaultIfNull( + x.getResources(), + new ArrayList()) + .stream()) + .anyMatch( + x -> + StringUtils.equals( + x.getId(), resource.get().getId())) + || BooleanUtils.isTrue(resource.get().getAnonymous()); + if (isAnonymous) { sessionService.anonymousSession(); - UserEntity user = Ctx.currentUser(); + UserDto user = Ctx.currentUser(); MDC.put("user", user.getUsername()); - try { - filterChain.doFilter(request, response); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } + doFilter(request, response, filterChain); } else { writeResponse(new BizException("loginRequired", "请先登录"), response); } + return; + } + + // + try { + + sessionService.rebuildSession(sessionId); + + } catch (BizException ex) { + + writeResponse(ex, response); + + return; + } + + var user = Ctx.currentUser(); + MDC.put("user", user.getUsername()); + + Optional userResource = + Stream.ofAll(userService.resources(user.getId())) + .filter(x -> StringUtils.equals(x.getId(), resource.get().getId())) + .headOption() + .toJavaOptional(); + + if (userResource.isEmpty() && BooleanUtils.isFalse(user.getSysAdmin())) { + + writeResponse(new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response); } else { - try { - - sessionService.rebuildSession(sessionId); - - } catch (BizException ex) { - - writeResponse(ex, response); - - return; - } - - UserEntity user = Ctx.currentUser(); - MDC.put("user", user.getUsername()); - - Optional userResource = - Stream.ofAll(userService.resources(user.getId())) - .filter(x -> StringUtils.equals(x.getId(), resource.get().getId())) - .headOption() - .toJavaOptional(); - - if (userResource.isEmpty() && BooleanUtils.isFalse(user.getSysAdmin())) { - - writeResponse( - new BizException("invalidAccess", "当前资源未授权,请联系机构管理员处理。"), response); - } else { - - try { - filterChain.doFilter(request, response); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (ServletException e) { - throw new RuntimeException(e); - } catch (Exception e) { - logger.warn(e); - throw e; - } - } + doFilter(request, response, filterChain); } }; } diff --git a/src/main/java/cn/lihongjie/coal/loginUser/controller/LoginUserController.java b/src/main/java/cn/lihongjie/coal/loginUser/controller/LoginUserController.java new file mode 100644 index 00000000..b4d07b4c --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/controller/LoginUserController.java @@ -0,0 +1,52 @@ +package cn.lihongjie.coal.loginUser.controller; + +import cn.lihongjie.coal.annotation.SysLog; +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.loginUser.dto.CreateLoginUserDto; +import cn.lihongjie.coal.loginUser.dto.LoginUserDto; +import cn.lihongjie.coal.loginUser.dto.UpdateLoginUserDto; +import cn.lihongjie.coal.loginUser.service.LoginUserService; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/loginUser") +@SysLog(module = "登录用户") +@Slf4j +public class LoginUserController { + @Autowired private LoginUserService service; + + @PostMapping("/create") + public LoginUserDto create(@RequestBody CreateLoginUserDto request) { + return this.service.create(request); + } + + @PostMapping("/update") + public LoginUserDto update(@RequestBody UpdateLoginUserDto request) { + return this.service.update(request); + } + + @PostMapping("/delete") + public Object delete(@RequestBody IdRequest request) { + this.service.delete(request); + return true; + } + + @PostMapping("/getById") + public LoginUserDto getById(@RequestBody IdRequest request) { + return this.service.getById(request.getId()); + } + + @PostMapping("/list") + public Page list(@RequestBody CommonQuery request) { + return this.service.list(request); + } +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/dto/CreateLoginUserDto.java b/src/main/java/cn/lihongjie/coal/loginUser/dto/CreateLoginUserDto.java new file mode 100644 index 00000000..fe4726fd --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/dto/CreateLoginUserDto.java @@ -0,0 +1,21 @@ +package cn.lihongjie.coal.loginUser.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; +import cn.lihongjie.coal.user.dto.UserDto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class CreateLoginUserDto extends CommonDto { + private UserDto user; + + private String ip; + private String location; + private String ua; + private String captcha; + private Integer timeout; + private LocalDateTime loginTime; + private LocalDateTime expireTime; +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/dto/LoginUserDto.java b/src/main/java/cn/lihongjie/coal/loginUser/dto/LoginUserDto.java new file mode 100644 index 00000000..7e65d741 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/dto/LoginUserDto.java @@ -0,0 +1,23 @@ +package cn.lihongjie.coal.loginUser.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; +import cn.lihongjie.coal.user.dto.UserDto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class LoginUserDto extends CommonDto { + + private UserDto user; + + private String ip; + private String location; + private String ua; + private String captcha; + + private Integer timeout; + private LocalDateTime loginTime; + private LocalDateTime expireTime; +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/dto/UpdateLoginUserDto.java b/src/main/java/cn/lihongjie/coal/loginUser/dto/UpdateLoginUserDto.java new file mode 100644 index 00000000..8effc105 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/dto/UpdateLoginUserDto.java @@ -0,0 +1,22 @@ +package cn.lihongjie.coal.loginUser.dto; + +import cn.lihongjie.coal.base.dto.CommonDto; +import cn.lihongjie.coal.user.dto.UserDto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class UpdateLoginUserDto extends CommonDto { + private UserDto user; + + private String ip; + private String location; + private String ua; + private String captcha; + + private Integer timeout; + private LocalDateTime loginTime; + private LocalDateTime expireTime; +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/entity/LoginUserEntity.java b/src/main/java/cn/lihongjie/coal/loginUser/entity/LoginUserEntity.java new file mode 100644 index 00000000..22ae1a0c --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/entity/LoginUserEntity.java @@ -0,0 +1,27 @@ +package cn.lihongjie.coal.loginUser.entity; + +import cn.lihongjie.coal.base.entity.CommonEntity; +import cn.lihongjie.coal.user.entity.UserEntity; + +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@Entity +public class LoginUserEntity extends CommonEntity { + + @ManyToOne private UserEntity user; + + private String ip; + private String location; + private String ua; + private String captcha; + + private Integer timeout; + private LocalDateTime loginTime; + private LocalDateTime expireTime; +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/mapper/LoginUserMapper.java b/src/main/java/cn/lihongjie/coal/loginUser/mapper/LoginUserMapper.java new file mode 100644 index 00000000..5d3b9b03 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/mapper/LoginUserMapper.java @@ -0,0 +1,18 @@ +package cn.lihongjie.coal.loginUser.mapper; + +import cn.lihongjie.coal.base.mapper.BaseMapper; +import cn.lihongjie.coal.base.mapper.CommonMapper; +import cn.lihongjie.coal.loginUser.dto.CreateLoginUserDto; +import cn.lihongjie.coal.loginUser.dto.LoginUserDto; +import cn.lihongjie.coal.loginUser.dto.UpdateLoginUserDto; +import cn.lihongjie.coal.loginUser.entity.LoginUserEntity; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; + +@Mapper( + componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING, + uses = {CommonMapper.class}, + mappingControl = DeepClone.class) +public interface LoginUserMapper + extends BaseMapper {} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/repository/LoginUserRepository.java b/src/main/java/cn/lihongjie/coal/loginUser/repository/LoginUserRepository.java new file mode 100644 index 00000000..e54804aa --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/repository/LoginUserRepository.java @@ -0,0 +1,27 @@ +package cn.lihongjie.coal.loginUser.repository; + +import cn.lihongjie.coal.base.dao.BaseRepository; +import cn.lihongjie.coal.loginUser.entity.LoginUserEntity; +import cn.lihongjie.coal.user.entity.UserEntity; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface LoginUserRepository extends BaseRepository { + @Transactional + @Modifying + @Query("update LoginUserEntity l set l.expireTime = ?1 where l.id = ?2") + void updateExpireTimeById(LocalDateTime expireTime, String id); + + List findAllByUser(UserEntity user); + + int deleteAllByExpireTimeBefore(LocalDateTime now); + + List findAllByExpireTimeBefore(LocalDateTime now); +} diff --git a/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java new file mode 100644 index 00000000..16459706 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/loginUser/service/LoginUserService.java @@ -0,0 +1,202 @@ +package cn.lihongjie.coal.loginUser.service; + +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.loginUser.dto.CreateLoginUserDto; +import cn.lihongjie.coal.loginUser.dto.LoginUserDto; +import cn.lihongjie.coal.loginUser.dto.UpdateLoginUserDto; +import cn.lihongjie.coal.loginUser.entity.LoginUserEntity; +import cn.lihongjie.coal.loginUser.mapper.LoginUserMapper; +import cn.lihongjie.coal.loginUser.repository.LoginUserRepository; +import cn.lihongjie.coal.sysconfig.service.SysConfigService; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationContext; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class LoginUserService extends BaseService { + public static final String CACHE_LOGIN_USER_BY_ID = "loginUserById"; + @Autowired SysConfigService sysConfigService; + @Autowired ApplicationContext applicationContext; + @Autowired CacheManager cacheManager; + @Autowired private LoginUserRepository repository; + @Autowired private LoginUserMapper mapper; + @Autowired private ConversionService conversionService; + private ScheduledExecutorService executorService; + + @PostConstruct + public void init() { + + executorService = Executors.newSingleThreadScheduledExecutor(); + + executorService.scheduleAtFixedRate(this::deleteExpireSession, 1, 1, TimeUnit.MINUTES); + } + + @SneakyThrows + @PreDestroy + public void destroy() { + executorService.shutdown(); + executorService.awaitTermination(1, TimeUnit.MINUTES); + } + + public LoginUserDto create(CreateLoginUserDto request) { + LoginUserEntity entity = mapper.toEntity(request); + + this.repository.save(entity); + return getById(entity.getId()); + } + + public LoginUserDto update(UpdateLoginUserDto request) { + LoginUserEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + + return getById(entity.getId()); + } + + public void delete(IdRequest request) { + this.repository.deleteAllById(request.getIds()); + } + + public LoginUserDto getById(String id) { + LoginUserEntity entity = repository.get(id); + + return mapper.toDto(entity); + } + + public Page list(CommonQuery query) { + Page page = + repository.findAll( + query.specification(conversionService), + PageRequest.of( + query.getPageNo(), + query.getPageSize(), + Sort.by(query.getOrders()))); + + return page.map(this.mapper::toDto); + } + + public void newLogin(LoginUserEntity entity) { + + entity.setLoginTime(LocalDateTime.now()); + int seconds = + Integer.parseInt( + sysConfigService.getConfigVal(Constants.SYSCONFIG_SESSION_TIMEOUT)); + entity.setTimeout(seconds); + entity.setExpireTime(LocalDateTime.now().plusSeconds(seconds)); + + List exists = this.repository.findAllByUser(entity.getUser()); + + if (!exists.isEmpty()) { + + int maxOnlineAccount = + Integer.parseInt( + sysConfigService.getConfigVal(Constants.SYSCONFIG_ACCOUNT_MAX_ONLINE)); + + if (exists.size() >= maxOnlineAccount) { + List toDelete = + exists.stream() + .sorted(Comparator.comparing(LoginUserEntity::getLoginTime)) + .limit(exists.size() - maxOnlineAccount + 1) + .collect(Collectors.toList()); + log.warn( + "用户 {} 已经在其他地方登录, 之前 {}个 登录将被强制下线", + entity.getUser().getUsername(), + toDelete.size()); + this.repository.deleteAll(toDelete); + } + } + + this.save(entity); + } + + public void deleteExpireSession() { + + List list = this.repository.findAllByExpireTimeBefore(LocalDateTime.now()); + if (CollectionUtils.isNotEmpty(list)) { + log.info("删除过期会话 {} 个", list.size()); + for (LoginUserEntity user : list) { + this.clearCache(user.getId()); + } + } + } + + public void touch(LoginUserDto loginDto) { + + Integer timeout = loginDto.getTimeout(); + + LocalDateTime expireTime = loginDto.getExpireTime(); + + LocalDateTime now = LocalDateTime.now(); + + if (now.isAfter(expireTime.minusSeconds(timeout / 2))) { + + LocalDateTime newExpireTime = + LocalDateTime.now() + .plusSeconds( + Integer.parseInt( + sysConfigService.getConfigVal( + Constants.SYSCONFIG_SESSION_TIMEOUT))); + this.repository.updateExpireTimeById(newExpireTime, loginDto.getId()); + + log.info("更新会话过期时间: {} from {} to {}", loginDto.getId(), expireTime, newExpireTime); + } + } + + @Cacheable(cacheNames = CACHE_LOGIN_USER_BY_ID, key = "#entity.id") + public LoginUserDto getFromCache(LoginUserEntity entity) { + + LoginUserDto dto = this.mapper.toDto(entity); + + return dto; + } + + public void clearCache(String id) { + if (id == null) { + this.cacheManager.getCache(CACHE_LOGIN_USER_BY_ID).clear(); + } else { + + this.cacheManager.getCache(CACHE_LOGIN_USER_BY_ID).evict(id); + } + } + + @Cacheable(cacheNames = CACHE_LOGIN_USER_BY_ID, key = "#id") + public LoginUserDto getFromCache(String id) { + + LoginUserDto dto = this.mapper.toDto(this.get(id)); + + return dto; + } + + public void deleteLogin(String sessionId) { + + this.repository.deleteById(sessionId); + this.clearCache(sessionId); + } +} diff --git a/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java b/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java index d65fd9e8..1ed361a6 100644 --- a/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java +++ b/src/main/java/cn/lihongjie/coal/permission/service/PermissionService.java @@ -3,6 +3,7 @@ package cn.lihongjie.coal.permission.service; 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.permission.dto.CreatePermissionDto; import cn.lihongjie.coal.permission.dto.PermissionDto; import cn.lihongjie.coal.permission.dto.PermissionExportDto; @@ -22,7 +23,11 @@ import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -46,26 +51,8 @@ public class PermissionService extends BaseService getByType(String type) { + return this.repository.findAllByPermissionType(type); + } + + public List getByTypes(String[] types) { + return this.repository.findAllByPermissionTypeIn(types); + } + + public PermissionDto update(UpdatePermissionDto request) { + PermissionEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + this.clearCache(); + + return getById(entity.getId()); + } + public void importAll(PermissionImportDto dto) { List all = resourceService.findAll(); @@ -165,14 +179,8 @@ public class PermissionService extends BaseService getByType(String type) { - return this.repository.findAllByPermissionType(type); - } - - public List getByTypes(String[] types) { - return this.repository.findAllByPermissionTypeIn(types); + this.clearCache(); } public void initDefault() { @@ -197,5 +205,28 @@ public class PermissionService extends BaseService getAllFromCache() { + + List all = this.findAll(); + + return all.stream().map(this.mapper::toDto).collect(Collectors.toList()); + } + + @Cacheable(cacheNames = Constants.CACHE_PERMISSION_BY_TYPE, key = "#type") + public List getByTypeFromCache(String type) { + + return applicationContext.getBean(this.getClass()).getAllFromCache().stream() + .filter(x -> StringUtils.equals(x.getPermissionType(), type)) + .collect(Collectors.toList()); + } + + public void clearCache() { + cacheManager.getCache(Constants.CACHE_PERMISSION).clear(); + cacheManager.getCache(Constants.CACHE_PERMISSION_BY_TYPE).clear(); } } diff --git a/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java b/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java index 073562af..3064691f 100644 --- a/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java +++ b/src/main/java/cn/lihongjie/coal/resource/service/ResourceService.java @@ -3,13 +3,13 @@ package cn.lihongjie.coal.resource.service; 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.resource.dto.CreateResourceDto; -import cn.lihongjie.coal.resource.dto.ResourceDto; -import cn.lihongjie.coal.resource.dto.ResourceTreeDto; -import cn.lihongjie.coal.resource.dto.UpdateResourceDto; +import cn.lihongjie.coal.common.Constants; +import cn.lihongjie.coal.permission.service.PermissionService; +import cn.lihongjie.coal.resource.dto.*; import cn.lihongjie.coal.resource.entity.ResourceEntity; import cn.lihongjie.coal.resource.mapper.ResourceMapper; import cn.lihongjie.coal.resource.repository.ResourceRepository; +import cn.lihongjie.coal.user.service.UserService; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; @@ -25,6 +25,9 @@ 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.Cacheable; +import org.springframework.context.ApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -41,6 +44,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; @Service @Slf4j @@ -56,33 +60,8 @@ public class ResourceService extends BaseService getAllUrls() { + Map handlerMethods = + requestMappingHandlerMapping.getHandlerMethods(); + List urls = new ArrayList<>(); + for (Map.Entry entry : handlerMethods.entrySet()) { + + RequestMappingInfo info = entry.getKey(); + HandlerMethod method = entry.getValue(); + if (!method.getMethod() + .getDeclaringClass() + .getCanonicalName() + .startsWith("cn.lihongjie")) { + continue; + } + for (PathPattern pattern : info.getPathPatternsCondition().getPatterns()) { + String ps = pattern.getPatternString(); + + urls.add(ps); + } + } + + return urls; + } + @Autowired UserService userService; + + public ResourceDto create(CreateResourceDto request) { + + ResourceEntity entity = mapper.toEntity(request); + + this.repository.save(entity); + this.clearCache(); + return getById(entity.getId()); + } + + public ResourceDto update(UpdateResourceDto request) { + ResourceEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + + this.clearCache(); + return getById(entity.getId()); + } @Transactional public void initUrlResource() { @@ -177,40 +201,51 @@ public class ResourceService extends BaseService getAllUrls() { - Map handlerMethods = - requestMappingHandlerMapping.getHandlerMethods(); - List urls = new ArrayList<>(); - for (Map.Entry entry : handlerMethods.entrySet()) { - - RequestMappingInfo info = entry.getKey(); - HandlerMethod method = entry.getValue(); - if (!method.getMethod() - .getDeclaringClass() - .getCanonicalName() - .startsWith("cn.lihongjie")) { - continue; - } - for (PathPattern pattern : info.getPathPatternsCondition().getPatterns()) { - String ps = pattern.getPatternString(); - - urls.add(ps); - } - } - - return urls; - } - + @Cacheable(cacheNames = Constants.CACHE_RESOURCE_MENU_TREE) public List menuTree() { return this.mapper.toTreeDto( this.repository.findAllByTypeAndParentIsNullOrderBySortKey("0")); } + @Cacheable(cacheNames = Constants.CACHE_RESOURCE_API_TREE) public List apiTree() { return this.mapper.toTreeDto( this.repository.findAllByTypeAndParentIsNullOrderBySortKey("3")); } + + @Cacheable(cacheNames = Constants.CACHE_RESOURCE) + public List resourcesFromCache() { + return this.repository.findAll().stream() + .map(re -> mapper.toDto(re)) + .collect(Collectors.toList()); + } + + @Cacheable(cacheNames = Constants.CACHE_RESOURCE_BY_URL, key = "#url") + public Optional findUrlFromCache(String url) { + + return applicationContext.getBean(this.getClass()).resourcesFromCache().stream() + .filter( + x -> { + return StringUtils.equals(x.getCode(), url) + && StringUtils.equals(x.getType(), "3"); + }) + .findAny(); + } + + public void clearCache() { + + cacheManager.getCache(Constants.CACHE_RESOURCE).clear(); + cacheManager.getCache(Constants.CACHE_RESOURCE_BY_URL).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(); + } } diff --git a/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java b/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java index b3cc17a6..f6552cc5 100644 --- a/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java +++ b/src/main/java/cn/lihongjie/coal/runner/InitDataRunner.java @@ -10,6 +10,7 @@ import cn.lihongjie.coal.script.service.ScriptService; import cn.lihongjie.coal.session.SessionService; import cn.lihongjie.coal.sysconfig.service.SysConfigService; import cn.lihongjie.coal.user.entity.UserEntity; +import cn.lihongjie.coal.user.mapper.UserMapper; import cn.lihongjie.coal.user.service.UserService; import lombok.extern.slf4j.Slf4j; @@ -44,6 +45,8 @@ public class InitDataRunner implements CommandLineRunner { @Autowired PermissionService permissionService; + @Autowired UserMapper userMapper; + @Override @Transactional public void run(String... args) throws Exception { @@ -56,7 +59,8 @@ public class InitDataRunner implements CommandLineRunner { SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(new SessionService.MyAuthentication(null, u, "")); + context.setAuthentication( + new SessionService.MyAuthentication(null, userMapper.toDto(u), "")); SecurityContextHolder.setContext(context); diff --git a/src/main/java/cn/lihongjie/coal/session/SessionService.java b/src/main/java/cn/lihongjie/coal/session/SessionService.java index 1c056e40..f7880688 100644 --- a/src/main/java/cn/lihongjie/coal/session/SessionService.java +++ b/src/main/java/cn/lihongjie/coal/session/SessionService.java @@ -4,13 +4,17 @@ 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.ip.IpQueryService; +import cn.lihongjie.coal.loginUser.dto.LoginUserDto; +import cn.lihongjie.coal.loginUser.entity.LoginUserEntity; +import cn.lihongjie.coal.loginUser.service.LoginUserService; import cn.lihongjie.coal.organization.entity.OrganizationEntity; import cn.lihongjie.coal.organization.service.OrganizationService; import cn.lihongjie.coal.sysconfig.service.SysConfigService; +import cn.lihongjie.coal.user.dto.UserDto; import cn.lihongjie.coal.user.entity.UserEntity; import cn.lihongjie.coal.user.service.UserService; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.wf.captcha.SpecCaptcha; import com.wf.captcha.base.Captcha; @@ -48,6 +52,8 @@ public class SessionService { @Autowired OrganizationService organizationService; @Autowired ObjectMapper objectMapper; + @Autowired LoginUserService loginUserService; + /** * 生成验证码 * @@ -76,6 +82,8 @@ public class SessionService { return new CaptchaDto(id, base64); } + @Autowired IpQueryService ipQueryService; + @SneakyThrows public void login(LoginDto dto) { HttpServletRequest request = @@ -93,7 +101,8 @@ public class SessionService { } if (!StringUtils.equals(expectCaptcha, dto.getCaptcha())) { - throw new BizException("验证码错误"); + stringRedisTemplate.opsForValue().getAndDelete(captchaId); + throw new BizException("验证码错误, 请刷新验证码重试"); } } @@ -115,21 +124,29 @@ public class SessionService { throw new BizException("用户名或者密码错误"); } - String sessionId = UUID.randomUUID().toString(); + LoginUserEntity entity = new LoginUserEntity(); + entity.setUser(user); + entity.setCaptcha(dto.getCaptcha()); + entity.setIp(RequestUtils.getIp(request)); + entity.setUa(RequestUtils.getUa(request)); + try { + + entity.setLocation(ipQueryService.query(entity.getIp())); + } catch (Exception e) { + log.warn("查询ip地址失败 {}", entity.getIp(), e); + } + loginUserService.newLogin(entity); + + LoginUserDto loginUserDto = loginUserService.getFromCache(entity); + + String sessionId = entity.getId(); SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(new MyAuthentication(dto, user, sessionId)); + context.setAuthentication( + new MyAuthentication(loginUserDto, loginUserDto.getUser(), sessionId)); SecurityContextHolder.setContext(context); - dto.setUserId(user.getId()); - dto.setSessionId(sessionId); - dto.setUa(RequestUtils.getUa(request)); - dto.setIp(RequestUtils.getIp(request)); - - stringRedisTemplate - .opsForValue() - .set(sessionId, objectMapper.writeValueAsString(dto), 1, TimeUnit.HOURS); } @SneakyThrows @@ -140,18 +157,14 @@ public class SessionService { return; } - String jsonstring = - stringRedisTemplate.opsForValue().getAndExpire(sessionId, 1, TimeUnit.HOURS); - - if (StringUtils.isEmpty(jsonstring)) { - throw new BizException("invalidToken", "会话已过期,请重新登录"); - } - LoginDto loginDto = null; - + LoginUserDto loginDto; try { - loginDto = objectMapper.readValue(jsonstring, LoginDto.class); - } catch (JsonProcessingException e) { - throw new BizException("invalidToken", "会话异常,请重新登录"); + + loginDto = loginUserService.getFromCache(sessionId); + } catch (Exception e) { + log.warn("会话已过期 {}", sessionId); + logout(sessionId); + throw new BizException("invalidToken", "会话已过期,请重新登录"); } HttpServletRequest request = @@ -161,37 +174,23 @@ public class SessionService { String currentUa = RequestUtils.getUa(request); if (!StringUtils.equalsIgnoreCase(currentIp, loginDto.getIp())) { - // 针对企业双线接入的情况, 允许使用两个IP登录 - if (StringUtils.isEmpty(loginDto.getIp2())) { + // 如果两个ip都不匹配, 则认为是异常登录 - loginDto.setIp2(currentIp); - // 写入新的ip地址 - stringRedisTemplate - .opsForValue() - .set( - sessionId, - objectMapper.writeValueAsString(loginDto), - 1, - TimeUnit.HOURS); - } else { - // 如果两个ip都不匹配, 则认为是异常登录 - if (!StringUtils.equalsIgnoreCase(currentIp, loginDto.getIp2())) { + log.warn("检测到IP变化: {} {}", loginDto, currentIp); - log.warn("检测到IP变化: {} {}", loginDto, currentIp); - - logout(loginDto.getSessionId()); - throw new BizException("invalidToken", "检测到IP发生变化,请重新登录"); - } - } + logout(loginDto.getId()); + throw new BizException("invalidToken", "检测到IP发生变化,请重新登录"); } if (!StringUtils.equalsIgnoreCase(currentUa, loginDto.getUa())) { log.warn("检测到浏览器变化: {} {}", loginDto, currentUa); - logout(loginDto.getSessionId()); + logout(loginDto.getId()); throw new BizException("invalidToken", "检测到浏览器发生变化,请重新登录"); } - UserEntity user = userService.get(loginDto.getUserId()); + loginUserService.touch(loginDto); + + UserDto user = loginDto.getUser(); SecurityContext context = SecurityContextHolder.createEmptyContext(); @@ -204,7 +203,7 @@ public class SessionService { if (sessionId != null) { - stringRedisTemplate.opsForValue().getAndDelete(sessionId); + loginUserService.deleteLogin(sessionId); try { userService.clearUserCache(Ctx.currentUser().getId()); @@ -214,13 +213,11 @@ public class SessionService { } } - - public void anonymousSession() { SecurityContext context = SecurityContextHolder.createEmptyContext(); - UserEntity user = new UserEntity(); + UserDto user = new UserDto(); user.setName("匿名用户"); user.setUsername("anonymous"); context.setAuthentication(new MyAuthentication(null, user, "-1")); @@ -230,11 +227,11 @@ public class SessionService { @Data public static class MyAuthentication implements Authentication { - private final LoginDto dto; - private final UserEntity user; + private final LoginUserDto dto; + private final UserDto user; private final String sessionId; - public MyAuthentication(LoginDto dto, UserEntity user, String sessionId) { + public MyAuthentication(LoginUserDto dto, UserDto user, String sessionId) { this.dto = dto; this.user = user; this.sessionId = sessionId; diff --git a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java index dccb19a6..f6ada294 100644 --- a/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java +++ b/src/main/java/cn/lihongjie/coal/sysconfig/service/SysConfigService.java @@ -33,6 +33,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -66,6 +67,32 @@ public class SysConfigService extends BaseService roles; private List otherRoles; + + @Data public static class RoleDto extends OrgCommonDto {} } diff --git a/src/main/java/cn/lihongjie/coal/user/service/UserService.java b/src/main/java/cn/lihongjie/coal/user/service/UserService.java index 45a7f3f9..fb3f7c05 100644 --- a/src/main/java/cn/lihongjie/coal/user/service/UserService.java +++ b/src/main/java/cn/lihongjie/coal/user/service/UserService.java @@ -4,6 +4,7 @@ import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.entity.BaseEntity; import cn.lihongjie.coal.base.service.BaseService; +import cn.lihongjie.coal.common.Constants; import cn.lihongjie.coal.common.Ctx; import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.organization.entity.OrganizationEntity; @@ -123,7 +124,7 @@ public class UserService extends BaseService { } } - @CacheEvict(cacheNames = "userResources", key = "#request.id") + @CacheEvict(cacheNames = Constants.CACHE_USER_RESOURCES, key = "#request.id") public UserDto update(UpdateUserDto request) { UserEntity user = this.repository.get(request.getId()); this.mapper.updateEntity(user, request); @@ -238,7 +239,7 @@ public class UserService extends BaseService { @Autowired CacheManager cacheManager; - @Cacheable(cacheNames = "userResources", key = "#id") + @Cacheable(cacheNames = Constants.CACHE_USER_RESOURCES, key = "#id") public List resources(String id) { UserEntity user = get(id); @@ -267,7 +268,7 @@ public class UserService extends BaseService { @Autowired PermissionMapper permissionMapper; - @Cacheable(cacheNames = "userPermissions", key = "#id") + @Cacheable(cacheNames = Constants.CACHE_USER_PERMISSIONS, key = "#id") public List permissions(String id) { UserEntity user = get(id); @@ -293,13 +294,13 @@ public class UserService extends BaseService { public void clearUserCache(String id) { try { - cacheManager.getCache("userResources").evict(id); + cacheManager.getCache(Constants.CACHE_USER_RESOURCES).evict(id); } catch (Exception e) { log.error("清除用户资源缓存失败", e); } try { - cacheManager.getCache("userPermissions").evict(id); + cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).evict(id); } catch (Exception e) { log.error("清除用户权限缓存失败", e); }