越权系统框架整改
This commit is contained in:
parent
d3dc26b1ea
commit
24a1021348
|
|
@ -4,6 +4,7 @@ import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.bonus.common.annotation.RequiresPermissions;
|
||||||
import com.bonus.common.annotation.SysLog;
|
import com.bonus.common.annotation.SysLog;
|
||||||
import com.bonus.common.enums.OperaType;
|
import com.bonus.common.enums.OperaType;
|
||||||
import com.bonus.common.utils.encryption.Sm4Utils;
|
import com.bonus.common.utils.encryption.Sm4Utils;
|
||||||
|
|
@ -60,7 +61,8 @@ public class SysUserController extends BaseController
|
||||||
/**
|
/**
|
||||||
* 获取用户列表
|
* 获取用户列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('system:user:list')")
|
// @PreAuthorize("@ss.hasPermi('system:user:list')")
|
||||||
|
@RequiresPermissions("system:user:list")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@SysLog(title = "用户管理", businessType = OperaType.QUERY, logType = 0, module = "系统管理->用户管理", details = "查询用户列表")
|
@SysLog(title = "用户管理", businessType = OperaType.QUERY, logType = 0, module = "系统管理->用户管理", details = "查询用户列表")
|
||||||
public TableDataInfo list(SysUser user)
|
public TableDataInfo list(SysUser user)
|
||||||
|
|
@ -253,7 +255,8 @@ public class SysUserController extends BaseController
|
||||||
/**
|
/**
|
||||||
* 获取部门树列表
|
* 获取部门树列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('system:user:list')")
|
// @PreAuthorize("@ss.hasPermi('system:user:list')")
|
||||||
|
@RequiresPermissions("system:user:list")
|
||||||
@GetMapping("/deptTree")
|
@GetMapping("/deptTree")
|
||||||
public AjaxResult deptTree(SysDept dept)
|
public AjaxResult deptTree(SysDept dept)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.bonus.common.annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限注解的验证模式
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum Logical
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 必须具有所有的元素
|
||||||
|
*/
|
||||||
|
AND,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只需具有其中一个元素
|
||||||
|
*/
|
||||||
|
OR
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.bonus.common.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录认证:只有登录之后才能进入该方法
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
public @interface RequiresLogin
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.bonus.common.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限认证:必须具有指定权限才能进入该方法
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||||
|
public @interface RequiresPermissions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 需要校验的权限码
|
||||||
|
*/
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证模式:AND | OR,默认AND
|
||||||
|
*/
|
||||||
|
Logical logical() default Logical.AND;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.bonus.common.context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:PermissionContextHolder
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-09-09-10:43
|
||||||
|
* @version:1.0
|
||||||
|
* @description:权限上下文持有类
|
||||||
|
*/
|
||||||
|
public class PermissionContextHolder {
|
||||||
|
|
||||||
|
private static final ThreadLocal<String> PERMISSION_CONTEXT = new ThreadLocal<>();
|
||||||
|
|
||||||
|
public static void setPermission(String permission) {
|
||||||
|
PERMISSION_CONTEXT.set(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPermission() {
|
||||||
|
return PERMISSION_CONTEXT.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
PERMISSION_CONTEXT.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.bonus.common.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未能通过的登录认证异常
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*/
|
||||||
|
public class NotLoginException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NotLoginException(String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.bonus.common.exception;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未能通过的权限认证异常
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*/
|
||||||
|
public class NotPermissionException extends RuntimeException
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public NotPermissionException(String permission)
|
||||||
|
{
|
||||||
|
super(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotPermissionException(String[] permissions)
|
||||||
|
{
|
||||||
|
super(StringUtils.join(permissions, ","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.bonus.framework.aspectj;
|
||||||
|
|
||||||
|
import com.bonus.common.annotation.RequiresPermissions;
|
||||||
|
import com.bonus.framework.auth.AuthLogic;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 Spring Aop 的注解鉴权
|
||||||
|
*
|
||||||
|
* @author kong
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
public class PreAuthorizeAspect
|
||||||
|
{
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AuthLogic authLogic;
|
||||||
|
/**
|
||||||
|
* 构建
|
||||||
|
*/
|
||||||
|
public PreAuthorizeAspect()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义AOP签名 (切入所有使用鉴权注解的方法)
|
||||||
|
*/
|
||||||
|
public static final String POINTCUT_SIGN = "@annotation(com.bonus.common.annotation.RequiresPermissions)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 声明AOP签名
|
||||||
|
*/
|
||||||
|
@Pointcut(POINTCUT_SIGN)
|
||||||
|
public void pointcut()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环绕切入
|
||||||
|
*
|
||||||
|
* @param joinPoint 切面对象
|
||||||
|
* @return 底层方法执行后的返回值
|
||||||
|
* @throws Throwable 底层方法抛出的异常
|
||||||
|
*/
|
||||||
|
@Around("pointcut()")
|
||||||
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
|
||||||
|
{
|
||||||
|
// 注解鉴权
|
||||||
|
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||||
|
checkMethodAnnotation(signature.getMethod(),joinPoint);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 执行原有逻辑
|
||||||
|
Object obj = joinPoint.proceed();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对一个Method对象进行注解检查
|
||||||
|
*/
|
||||||
|
public void checkMethodAnnotation(Method method,ProceedingJoinPoint joinPoint)
|
||||||
|
{
|
||||||
|
|
||||||
|
// 校验 @RequiresPermissions 注解
|
||||||
|
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
|
||||||
|
if (requiresPermissions != null)
|
||||||
|
{
|
||||||
|
authLogic.checkPermi(requiresPermissions,joinPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,271 @@
|
||||||
|
package com.bonus.framework.auth;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import com.bonus.common.annotation.Logical;
|
||||||
|
import com.bonus.common.annotation.RequiresLogin;
|
||||||
|
import com.bonus.common.annotation.RequiresPermissions;
|
||||||
|
import com.bonus.common.context.PermissionContextHolder;
|
||||||
|
import com.bonus.common.core.domain.model.LoginUser;
|
||||||
|
import com.bonus.common.exception.NotLoginException;
|
||||||
|
import com.bonus.common.utils.DateUtils;
|
||||||
|
import com.bonus.common.utils.SecurityUtils;
|
||||||
|
import com.bonus.common.utils.StringUtils;
|
||||||
|
import com.bonus.common.utils.ip.IpUtils;
|
||||||
|
import com.bonus.framework.manager.AsyncManager;
|
||||||
|
import com.bonus.framework.manager.factory.AsyncFactory;
|
||||||
|
import com.bonus.framework.web.service.TokenService;
|
||||||
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.PatternMatchUtils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:AuthLogic
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-09-08-17:54
|
||||||
|
* @version:1.0
|
||||||
|
* @description:鉴权实现类
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AuthLogic {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TokenService tokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有权限标识
|
||||||
|
*/
|
||||||
|
private static final String ALL_PERMISSION = "*:*:*";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员角色权限标识
|
||||||
|
*/
|
||||||
|
private static final String SUPER_ADMIN = "admin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员角色权限标识
|
||||||
|
*/
|
||||||
|
private static final String COMPANY_ADMIN = "company_admin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检验用户是否已经登录,如未登录,则抛出异常
|
||||||
|
*/
|
||||||
|
public void checkLogin() {
|
||||||
|
getLoginUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户缓存信息, 如果未登录,则抛出异常
|
||||||
|
*
|
||||||
|
* @return 用户缓存信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser() {
|
||||||
|
String token = tokenService.getToken();
|
||||||
|
if (token == null) {
|
||||||
|
throw new NotLoginException("未提供token");
|
||||||
|
}
|
||||||
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
throw new NotLoginException("无效的token");
|
||||||
|
}
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户缓存信息, 如果未登录,则抛出异常
|
||||||
|
*
|
||||||
|
* @param token 前端传递的认证信息
|
||||||
|
* @return 用户缓存信息
|
||||||
|
*/
|
||||||
|
public LoginUser getLoginUser(String token) {
|
||||||
|
return tokenService.getLoginUser(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否具备某权限
|
||||||
|
*
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public boolean hasPermi(String permission) {
|
||||||
|
return hasPermi(getPermiList(), permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException
|
||||||
|
*
|
||||||
|
* @param requiresPermissions 注解对象
|
||||||
|
*/
|
||||||
|
public void checkPermi(RequiresPermissions requiresPermissions, ProceedingJoinPoint joinPoint) {
|
||||||
|
// 获取请求参数
|
||||||
|
boolean needPermission = true;
|
||||||
|
Object[] args = joinPoint.getArgs();
|
||||||
|
|
||||||
|
for (Object obj : args) {
|
||||||
|
if (obj instanceof JSONObject) {
|
||||||
|
JSONObject jsonObject = (JSONObject) obj;
|
||||||
|
if ("1".equals(jsonObject.getStr("skipPermission"))) {
|
||||||
|
needPermission = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needPermission) {
|
||||||
|
// 使用自定义的PermissionContextHolder替代SecurityContextHolder
|
||||||
|
PermissionContextHolder.setPermission(StringUtils.join(requiresPermissions.value(), ","));
|
||||||
|
try {
|
||||||
|
if (requiresPermissions.logical() == Logical.AND) {
|
||||||
|
checkPermiAnd(requiresPermissions.value());
|
||||||
|
} else {
|
||||||
|
checkPermiOr(requiresPermissions.value());
|
||||||
|
}
|
||||||
|
} catch (AccessDeniedException e) {
|
||||||
|
// 记录越权日志(异步处理,不阻塞当前线程)
|
||||||
|
addErrorLogsAsync(joinPoint, requiresPermissions, e.getMessage());
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
// 清理ThreadLocal,防止内存泄漏
|
||||||
|
PermissionContextHolder.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步记录越权日志(不阻塞当前线程)
|
||||||
|
*/
|
||||||
|
private void addErrorLogsAsync(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions, String errorMessage) {
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = getLoginUser();
|
||||||
|
loginUser.setIpaddr(IpUtils.getIpAddr());
|
||||||
|
SysLogsVo vo = SysLogsVo.getExceedAuthorithSysLogsVo(loginUser, joinPoint);
|
||||||
|
// 假设LogsUtils.setRequestValue存在,如果不存在需要处理
|
||||||
|
LogsUtils.setRequestValue(joinPoint, vo, null);
|
||||||
|
|
||||||
|
// 设置基础信息
|
||||||
|
vo.setParams(StringUtils.join(requiresPermissions.value(), ","));
|
||||||
|
vo.setOperTime(DateUtils.getTime());
|
||||||
|
vo.setTimes("0");
|
||||||
|
|
||||||
|
// 异步查询日志模块信息并记录日志
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
SysLogsVo queryVo = new SysLogsVo();
|
||||||
|
queryVo.setParams(requiresPermissions.value()[0]);
|
||||||
|
|
||||||
|
// 异步查询日志模块信息
|
||||||
|
Future<Map<String, String>> mapFuture = AsyncManager.me().executeGetLogsModule(queryVo);
|
||||||
|
Map<String, String> result = mapFuture.get(); // 这里在异步线程中阻塞是可以的
|
||||||
|
|
||||||
|
// 设置查询结果
|
||||||
|
vo.setModel(result.get("module"));
|
||||||
|
vo.setTitle(result.get("title"));
|
||||||
|
vo.setOperateDetail(result.get("detail"));
|
||||||
|
vo.setOperType(result.get("bussType"));
|
||||||
|
vo.setResultData(result.get("resultData"));
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
AsyncManager.me().execute(AsyncFactory.addSysOperLog(vo));
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("越权日志记录失败: " + e.getMessage());
|
||||||
|
// 即使查询失败也记录基础日志信息
|
||||||
|
vo.setTitle("权限验证失败");
|
||||||
|
vo.setOperateDetail("查询操作模块信息失败");
|
||||||
|
AsyncManager.me().execute(AsyncFactory.addSysOperLog(vo));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("越权日志记录初始化失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定权限,必须全部拥有
|
||||||
|
*
|
||||||
|
* @param permissions 权限列表
|
||||||
|
*/
|
||||||
|
public void checkPermiAnd(String... permissions) {
|
||||||
|
Set<String> permissionList = getPermiList();
|
||||||
|
|
||||||
|
for (String permission : permissions) {
|
||||||
|
System.err.println(hasPermi(permissionList, permission));
|
||||||
|
if (!hasPermi(permissionList, permission)) {
|
||||||
|
// throw new NotPermissionException(permission);
|
||||||
|
throw new AccessDeniedException(permission+":越权访问");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否含有指定权限,只需包含其中一个
|
||||||
|
*
|
||||||
|
* @param permissions 权限码数组
|
||||||
|
*/
|
||||||
|
public void checkPermiOr(String... permissions) {
|
||||||
|
Set<String> permissionList = getPermiList();
|
||||||
|
for (String permission : permissions) {
|
||||||
|
if (hasPermi(permissionList, permission)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permissions.length > 0) {
|
||||||
|
// throw new NotPermissionException(permissions);
|
||||||
|
throw new AccessDeniedException("越权访问");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresLogin)鉴权
|
||||||
|
*
|
||||||
|
* @param at 注解对象
|
||||||
|
*/
|
||||||
|
public void checkByAnnotation(RequiresLogin at) {
|
||||||
|
this.checkLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据注解(@RequiresPermissions)鉴权
|
||||||
|
*
|
||||||
|
* @param at 注解对象
|
||||||
|
*/
|
||||||
|
public void checkByAnnotation(RequiresPermissions at) {
|
||||||
|
String[] permissionArray = at.value();
|
||||||
|
if (at.logical() == Logical.AND) {
|
||||||
|
this.checkPermiAnd(permissionArray);
|
||||||
|
} else {
|
||||||
|
this.checkPermiOr(permissionArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前账号的权限列表
|
||||||
|
*
|
||||||
|
* @return 权限列表
|
||||||
|
*/
|
||||||
|
public Set<String> getPermiList() {
|
||||||
|
try {
|
||||||
|
LoginUser loginUser = getLoginUser();
|
||||||
|
return loginUser.getPermissions();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new HashSet<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否包含权限
|
||||||
|
*
|
||||||
|
* @param authorities 权限列表
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public boolean hasPermi(Collection<String> authorities, String permission) {
|
||||||
|
return authorities.stream().filter(StringUtils::hasText)
|
||||||
|
.anyMatch(x -> ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.bonus.framework.auth;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.bonus.common.utils.ServletUtils;
|
||||||
|
import com.bonus.common.utils.StringUtils;
|
||||||
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author bonus
|
||||||
|
*/
|
||||||
|
public class LogsUtils {
|
||||||
|
/**
|
||||||
|
* 获取请求的参数,放到log中
|
||||||
|
*
|
||||||
|
* @param operLog 操作日志
|
||||||
|
* @throws Exception 异常
|
||||||
|
*/
|
||||||
|
public static void setRequestValue(ProceedingJoinPoint joinPoint, SysLogsVo operLog, String[] excludeParamNames) throws Exception {
|
||||||
|
String requestMethod = operLog.getMethod();
|
||||||
|
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
|
||||||
|
boolean bResult = HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod);
|
||||||
|
if (StringUtils.isEmpty(paramsMap) && bResult)
|
||||||
|
{
|
||||||
|
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
|
||||||
|
operLog.setParams(StringUtils.substring(params, 0, 2000));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
operLog.setParams(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数拼装
|
||||||
|
*/
|
||||||
|
private static String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
|
||||||
|
{
|
||||||
|
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(excludeParamNames));
|
||||||
|
params += jsonObj.toString() + " ";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 排除敏感属性字段 */
|
||||||
|
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
|
||||||
|
/**
|
||||||
|
* 忽略敏感属性
|
||||||
|
*/
|
||||||
|
public static PropertyPreExcludeFilter2 excludePropertyPreFilter(String[] excludeParamNames)
|
||||||
|
{
|
||||||
|
return new PropertyPreExcludeFilter2().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否需要过滤的对象。
|
||||||
|
*
|
||||||
|
* @param o 对象信息。
|
||||||
|
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.bonus.framework.auth;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除JSON敏感属性
|
||||||
|
*
|
||||||
|
* @author bonus
|
||||||
|
*/
|
||||||
|
public class PropertyPreExcludeFilter2 extends SimplePropertyPreFilter
|
||||||
|
{
|
||||||
|
public PropertyPreExcludeFilter2()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyPreExcludeFilter2 addExcludes(String... filters)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < filters.length; i++)
|
||||||
|
{
|
||||||
|
this.getExcludes().add(filters[i]);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package com.bonus.framework.manager;
|
package com.bonus.framework.manager;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import com.bonus.common.utils.Threads;
|
import com.bonus.common.utils.Threads;
|
||||||
import com.bonus.common.utils.spring.SpringUtils;
|
import com.bonus.common.utils.spring.SpringUtils;
|
||||||
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
|
import com.bonus.system.service.ISysOperLogService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步任务管理器
|
* 异步任务管理器
|
||||||
|
|
@ -45,6 +48,48 @@ public class AsyncManager
|
||||||
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
|
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行带返回值的任务
|
||||||
|
*
|
||||||
|
* @param task 带返回值的任务
|
||||||
|
* @param <T> 返回值类型
|
||||||
|
* @return Future对象,用于获取执行结果
|
||||||
|
*/
|
||||||
|
public <T> Future<T> execute(Callable<T> task)
|
||||||
|
{
|
||||||
|
return executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行FutureTask任务
|
||||||
|
*
|
||||||
|
* @param futureTask FutureTask任务
|
||||||
|
* @param <T> 返回值类型
|
||||||
|
* @return Future对象,用于获取执行结果
|
||||||
|
*/
|
||||||
|
public <T> Future<T> execute(FutureTask<T> futureTask)
|
||||||
|
{
|
||||||
|
executor.schedule(futureTask, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
|
||||||
|
return futureTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行查询日志模块的异步任务
|
||||||
|
*
|
||||||
|
* @param sysLogsVo 操作日志信息
|
||||||
|
* @return Future对象,用于获取查询结果
|
||||||
|
*/
|
||||||
|
public Future<Map<String, String>> executeGetLogsModule(final SysLogsVo sysLogsVo)
|
||||||
|
{
|
||||||
|
Callable<Map<String, String>> callable = new Callable<Map<String, String>>() {
|
||||||
|
@Override
|
||||||
|
public Map<String, String> call() throws Exception {
|
||||||
|
return SpringUtils.getBean(ISysOperLogService.class).getLogsModule(sysLogsVo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return execute(callable);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止任务线程池
|
* 停止任务线程池
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package com.bonus.framework.manager.factory;
|
package com.bonus.framework.manager.factory;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
|
||||||
import com.bonus.system.domain.SysLogsVo;
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
@ -121,4 +124,23 @@ public class AsyncFactory
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询操作的日志模块
|
||||||
|
*
|
||||||
|
* @param sysLogsVo 操作日志信息
|
||||||
|
* @return 任务task
|
||||||
|
*/
|
||||||
|
public static FutureTask<Map<String,String>> getLogsModule(final SysLogsVo sysLogsVo)
|
||||||
|
{
|
||||||
|
Callable<Map<String,String>> callable = new Callable<Map<String,String>>() {
|
||||||
|
@Override
|
||||||
|
public Map<String,String> call() throws Exception {
|
||||||
|
return SpringUtils.getBean(ISysOperLogService.class).getLogsModule(sysLogsVo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new FutureTask<>(callable);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,4 +236,31 @@ public class TokenService
|
||||||
{
|
{
|
||||||
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
|
return CacheConstants.LOGIN_TOKEN_KEY + uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求token
|
||||||
|
*/
|
||||||
|
public String getToken()
|
||||||
|
{
|
||||||
|
return getToken(ServletUtils.getRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginUser getLoginUser(String token){
|
||||||
|
if (StringUtils.isNotEmpty(token)){
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
// 解析对应的权限以及用户信息
|
||||||
|
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
|
||||||
|
String userKey = getTokenKey(uuid);
|
||||||
|
LoginUser user = redisCache.getCacheObject(userKey);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
log.error("获取用户信息异常'{}'", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.bonus.system.domain;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志菜单头
|
||||||
|
* @author weiweiw
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysLogsMenuHead {
|
||||||
|
|
||||||
|
|
||||||
|
public String menuName;
|
||||||
|
|
||||||
|
private String menuType;
|
||||||
|
|
||||||
|
public String menuName2;
|
||||||
|
|
||||||
|
public String menuName3;
|
||||||
|
|
||||||
|
public String menuName4;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.bonus.system.mapper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bonus.system.domain.SysLogsMenuHead;
|
||||||
import com.bonus.system.domain.SysLogsVo;
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
import com.bonus.system.domain.SysOperLog;
|
import com.bonus.system.domain.SysOperLog;
|
||||||
|
|
||||||
|
|
@ -65,4 +66,13 @@ public interface SysOperLogMapper
|
||||||
* @date 2025/9/5 13:41
|
* @date 2025/9/5 13:41
|
||||||
*/
|
*/
|
||||||
SysLogsVo getModule(String operUri);
|
SysLogsVo getModule(String operUri);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询 异常日志路径
|
||||||
|
* @param sysLogsVo
|
||||||
|
* @return List<SysLogsMenuHead>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2025/9/9 9:55
|
||||||
|
*/
|
||||||
|
List<SysLogsMenuHead> getLogsErrorModule(SysLogsVo sysLogsVo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.bonus.system.service;
|
package com.bonus.system.service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.bonus.system.domain.SysLogsVo;
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
import com.bonus.system.domain.SysOperLog;
|
import com.bonus.system.domain.SysOperLog;
|
||||||
|
|
@ -56,4 +57,13 @@ public interface ISysOperLogService
|
||||||
* @date 2025/9/5 13:14
|
* @date 2025/9/5 13:14
|
||||||
*/
|
*/
|
||||||
void addSysOperLog(SysLogsVo sysLogsVo);
|
void addSysOperLog(SysLogsVo sysLogsVo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询操作模块
|
||||||
|
* @param sysLogsVo
|
||||||
|
* @return Map<String,String>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2025/9/9 9:51
|
||||||
|
*/
|
||||||
|
Map<String, String> getLogsModule(SysLogsVo sysLogsVo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
package com.bonus.system.service.impl;
|
package com.bonus.system.service.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.bonus.common.utils.global.SystemGlobal;
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.bonus.common.enums.OperaType;
|
||||||
|
import com.bonus.system.domain.SysLogsMenuHead;
|
||||||
import com.bonus.system.domain.SysLogsVo;
|
import com.bonus.system.domain.SysLogsVo;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -26,6 +32,8 @@ public class SysOperLogServiceImpl implements ISysOperLogService
|
||||||
@Autowired
|
@Autowired
|
||||||
private SysOperLogMapper operLogMapper;
|
private SysOperLogMapper operLogMapper;
|
||||||
|
|
||||||
|
public static final String MENU_TYPE_FILE = "F";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增操作日志
|
* 新增操作日志
|
||||||
*
|
*
|
||||||
|
|
@ -103,4 +111,77 @@ public class SysOperLogServiceImpl implements ISysOperLogService
|
||||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getLogsModule(SysLogsVo sysLogsVo) {
|
||||||
|
Map<String, String> maps= Maps.newHashMap();
|
||||||
|
Map result=Maps.newHashMap();
|
||||||
|
result.put("code","403");
|
||||||
|
result.put("data","");
|
||||||
|
result.put("msg","数据接口未授权");
|
||||||
|
try{
|
||||||
|
List<SysLogsMenuHead> list =operLogMapper.getLogsErrorModule(sysLogsVo);
|
||||||
|
if(CollectionUtils.isNotEmpty(list)){
|
||||||
|
SysLogsMenuHead vo=list.get(0);
|
||||||
|
StringBuffer sb=new StringBuffer();
|
||||||
|
String type=vo.getMenuType();
|
||||||
|
if(StringUtils.isNotEmpty(vo.getMenuName4())){
|
||||||
|
sb.append(vo.getMenuName4());
|
||||||
|
}
|
||||||
|
if(StringUtils.isNotEmpty(vo.getMenuName3())){
|
||||||
|
sbAppend(sb);
|
||||||
|
sb.append(vo.getMenuName3());
|
||||||
|
}
|
||||||
|
if(StringUtils.isNotEmpty(vo.getMenuName2())){
|
||||||
|
sbAppend(sb);
|
||||||
|
sb.append(vo.getMenuName2());
|
||||||
|
}
|
||||||
|
if( MENU_TYPE_FILE .equals(type)){
|
||||||
|
if(vo.getMenuName().contains(OperaType.INSERT)){
|
||||||
|
maps.put("bussType",OperaType.INSERT);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.UPDATE)){
|
||||||
|
maps.put("bussType",OperaType.UPDATE);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.DELETE)){
|
||||||
|
maps.put("bussType",OperaType.DELETE);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.IMPORT)){
|
||||||
|
maps.put("bussType",OperaType.IMPORT);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.DOWNLOAD)){
|
||||||
|
maps.put("bussType",OperaType.DOWNLOAD);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.EXPORT)){
|
||||||
|
maps.put("bussType",OperaType.EXPORT);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.FLASH)){
|
||||||
|
maps.put("bussType",OperaType.FLASH);
|
||||||
|
}else if(vo.getMenuName().contains(OperaType.GRANT)){
|
||||||
|
maps.put("bussType",OperaType.GRANT);
|
||||||
|
} else {
|
||||||
|
maps.put("bussType",OperaType.OTHER);
|
||||||
|
}
|
||||||
|
maps.put("title",vo.getMenuName2());
|
||||||
|
}else{
|
||||||
|
maps.put("bussType",OperaType.QUERY);
|
||||||
|
sbAppend(sb);
|
||||||
|
sb.append(vo.getMenuName());
|
||||||
|
maps.put("title",vo.getMenuName());
|
||||||
|
}
|
||||||
|
maps.put("detail", "进行数据的"+maps.get("bussType"));
|
||||||
|
maps.put("module",sb.toString());
|
||||||
|
maps.put("resultData", JSON.toJSONString(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
}catch (Exception e){
|
||||||
|
maps.put("resultData", JSON.toJSONString(result));
|
||||||
|
maps.put("title","智慧图档");
|
||||||
|
maps.put("module","智慧图档->数据接口");
|
||||||
|
maps.put("detail","进行数据访问");
|
||||||
|
maps.put("bussType", "未知");
|
||||||
|
log.error(e.toString(),e);
|
||||||
|
}
|
||||||
|
return maps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sbAppend(StringBuffer sb){
|
||||||
|
if(StringUtils.isNotEmpty(sb.toString())){
|
||||||
|
sb.append("->");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
where sm3.component=#{module}
|
where sm3.component=#{module}
|
||||||
limit 1
|
limit 1
|
||||||
</select>
|
</select>
|
||||||
|
<!--查询 异常日志路径-->
|
||||||
|
<select id="getLogsErrorModule" resultType="com.bonus.system.domain.SysLogsMenuHead">
|
||||||
|
select sm.menu_name menuName,sm.menu_type menuType,sm2.menu_name menuName2,
|
||||||
|
sm3.menu_name menuName3,sm4.menu_name menuName4
|
||||||
|
FROM da_ky_sys_menu sm
|
||||||
|
left join da_ky_sys_menu sm2 on sm.parent_id=sm2.menu_id and sm2.visible=0
|
||||||
|
left join da_ky_sys_menu sm3 on sm2.parent_id=sm3.menu_id and sm3.visible=0
|
||||||
|
left join da_ky_sys_menu sm4 on sm3.parent_id=sm4.menu_id and sm4.visible=0
|
||||||
|
where sm.perms=#{params}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<update id="cleanOperLog">
|
<update id="cleanOperLog">
|
||||||
truncate table da_ky_sys_oper_log
|
truncate table da_ky_sys_oper_log
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue