完善用户登录

This commit is contained in:
2023-11-14 20:23:55 +08:00
parent 1e639513f1
commit d305a7ec71
18 changed files with 705 additions and 211 deletions

View File

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

View File

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

View File

@@ -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<TransactionStatus> 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<ResourceEntity> resource = resourceService.findUrl(getRequestURI(request));
Optional<ResourceDto> resource =
resourceService.findUrlFromCache(getRequestURI(request));
if (resource.isEmpty()) {
writeResponse(new BizException("invalidUrl", "资源未找到"), response);
return;
}
request.setAttribute("__resourceEntity", resource.get());
// 未登录用户访问
if (StringUtils.isEmpty(sessionId)) {
// 找到匿名权限
List<PermissionEntity> permissions = permissionService.getByType("0");
List<PermissionDto> permissions = permissionService.getByTypeFromCache("0");
if (permissions.stream()
.flatMap(
x ->
ObjectUtils.defaultIfNull(
x.getResources(),
new ArrayList<ResourceEntity>())
.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<PermissionDto>())
.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<ResourceDto> 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<ResourceDto> 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);
}
};
}

View File

@@ -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<LoginUserDto> list(@RequestBody CommonQuery request) {
return this.service.list(request);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<LoginUserEntity, LoginUserDto, CreateLoginUserDto, UpdateLoginUserDto> {}

View File

@@ -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<LoginUserEntity> {
@Transactional
@Modifying
@Query("update LoginUserEntity l set l.expireTime = ?1 where l.id = ?2")
void updateExpireTimeById(LocalDateTime expireTime, String id);
List<LoginUserEntity> findAllByUser(UserEntity user);
int deleteAllByExpireTimeBefore(LocalDateTime now);
List<LoginUserEntity> findAllByExpireTimeBefore(LocalDateTime now);
}

View File

@@ -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<LoginUserEntity, LoginUserRepository> {
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<LoginUserDto> list(CommonQuery query) {
Page<LoginUserEntity> 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<LoginUserEntity> 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<LoginUserEntity> 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<LoginUserEntity> 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);
}
}

View File

@@ -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<PermissionEntity, PermissionR
@PostConstruct
public void init() {}
public PermissionDto create(CreatePermissionDto request) {
PermissionEntity entity = mapper.toEntity(request);
this.repository.save(entity);
return getById(entity.getId());
}
public PermissionDto update(UpdatePermissionDto request) {
PermissionEntity entity = this.repository.get(request.getId());
this.mapper.updateEntity(entity, request);
this.repository.save(entity);
return getById(entity.getId());
}
@Autowired ApplicationContext applicationContext;
@Autowired CacheManager cacheManager;
public void delete(IdRequest request) {
@@ -101,6 +88,33 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
.collect(Collectors.toList());
}
public PermissionDto create(CreatePermissionDto request) {
PermissionEntity entity = mapper.toEntity(request);
this.repository.save(entity);
this.clearCache();
return getById(entity.getId());
}
public List<PermissionEntity> getByType(String type) {
return this.repository.findAllByPermissionType(type);
}
public List<PermissionEntity> 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<ResourceEntity> all = resourceService.findAll();
@@ -165,14 +179,8 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
this.repository.save(permission);
}
}
public List<PermissionEntity> getByType(String type) {
return this.repository.findAllByPermissionType(type);
}
public List<PermissionEntity> getByTypes(String[] types) {
return this.repository.findAllByPermissionTypeIn(types);
this.clearCache();
}
public void initDefault() {
@@ -197,5 +205,28 @@ public class PermissionService extends BaseService<PermissionEntity, PermissionR
this.repository.save(permission);
}
this.clearCache();
}
@Cacheable(cacheNames = Constants.CACHE_PERMISSION)
public List<PermissionDto> getAllFromCache() {
List<PermissionEntity> all = this.findAll();
return all.stream().map(this.mapper::toDto).collect(Collectors.toList());
}
@Cacheable(cacheNames = Constants.CACHE_PERMISSION_BY_TYPE, key = "#type")
public List<PermissionDto> 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();
}
}

View File

@@ -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<ResourceEntity, ResourceReposit
@PostConstruct
public void init() {}
public ResourceDto create(CreateResourceDto request) {
ResourceEntity entity = mapper.toEntity(request);
this.repository.save(entity);
entityManager
.unwrap(Session.class)
.getSessionFactory()
.getCache()
.evictCollectionData("cn.lihongjie.coal.resource.entity.ResourceEntity.children");
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);
entityManager
.unwrap(Session.class)
.getSessionFactory()
.getCache()
.evictCollectionData("cn.lihongjie.coal.resource.entity.ResourceEntity.children");
return getById(entity.getId());
}
@Autowired ApplicationContext applicationContext;
@Autowired CacheManager cacheManager;
public void delete(IdRequest request) {
@@ -129,6 +108,51 @@ public class ResourceService extends BaseService<ResourceEntity, ResourceReposit
.stream()
.findFirst();
}
@Autowired PermissionService permissionService;
private List<String> getAllUrls() {
Map<RequestMappingInfo, HandlerMethod> handlerMethods =
requestMappingHandlerMapping.getHandlerMethods();
List<String> urls = new ArrayList<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> 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<ResourceEntity, ResourceReposit
}
this.save(root);
this.clearCache();
}
private List<String> getAllUrls() {
Map<RequestMappingInfo, HandlerMethod> handlerMethods =
requestMappingHandlerMapping.getHandlerMethods();
List<String> urls = new ArrayList<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> 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<ResourceTreeDto> menuTree() {
return this.mapper.toTreeDto(
this.repository.findAllByTypeAndParentIsNullOrderBySortKey("0"));
}
@Cacheable(cacheNames = Constants.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();
}
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();
}
}

View File

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

View File

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

View File

@@ -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<SysConfigEntity, SysConfigRepo
entity.setType("3");
repository.save(entity);
}
if (!all.containsKey(Constants.SYSCONFIG_SESSION_TIMEOUT)) {
SysConfigEntity entity = new SysConfigEntity();
entity.setName("登录会话超时时间s");
entity.setCode(Constants.SYSCONFIG_SESSION_TIMEOUT);
entity.setConfigVal(TimeUnit.HOURS.toSeconds(1) + "");
entity.setDictCode(null);
entity.setMaxValue((int) TimeUnit.HOURS.toSeconds(24));
entity.setMinValue((int) TimeUnit.MINUTES.toSeconds(1));
entity.setRegexValidator(null);
entity.setType("2");
repository.save(entity);
}
if (!all.containsKey(Constants.SYSCONFIG_ACCOUNT_MAX_ONLINE)) {
SysConfigEntity entity = new SysConfigEntity();
entity.setName("账户同时登录人数");
entity.setCode(Constants.SYSCONFIG_ACCOUNT_MAX_ONLINE);
entity.setConfigVal("1");
entity.setDictCode(null);
entity.setMaxValue(1000);
entity.setMinValue(1);
entity.setRegexValidator(null);
entity.setType("2");
repository.save(entity);
}
}
@Autowired

View File

@@ -25,9 +25,14 @@ public class UserDto extends OrgCommonDto {
@Comment("机构管理员标识")
private Boolean orgAdmin;
@Comment("系统管理员标识")
private Boolean sysAdmin;
private List<RoleDto> roles;
private List<RoleDto> otherRoles;
@Data
public static class RoleDto extends OrgCommonDto {}
}

View File

@@ -4,6 +4,7 @@ import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.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<UserEntity, UserRepository> {
}
}
@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<UserEntity, UserRepository> {
@Autowired CacheManager cacheManager;
@Cacheable(cacheNames = "userResources", key = "#id")
@Cacheable(cacheNames = Constants.CACHE_USER_RESOURCES, key = "#id")
public List<ResourceDto> resources(String id) {
UserEntity user = get(id);
@@ -267,7 +268,7 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
@Autowired PermissionMapper permissionMapper;
@Cacheable(cacheNames = "userPermissions", key = "#id")
@Cacheable(cacheNames = Constants.CACHE_USER_PERMISSIONS, key = "#id")
public List<PermissionSimpleDto> permissions(String id) {
UserEntity user = get(id);
@@ -293,13 +294,13 @@ public class UserService extends BaseService<UserEntity, UserRepository> {
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);
}