赞
踩
<!--security安全框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--springboot_redis缓存框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration public class RedisConfig { /** * *redis序列化的工具定置类,下面这个请一定开启配置 * *127.0.0.1:6379> keys * * *1) “ord:102” 序列化过 * *2)“\xaclxedlxeelx05tixeelaord:102” 野生,没有序列化过 * *this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法 * *this.redisTemplate.opsForList();// 提供了操作List类型的所有方法 * *this.redisTemplate.opsForset(); //提供了操作set类型的所有方法 * *this.redisTemplate.opsForHash(); //提供了操作hash类型的所有方认 * *this.redisTemplate.opsForZSet(); //提供了操作zset类型的所有方法 * param LettuceConnectionFactory * return */ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); // 设置key序列化方式string redisTemplate.setKeySerializer(RedisSerializer.string()); // RedisSerializer.string() 等价于 new StringRedisSerializer() // 设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化 redisTemplate.setValueSerializer(RedisSerializer.json()); // RedisSerializer.json() 等价于 new GenericJackson2JsonRedisSerializer() // 设置hash的key的序列化方式 redisTemplate.setHashKeySerializer(RedisSerializer.string()); // 设置hash的value的序列化方式 redisTemplate.setHashValueSerializer(RedisSerializer.json()); // 使配置生效 redisTemplate.afterPropertiesSet(); return redisTemplate; } }
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class SecurityConfiguration { /** * 接口文档放行 */ public static final List<String> DOC_WHITE_LIST = List.of("/doc.html", "/webjars/**", "/v3/api-docs/**"); @Autowired private JWTAuthenticationFilter jwtAuthenticationFilter; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired private AccessDeniedHandler accessDeniedHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 禁用basic明文验证 .httpBasic(AbstractHttpConfigurer::disable) // 前后端分离架构不需要csrf保护 .csrf(AbstractHttpConfigurer::disable) // 禁用默认登录页 .formLogin(AbstractHttpConfigurer::disable) // 禁用默认登出页 .logout(AbstractHttpConfigurer::disable) // 前后端分离是无状态的,不需要session了,直接禁用。 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests((authorize) -> authorize ///静态资源,可匿名访问 .requestMatchers(HttpMethod.GET, DOC_WHITE_LIST.toArray(new String[0])).permitAll() .anyRequest().authenticated()) // 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)); return http.build(); } }
@Component @Log4j2 public class JWTAuthenticationFilter extends OncePerRequestFilter { private static final String TOKEN_ILLEGAL = "40001"; private static final String TOKEN_KICK = "40002"; private static final String TOKEN_INVALID = "40003"; @Autowired private TokenService tokenService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取当前请求的uri String uri = request.getRequestURI(); log.info("请求路径:" + uri); // 判断是否是认证请求路径 // 是:直接放行 if (uri.contains("/doc.html") || uri.contains("/webjars") || uri.contains("/v3/api-docs")) { filterChain.doFilter(request, response); return; } // 否:获取请求头中携带的token String authorization = request.getHeader("Authorization"); log.info("携带authorization:" + authorization); // 判断是否携带token // 否:抛出异常 if (StringUtils.isBlank(authorization)) { request.setAttribute(CacheConstants.TOKEN_EXCEPTION, "请登录后再操作"); request.setAttribute(CacheConstants.TOKEN_EXCEPTION_CODE, TOKEN_ILLEGAL); throw new RuntimeException("无效token"); } String token = authorization.replace("Bearer ", ""); if (StringUtils.isBlank(token)) { request.setAttribute(CacheConstants.TOKEN_EXCEPTION, "请登录后再操作"); request.setAttribute(CacheConstants.TOKEN_EXCEPTION_CODE, TOKEN_ILLEGAL); throw new RuntimeException("无效token"); } try { // 查看token是否有效 String userIdByToken = tokenService.getUserIdByToken(token); if (StringUtils.isBlank(userIdByToken)) { request.setAttribute(CacheConstants.TOKEN_EXCEPTION, "无效token"); request.setAttribute(CacheConstants.TOKEN_EXCEPTION_CODE, TOKEN_ILLEGAL); throw new RuntimeException("无效token"); } // 根据userId获取用户信息 LoginUser loginUser = tokenService.getUserByUserId(userIdByToken); if (null == loginUser) { request.setAttribute(CacheConstants.TOKEN_EXCEPTION, "登录已过期"); request.setAttribute(CacheConstants.TOKEN_EXCEPTION_CODE, TOKEN_INVALID); throw new RuntimeException("登录已过期"); } // 放行 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request, response); } catch (RuntimeException e) { log.error("用户信息获取失败token{}======== ", token); log.error("用户信息获取失败exception{}============", e); throw e; } catch (Exception e) { log.error("用户信息获取失败token{}========", token); log.error("用户信息获取失败exception{}============", e); request.setAttribute(CacheConstants.TOKEN_EXCEPTION, "系统错误,请联系管理员"); request.setAttribute(CacheConstants.TOKEN_EXCEPTION_CODE, HttpStatus.INTERNAL_SERVER_ERROR); throw new RuntimeException("系统错误,请联系管理员"); } } }
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResponseUtils.buildResponse(response, ErrorCode.ACCESS_ERROR.getMessage(), ErrorCode.ACCESS_ERROR.getCode());
}
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 获取错误信息
String exception = (String) request.getAttribute(CacheConstants.TOKEN_EXCEPTION);
// 获取错误状态码
String exceptionCode = request.getAttribute(CacheConstants.TOKEN_EXCEPTION_CODE).toString();
ResponseUtils.buildResponse(response, exception, exceptionCode);
}
}
ResponseUtils
public class ResponseUtils {
private static final String CONTENT_TYPE = "application/json;charset=utf-8";
public static void buildResponse(HttpServletResponse response, String msg, String httpStatus) throws IOException {
response.setContentType(CONTENT_TYPE); // 返回JSON
response.setStatus(HttpStatus.OK); // 状态码
ErrorVO errorVO = new ErrorVO(httpStatus, msg);
response.getWriter().write(JSONObject.toJSONString(errorVO));
}
}
RestControllerAdviceHandler(因捕获不了AccessDeniedException异常 只能定义一个全局捕获异常,大家有什么好的方法求推荐)
@ControllerAdvice
@Log4j2
public class RestControllerAdviceHandler {
@ExceptionHandler(AccessDeniedException.class)
@ResponseBody
public ResponseDto handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
String errorCode = ErrorCode.ACCESS_ERROR.getCode();
String message = ErrorCode.ACCESS_ERROR.getMessage();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return ResponseDto.failure(errorCode, message);
}
}
@Service("ss") public class PermissionService { /** * 所有权限标识 */ private static final String ALL_PERMISSION = "*:*:*"; /** * 管理员角色权限标识 */ private static final String SUPER_ADMIN = "admin"; private static final String ROLE_DELIMETER = ","; private static final String PERMISSION_DELIMETER = ","; @Autowired private TokenService tokenService; /** * 验证用户是否具备某权限 * * @param permission 权限字符串 * @return 用户是否具备某权限 */ public boolean hasPermi(String permission) { if (StringUtils.isEmpty(permission)) { return false; } LoginUser loginUser = tokenService.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permission); return hasPermissions(loginUser.getPermissions(), permission); } /** * 验证用户是否不具备某权限,与 hasPermi逻辑相反 * * @param permission 权限字符串 * @return 用户是否不具备某权限 */ public boolean lacksPermi(String permission) { return hasPermi(permission) != true; } /** * 验证用户是否具有以下任意一个权限 * * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 * @return 用户是否具有以下任意一个权限 */ public boolean hasAnyPermi(String permissions) { if (StringUtils.isEmpty(permissions)) { return false; } LoginUser loginUser = tokenService.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permissions); Set<String> authorities = loginUser.getPermissions(); for (String permission : permissions.split(PERMISSION_DELIMETER)) { if (permission != null && hasPermissions(authorities, permission)) { return true; } } return false; } /** * 判断用户是否拥有某个角色 * * @param roleStr 角色字符串 * @return 用户是否具备某角色 */ public boolean hasRole(String roleStr) { if (StringUtils.isEmpty(roleStr)) { return false; } LoginUser loginUser = tokenService.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { return false; } for (Role role : loginUser.getUser().getRoles()) { String roleKey = role.getRoleKey(); if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(roleStr))) { return true; } } return false; } /** * 验证用户是否不具备某角色,与 isRole逻辑相反。 * * @param role 角色名称 * @return 用户是否不具备某角色 */ public boolean lacksRole(String role) { return hasRole(role) != true; } /** * 验证用户是否具有以下任意一个角色 * * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ public boolean hasAnyRoles(String roles) { if (StringUtils.isEmpty(roles)) { return false; } LoginUser loginUser = tokenService.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { return false; } for (String role : roles.split(ROLE_DELIMETER)) { if (hasRole(role)) { return true; } } return false; } /** * 判断是否包含权限 * * @param permissions 权限列表 * @param permission 权限字符串 * @return 用户是否具备某权限 */ private boolean hasPermissions(Set<String> permissions, String permission) { return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); } }
LoginUser
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.liquidnet.service.user.domain.model; import com.alibaba.fastjson.annotation.JSONField; import com.fasterxml.jackson.annotation.JsonIgnore; import com.liquidnet.service.user.entity.User; import java.util.Collection; import java.util.Set; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; public class LoginUser implements UserDetails { private static final long serialVersionUID = 1L; private Long userId; private Long deptId; private String token; private Long loginTime; private Long expireTime; private String ipaddr; private String loginLocation; private String browser; private String os; private Set<String> permissions; private Set<String> roles; private User user; public Long getUserId() { return this.userId; } public void setUserId(Long userId) { this.userId = userId; } public Long getDeptId() { return this.deptId; } public void setDeptId(Long deptId) { this.deptId = deptId; } public String getToken() { return this.token; } public void setToken(String token) { this.token = token; } public LoginUser() { } public LoginUser(User user, Set<String> permissions) { this.user = user; this.permissions = permissions; } public LoginUser(Long userId, Long deptId, User user, Set<String> permissions, Set<String> roles) { this.userId = userId; this.deptId = deptId; this.user = user; this.permissions = permissions; this.roles = roles; } @JSONField( serialize = false ) @JsonIgnore public String getPassword() { return this.user.getPassword(); } @JsonIgnore public String getUsername() { return this.user.getUserName(); } @JSONField( serialize = false ) @JsonIgnore public boolean isAccountNonExpired() { return true; } @JSONField( serialize = false ) @JsonIgnore public boolean isAccountNonLocked() { return true; } @JSONField( serialize = false ) @JsonIgnore public boolean isCredentialsNonExpired() { return true; } @JSONField( serialize = false ) @JsonIgnore public boolean isEnabled() { return true; } public Long getLoginTime() { return this.loginTime; } public void setLoginTime(Long loginTime) { this.loginTime = loginTime; } public String getIpaddr() { return this.ipaddr; } public void setIpaddr(String ipaddr) { this.ipaddr = ipaddr; } public String getLoginLocation() { return this.loginLocation; } public void setLoginLocation(String loginLocation) { this.loginLocation = loginLocation; } public String getBrowser() { return this.browser; } public void setBrowser(String browser) { this.browser = browser; } public String getOs() { return this.os; } public void setOs(String os) { this.os = os; } public Long getExpireTime() { return this.expireTime; } public void setExpireTime(Long expireTime) { this.expireTime = expireTime; } public Set<String> getPermissions() { return this.permissions; } public Set<String> getRoles() { return this.roles; } public void setPermissions(Set<String> permissions) { this.permissions = permissions; } public void setRoles(Set<String> roles) { this.roles = roles; } public User getUser() { return this.user; } public void setUser(User user) { this.user = user; } public Collection<? extends GrantedAuthority> getAuthorities() { return null; } }
TokenService
package com.chunjuan.base.service.impl; import com.chunjuan.base.common.constant.CacheConstants; import com.chunjuan.base.service.interfaces.ITokenService; import com.chunjuan.base.utils.RedisCache; import com.chunjuan.base.utils.ServletUtils; import com.chunjuan.base.utils.StringUtils; import com.liquidnet.service.user.domain.model.LoginUser; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Slf4j @Service public class TokenService implements ITokenService { @Autowired protected HttpServletRequest request; @Autowired private RedisCache redisService; @Override public String getUserIdByToken(String token) { Object obj = redisService.getCacheObject(getTokenKey(token)); if (obj != null) { return (String) obj; } return null; } @Override public LoginUser getUserByUserId(String userId) { Object obj = redisService.getCacheObject(getUserKey(userId)); if (obj != null) { return (LoginUser) obj; } return null; } @Override public LoginUser getLoginUser() { LoginUser loginUser = null; String authorization = ServletUtils.getRequest().getHeader("Authorization"); if (StringUtils.isEmpty(authorization)) { return null; } // 获取请求携带的令牌 String token = authorization.replace("Bearer ", ""); if (StringUtils.isEmpty(token)) { return null; } try { String userId = null; String tokenKey = getTokenKey(token); if (StringUtils.isNotEmpty(tokenKey)) { userId = (String) redisService.getCacheObject(tokenKey); // 解析对应的权限以及用户信息 String userKey = getUserKey(userId); loginUser = (LoginUser) redisService.getCacheObject(userKey); } return loginUser; } catch (Exception e) { log.error("从缓存获取用户信息异常:{}", e.getMessage()); throw new RuntimeException("用户获取失败,请重新登录!"); } } private String getTokenKey(String token) { return CacheConstants.LOGIN_TOKEN_KEY + token; } private String getUserKey(String userId) { return CacheConstants.LOGIN_USER_KEY + userId; } }
RedisCache
package com.chunjuan.base.utils; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * @author zjp * @create 2023-06-20 15:22 */ @SuppressWarnings(value = {"unchecked", "rawtypes"}) @Component @RequiredArgsConstructor public class RedisCache { private final RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public <T> void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(final String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public <T> long setCacheList(final String key, final List<T> dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public <T> T getCacheMapValue(final String key, final String hKey) { HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 删除Hash中的数据 * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(final String pattern) { return redisTemplate.keys(pattern); } }
LogAspect 日志切面
package com.chunjuan.base.aspect; import com.alibaba.fastjson.JSON; import com.chunjuan.base.common.AsyncFactory; import com.chunjuan.base.common.AsyncManager; import com.chunjuan.base.common.annotation.Log; import com.chunjuan.base.common.filter.PropertyPreExcludeFilter; import com.chunjuan.base.entity.UserOperaLog; import com.chunjuan.base.enums.BusinessStatus; import com.chunjuan.base.service.impl.TokenService; import com.chunjuan.base.utils.ServletUtils; import com.chunjuan.base.utils.StringUtils; import com.chunjuan.base.utils.ip.IpUtils; import com.liquidnet.service.user.domain.model.LoginUser; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; import java.util.Collection; import java.util.Map; import java.util.TimerTask; /** * 操作日志记录处理 * * @author anjiabin */ @Aspect @Component public class LogAspect { /** * 排除敏感属性字段 */ public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; private static final Logger log = LoggerFactory.getLogger(LogAspect.class); @Autowired private TokenService tokenService; /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { handleLog(joinPoint, controllerLog, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { handleLog(joinPoint, controllerLog, e, null); } protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { try { // 获取当前的用户 LoginUser loginUser = tokenService.getLoginUser(); // *========数据库日志=========*// UserOperaLog operLog = new UserOperaLog(); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); operLog.setOperaIp(ip); operLog.setOperaUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); if (loginUser != null) { operLog.setOperaName(loginUser.getUsername()); } if (e != null) { operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 设置请求方式 operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); // 处理设置注解上的参数 getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); TimerTask timerTask = AsyncFactory.recordOper(operLog); // 保存数据库 AsyncManager.me().execute(timerTask); } catch (Exception exp) { // 记录本地异常日志 log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param log 日志 * @param operLog 操作日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, UserOperaLog operLog, Object jsonResult) throws Exception { // 设置action动作 operLog.setBusinessType(log.businessType().ordinal()); // 设置标题 operLog.setTitle(log.title()); // 设置操作人类别 operLog.setOperatorType(log.operatorType().ordinal()); // 是否需要保存request,参数和值 if (log.isSaveRequestData()) { // 获取参数的信息,传入到数据库中。 setRequestValue(joinPoint, operLog); } // 是否需要保存response,参数和值 if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); } } /** * 获取请求的参数,放到log中 * * @param operLog 操作日志 * @throws Exception 异常 */ private void setRequestValue(JoinPoint joinPoint, UserOperaLog operLog) throws Exception { String requestMethod = operLog.getRequestMethod(); if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { String params = argsArrayToString(joinPoint.getArgs()); operLog.setOperaParam(StringUtils.substring(params, 0, 2000)); } else { Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); operLog.setOperaParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter()), 0, 2000)); } } /** * 参数拼装 */ private String argsArrayToString(Object[] paramsArray) { String params = ""; if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) { if (StringUtils.isNotNull(o) && !isFilterObject(o)) { try { String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter()); params += jsonObj.toString() + " "; } catch (Exception e) { } } } } return params.trim(); } /** * 忽略敏感属性 */ public PropertyPreExcludeFilter excludePropertyPreFilter() { return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES); } /** * 判断是否需要过滤的对象。 * * @param o 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @SuppressWarnings("rawtypes") public boolean isFilterObject(final Object o) { Class<?> clazz = o.getClass(); if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } else if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; for (Object value : collection) { return value instanceof MultipartFile; } } else if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } }
Log
package com.chunjuan.base.common.annotation; import com.chunjuan.base.enums.BusinessType; import com.chunjuan.base.enums.OperatorType; import java.lang.annotation.*; /** * 自定义操作日志记录注解 * * @author anjiabin */ @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模块 */ public String title() default ""; /** * 功能 */ public BusinessType businessType() default BusinessType.OTHER; /** * 操作人类别 */ public OperatorType operatorType() default OperatorType.MANAGE; /** * 是否保存请求的参数 */ public boolean isSaveRequestData() default true; /** * 是否保存响应的参数 */ public boolean isSaveResponseData() default true; }
AsyncFactory
public class AsyncFactory { /** * 操作日志记录 * * @param operLog 操作日志信息 * @return 任务task */ public static TimerTask recordOper(final UserOperaLog operLog) { return new TimerTask() { @Override public void run() { // 远程查询操作地点 operLog.setOperaLocation(AddressUtils.getRealAddressByIP(operLog.getOperaIp())); SpringUtils.getBean(IUserOperaLogService.class).save(operLog); } }; } }
AsyncManager
package com.chunjuan.base.common; import com.chunjuan.base.utils.Threads; import com.chunjuan.base.utils.spring.SpringUtils; import java.util.TimerTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 异步任务管理器 * * @author anjiabin */ public class AsyncManager { private static AsyncManager me = new AsyncManager(); /** * 操作延迟10毫秒 */ private final int OPERATE_DELAY_TIME = 10; /** * 异步操作任务调度线程池 */ private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); /** * 单例模式 */ private AsyncManager() { } public static AsyncManager me() { return me; } /** * 执行任务 * * @param task 任务 */ public void execute(TimerTask task) { executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } /** * 停止任务线程池 */ public void shutdown() { Threads.shutdownAndAwaitTermination(executor); } }
ShutdownManager
package com.chunjuan.base.common; import jakarta.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 确保应用退出时能关闭后台线程 * * @author anjiabin */ @Component public class ShutdownManager { private static final Logger logger = LoggerFactory.getLogger("sys-user"); @PreDestroy public void destroy() { shutdownAsyncManager(); } /** * 停止异步执行任务 */ private void shutdownAsyncManager() { try { logger.info("====关闭后台任务任务线程池===="); AsyncManager.me().shutdown(); } catch (Exception e) { logger.error(e.getMessage(), e); } } }
ThreadPoolConfig
package com.chunjuan.base.config; import com.chunjuan.base.utils.Threads; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; /** * 线程池配置 * * @author anjiabin **/ @Configuration public class ThreadPoolConfig { // 核心线程池大小 private int corePoolSize = 50; // 最大可创建的线程数 private int maxPoolSize = 200; // 队列最大长度 private int queueCapacity = 1000; // 线程池维护线程所允许的空闲时间 private int keepAliveSeconds = 300; /** * 执行周期性或定时任务 */ @Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Threads.printException(r, t); } }; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。