Java操作日志案例

wen java案例 2

本文目录导读:

Java操作日志案例

  1. 基于Spring AOP + 自定义注解的方式(最常用)
  2. 基于Spring的事件机制实现
  3. 使用日志框架记录(简易版)
  4. 异步批量处理方案
  5. 配置类示例
  6. 查询操作日志
  7. 使用要点

我来提供一个完整的Java操作日志案例,包含多种实现方式:

基于Spring AOP + 自定义注解的方式(最常用)

自定义操作日志注解

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
    // 操作模块
    String module() default "";
    // 操作类型(新增、修改、删除、查询等)
    String operation() default "";
    // 操作描述
    String description() default "";
    // 是否保存请求参数
    boolean saveRequest() default true;
    // 是否保存响应结果
    boolean saveResponse() default false;
}

操作日志实体类

import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "operation_log")
public class OperationLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "module")
    private String module;           // 操作模块
    @Column(name = "operation")
    private String operation;        // 操作类型
    @Column(name = "description")
    private String description;      // 操作描述
    @Column(name = "request_params")
    private String requestParams;    // 请求参数
    @Column(name = "response_result")
    private String responseResult;   // 响应结果
    @Column(name = "operator_id")
    private Long operatorId;         // 操作人ID
    @Column(name = "operator_name")
    private String operatorName;     // 操作人姓名
    @Column(name = "ip_address")
    private String ipAddress;        // 操作IP
    @Column(name = "operation_time")
    private LocalDateTime operationTime; // 操作时间
    @Column(name = "execution_time")
    private Long executionTime;      // 执行耗时(毫秒)
    @Column(name = "status")
    private Integer status;          // 操作状态:1-成功,0-失败
    @Column(name = "error_message")
    private String errorMessage;     // 错误信息
    // getters and setters 省略
}

Service层

import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class OperationLogService {
    public void saveLog(OperationLog log) {
        // 这里可以保存到数据库
        // 或者异步发送到消息队列
        System.out.println("保存操作日志: " + log);
        // logRepository.save(log);
    }
}

AOP切面实现

import com.fasterxml.jackson.databind.ObjectMapper;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
@Aspect
@Component
public class OperationLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
    @Autowired
    private OperationLogService operationLogService;
    @Autowired
    private ObjectMapper objectMapper;
    // 定义切点:使用自定义注解的方法
    @Pointcut("@annotation(com.example.log.OperationLog)")
    public void operationLogPointcut() {}
    // 环绕通知
    @Around("operationLogPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        OperationLog operationLog = getOperationLogAnnotation(joinPoint);
        // 构建日志对象
        OperationLog log = new OperationLog();
        log.setModule(operationLog.module());
        log.setOperation(operationLog.operation());
        log.setDescription(operationLog.description());
        log.setOperationTime(LocalDateTime.now());
        log.setOperatorId(getCurrentUserId());
        log.setOperatorName(getCurrentUsername());
        log.setIpAddress(getIpAddress());
        // 保存请求参数
        if (operationLog.saveRequest()) {
            try {
                log.setRequestParams(objectMapper.writeValueAsString(joinPoint.getArgs()));
            } catch (Exception e) {
                log.setRequestParams(Arrays.toString(joinPoint.getArgs()));
            }
        }
        // 执行目标方法
        Object result = null;
        try {
            result = joinPoint.proceed();
            log.setStatus(1);  // 成功
            log.setResponseResult(objectMapper.writeValueAsString(result));
        } catch (Exception e) {
            log.setStatus(0);  // 失败
            log.setErrorMessage(e.getMessage());
            throw e;
        } finally {
            // 计算耗时
            log.setExecutionTime(System.currentTimeMillis() - startTime);
            // 异步保存日志
            OperationLogService.logService.saveLog(log);
        }
        return result;
    }
    // 获取注解信息
    private OperationLog getOperationLogAnnotation(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(OperationLog.class);
    }
    // 获取IP地址
    private String getIpAddress() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String ip = request.getHeader("X-Forwarded-For");
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getHeader("X-Real-IP");
                }
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
                return ip;
            }
        } catch (Exception e) {
            logger.error("获取IP地址失败", e);
        }
        return "unknown";
    }
    // 获取当前用户ID
    private Long getCurrentUserId() {
        // 从SecurityContext或Session中获取
        // 示例代码
        return 1L;
    }
    // 获取当前用户名
    private String getCurrentUsername() {
        // 从SecurityContext或Session中获取
        return "admin";
    }
}

使用示例

import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
    @PostMapping
    @OperationLog(module = "用户管理", operation = "新增", description = "创建新用户")
    public User createUser(@RequestBody User user) {
        // 业务逻辑
        return userService.createUser(user);
    }
    @PutMapping("/{id}")
    @OperationLog(module = "用户管理", operation = "修改", description = "修改用户信息")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        // 业务逻辑
        user.setId(id);
        return userService.updateUser(user);
    }
    @DeleteMapping("/{id}")
    @OperationLog(module = "用户管理", operation = "删除", description = "删除用户")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

基于Spring的事件机制实现

操作日志事件

import org.springframework.context.ApplicationEvent;
public class OperationLogEvent extends ApplicationEvent {
    private final OperationLogData logData;
    public OperationLogEvent(Object source, OperationLogData logData) {
        super(source);
        this.logData = logData;
    }
    public OperationLogData getLogData() {
        return logData;
    }
}

操作日志数据

import lombok.Data;
import java.time.LocalDateTime;
@Data
public class OperationLogData {
    private String module;
    private String operation;
    private String description;
    private String requestParams;
    private String responseResult;
    private Long operatorId;
    private String operatorName;
    private String ipAddress;
    private LocalDateTime operationTime;
    private Long executionTime;
    private Integer status;
    private String errorMessage;
}

事件监听器

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class OperationLogListener {
    @EventListener
    @Async  // 异步处理
    public void onOperationLogEvent(OperationLogEvent event) {
        OperationLogData logData = event.getLogData();
        // 保存日志到数据库
        saveLog(logData);
    }
    private void saveLog(OperationLogData logData) {
        // 实现日志保存逻辑
    }
}

日志工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class OperationLogUtils {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    public void recordLog(OperationLogData logData) {
        eventPublisher.publishEvent(new OperationLogEvent(this, logData));
    }
}

使用日志框架记录(简易版)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SimpleLoggerExample {
    private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerExample.class);
    public void businessMethod() {
        // 记录操作日志
        logger.info("用户操作日志: userId={}, operation={}, timestamp={}", 
                    "12345", "查询订单", System.currentTimeMillis());
        // 记录错误日志
        try {
            // 业务逻辑
        } catch (Exception e) {
            logger.error("操作失败: user={}, operation={}, error={}", 
                        "12345", "创建订单", e.getMessage(), e);
        }
    }
}

异步批量处理方案

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@Component
public class AsyncLogProcessor {
    private final BlockingQueue<LogEntry> logQueue = new LinkedBlockingQueue<>(10000);
    @Async
    public void asyncProcessLog(LogEntry logEntry) {
        // 异步处理日志
        boolean offer = logQueue.offer(logEntry);
        if (!offer) {
            logger.warn("日志队列已满,日志丢失: {}", logEntry);
        }
    }
    // 批量消费者
    @Scheduled(fixedRate = 5000)  // 每5秒批量处理一次
    public void batchProcess() {
        List<LogEntry> batchLogs = new ArrayList<>();
        logQueue.drainTo(batchLogs, 100);  // 每次最多处理100条
        if (!batchLogs.isEmpty()) {
            // 批量插入数据库
            logRepository.batchInsert(batchLogs);
        }
    }
}

配置类示例

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@Configuration
@EnableAsync
@EnableScheduling
public class LogConfig {
    // 线程池配置
    @Bean(name = "logExecutor")
    public Executor logExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("log-thread-");
        executor.initialize();
        return executor;
    }
}

查询操作日志

import org.springframework.web.bind.annotation.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@RestController
@RequestMapping("/api/logs")
public class OperationLogController {
    @Autowired
    private OperationLogService operationLogService;
    @GetMapping
    @OperationLog(module = "日志管理", operation = "查询", description = "查询操作日志", saveResult = true)
    public Page<OperationLog> queryLogs(
            @RequestParam(required = false) String module,
            @RequestParam(required = false) String operation,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return operationLogService.queryLogs(module, operation, startDate, endDate, 
                                            PageRequest.of(page, size));
    }
    @GetMapping("/{id}")
    public OperationLog getLogDetail(@PathVariable Long id) {
        return operationLogService.getLogById(id);
    }
}

使用要点

  1. 切面优先级:如果有多个切面,需要设置合适的优先级顺序
  2. 异步处理:建议使用异步方式保存日志,避免影响业务性能
  3. 敏感信息处理:记录参数时需要脱敏处理,如密码等
  4. 日志存储:根据业务量选择合适的存储方式(数据库、ES等)
  5. 日志清理:定期清理过期日志,避免数据膨胀

这个案例提供了完整的操作日志实现方案,可以根据实际需求选择合适的方案。

上一篇MDC案例

下一篇Java黑白名单案例

抱歉,评论功能暂时关闭!