diff --git a/bonus-admin/src/main/java/com/bonus/web/controller/system/SysUserController.java b/bonus-admin/src/main/java/com/bonus/web/controller/system/SysUserController.java index 489e10a..3b3a993 100644 --- a/bonus-admin/src/main/java/com/bonus/web/controller/system/SysUserController.java +++ b/bonus-admin/src/main/java/com/bonus/web/controller/system/SysUserController.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; +import com.bonus.common.annotation.RequiresPermissions; import com.bonus.common.annotation.SysLog; import com.bonus.common.enums.OperaType; 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") @SysLog(title = "用户管理", businessType = OperaType.QUERY, logType = 0, module = "系统管理->用户管理", details = "查询用户列表") 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") public AjaxResult deptTree(SysDept dept) { diff --git a/bonus-common/src/main/java/com/bonus/common/annotation/Logical.java b/bonus-common/src/main/java/com/bonus/common/annotation/Logical.java new file mode 100644 index 0000000..c8b5745 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/annotation/Logical.java @@ -0,0 +1,20 @@ +package com.bonus.common.annotation; + +/** + * 权限注解的验证模式 + * + * @author bonus + * + */ +public enum Logical +{ + /** + * 必须具有所有的元素 + */ + AND, + + /** + * 只需具有其中一个元素 + */ + OR +} diff --git a/bonus-common/src/main/java/com/bonus/common/annotation/RequiresLogin.java b/bonus-common/src/main/java/com/bonus/common/annotation/RequiresLogin.java new file mode 100644 index 0000000..ecb625f --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/annotation/RequiresLogin.java @@ -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 +{ +} diff --git a/bonus-common/src/main/java/com/bonus/common/annotation/RequiresPermissions.java b/bonus-common/src/main/java/com/bonus/common/annotation/RequiresPermissions.java new file mode 100644 index 0000000..be4c85d --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/annotation/RequiresPermissions.java @@ -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; + + + + + + + + +} diff --git a/bonus-common/src/main/java/com/bonus/common/context/PermissionContextHolder.java b/bonus-common/src/main/java/com/bonus/common/context/PermissionContextHolder.java new file mode 100644 index 0000000..482fde0 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/context/PermissionContextHolder.java @@ -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 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(); + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/exception/NotLoginException.java b/bonus-common/src/main/java/com/bonus/common/exception/NotLoginException.java new file mode 100644 index 0000000..77e3d91 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/exception/NotLoginException.java @@ -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); + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/exception/NotPermissionException.java b/bonus-common/src/main/java/com/bonus/common/exception/NotPermissionException.java new file mode 100644 index 0000000..6d89d14 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/exception/NotPermissionException.java @@ -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, ",")); + } +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/aspectj/PreAuthorizeAspect.java b/bonus-framework/src/main/java/com/bonus/framework/aspectj/PreAuthorizeAspect.java new file mode 100644 index 0000000..28aba89 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/aspectj/PreAuthorizeAspect.java @@ -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); + } + } +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java b/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java new file mode 100644 index 0000000..b321688 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/auth/AuthLogic.java @@ -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> mapFuture = AsyncManager.me().executeGetLogsModule(queryVo); + Map 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 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 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 getPermiList() { + try { + LoginUser loginUser = getLoginUser(); + return loginUser.getPermissions(); + } catch (Exception e) { + return new HashSet<>(); + } + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(Collection authorities, String permission) { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } +} \ No newline at end of file diff --git a/bonus-framework/src/main/java/com/bonus/framework/auth/LogsUtils.java b/bonus-framework/src/main/java/com/bonus/framework/auth/LogsUtils.java new file mode 100644 index 0000000..d695196 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/auth/LogsUtils.java @@ -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; + } + +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/auth/PropertyPreExcludeFilter2.java b/bonus-framework/src/main/java/com/bonus/framework/auth/PropertyPreExcludeFilter2.java new file mode 100644 index 0000000..eb04789 --- /dev/null +++ b/bonus-framework/src/main/java/com/bonus/framework/auth/PropertyPreExcludeFilter2.java @@ -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; + } +} diff --git a/bonus-framework/src/main/java/com/bonus/framework/manager/AsyncManager.java b/bonus-framework/src/main/java/com/bonus/framework/manager/AsyncManager.java index 197c705..c21d621 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/manager/AsyncManager.java +++ b/bonus-framework/src/main/java/com/bonus/framework/manager/AsyncManager.java @@ -1,10 +1,13 @@ package com.bonus.framework.manager; +import java.util.Map; import java.util.TimerTask; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; + import com.bonus.common.utils.Threads; 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); } + /** + * 执行带返回值的任务 + * + * @param task 带返回值的任务 + * @param 返回值类型 + * @return Future对象,用于获取执行结果 + */ + public Future execute(Callable task) + { + return executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 执行FutureTask任务 + * + * @param futureTask FutureTask任务 + * @param 返回值类型 + * @return Future对象,用于获取执行结果 + */ + public Future execute(FutureTask futureTask) + { + executor.schedule(futureTask, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + return futureTask; + } + + /** + * 执行查询日志模块的异步任务 + * + * @param sysLogsVo 操作日志信息 + * @return Future对象,用于获取查询结果 + */ + public Future> executeGetLogsModule(final SysLogsVo sysLogsVo) + { + Callable> callable = new Callable>() { + @Override + public Map call() throws Exception { + return SpringUtils.getBean(ISysOperLogService.class).getLogsModule(sysLogsVo); + } + }; + return execute(callable); + } + /** * 停止任务线程池 */ diff --git a/bonus-framework/src/main/java/com/bonus/framework/manager/factory/AsyncFactory.java b/bonus-framework/src/main/java/com/bonus/framework/manager/factory/AsyncFactory.java index 01332c7..c6a3832 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/manager/factory/AsyncFactory.java +++ b/bonus-framework/src/main/java/com/bonus/framework/manager/factory/AsyncFactory.java @@ -1,6 +1,9 @@ package com.bonus.framework.manager.factory; +import java.util.Map; import java.util.TimerTask; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; import com.bonus.system.domain.SysLogsVo; import org.slf4j.Logger; @@ -121,4 +124,23 @@ public class AsyncFactory } }; } + + + /** + * 查询操作的日志模块 + * + * @param sysLogsVo 操作日志信息 + * @return 任务task + */ + public static FutureTask> getLogsModule(final SysLogsVo sysLogsVo) + { + Callable> callable = new Callable>() { + @Override + public Map call() throws Exception { + return SpringUtils.getBean(ISysOperLogService.class).getLogsModule(sysLogsVo); + } + }; + return new FutureTask<>(callable); + } + } diff --git a/bonus-framework/src/main/java/com/bonus/framework/web/service/TokenService.java b/bonus-framework/src/main/java/com/bonus/framework/web/service/TokenService.java index b521d5b..79124c5 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/web/service/TokenService.java +++ b/bonus-framework/src/main/java/com/bonus/framework/web/service/TokenService.java @@ -236,4 +236,31 @@ public class TokenService { 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; + } } diff --git a/bonus-system/src/main/java/com/bonus/system/domain/SysLogsMenuHead.java b/bonus-system/src/main/java/com/bonus/system/domain/SysLogsMenuHead.java new file mode 100644 index 0000000..295e0fd --- /dev/null +++ b/bonus-system/src/main/java/com/bonus/system/domain/SysLogsMenuHead.java @@ -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; + + +} diff --git a/bonus-system/src/main/java/com/bonus/system/mapper/SysOperLogMapper.java b/bonus-system/src/main/java/com/bonus/system/mapper/SysOperLogMapper.java index 0d702cb..0e7dac3 100644 --- a/bonus-system/src/main/java/com/bonus/system/mapper/SysOperLogMapper.java +++ b/bonus-system/src/main/java/com/bonus/system/mapper/SysOperLogMapper.java @@ -2,6 +2,7 @@ package com.bonus.system.mapper; import java.util.List; +import com.bonus.system.domain.SysLogsMenuHead; import com.bonus.system.domain.SysLogsVo; import com.bonus.system.domain.SysOperLog; @@ -65,4 +66,13 @@ public interface SysOperLogMapper * @date 2025/9/5 13:41 */ SysLogsVo getModule(String operUri); + + /** + * 查询 异常日志路径 + * @param sysLogsVo + * @return List + * @author cwchen + * @date 2025/9/9 9:55 + */ + List getLogsErrorModule(SysLogsVo sysLogsVo); } diff --git a/bonus-system/src/main/java/com/bonus/system/service/ISysOperLogService.java b/bonus-system/src/main/java/com/bonus/system/service/ISysOperLogService.java index c299346..04870fd 100644 --- a/bonus-system/src/main/java/com/bonus/system/service/ISysOperLogService.java +++ b/bonus-system/src/main/java/com/bonus/system/service/ISysOperLogService.java @@ -1,6 +1,7 @@ package com.bonus.system.service; import java.util.List; +import java.util.Map; import com.bonus.system.domain.SysLogsVo; import com.bonus.system.domain.SysOperLog; @@ -56,4 +57,13 @@ public interface ISysOperLogService * @date 2025/9/5 13:14 */ void addSysOperLog(SysLogsVo sysLogsVo); + + /** + * 查询操作模块 + * @param sysLogsVo + * @return Map + * @author cwchen + * @date 2025/9/9 9:51 + */ + Map getLogsModule(SysLogsVo sysLogsVo); } diff --git a/bonus-system/src/main/java/com/bonus/system/service/impl/SysOperLogServiceImpl.java b/bonus-system/src/main/java/com/bonus/system/service/impl/SysOperLogServiceImpl.java index 579f37b..d0bd1fa 100644 --- a/bonus-system/src/main/java/com/bonus/system/service/impl/SysOperLogServiceImpl.java +++ b/bonus-system/src/main/java/com/bonus/system/service/impl/SysOperLogServiceImpl.java @@ -1,10 +1,16 @@ package com.bonus.system.service.impl; +import java.util.Collections; 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.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -26,6 +32,8 @@ public class SysOperLogServiceImpl implements ISysOperLogService @Autowired private SysOperLogMapper operLogMapper; + public static final String MENU_TYPE_FILE = "F"; + /** * 新增操作日志 * @@ -103,4 +111,77 @@ public class SysOperLogServiceImpl implements ISysOperLogService TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } + + @Override + public Map getLogsModule(SysLogsVo sysLogsVo) { + Map maps= Maps.newHashMap(); + Map result=Maps.newHashMap(); + result.put("code","403"); + result.put("data",""); + result.put("msg","数据接口未授权"); + try{ + List 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("->"); + } + } } diff --git a/bonus-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/bonus-system/src/main/resources/mapper/system/SysOperLogMapper.xml index 2ddf755..c4dd840 100644 --- a/bonus-system/src/main/resources/mapper/system/SysOperLogMapper.xml +++ b/bonus-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -106,8 +106,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where sm3.component=#{module} limit 1 + + - + truncate table da_ky_sys_oper_log