From b6f9386dd6ad0aaeab02ae5b20d6677607e1c0e8 Mon Sep 17 00:00:00 2001 From: ovo Date: Sat, 7 Dec 2024 19:03:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/annotation/OperationLog.java | 18 +++ .../backend/aspect/OperationLogAspect.java | 108 ++++++++++++++++++ .../java/com/guwan/backend/entity/SysLog.java | 37 ++++++ .../backend/service/impl/UserServiceImpl.java | 4 + .../db/migration/V2__create_sys_log.sql | 18 +++ 5 files changed, 185 insertions(+) create mode 100644 src/main/java/com/guwan/backend/annotation/OperationLog.java create mode 100644 src/main/java/com/guwan/backend/aspect/OperationLogAspect.java create mode 100644 src/main/java/com/guwan/backend/entity/SysLog.java create mode 100644 src/main/resources/db/migration/V2__create_sys_log.sql diff --git a/src/main/java/com/guwan/backend/annotation/OperationLog.java b/src/main/java/com/guwan/backend/annotation/OperationLog.java new file mode 100644 index 0000000..804d7d6 --- /dev/null +++ b/src/main/java/com/guwan/backend/annotation/OperationLog.java @@ -0,0 +1,18 @@ +package com.guwan.backend.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface OperationLog { + /** + * 操作描述 + */ + String description() default ""; + + /** + * 操作类型(如:新增、修改、删除、查询等) + */ + String operationType() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/aspect/OperationLogAspect.java b/src/main/java/com/guwan/backend/aspect/OperationLogAspect.java new file mode 100644 index 0000000..2617abd --- /dev/null +++ b/src/main/java/com/guwan/backend/aspect/OperationLogAspect.java @@ -0,0 +1,108 @@ +package com.guwan.backend.aspect; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.guwan.backend.annotation.OperationLog; +import com.guwan.backend.entity.SysLog; +import com.guwan.backend.mapper.SysLogMapper; +import com.guwan.backend.security.CustomUserDetails; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.time.LocalDateTime; +import java.util.Arrays; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class OperationLogAspect { + + private final SysLogMapper sysLogMapper; + private final ObjectMapper objectMapper; + + @Around("@annotation(operationLog)") + public Object around(ProceedingJoinPoint point, OperationLog operationLog) throws Throwable { + long beginTime = System.currentTimeMillis(); + SysLog sysLog = new SysLog(); + + try { + // 执行方法 + Object result = point.proceed(); + // 设置状态为成功 + sysLog.setStatus(1); + return result; + } catch (Exception e) { + // 记录异常信息 + sysLog.setStatus(0); + sysLog.setErrorMsg(e.getMessage()); + throw e; + } finally { + // 记录日志 + saveLog(point, operationLog, beginTime, sysLog); + } + } + + private void saveLog(ProceedingJoinPoint joinPoint, OperationLog operationLog, long beginTime, SysLog sysLog) { + try { + // 获取当前请求 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes != null) { + HttpServletRequest request = attributes.getRequest(); + sysLog.setIp(getIpAddress(request)); + sysLog.setUserAgent(request.getHeader("User-Agent")); + } + + // 获取当前用户信息 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { + CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); + sysLog.setUserId(userDetails.getUserId()); + sysLog.setUsername(userDetails.getUsername()); + } + + // 获取方法信息 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + sysLog.setMethod(method.getDeclaringClass().getName() + "." + method.getName()); + + // 获取请求参数 + String params = Arrays.toString(joinPoint.getArgs()); + sysLog.setParams(params.length() > 2000 ? params.substring(0, 2000) : params); + + // 设置操作信息 + sysLog.setOperation(operationLog.description()); + sysLog.setCreateTime(LocalDateTime.now()); + sysLog.setTimeConsuming(System.currentTimeMillis() - beginTime); + + // 保存日志 + sysLogMapper.insert(sysLog); + } catch (Exception e) { + log.error("记录操作日志失败", e); + } + } + + private String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/entity/SysLog.java b/src/main/java/com/guwan/backend/entity/SysLog.java new file mode 100644 index 0000000..679c1b9 --- /dev/null +++ b/src/main/java/com/guwan/backend/entity/SysLog.java @@ -0,0 +1,37 @@ +package com.guwan.backend.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("sys_log") +public class SysLog { + @TableId(type = IdType.AUTO) + private Long id; + + private Long userId; + + private String username; + + private String operation; + + private String method; + + private String params; + + private String ip; + + private String location; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + private Long timeConsuming; + + private String userAgent; + + private Integer status; + + private String errorMsg; +} \ No newline at end of file diff --git a/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java b/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java index 8a4e0f7..a30cfde 100644 --- a/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java +++ b/src/main/java/com/guwan/backend/service/impl/UserServiceImpl.java @@ -1,11 +1,13 @@ package com.guwan.backend.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.guwan.backend.annotation.OperationLog; import com.guwan.backend.dto.user.LoginDto; import com.guwan.backend.dto.user.RegisterDTO; import com.guwan.backend.dto.user.UserDTO; import com.guwan.backend.entity.User; import com.guwan.backend.mapper.UserMapper; +import com.guwan.backend.security.CustomUserDetails; import com.guwan.backend.service.EmailService; import com.guwan.backend.service.UserService; import com.guwan.backend.service.VerificationService; @@ -46,6 +48,7 @@ public class UserServiceImpl implements UserService { @Override @Transactional + @OperationLog(description = "用户注册", operationType = "注册") public UserDTO register(RegisterDTO request) { // 检查用户名是否已存在 if (findByUsername(request.getUsername()) != null) { @@ -93,6 +96,7 @@ public class UserServiceImpl implements UserService { } @Override + @OperationLog(description = "用户登录", operationType = "登录") public UserDTO login(LoginDto request) { User user = null; System.out.println("request = " + request); diff --git a/src/main/resources/db/migration/V2__create_sys_log.sql b/src/main/resources/db/migration/V2__create_sys_log.sql new file mode 100644 index 0000000..ce0cda9 --- /dev/null +++ b/src/main/resources/db/migration/V2__create_sys_log.sql @@ -0,0 +1,18 @@ +CREATE TABLE `sys_log` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID', + `user_id` bigint DEFAULT NULL COMMENT '用户ID', + `username` varchar(50) DEFAULT NULL COMMENT '用户名', + `operation` varchar(50) DEFAULT NULL COMMENT '操作', + `method` varchar(200) DEFAULT NULL COMMENT '方法名', + `params` varchar(2000) DEFAULT NULL COMMENT '参数', + `ip` varchar(64) DEFAULT NULL COMMENT 'IP地址', + `location` varchar(255) DEFAULT NULL COMMENT '操作地点', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `time_consuming` bigint DEFAULT NULL COMMENT '耗时(毫秒)', + `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理', + `status` tinyint DEFAULT NULL COMMENT '状态(1-成功,0-失败)', + `error_msg` varchar(2000) DEFAULT NULL COMMENT '错误信息', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统操作日志'; \ No newline at end of file