diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java new file mode 100644 index 00000000..b2f9aa35 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/CacheKey.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.zhengjie.utils; + + +/** + * @author: liaojinlong + * @date: 2020/6/11 15:49 + * @apiNote: 关于缓存的Key 集合 + */ +public interface CacheKey { + /** + * 内置 用户、岗位、应用、菜单、角色 相关key + */ + String USER_MODIFY_TIME_KEY = "user:modify:time:key:"; + String APP_MODIFY_TIME_KEY = "app:modify:time:key:"; + String JOB_MODIFY_TIME_KEY = "job:modify:time:key:"; + String MENU_MODIFY_TIME_KEY = "menu:modify:time:key:"; + String ROLE_MODIFY_TIME_KEY = "role:modify:time:key:"; + String DEPT_MODIFY_TIME_KEY = "dept:modify:time:key:"; + + /** + * 用户 + */ + String USER_ID = "user::id:"; + String USER_NAME = "user::username:"; + /** + * s数据 + */ + + String DATE_USER = "data::user:"; + /** + * 菜单 + */ + String MENU_USER = "menu::user:"; + /** + * 角色授权 + */ + String ROLE_AUTH = "role::auth:"; + + String ROLE_ID = "role::id:"; +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java new file mode 100644 index 00000000..812f5639 --- /dev/null +++ b/eladmin-common/src/main/java/me/zhengjie/utils/DateUtil.java @@ -0,0 +1,169 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.zhengjie.utils; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * @author: liaojinlong + * @date: 2020/6/11 16:28 + * @apiNote: JDK 8 新日期类 格式化与字符串转换 工具类 + */ +public class DateUtil { + public static final DateTimeFormatter dfyMdHms = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + public static final DateTimeFormatter dfyMd = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * LocalDateTime 转时间戳 + * + * @param localDateTime + * @return/ + */ + public static Long getTimeStamp(LocalDateTime localDateTime) { + return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond(); + } + + /** + * 时间戳转LocalDateTime + * + * @param timeStamp + * @return/ + */ + public static LocalDateTime fromTimeStamp(Long timeStamp) { + return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset()); + } + + /** + * LocalDateTime 转 Date + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param localDateTime + * @return/ + */ + public static Date toDate(LocalDateTime localDateTime) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + /** + * LocalDate 转 Date + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param localDate + * @return/ + */ + public static Date toDate(LocalDate localDate) { + return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault()))); + } + + + /** + * Date转 LocalDateTime + * Jdk8 后 不推荐使用 {@link Date} Date + * + * @param date + * @return/ + */ + public static LocalDateTime toLocalDateTime(Date date) { + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + } + + /** + * 日期 格式化 + * + * @param localDateTime + * @param patten + * @return/ + */ + public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) { + DateTimeFormatter df = DateTimeFormatter.ofPattern(patten); + return df.format(localDateTime); + } + + /** + * 日期 格式化 + * + * @param localDateTime + * @param df + * @return/ + */ + public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) { + return df.format(localDateTime); + } + + /** + * 日期格式化 yyyy-MM-dd HH:mm:ss + * + * @param localDateTime + * @return/ + */ + public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) { + return dfyMdHms.format(localDateTime); + } + + /** + * 日期格式化 yyyy-MM-dd + * + * @param localDateTime + * @return/ + */ + public String localDateTimeFormatyMd(LocalDateTime localDateTime) { + return dfyMd.format(localDateTime); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd + * + * @param localDateTime + * @return/ + */ + public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.from(dateTimeFormatter.parse(localDateTime)); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd + * + * @param localDateTime + * @return/ + */ + public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) { + return LocalDateTime.from(dateTimeFormatter.parse(localDateTime)); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd + * + * @param localDateTime + * @return/ + */ + public static LocalDateTime parseLocalDateTimeFormatyMd(String localDateTime) { + return LocalDateTime.from(dfyMd.parse(localDateTime)); + } + + /** + * 字符串转 LocalDateTime ,字符串格式 yyyy-MM-dd HH:mm:ss + * + * @param localDateTime + * @return/ + */ + public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) { + return LocalDateTime.from(dfyMdHms.parse(localDateTime)); + } +} diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java index 5004eb84..649b7e2c 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java @@ -174,19 +174,19 @@ public class RedisUtils { if (keys != null && keys.length > 0) { if (keys.length == 1) { boolean result = redisTemplate.delete(keys[0]); - System.out.println("--------------------------------------------"); - System.out.println(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result)); - System.out.println("--------------------------------------------"); + log.debug("--------------------------------------------"); + log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString()); + log.debug("--------------------------------------------"); } else { Set keySet = new HashSet<>(); for (String key : keys) { keySet.addAll(redisTemplate.keys(key)); } long count = redisTemplate.delete(keySet); - System.out.println("--------------------------------------------"); - System.out.println("成功删除缓存:" + keySet.toString()); - System.out.println("缓存删除数量:" + count + "个"); - System.out.println("--------------------------------------------"); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keySet.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); } } } @@ -697,9 +697,9 @@ public class RedisUtils { } long count = redisTemplate.delete(keys); // 此处提示可自行删除 - System.out.println("--------------------------------------------"); - System.out.println("成功删除缓存:" + keys.toString()); - System.out.println("缓存删除数量:" + count + "个"); - System.out.println("--------------------------------------------"); + log.debug("--------------------------------------------"); + log.debug("成功删除缓存:" + keys.toString()); + log.debug("缓存删除数量:" + count + "个"); + log.debug("--------------------------------------------"); } } diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java index 4c27c136..f0f31003 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/StringUtils.java @@ -26,6 +26,7 @@ import org.lionsoul.ip2region.DbSearcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; + import javax.servlet.http.HttpServletRequest; import java.io.File; import java.net.InetAddress; @@ -41,7 +42,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { private static final Logger log = LoggerFactory.getLogger(StringUtils.class); private static boolean ipLocal = false; - private static DbSearcher searcher = null; + private static File file = null; + private static DbConfig config; private static final char SEPARATOR = '_'; private static final String UNKNOWN = "unknown"; @@ -54,11 +56,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { */ String path = "ip2region/ip2region.db"; String name = "ip2region.db"; - DbConfig config; try { config = new DbConfig(); - File file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name); - searcher = new DbSearcher(config, file.getPath()); + file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name); } catch (Exception e) { log.error(e.getMessage(), e); } @@ -206,9 +206,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils { */ public static String getLocalCityInfo(String ip) { try { - DataBlock dataBlock; - dataBlock = searcher.binarySearch(ip); - String address = dataBlock.getRegion().replace("0|", ""); + DataBlock dataBlock = new DbSearcher(config, file.getPath()) + .binarySearch(ip); + String region = dataBlock.getRegion(); + String address = region.replace("0|", ""); char symbol = '|'; if (address.charAt(address.length() - 1) == symbol) { address = address.substring(0, address.length() - 1); diff --git a/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java new file mode 100644 index 00000000..a2d464cc --- /dev/null +++ b/eladmin-common/src/test/java/me/zhengjie/utils/DateUtilsTest.java @@ -0,0 +1,26 @@ +package me.zhengjie.utils; + +import org.junit.Test; + +import java.time.LocalDateTime; +import java.util.Date; + +public class DateUtilsTest { + @Test + public void test1() { + long l = System.currentTimeMillis() / 1000; + LocalDateTime localDateTime = DateUtil.fromTimeStamp(l); + System.out.printf(DateUtil.localDateTimeFormatyMdHms(localDateTime)); + } + + @Test + public void test2() { + LocalDateTime now = LocalDateTime.now(); + System.out.println(DateUtil.localDateTimeFormatyMdHms(now)); + Date date = DateUtil.toDate(now); + LocalDateTime localDateTime = DateUtil.toLocalDateTime(date); + System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime)); + LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000); + System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1)); + } +} diff --git a/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java b/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java index 1aa33a56..a050187d 100644 --- a/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java +++ b/eladmin-logging/src/main/java/me/zhengjie/annotation/Log.java @@ -15,6 +15,8 @@ */ package me.zhengjie.annotation; +import me.zhengjie.annotation.type.LogActionType; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -27,5 +29,14 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { - String value() default ""; + String value() default ""; + + /** + * 是否启用 + * + * @return + */ + boolean enable() default true; + + LogActionType type() default LogActionType.SELECT; } diff --git a/eladmin-logging/src/main/java/me/zhengjie/annotation/type/LogActionType.java b/eladmin-logging/src/main/java/me/zhengjie/annotation/type/LogActionType.java new file mode 100644 index 00000000..26549177 --- /dev/null +++ b/eladmin-logging/src/main/java/me/zhengjie/annotation/type/LogActionType.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package me.zhengjie.annotation.type; + +/** + * @author: liaojinlong + * @date: 2020/6/11 19:47 + * @apiNote: 日志类型 + */ + +public enum LogActionType { + /** + * 增删改查 + */ + ADD("新增"), + SELECT("查询"), + UPDATE("更新"), + DELETE("删除"); + private String value; + + LogActionType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java index e770570c..65d7e235 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/quartz/config/JobRunner.java @@ -19,9 +19,12 @@ import lombok.RequiredArgsConstructor; import me.zhengjie.modules.quartz.domain.QuartzJob; import me.zhengjie.modules.quartz.repository.QuartzJobRepository; import me.zhengjie.modules.quartz.utils.QuartzManage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; + import java.util.List; /** @@ -31,19 +34,20 @@ import java.util.List; @Component @RequiredArgsConstructor public class JobRunner implements ApplicationRunner { - + private static final Logger log = LoggerFactory.getLogger(JobRunner.class); private final QuartzJobRepository quartzJobRepository; private final QuartzManage quartzManage; /** * 项目启动时重新激活启用的定时任务 + * * @param applicationArguments / */ @Override - public void run(ApplicationArguments applicationArguments){ - System.out.println("--------------------注入定时任务---------------------"); + public void run(ApplicationArguments applicationArguments) { + log.info("--------------------注入定时任务---------------------"); List quartzJobs = quartzJobRepository.findByIsPauseIsFalse(); quartzJobs.forEach(quartzManage::addJob); - System.out.println("--------------------定时任务注入完成---------------------"); + log.info("--------------------定时任务注入完成---------------------"); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java index 2f110398..07dc2724 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/SecurityConfig.java @@ -17,7 +17,10 @@ package me.zhengjie.modules.security.config; import lombok.RequiredArgsConstructor; import me.zhengjie.annotation.AnonymousAccess; +import me.zhengjie.modules.security.config.bean.SecurityProperties; import me.zhengjie.modules.security.security.*; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.modules.security.service.UserCacheClean; import me.zhengjie.utils.enums.RequestMethodEnum; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -37,6 +40,7 @@ import org.springframework.web.filter.CorsFilter; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + import java.util.*; /** @@ -53,6 +57,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final JwtAuthenticationEntryPoint authenticationErrorHandler; private final JwtAccessDeniedHandler jwtAccessDeniedHandler; private final ApplicationContext applicationContext; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + private final UserCacheClean userCacheClean; @Bean GrantedAuthorityDefaults grantedAuthorityDefaults() { @@ -144,7 +151,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { if (null != anonymousAccess) { List requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods()); RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name()); - switch (Objects.requireNonNull(request)){ + switch (Objects.requireNonNull(request)) { case GET: get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); break; @@ -176,6 +183,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { } private TokenConfigurer securityConfigurerAdapter() { - return new TokenConfigurer(tokenProvider); + return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java index 7d948ed0..35ebcb1f 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java @@ -19,10 +19,12 @@ package me.zhengjie.modules.security.config.bean; import com.wf.captcha.*; import com.wf.captcha.base.Captcha; import me.zhengjie.exception.BadConfigurationException; + import java.util.Objects; /** * 配置文件读取 + * * @author liaojinlong * @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6 */ @@ -34,6 +36,10 @@ public class LoginProperties { private boolean singleLogin = false; private LoginCode loginCode; + /** + * 用户登录信息缓存 + */ + private boolean cacheEnable; public boolean isSingleLogin() { return singleLogin; @@ -51,6 +57,14 @@ public class LoginProperties { this.loginCode = loginCode; } + public boolean isCacheEnable() { + return cacheEnable; + } + + public void setCacheEnable(boolean cacheEnable) { + this.cacheEnable = cacheEnable; + } + /** * 获取验证码生产类 * diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java index 32a3beb6..94e25477 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenConfigurer.java @@ -16,6 +16,9 @@ package me.zhengjie.modules.security.security; import lombok.RequiredArgsConstructor; +import me.zhengjie.modules.security.config.bean.SecurityProperties; +import me.zhengjie.modules.security.service.OnlineUserService; +import me.zhengjie.modules.security.service.UserCacheClean; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; @@ -28,10 +31,13 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic public class TokenConfigurer extends SecurityConfigurerAdapter { private final TokenProvider tokenProvider; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + private final UserCacheClean userCacheClean; @Override public void configure(HttpSecurity http) { - TokenFilter customFilter = new TokenFilter(tokenProvider); + TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean); http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java index 84723b7d..7b278bc1 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java @@ -17,64 +17,93 @@ package me.zhengjie.modules.security.security; import cn.hutool.core.util.StrUtil; import io.jsonwebtoken.ExpiredJwtException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import me.zhengjie.modules.security.config.bean.SecurityProperties; +import me.zhengjie.modules.security.service.UserCacheClean; import me.zhengjie.modules.security.service.dto.OnlineUserDto; import me.zhengjie.modules.security.service.OnlineUserService; -import me.zhengjie.utils.SpringContextHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; + import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.util.Objects; /** * @author / */ -@Slf4j -@RequiredArgsConstructor public class TokenFilter extends GenericFilterBean { + private static final Logger log = LoggerFactory.getLogger(TokenFilter.class); - private final TokenProvider tokenProvider; - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) - throws IOException, ServletException { - HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; - String token = resolveToken(httpServletRequest); - // 对于 Token 为空的不需要去查 Redis - if(StrUtil.isNotBlank(token)){ - OnlineUserDto onlineUserDto = null; - SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class); - try { - OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class); - onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token); - } catch (ExpiredJwtException e) { - log.error(e.getMessage()); - } - if (onlineUserDto != null && StringUtils.hasText(token)) { - Authentication authentication = tokenProvider.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); - // Token 续期 - tokenProvider.checkRenewal(token); - } - } - filterChain.doFilter(servletRequest, servletResponse); - } + private final TokenProvider tokenProvider; + private final SecurityProperties properties; + private final OnlineUserService onlineUserService; + private final UserCacheClean userCacheClean; - private String resolveToken(HttpServletRequest request) { - SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class); - String bearerToken = request.getHeader(properties.getHeader()); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) { - // 去掉令牌前缀 - return bearerToken.replace(properties.getTokenStartWith(),""); - } - return null; - } + /** + * @param tokenProvider Token + * @param properties JWT + * @param onlineUserService 用户在线 + * @param userCacheClean 用户缓存清理工具 + */ + public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) { + this.properties = properties; + this.onlineUserService = onlineUserService; + this.tokenProvider = tokenProvider; + this.userCacheClean = userCacheClean; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + String token = resolveToken(httpServletRequest); + // 对于 Token 为空的不需要去查 Redis + if (StrUtil.isNotBlank(token)) { + OnlineUserDto onlineUserDto = null; + boolean cleanUserCache = false; + try { + onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token); + } catch (ExpiredJwtException e) { + log.error(e.getMessage()); + cleanUserCache = true; + } finally { + if (cleanUserCache || Objects.isNull(onlineUserDto)) { + userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY))); + } + } + if (onlineUserDto != null && StringUtils.hasText(token)) { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + // Token 续期 + tokenProvider.checkRenewal(token); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } + + /** + * 初步检测Token + * + * @param request + * @return + */ + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader(properties.getHeader()); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) { + // 去掉令牌前缀 + return bearerToken.replace(properties.getTokenStartWith(), ""); + } else { + log.debug("非法Token:{}", bearerToken); + } + return null; + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java index 91439056..df294967 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java @@ -22,7 +22,6 @@ import cn.hutool.core.util.ObjectUtil; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.zhengjie.modules.security.config.bean.SecurityProperties; import me.zhengjie.utils.RedisUtils; @@ -33,6 +32,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Component; + import javax.servlet.http.HttpServletRequest; import java.security.Key; import java.util.Arrays; @@ -47,75 +47,101 @@ import java.util.stream.Collectors; */ @Slf4j @Component -@RequiredArgsConstructor public class TokenProvider implements InitializingBean { - private final SecurityProperties properties; - private final RedisUtils redisUtils; - private static final String AUTHORITIES_KEY = "auth"; - private Key key; + private final SecurityProperties properties; + private final RedisUtils redisUtils; + public static final String AUTHORITIES_KEY = "auth"; + private Key key; + private JwtParser jwtParser; + private JwtBuilder jwtBuilder; - @Override - public void afterPropertiesSet() { - byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret()); - this.key = Keys.hmacShaKeyFor(keyBytes); - } + public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) { + this.properties = properties; + this.redisUtils = redisUtils; + } - public String createToken(Authentication authentication) { - String authorities = authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); + @Override + public void afterPropertiesSet() { + byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret()); + this.key = Keys.hmacShaKeyFor(keyBytes); + jwtParser = Jwts.parserBuilder() + .setSigningKey(key) + .build(); + jwtBuilder = Jwts.builder() + .signWith(key, SignatureAlgorithm.HS512); + } - return Jwts.builder() - .setSubject(authentication.getName()) - .claim(AUTHORITIES_KEY, authorities) - .signWith(key, SignatureAlgorithm.HS512) - // 加入ID确保生成的 Token 都不一致 - .setId(IdUtil.simpleUUID()) - .compact(); - } + /** + * 创建Token 设置永不过期, + * Token 的时间有效性转到Redis 维护 + * + * @param authentication + * @return + */ + public String createToken(Authentication authentication) { + /** + * 获取权限列表 + */ + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); - Authentication getAuthentication(String token) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); + return jwtBuilder + // 加入ID确保生成的 Token 都不一致 + .setId(IdUtil.simpleUUID()) + .claim(AUTHORITIES_KEY, authorities) + .setSubject(authentication.getName()) + .compact(); + } - // fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException - Object authoritiesStr = claims.get(AUTHORITIES_KEY); - Collection authorities = - ObjectUtil.isNotEmpty(authoritiesStr) ? - Arrays.stream(authoritiesStr.toString().split(",")) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()) : Collections.emptyList(); + /** + * 依据Token 获取鉴权信息 + * + * @param token + * @return + */ + Authentication getAuthentication(String token) { + Claims claims = getClaims(token); - User principal = new User(claims.getSubject(), "", authorities); + // fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException + Object authoritiesStr = claims.get(AUTHORITIES_KEY); + Collection authorities = + ObjectUtil.isNotEmpty(authoritiesStr) ? + Arrays.stream(authoritiesStr.toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()) : Collections.emptyList(); + User principal = new User(claims.getSubject(), "******", authorities); + return new UsernamePasswordAuthenticationToken(principal, token, authorities); + } - return new UsernamePasswordAuthenticationToken(principal, token, authorities); - } + public Claims getClaims(String token) { + return jwtParser + .parseClaimsJws(token) + .getBody(); + } - /** - * @param token 需要检查的token - */ - public void checkRenewal(String token){ - // 判断是否续期token,计算token的过期时间 - long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000; - Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time); - // 判断当前时间与过期时间的时间差 - long differ = expireDate.getTime() - System.currentTimeMillis(); - // 如果在续期检查的范围内,则续期 - if(differ <= properties.getDetect()){ - long renew = time + properties.getRenew(); - redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS); - } - } + /** + * @param token 需要检查的token + */ + public void checkRenewal(String token) { + // 判断是否续期token,计算token的过期时间 + long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000; + Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time); + // 判断当前时间与过期时间的时间差 + long differ = expireDate.getTime() - System.currentTimeMillis(); + // 如果在续期检查的范围内,则续期 + if (differ <= properties.getDetect()) { + long renew = time + properties.getRenew(); + redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS); + } + } - public String getToken(HttpServletRequest request){ - final String requestHeader = request.getHeader(properties.getHeader()); - if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) { - return requestHeader.substring(7); - } - return null; - } + public String getToken(HttpServletRequest request) { + final String requestHeader = request.getHeader(properties.getHeader()); + if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) { + return requestHeader.substring(7); + } + return null; + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java new file mode 100644 index 00000000..fbd7093b --- /dev/null +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheClean.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.zhengjie.modules.security.service; + +import me.zhengjie.utils.StringUtils; +import org.springframework.stereotype.Component; + +/** + * @author: liaojinlong + * @date: 2020/6/11 18:01 + * @apiNote: 用于清理 用户登录信息缓存,为防止Spring循环依赖与安全考虑 ,单独构成工具类 + */ +@Component +public class UserCacheClean { + + + /** + * 清理特定用户缓存信息
+ * 用户信息变更时 + * + * @param userName + */ + public void cleanUserCache(String userName) { + if (StringUtils.isNotEmpty(userName)) { + UserDetailsServiceImpl.userDtoCache.remove(userName); + } + } + + /** + * 清理所有用户的缓存信息
+ * ,如发生角色授权信息变化,可以简便的全部失效缓存 + */ + public void cleanAll() { + UserDetailsServiceImpl.userDtoCache.clear(); + } +} diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java index 71f263e1..857bf021 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserDetailsServiceImpl.java @@ -18,6 +18,7 @@ package me.zhengjie.modules.security.service; import lombok.RequiredArgsConstructor; import me.zhengjie.exception.BadRequestException; import me.zhengjie.exception.EntityNotFoundException; +import me.zhengjie.modules.security.config.bean.LoginProperties; import me.zhengjie.modules.security.service.dto.JwtUserDto; import me.zhengjie.modules.system.service.DataService; import me.zhengjie.modules.system.service.RoleService; @@ -27,6 +28,9 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * @author Zheng Jie * @date 2018-11-22 @@ -34,31 +38,52 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService { - private final UserService userService; private final RoleService roleService; private final DataService dataService; + private final LoginProperties loginProperties; + + public void setEnableCache(boolean enableCache) { + this.loginProperties.setCacheEnable(enableCache); + } + + /** + * 用户信息缓存 + * + * @see {@link UserCacheClean} + */ + static Map userDtoCache = new ConcurrentHashMap<>(); @Override public JwtUserDto loadUserByUsername(String username) { - UserDto user; - try { - user = userService.findByName(username); - } catch (EntityNotFoundException e) { - // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException - throw new UsernameNotFoundException("", e); + boolean searchDb = true; + JwtUserDto jwtUserDto = null; + if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) { + jwtUserDto = userDtoCache.get(username); + searchDb = false; } - if (user == null) { - throw new UsernameNotFoundException(""); - } else { - if (!user.getEnabled()) { - throw new BadRequestException("账号未激活"); + if (searchDb) { + UserDto user; + try { + user = userService.findByName(username); + } catch (EntityNotFoundException e) { + // SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException + throw new UsernameNotFoundException("", e); + } + if (user == null) { + throw new UsernameNotFoundException(""); + } else { + if (!user.getEnabled()) { + throw new BadRequestException("账号未激活"); + } + jwtUserDto = new JwtUserDto( + user, + dataService.getDeptIds(user), + roleService.mapToGrantedAuthorities(user) + ); + userDtoCache.put(username, jwtUserDto); } - return new JwtUserDto( - user, - dataService.getDeptIds(user), - roleService.mapToGrantedAuthorities(user) - ); } + return jwtUserDto; } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java index c680d35d..d694334c 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/RoleServiceImpl.java @@ -15,8 +15,10 @@ */ package me.zhengjie.modules.system.service.impl; +import cn.hutool.core.collection.CollectionUtil; import lombok.RequiredArgsConstructor; import me.zhengjie.exception.BadRequestException; +import me.zhengjie.modules.security.service.UserCacheClean; import me.zhengjie.modules.system.domain.Menu; import me.zhengjie.modules.system.domain.Role; import me.zhengjie.exception.EntityExistException; @@ -40,6 +42,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; + import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; @@ -59,6 +62,7 @@ public class RoleServiceImpl implements RoleService { private final RoleSmallMapper roleSmallMapper; private final RedisUtils redisUtils; private final UserRepository userRepository; + private final UserCacheClean userCacheClean; @Override public List queryAll() { @@ -68,12 +72,12 @@ public class RoleServiceImpl implements RoleService { @Override public List queryAll(RoleQueryCriteria criteria) { - return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder))); + return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder))); } @Override public Object queryAll(RoleQueryCriteria criteria, Pageable pageable) { - Page page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + Page page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); return PageUtil.toPage(page.map(roleMapper::toDto)); } @@ -82,15 +86,15 @@ public class RoleServiceImpl implements RoleService { @Transactional(rollbackFor = Exception.class) public RoleDto findById(long id) { Role role = roleRepository.findById(id).orElseGet(Role::new); - ValidationUtil.isNull(role.getId(),"Role","id",id); + ValidationUtil.isNull(role.getId(), "Role", "id", id); return roleMapper.toDto(role); } @Override @Transactional(rollbackFor = Exception.class) public void create(Role resources) { - if(roleRepository.findByName(resources.getName()) != null){ - throw new EntityExistException(Role.class,"username",resources.getName()); + if (roleRepository.findByName(resources.getName()) != null) { + throw new EntityExistException(Role.class, "username", resources.getName()); } roleRepository.save(resources); } @@ -99,12 +103,12 @@ public class RoleServiceImpl implements RoleService { @Transactional(rollbackFor = Exception.class) public void update(Role resources) { Role role = roleRepository.findById(resources.getId()).orElseGet(Role::new); - ValidationUtil.isNull(role.getId(),"Role","id",resources.getId()); + ValidationUtil.isNull(role.getId(), "Role", "id", resources.getId()); Role role1 = roleRepository.findByName(resources.getName()); - if(role1 != null && !role1.getId().equals(role.getId())){ - throw new EntityExistException(Role.class,"username",resources.getName()); + if (role1 != null && !role1.getId().equals(role.getId())) { + throw new EntityExistException(Role.class, "username", resources.getName()); } role.setName(resources.getName()); role.setDescription(resources.getDescription()); @@ -120,16 +124,13 @@ public class RoleServiceImpl implements RoleService { public void updateMenu(Role resources, RoleDto roleDTO) { Role role = roleMapper.toEntity(roleDTO); List users = userRepository.findByRoleId(role.getId()); - Set userIds = users.stream().map(User::getId).collect(Collectors.toSet()); // 更新菜单 role.setMenus(resources.getMenus()); - // 清理缓存 - redisUtils.delByKeys("menu::user:",userIds); - redisUtils.delByKeys("role::auth:",userIds); - redisUtils.del("role::id:" + resources.getId()); + cleanCache(resources, users); roleRepository.save(role); } + @Override @Transactional(rollbackFor = Exception.class) public void untiedMenu(Long menuId) { @@ -166,7 +167,7 @@ public class RoleServiceImpl implements RoleService { public List mapToGrantedAuthorities(UserDto user) { Set permissions = new HashSet<>(); // 如果是管理员直接返回 - if(user.getIsAdmin()){ + if (user.getIsAdmin()) { permissions.add("admin"); return permissions.stream().map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); @@ -183,7 +184,7 @@ public class RoleServiceImpl implements RoleService { public void download(List roles, HttpServletResponse response) throws IOException { List> list = new ArrayList<>(); for (RoleDto role : roles) { - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); map.put("角色名称", role.getName()); map.put("角色级别", role.getLevel()); map.put("描述", role.getDescription()); @@ -193,21 +194,9 @@ public class RoleServiceImpl implements RoleService { FileUtil.downloadExcel(list, response); } - /** - * 清理缓存 - * @param id / - */ - public void delCaches(Long id){ - List users = userRepository.findByRoleId(id); - Set userIds = users.stream().map(User::getId).collect(Collectors.toSet()); - redisUtils.delByKeys("data::user:",userIds); - redisUtils.delByKeys("menu::user:",userIds); - redisUtils.delByKeys("role::auth:",userIds); - } - @Override public void verification(Set ids) { - if(userRepository.countByRoles(ids) > 0){ + if (userRepository.countByRoles(ids) > 0) { throw new BadRequestException("所选角色存在用户关联,请解除关联再试!"); } } @@ -216,4 +205,43 @@ public class RoleServiceImpl implements RoleService { public List findInMenuId(List menuIds) { return roleRepository.findInMenuId(menuIds); } + + /** + * 清理缓存 + * + * @param id / + */ + public void delCaches(Long id) { + List users = userRepository.findByRoleId(id); + if (CollectionUtil.isNotEmpty(users)) { + users.stream().forEach(item -> { + userCacheClean.cleanUserCache(item.getUsername()); + }); + Set userIds = users.stream().map(User::getId).collect(Collectors.toSet()); + redisUtils.delByKeys(CacheKey.DATE_USER, userIds); + redisUtils.delByKeys(CacheKey.MENU_USER, userIds); + redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds); + } + + } + + /** + * 清理缓存 + * + * @param resources + * @param users + */ + private void cleanCache(Role resources, List users) { + // 清理缓存 + if (CollectionUtil.isNotEmpty(users)) { + users.stream().forEach(item -> { + userCacheClean.cleanUserCache(item.getUsername()); + }); + Set userIds = users.stream().map(User::getId).collect(Collectors.toSet()); + redisUtils.delByKeys(CacheKey.MENU_USER, userIds); + redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds); + redisUtils.del(CacheKey.ROLE_ID + resources.getId()); + } + } + } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java index 5c1e5ae9..7f788e8c 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/UserServiceImpl.java @@ -17,6 +17,7 @@ package me.zhengjie.modules.system.service.impl; import lombok.RequiredArgsConstructor; import me.zhengjie.config.FileProperties; +import me.zhengjie.modules.security.service.UserCacheClean; import me.zhengjie.modules.system.domain.User; import me.zhengjie.exception.EntityExistException; import me.zhengjie.exception.EntityNotFoundException; @@ -35,7 +36,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; + import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotBlank; import java.io.File; import java.io.IOException; import java.util.*; @@ -54,16 +57,17 @@ public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final FileProperties properties; private final RedisUtils redisUtils; + private final UserCacheClean userCacheClean; @Override public Object queryAll(UserQueryCriteria criteria, Pageable pageable) { - Page page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable); + Page page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable); return PageUtil.toPage(page.map(userMapper::toDto)); } @Override public List queryAll(UserQueryCriteria criteria) { - List users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)); + List users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)); return userMapper.toDto(users); } @@ -72,18 +76,18 @@ public class UserServiceImpl implements UserService { @Transactional(rollbackFor = Exception.class) public UserDto findById(long id) { User user = userRepository.findById(id).orElseGet(User::new); - ValidationUtil.isNull(user.getId(),"User","id",id); + ValidationUtil.isNull(user.getId(), "User", "id", id); return userMapper.toDto(user); } @Override @Transactional(rollbackFor = Exception.class) public void create(User resources) { - if(userRepository.findByUsername(resources.getUsername())!=null){ - throw new EntityExistException(User.class,"username",resources.getUsername()); + if (userRepository.findByUsername(resources.getUsername()) != null) { + throw new EntityExistException(User.class, "username", resources.getUsername()); } - if(userRepository.findByEmail(resources.getEmail())!=null){ - throw new EntityExistException(User.class,"email",resources.getEmail()); + if (userRepository.findByEmail(resources.getEmail()) != null) { + throw new EntityExistException(User.class, "email", resources.getEmail()); } userRepository.save(resources); } @@ -92,22 +96,22 @@ public class UserServiceImpl implements UserService { @Transactional(rollbackFor = Exception.class) public void update(User resources) { User user = userRepository.findById(resources.getId()).orElseGet(User::new); - ValidationUtil.isNull(user.getId(),"User","id",resources.getId()); + ValidationUtil.isNull(user.getId(), "User", "id", resources.getId()); User user1 = userRepository.findByUsername(resources.getUsername()); User user2 = userRepository.findByEmail(resources.getEmail()); - if(user1 !=null&&!user.getId().equals(user1.getId())){ - throw new EntityExistException(User.class,"username",resources.getUsername()); + if (user1 != null && !user.getId().equals(user1.getId())) { + throw new EntityExistException(User.class, "username", resources.getUsername()); } - if(user2!=null&&!user.getId().equals(user2.getId())){ - throw new EntityExistException(User.class,"email",resources.getEmail()); + if (user2 != null && !user.getId().equals(user2.getId())) { + throw new EntityExistException(User.class, "email", resources.getEmail()); } // 如果用户的角色改变 if (!resources.getRoles().equals(user.getRoles())) { - redisUtils.del("data::user:" + resources.getId()); - redisUtils.del("menu::user:" + resources.getId()); - redisUtils.del("role::auth:" + resources.getId()); + redisUtils.del(CacheKey.DATE_USER + resources.getId()); + redisUtils.del(CacheKey.MENU_USER + resources.getId()); + redisUtils.del(CacheKey.ROLE_AUTH + resources.getId()); } // 如果用户名称修改 if(!resources.getUsername().equals(user.getUsername())){ @@ -164,8 +168,9 @@ public class UserServiceImpl implements UserService { @Override @Transactional(rollbackFor = Exception.class) public void updatePass(String username, String pass) { - userRepository.updatePass(username,pass,new Date()); + userRepository.updatePass(username, pass, new Date()); redisUtils.del("user::username:" + username); + flushCache(username); } @Override @@ -177,18 +182,23 @@ public class UserServiceImpl implements UserService { user.setAvatarPath(Objects.requireNonNull(file).getPath()); user.setAvatarName(file.getName()); userRepository.save(user); - if(StringUtils.isNotBlank(oldPath)){ + if (StringUtils.isNotBlank(oldPath)) { FileUtil.del(oldPath); } - redisUtils.del("user::username:" + user.getUsername()); - return new HashMap(1){{put("avatar",file.getName());}}; + @NotBlank String username = user.getUsername(); + redisUtils.del(CacheKey.USER_NAME + username); + flushCache(username); + return new HashMap(1) {{ + put("avatar", file.getName()); + }}; } @Override @Transactional(rollbackFor = Exception.class) public void updateEmail(String username, String email) { - userRepository.updateEmail(username,email); - redisUtils.del("user::username:" + username); + userRepository.updateEmail(username, email); + redisUtils.del(CacheKey.USER_NAME + username); + flushCache(username); } @Override @@ -196,7 +206,7 @@ public class UserServiceImpl implements UserService { List> list = new ArrayList<>(); for (UserDto userDTO : queryAll) { List roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList()); - Map map = new LinkedHashMap<>(); + Map map = new LinkedHashMap<>(); map.put("用户名", userDTO.getUsername()); map.put("角色", roles); map.put("部门", userDTO.getDept().getName()); @@ -213,10 +223,21 @@ public class UserServiceImpl implements UserService { /** * 清理缓存 + * * @param id / */ - public void delCaches(Long id, String username){ - redisUtils.del("user::id:" + id); - redisUtils.del("user::username:" + username); + public void delCaches(Long id, String username) { + redisUtils.del(CacheKey.USER_ID + id); + redisUtils.del(CacheKey.USER_NAME + username); + flushCache(username); + } + + /** + * 清理 登陆时 用户缓存信息 + * + * @param username + */ + private void flushCache(String username) { + userCacheClean.cleanUserCache(username); } } diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index 0811295c..d9a67418 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -46,6 +46,8 @@ spring: # 登录相关配置 login: + # 登录缓存 + cache-enable: true # 是否限制单用户登录 single: false # 验证码 diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index cd26ca0f..a4122c28 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -48,6 +48,8 @@ spring: # 登录相关配置 login: + # 登录缓存 + cache-enable: true # 是否限制单用户登录 single: false # 验证码 @@ -83,7 +85,7 @@ jwt: # IP 本地解析 ip: - local-parsing: true + local-parsing: false #是否允许生成代码,生产环境设置为false generator: diff --git a/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java b/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java new file mode 100644 index 00000000..008f61a8 --- /dev/null +++ b/eladmin-system/src/test/java/me/zhengjie/LoginCacheTest.java @@ -0,0 +1,34 @@ +package me.zhengjie; + +import me.zhengjie.modules.security.service.UserDetailsServiceImpl; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class LoginCacheTest { + @Resource(name = "userDetailsService") + private UserDetailsServiceImpl userDetailsService; + private int size = 10000; + + @Test + public void testCache() { + long start1 = System.currentTimeMillis(); + for (int i = 0; i < size; i++) { + userDetailsService.loadUserByUsername("admin"); + } + long end1 = System.currentTimeMillis(); + //关闭缓存 + userDetailsService.setEnableCache(false); + long start2 = System.currentTimeMillis(); + for (int i = 0; i < size; i++) { + userDetailsService.loadUserByUsername("admin"); + } + long end2 = System.currentTimeMillis(); + System.out.printf("使用缓存:" + (end1 - start1) + "毫秒\n 不使用缓存:" + (end2 - start2) + "毫秒"); + } +}