本文目录导读:

我来提供一个完整的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);
}
}
使用要点
- 切面优先级:如果有多个切面,需要设置合适的优先级顺序
- 异步处理:建议使用异步方式保存日志,避免影响业务性能
- 敏感信息处理:记录参数时需要脱敏处理,如密码等
- 日志存储:根据业务量选择合适的存储方式(数据库、ES等)
- 日志清理:定期清理过期日志,避免数据膨胀
这个案例提供了完整的操作日志实现方案,可以根据实际需求选择合适的方案。