diff --git a/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java b/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java index db0b2df0..34acd80e 100644 --- a/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java +++ b/src/main/java/com/youlai/boot/common/enums/LogModuleEnum.java @@ -14,15 +14,15 @@ import lombok.Getter; @Getter public enum LogModuleEnum { - + EXCEPTION("异常"), LOGIN("登录"), USER("用户"), DEPT("部门"), ROLE("角色"), MENU("菜单"), DICT("字典"), - OTHER("其他") - ; + SETTING("系统配置"), + OTHER("其他"); @JsonValue private final String moduleName; diff --git a/src/main/java/com/youlai/boot/core/annotation/Log.java b/src/main/java/com/youlai/boot/core/annotation/Log.java index 61a3b60c..3c9f605f 100644 --- a/src/main/java/com/youlai/boot/core/annotation/Log.java +++ b/src/main/java/com/youlai/boot/core/annotation/Log.java @@ -15,9 +15,35 @@ import java.lang.annotation.*; @Documented public @interface Log { + /** + * 日志描述 + * + * @return 日志描述 + */ String value() default ""; - LogModuleEnum module() ; + /** + * 日志模块 + * + * @return 日志模块 + */ + + LogModuleEnum module(); + + /** + * 是否记录请求参数 + * + * @return 是否记录请求参数 + */ + boolean params() default true; + + /** + * 是否记录响应结果 + *
+ * 响应结果默认不记录,避免日志过大 + * @return 是否记录响应结果 + */ + boolean result() default false; } \ No newline at end of file diff --git a/src/main/java/com/youlai/boot/core/aspect/LogAspect.java b/src/main/java/com/youlai/boot/core/aspect/LogAspect.java index c4cc50cf..ecae1d5a 100644 --- a/src/main/java/com/youlai/boot/core/aspect/LogAspect.java +++ b/src/main/java/com/youlai/boot/core/aspect/LogAspect.java @@ -5,19 +5,32 @@ import cn.hutool.core.date.TimeInterval; import cn.hutool.core.util.StrUtil; import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgentUtil; +import cn.hutool.json.JSONUtil; +import com.aliyun.oss.HttpMethod; import com.youlai.boot.common.constant.SecurityConstants; +import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.util.IPUtils; -import com.youlai.boot.system.model.entity.Log; import com.youlai.boot.core.security.util.SecurityUtils; +import com.youlai.boot.system.model.entity.Log; import com.youlai.boot.system.service.LogService; +import groovyjarjarpicocli.CommandLine; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; +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.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.HandlerMapping; + +import java.util.Collection; +import java.util.Map; /** * 日志切面 @@ -38,8 +51,32 @@ public class LogAspect { public void logPointcut() { } - @Around("logPointcut() && @annotation(logAnnotation)") - public Object logExecutionTime(ProceedingJoinPoint joinPoint, com.youlai.boot.core.annotation.Log logAnnotation) throws Throwable { + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "logPointcut() && @annotation(logAnnotation)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, com.youlai.boot.core.annotation.Log logAnnotation, Object jsonResult) { + this.saveLog(joinPoint, null, jsonResult, logAnnotation); + } + + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "logPointcut()", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Exception e) { + this.saveLog(joinPoint, e, null,null); + } + + /** + * 保持日志 + */ + private void saveLog(final JoinPoint joinPoint, final Exception e, Object jsonResult, com.youlai.boot.core.annotation.Log logAnnotation) { String requestURI = request.getRequestURI(); Long userId = null; @@ -50,13 +87,28 @@ public class LogAspect { TimeInterval timer = DateUtil.timer(); // 执行方法 - Object proceed = joinPoint.proceed(); long executionTime = timer.interval(); // 创建日志记录 Log log = new Log(); - log.setModule(logAnnotation.module()); - log.setContent(logAnnotation.value()); + if (logAnnotation == null && e != null) { + log.setModule(LogModuleEnum.EXCEPTION); + log.setContent("系统发生异常"); + this.setRequestParameters(joinPoint, log); + log.setResponseContent(JSONUtil.toJsonStr(e.getStackTrace())); + }else{ + log.setModule(logAnnotation.module()); + log.setContent(logAnnotation.value()); + // 请求参数 + if (logAnnotation.params()) { + this.setRequestParameters(joinPoint, log); + } + // 响应结果 + if (logAnnotation.result() && jsonResult != null) { + log.setResponseContent(JSONUtil.toJsonStr(jsonResult)); + } + } + log.setRequestUri(requestURI); // 登录方法需要在登录成功后获取用户ID if (userId == null) { @@ -76,6 +128,7 @@ public class LogAspect { } } } + log.setExecutionTime(executionTime); // 获取浏览器和终端系统信息 String userAgentString = request.getHeader("User-Agent"); @@ -87,9 +140,67 @@ public class LogAspect { log.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString)); // 保存日志到数据库 logService.save(log); - - return proceed; } + /** + * 设置请求参数到日志对象中 + * + * @param joinPoint 切点 + * @param log 操作日志 + */ + private void setRequestParameters(JoinPoint joinPoint, Log log) { + String requestMethod = request.getMethod(); + log.setRequestMethod(requestMethod); + if (HttpMethod.GET.name().equalsIgnoreCase(requestMethod) || HttpMethod.PUT.name().equalsIgnoreCase(requestMethod) || HttpMethod.POST.name().equalsIgnoreCase(requestMethod)) { + String params = convertArgumentsToString(joinPoint.getArgs()); + log.setRequestParams(StrUtil.sub(params, 0, 65535)); + } else { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + Map paramsMap = (Map) attributes.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + log.setRequestParams(StrUtil.sub(paramsMap.toString(), 0, 65535)); + } else { + log.setRequestParams(""); + } + } + } + + /** + * 将参数数组转换为字符串 + * + * @param paramsArray 参数数组 + * @return 参数字符串 + */ + private String convertArgumentsToString(Object[] paramsArray) { + StringBuilder params = new StringBuilder(); + if (paramsArray != null) { + for (Object param : paramsArray) { + if (!shouldFilterObject(param)) { + params.append(JSONUtil.toJsonStr(param)).append(" "); + } + } + } + return params.toString().trim(); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param obj 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + private boolean shouldFilterObject(Object obj) { + Class clazz = obj.getClass(); + if (clazz.isArray()) { + return MultipartFile.class.isAssignableFrom(clazz.getComponentType()); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) obj; + return collection.stream().anyMatch(item -> item instanceof MultipartFile); + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) obj; + return map.values().stream().anyMatch(value -> value instanceof MultipartFile); + } + return obj instanceof MultipartFile || obj instanceof HttpServletRequest || obj instanceof HttpServletResponse; + } } diff --git a/src/main/java/com/youlai/boot/system/controller/ConfigController.java b/src/main/java/com/youlai/boot/system/controller/ConfigController.java index 174c9c3d..2719229a 100644 --- a/src/main/java/com/youlai/boot/system/controller/ConfigController.java +++ b/src/main/java/com/youlai/boot/system/controller/ConfigController.java @@ -1,8 +1,10 @@ package com.youlai.boot.system.controller; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; +import com.youlai.boot.core.annotation.Log; import com.youlai.boot.system.model.form.ConfigForm; import com.youlai.boot.system.model.query.ConfigPageQuery; import com.youlai.boot.system.model.vo.ConfigVO; @@ -35,6 +37,7 @@ public class ConfigController { @Operation(summary = "系统配置分页列表") @GetMapping("/page") @PreAuthorize("@ss.hasPerm('sys:config:query')") + @Log( value = "系统配置分页列表",module = LogModuleEnum.SETTING) public PageResult page(@ParameterObject ConfigPageQuery configPageQuery) { IPage result = configService.page(configPageQuery); return PageResult.success(result); @@ -43,6 +46,7 @@ public class ConfigController { @Operation(summary = "新增系统配置") @PostMapping @PreAuthorize("@ss.hasPerm('sys:config:add')") + @Log( value = "新增系统配置",module = LogModuleEnum.SETTING) public Result save(@RequestBody @Valid ConfigForm configForm) { return Result.judge(configService.save(configForm)); } @@ -59,6 +63,7 @@ public class ConfigController { @Operation(summary = "刷新系统配置缓存") @PutMapping("/refresh") @PreAuthorize("@ss.hasPerm('sys:config:refresh')") + @Log( value = "刷新系统配置缓存",module = LogModuleEnum.SETTING) public Result refreshCache() { return Result.judge(configService.refreshCache()); } @@ -66,6 +71,7 @@ public class ConfigController { @Operation(summary = "修改系统配置") @PutMapping(value = "/{id}") @PreAuthorize("@ss.hasPerm('sys:config:update')") + @Log( value = "修改系统配置",module = LogModuleEnum.SETTING) public Result update(@Valid @PathVariable Long id, @RequestBody ConfigForm configForm) { return Result.judge(configService.edit(id, configForm)); } @@ -73,6 +79,7 @@ public class ConfigController { @Operation(summary = "删除系统配置") @DeleteMapping("/{id}") @PreAuthorize("@ss.hasPerm('sys:config:delete')") + @Log( value = "删除系统配置",module = LogModuleEnum.SETTING) public Result delete(@PathVariable Long id) { return Result.judge(configService.delete(id)); } diff --git a/src/main/java/com/youlai/boot/system/controller/UserController.java b/src/main/java/com/youlai/boot/system/controller/UserController.java index 02775322..f735f15c 100644 --- a/src/main/java/com/youlai/boot/system/controller/UserController.java +++ b/src/main/java/com/youlai/boot/system/controller/UserController.java @@ -70,6 +70,7 @@ public class UserController { @PostMapping @PreAuthorize("@ss.hasPerm('sys:user:add')") @RepeatSubmit + @Log(value = "新增用户", module = LogModuleEnum.USER) public Result saveUser( @RequestBody @Valid UserForm userForm ) { @@ -79,6 +80,7 @@ public class UserController { @Operation(summary = "用户表单数据") @GetMapping("/{userId}/form") + @Log(value = "用户表单数据", module = LogModuleEnum.USER) public Result getUserForm( @Parameter(description = "用户ID") @PathVariable Long userId ) { @@ -89,6 +91,7 @@ public class UserController { @Operation(summary = "修改用户") @PutMapping(value = "/{userId}") @PreAuthorize("@ss.hasPerm('sys:user:edit')") + @Log(value = "修改用户", module = LogModuleEnum.USER) public Result updateUser( @Parameter(description = "用户ID") @PathVariable Long userId, @RequestBody @Valid UserForm userForm @@ -100,6 +103,7 @@ public class UserController { @Operation(summary = "删除用户") @DeleteMapping("/{ids}") @PreAuthorize("@ss.hasPerm('sys:user:delete')") + @Log(value = "删除用户", module = LogModuleEnum.USER) public Result deleteUsers( @Parameter(description = "用户ID,多个以英文逗号(,)分割") @PathVariable String ids ) { @@ -109,6 +113,7 @@ public class UserController { @Operation(summary = "修改用户状态") @PatchMapping(value = "/{userId}/status") + @Log(value = "修改用户状态", module = LogModuleEnum.USER) public Result updateUserStatus( @Parameter(description = "用户ID") @PathVariable Long userId, @Parameter(description = "用户状态(1:启用;0:禁用)") @RequestParam Integer status @@ -122,6 +127,7 @@ public class UserController { @Operation(summary = "获取当前登录用户信息") @GetMapping("/me") + @Log(value = "获取当前登录用户信息", module = LogModuleEnum.USER) public Result getCurrentUserInfo() { UserInfoVO userInfoVO = userService.getCurrentUserInfo(); return Result.success(userInfoVO); @@ -129,6 +135,7 @@ public class UserController { @Operation(summary = "用户导入模板下载") @GetMapping("/template") + @Log(value = "用户导入模板下载", module = LogModuleEnum.USER) public void downloadTemplate(HttpServletResponse response) throws IOException { String fileName = "用户导入模板.xlsx"; response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); @@ -145,6 +152,7 @@ public class UserController { @Operation(summary = "导入用户") @PostMapping("/import") + @Log(value = "导入用户", module = LogModuleEnum.USER) public Result importUsers(MultipartFile file) throws IOException { UserImportListener listener = new UserImportListener(); String msg = ExcelUtils.importExcel(file.getInputStream(), UserImportDTO.class, listener); @@ -153,6 +161,7 @@ public class UserController { @Operation(summary = "导出用户") @GetMapping("/export") + @Log(value = "导出用户", module = LogModuleEnum.USER) public void exportUsers(UserPageQuery queryParams, HttpServletResponse response) throws IOException { String fileName = "用户列表.xlsx"; response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); @@ -165,6 +174,7 @@ public class UserController { @Operation(summary = "获取个人中心用户信息") @GetMapping("/profile") + @Log(value = "获取个人中心用户信息", module = LogModuleEnum.USER) public Result getUserProfile() { Long userId = SecurityUtils.getUserId(); UserProfileVO userProfile = userService.getUserProfile(userId); @@ -173,6 +183,7 @@ public class UserController { @Operation(summary = "个人中心修改用户信息") @PutMapping("/profile") + @Log(value = "个人中心修改用户信息", module = LogModuleEnum.USER) public Result updateUserProfile(@RequestBody UserProfileForm formData) { boolean result = userService.updateUserProfile(formData); return Result.judge(result); diff --git a/src/main/java/com/youlai/boot/system/model/entity/Log.java b/src/main/java/com/youlai/boot/system/model/entity/Log.java index db4e8748..a6016718 100644 --- a/src/main/java/com/youlai/boot/system/model/entity/Log.java +++ b/src/main/java/com/youlai/boot/system/model/entity/Log.java @@ -1,13 +1,13 @@ package com.youlai.boot.system.model.entity; import com.baomidou.mybatisplus.annotation.*; +import com.youlai.boot.common.enums.LogModuleEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; -import com.youlai.boot.common.enums.LogModuleEnum; -import lombok.Data; - /** * 系统日志 实体类 * @@ -17,72 +17,57 @@ import lombok.Data; @TableName("sys_log") @Data public class Log implements Serializable { - /** - * 主键 - */ + + @Schema(description = "主键") @TableId(type = IdType.AUTO) private Long id; - /** - * 日志模块 - */ + @Schema(description = "日志模块") private LogModuleEnum module; + @Schema(description = "请求方式") + @TableField(value = "request_method") + private String requestMethod; - /** - * 日志内容 - */ + @Schema(description = "请求参数") + @TableField(value = "request_params") + private String requestParams; + + @Schema(description = "响应参数") + @TableField(value = "response_content") + private String responseContent; + + @Schema(description = "日志内容") private String content; - /** - * 请求路径 - */ + @Schema(description = "请求路径") private String requestUri; - /** - * IP 地址 - */ + @Schema(description = "IP 地址") private String ip; - /** - * 省份 - */ + @Schema(description = "省份") private String province; - /** - * 城市 - */ + @Schema(description = "城市") private String city; - /** - * 浏览器 - */ + @Schema(description = "浏览器") private String browser; - /** - * 浏览器版本 - */ + @Schema(description = "浏览器版本") private String browserVersion; - /** - * 终端系统 - */ + @Schema(description = "终端系统") private String os; - /** - * 执行时间(毫秒) - */ + @Schema(description = "执行时间(毫秒)") private Long executionTime; - - /** - * 创建人ID - */ + @Schema(description = "创建人ID") private Long createBy; - /** - * 创建时间 - */ + @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;