!13 feat(core): 完善日志切面并优化日志实体,增强日志记录

Merge pull request !13 from stackcn/master
This commit is contained in:
郝先瑞
2024-12-06 07:52:03 +00:00
committed by Gitee
6 changed files with 197 additions and 57 deletions

View File

@@ -14,15 +14,15 @@ import lombok.Getter;
@Getter @Getter
public enum LogModuleEnum { public enum LogModuleEnum {
EXCEPTION("异常"),
LOGIN("登录"), LOGIN("登录"),
USER("用户"), USER("用户"),
DEPT("部门"), DEPT("部门"),
ROLE("角色"), ROLE("角色"),
MENU("菜单"), MENU("菜单"),
DICT("字典"), DICT("字典"),
OTHER("其他") SETTING("系统配置"),
; OTHER("其他");
@JsonValue @JsonValue
private final String moduleName; private final String moduleName;

View File

@@ -15,9 +15,35 @@ import java.lang.annotation.*;
@Documented @Documented
public @interface Log { public @interface Log {
/**
* 日志描述
*
* @return 日志描述
*/
String value() default ""; String value() default "";
LogModuleEnum module() ; /**
* 日志模块
*
* @return 日志模块
*/
LogModuleEnum module();
/**
* 是否记录请求参数
*
* @return 是否记录请求参数
*/
boolean params() default true;
/**
* 是否记录响应结果
* <br/>
* 响应结果默认不记录,避免日志过大
* @return 是否记录响应结果
*/
boolean result() default false;
} }

View File

@@ -5,19 +5,32 @@ import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent; import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil; 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.constant.SecurityConstants;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.util.IPUtils; 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.core.security.util.SecurityUtils;
import com.youlai.boot.system.model.entity.Log;
import com.youlai.boot.system.service.LogService; import com.youlai.boot.system.service.LogService;
import groovyjarjarpicocli.CommandLine;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; 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() { 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(); String requestURI = request.getRequestURI();
Long userId = null; Long userId = null;
@@ -50,13 +87,28 @@ public class LogAspect {
TimeInterval timer = DateUtil.timer(); TimeInterval timer = DateUtil.timer();
// 执行方法 // 执行方法
Object proceed = joinPoint.proceed();
long executionTime = timer.interval(); long executionTime = timer.interval();
// 创建日志记录 // 创建日志记录
Log log = new Log(); Log log = new Log();
log.setModule(logAnnotation.module()); if (logAnnotation == null && e != null) {
log.setContent(logAnnotation.value()); 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); log.setRequestUri(requestURI);
// 登录方法需要在登录成功后获取用户ID // 登录方法需要在登录成功后获取用户ID
if (userId == null) { if (userId == null) {
@@ -76,6 +128,7 @@ public class LogAspect {
} }
} }
} }
log.setExecutionTime(executionTime); log.setExecutionTime(executionTime);
// 获取浏览器和终端系统信息 // 获取浏览器和终端系统信息
String userAgentString = request.getHeader("User-Agent"); String userAgentString = request.getHeader("User-Agent");
@@ -87,9 +140,67 @@ public class LogAspect {
log.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString)); log.setBrowserVersion(userAgent.getBrowser().getVersion(userAgentString));
// 保存日志到数据库 // 保存日志到数据库
logService.save(log); 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;
}
} }

View File

@@ -1,8 +1,10 @@
package com.youlai.boot.system.controller; package com.youlai.boot.system.controller;
import com.baomidou.mybatisplus.core.metadata.IPage; 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.PageResult;
import com.youlai.boot.common.result.Result; 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.form.ConfigForm;
import com.youlai.boot.system.model.query.ConfigPageQuery; import com.youlai.boot.system.model.query.ConfigPageQuery;
import com.youlai.boot.system.model.vo.ConfigVO; import com.youlai.boot.system.model.vo.ConfigVO;
@@ -35,6 +37,7 @@ public class ConfigController {
@Operation(summary = "系统配置分页列表") @Operation(summary = "系统配置分页列表")
@GetMapping("/page") @GetMapping("/page")
@PreAuthorize("@ss.hasPerm('sys:config:query')") @PreAuthorize("@ss.hasPerm('sys:config:query')")
@Log( value = "系统配置分页列表",module = LogModuleEnum.SETTING)
public PageResult<ConfigVO> page(@ParameterObject ConfigPageQuery configPageQuery) { public PageResult<ConfigVO> page(@ParameterObject ConfigPageQuery configPageQuery) {
IPage<ConfigVO> result = configService.page(configPageQuery); IPage<ConfigVO> result = configService.page(configPageQuery);
return PageResult.success(result); return PageResult.success(result);
@@ -43,6 +46,7 @@ public class ConfigController {
@Operation(summary = "新增系统配置") @Operation(summary = "新增系统配置")
@PostMapping @PostMapping
@PreAuthorize("@ss.hasPerm('sys:config:add')") @PreAuthorize("@ss.hasPerm('sys:config:add')")
@Log( value = "新增系统配置",module = LogModuleEnum.SETTING)
public Result<?> save(@RequestBody @Valid ConfigForm configForm) { public Result<?> save(@RequestBody @Valid ConfigForm configForm) {
return Result.judge(configService.save(configForm)); return Result.judge(configService.save(configForm));
} }
@@ -59,6 +63,7 @@ public class ConfigController {
@Operation(summary = "刷新系统配置缓存") @Operation(summary = "刷新系统配置缓存")
@PutMapping("/refresh") @PutMapping("/refresh")
@PreAuthorize("@ss.hasPerm('sys:config:refresh')") @PreAuthorize("@ss.hasPerm('sys:config:refresh')")
@Log( value = "刷新系统配置缓存",module = LogModuleEnum.SETTING)
public Result<ConfigForm> refreshCache() { public Result<ConfigForm> refreshCache() {
return Result.judge(configService.refreshCache()); return Result.judge(configService.refreshCache());
} }
@@ -66,6 +71,7 @@ public class ConfigController {
@Operation(summary = "修改系统配置") @Operation(summary = "修改系统配置")
@PutMapping(value = "/{id}") @PutMapping(value = "/{id}")
@PreAuthorize("@ss.hasPerm('sys:config:update')") @PreAuthorize("@ss.hasPerm('sys:config:update')")
@Log( value = "修改系统配置",module = LogModuleEnum.SETTING)
public Result<?> update(@Valid @PathVariable Long id, @RequestBody ConfigForm configForm) { public Result<?> update(@Valid @PathVariable Long id, @RequestBody ConfigForm configForm) {
return Result.judge(configService.edit(id, configForm)); return Result.judge(configService.edit(id, configForm));
} }
@@ -73,6 +79,7 @@ public class ConfigController {
@Operation(summary = "删除系统配置") @Operation(summary = "删除系统配置")
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@PreAuthorize("@ss.hasPerm('sys:config:delete')") @PreAuthorize("@ss.hasPerm('sys:config:delete')")
@Log( value = "删除系统配置",module = LogModuleEnum.SETTING)
public Result<?> delete(@PathVariable Long id) { public Result<?> delete(@PathVariable Long id) {
return Result.judge(configService.delete(id)); return Result.judge(configService.delete(id));
} }

View File

@@ -70,6 +70,7 @@ public class UserController {
@PostMapping @PostMapping
@PreAuthorize("@ss.hasPerm('sys:user:add')") @PreAuthorize("@ss.hasPerm('sys:user:add')")
@RepeatSubmit @RepeatSubmit
@Log(value = "新增用户", module = LogModuleEnum.USER)
public Result<?> saveUser( public Result<?> saveUser(
@RequestBody @Valid UserForm userForm @RequestBody @Valid UserForm userForm
) { ) {
@@ -79,6 +80,7 @@ public class UserController {
@Operation(summary = "用户表单数据") @Operation(summary = "用户表单数据")
@GetMapping("/{userId}/form") @GetMapping("/{userId}/form")
@Log(value = "用户表单数据", module = LogModuleEnum.USER)
public Result<UserForm> getUserForm( public Result<UserForm> getUserForm(
@Parameter(description = "用户ID") @PathVariable Long userId @Parameter(description = "用户ID") @PathVariable Long userId
) { ) {
@@ -89,6 +91,7 @@ public class UserController {
@Operation(summary = "修改用户") @Operation(summary = "修改用户")
@PutMapping(value = "/{userId}") @PutMapping(value = "/{userId}")
@PreAuthorize("@ss.hasPerm('sys:user:edit')") @PreAuthorize("@ss.hasPerm('sys:user:edit')")
@Log(value = "修改用户", module = LogModuleEnum.USER)
public Result<Void> updateUser( public Result<Void> updateUser(
@Parameter(description = "用户ID") @PathVariable Long userId, @Parameter(description = "用户ID") @PathVariable Long userId,
@RequestBody @Valid UserForm userForm @RequestBody @Valid UserForm userForm
@@ -100,6 +103,7 @@ public class UserController {
@Operation(summary = "删除用户") @Operation(summary = "删除用户")
@DeleteMapping("/{ids}") @DeleteMapping("/{ids}")
@PreAuthorize("@ss.hasPerm('sys:user:delete')") @PreAuthorize("@ss.hasPerm('sys:user:delete')")
@Log(value = "删除用户", module = LogModuleEnum.USER)
public Result<Void> deleteUsers( public Result<Void> deleteUsers(
@Parameter(description = "用户ID多个以英文逗号(,)分割") @PathVariable String ids @Parameter(description = "用户ID多个以英文逗号(,)分割") @PathVariable String ids
) { ) {
@@ -109,6 +113,7 @@ public class UserController {
@Operation(summary = "修改用户状态") @Operation(summary = "修改用户状态")
@PatchMapping(value = "/{userId}/status") @PatchMapping(value = "/{userId}/status")
@Log(value = "修改用户状态", module = LogModuleEnum.USER)
public Result<Void> updateUserStatus( public Result<Void> updateUserStatus(
@Parameter(description = "用户ID") @PathVariable Long userId, @Parameter(description = "用户ID") @PathVariable Long userId,
@Parameter(description = "用户状态(1:启用;0:禁用)") @RequestParam Integer status @Parameter(description = "用户状态(1:启用;0:禁用)") @RequestParam Integer status
@@ -122,6 +127,7 @@ public class UserController {
@Operation(summary = "获取当前登录用户信息") @Operation(summary = "获取当前登录用户信息")
@GetMapping("/me") @GetMapping("/me")
@Log(value = "获取当前登录用户信息", module = LogModuleEnum.USER)
public Result<UserInfoVO> getCurrentUserInfo() { public Result<UserInfoVO> getCurrentUserInfo() {
UserInfoVO userInfoVO = userService.getCurrentUserInfo(); UserInfoVO userInfoVO = userService.getCurrentUserInfo();
return Result.success(userInfoVO); return Result.success(userInfoVO);
@@ -129,6 +135,7 @@ public class UserController {
@Operation(summary = "用户导入模板下载") @Operation(summary = "用户导入模板下载")
@GetMapping("/template") @GetMapping("/template")
@Log(value = "用户导入模板下载", module = LogModuleEnum.USER)
public void downloadTemplate(HttpServletResponse response) throws IOException { public void downloadTemplate(HttpServletResponse response) throws IOException {
String fileName = "用户导入模板.xlsx"; String fileName = "用户导入模板.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
@@ -145,6 +152,7 @@ public class UserController {
@Operation(summary = "导入用户") @Operation(summary = "导入用户")
@PostMapping("/import") @PostMapping("/import")
@Log(value = "导入用户", module = LogModuleEnum.USER)
public Result<String> importUsers(MultipartFile file) throws IOException { public Result<String> importUsers(MultipartFile file) throws IOException {
UserImportListener listener = new UserImportListener(); UserImportListener listener = new UserImportListener();
String msg = ExcelUtils.importExcel(file.getInputStream(), UserImportDTO.class, listener); String msg = ExcelUtils.importExcel(file.getInputStream(), UserImportDTO.class, listener);
@@ -153,6 +161,7 @@ public class UserController {
@Operation(summary = "导出用户") @Operation(summary = "导出用户")
@GetMapping("/export") @GetMapping("/export")
@Log(value = "导出用户", module = LogModuleEnum.USER)
public void exportUsers(UserPageQuery queryParams, HttpServletResponse response) throws IOException { public void exportUsers(UserPageQuery queryParams, HttpServletResponse response) throws IOException {
String fileName = "用户列表.xlsx"; String fileName = "用户列表.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
@@ -165,6 +174,7 @@ public class UserController {
@Operation(summary = "获取个人中心用户信息") @Operation(summary = "获取个人中心用户信息")
@GetMapping("/profile") @GetMapping("/profile")
@Log(value = "获取个人中心用户信息", module = LogModuleEnum.USER)
public Result<UserProfileVO> getUserProfile() { public Result<UserProfileVO> getUserProfile() {
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
UserProfileVO userProfile = userService.getUserProfile(userId); UserProfileVO userProfile = userService.getUserProfile(userId);
@@ -173,6 +183,7 @@ public class UserController {
@Operation(summary = "个人中心修改用户信息") @Operation(summary = "个人中心修改用户信息")
@PutMapping("/profile") @PutMapping("/profile")
@Log(value = "个人中心修改用户信息", module = LogModuleEnum.USER)
public Result<?> updateUserProfile(@RequestBody UserProfileForm formData) { public Result<?> updateUserProfile(@RequestBody UserProfileForm formData) {
boolean result = userService.updateUserProfile(formData); boolean result = userService.updateUserProfile(formData);
return Result.judge(result); return Result.judge(result);

View File

@@ -1,13 +1,13 @@
package com.youlai.boot.system.model.entity; package com.youlai.boot.system.model.entity;
import com.baomidou.mybatisplus.annotation.*; 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.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.youlai.boot.common.enums.LogModuleEnum;
import lombok.Data;
/** /**
* 系统日志 实体类 * 系统日志 实体类
* *
@@ -17,72 +17,57 @@ import lombok.Data;
@TableName("sys_log") @TableName("sys_log")
@Data @Data
public class Log implements Serializable { public class Log implements Serializable {
/**
* 主键 @Schema(description = "主键")
*/
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long id; private Long id;
/** @Schema(description = "日志模块")
* 日志模块
*/
private LogModuleEnum module; 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; private String content;
/** @Schema(description = "请求路径")
* 请求路径
*/
private String requestUri; private String requestUri;
/** @Schema(description = "IP 地址")
* IP 地址
*/
private String ip; private String ip;
/** @Schema(description = "省份")
* 省份
*/
private String province; private String province;
/** @Schema(description = "城市")
* 城市
*/
private String city; private String city;
/** @Schema(description = "浏览器")
* 浏览器
*/
private String browser; private String browser;
/** @Schema(description = "浏览器版本")
* 浏览器版本
*/
private String browserVersion; private String browserVersion;
/** @Schema(description = "终端系统")
* 终端系统
*/
private String os; private String os;
/** @Schema(description = "执行时间(毫秒)")
* 执行时间(毫秒)
*/
private Long executionTime; private Long executionTime;
@Schema(description = "创建人ID")
/**
* 创建人ID
*/
private Long createBy; private Long createBy;
/** @Schema(description = "创建时间")
* 创建时间
*/
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime; private LocalDateTime createTime;