本文目录导读:

我将为您提供一个完整的Java操作日志实现案例,包含数据库设计、核心代码和配置。
数据库表设计
-- 操作日志表 CREATE TABLE `sys_operation_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID', `module` varchar(50) DEFAULT NULL COMMENT '模块名称', `operation_type` varchar(50) DEFAULT NULL COMMENT '操作类型', `operation_desc` varchar(200) DEFAULT NULL COMMENT '操作描述', `method` varchar(200) DEFAULT NULL COMMENT '请求方法', `params` text COMMENT '请求参数', `result` text COMMENT '返回结果', `operator_id` bigint(20) DEFAULT NULL COMMENT '操作人ID', `operator_name` varchar(50) DEFAULT NULL COMMENT '操作人名称', `operation_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', `ip` varchar(50) DEFAULT NULL COMMENT 'IP地址', `status` int(1) DEFAULT '1' COMMENT '操作状态(1成功,0失败)', `error_msg` text COMMENT '错误信息', `cost_time` bigint(20) DEFAULT NULL COMMENT '耗时(毫秒)', PRIMARY KEY (`id`), KEY `idx_module` (`module`), KEY `idx_operator` (`operator_id`), KEY `idx_time` (`operation_time`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表';
Maven依赖配置
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("sys_operation_log")
public class OperationLog {
@TableId(type = IdType.AUTO)
private Long id;
/** 模块名称 */
private String module;
/** 操作类型 */
private String operationType;
/** 操作描述 */
private String operationDesc;
/** 请求方法 */
private String method;
/** 请求参数 */
private String params;
/** 返回结果 */
private String result;
/** 操作人ID */
private Long operatorId;
/** 操作人名称 */
private String operatorName;
/** 操作时间 */
private Date operationTime;
/** IP地址 */
private String ip;
/** 操作状态(1成功,0失败) */
private Integer status;
/** 错误信息 */
private String errorMsg;
/** 耗时(毫秒) */
private Long costTime;
}
Mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperationLogMapper extends BaseMapper<OperationLog> {
}
自定义注解
import java.lang.annotation.*;
/**
* 操作日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLogAnnotation {
/** 模块名称 */
String module() default "";
/** 操作类型 */
String operationType() default "";
/** 操作描述 */
String desc() default "";
/** 是否保存请求参数 */
boolean saveParams() default true;
/** 是否保存返回结果 */
boolean saveResult() default true;
}
AOP切面实现
import com.alibaba.fastjson.JSON;
import com.example.demo.entity.OperationLog;
import com.example.demo.mapper.OperationLogMapper;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
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 javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
@Slf4j
@Aspect
@Component
public class OperationLogAspect {
@Autowired
private OperationLogMapper operationLogMapper;
/**
* 定义切点:使用自定义注解
*/
@Pointcut("@annotation(com.example.demo.annotation.OperationLogAnnotation)")
public void operationLogPointcut() {
}
/**
* 环绕通知
*/
@Around("operationLogPointcut()")
public Object saveOperationLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
OperationLog operationLog = new OperationLog();
boolean success = true;
String errorMsg = "";
Object result = null;
try {
// 执行方法
result = joinPoint.proceed();
return result;
} catch (Throwable e) {
success = false;
errorMsg = e.getMessage();
throw e;
} finally {
try {
// 构建日志信息
buildOperationLog(joinPoint, request, operationLog, startTime, success, result, errorMsg);
// 保存日志到数据库
operationLogMapper.insert(operationLog);
} catch (Exception e) {
log.error("保存操作日志失败", e);
}
}
}
/**
* 构建操作日志
*/
private void buildOperationLog(ProceedingJoinPoint joinPoint, HttpServletRequest request,
OperationLog operationLog, long startTime, boolean success,
Object result, String errorMsg) {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLogAnnotation annotation = method.getAnnotation(OperationLogAnnotation.class);
// 设置基础信息
operationLog.setModule(annotation.module());
operationLog.setOperationType(annotation.operationType());
operationLog.setOperationDesc(annotation.desc());
operationLog.setMethod(method.getDeclaringClass().getName() + "." + method.getName());
operationLog.setOperationTime(new Date());
operationLog.setStatus(success ? 1 : 0);
operationLog.setErrorMsg(errorMsg);
operationLog.setCostTime(System.currentTimeMillis() - startTime);
// 设置操作人信息(从Session或SecurityContext获取)
setOperatorInfo(operationLog, request);
// 设置IP地址
operationLog.setIp(getIpAddr(request));
// 设置请求参数
if (annotation.saveParams()) {
Object[] args = joinPoint.getArgs();
operationLog.setParams(getParams(args));
}
// 设置返回结果
if (annotation.saveResult() && success) {
operationLog.setResult(JSON.toJSONString(result));
}
}
/**
* 设置操作人信息
*/
private void setOperatorInfo(OperationLog operationLog, HttpServletRequest request) {
// 从Session获取用户信息
Object userId = request.getSession().getAttribute("userId");
Object userName = request.getSession().getAttribute("userName");
if (userId != null) {
operationLog.setOperatorId(Long.valueOf(userId.toString()));
}
if (userName != null) {
operationLog.setOperatorName(userName.toString());
}
}
/**
* 获取请求参数(忽略文件类型)
*/
protected String getParams(Object[] paramsArray) {
if (paramsArray != null && paramsArray.length > 0) {
StringBuilder params = new StringBuilder();
for (Object param : paramsArray) {
if (param instanceof MultipartFile) {
params.append("文件上传类型参数");
} else if (param instanceof HttpServletRequest) {
// 忽略request对象
} else {
try {
params.append(JSON.toJSONString(param)).append(" ");
} catch (Exception e) {
params.append(param.toString()).append(" ");
}
}
}
return params.toString().trim();
}
return "";
}
/**
* 获取IP地址
*/
private String getIpAddr(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;
}
}
异步保存日志(优化方案)
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class AsyncOperationLogService {
@Autowired
private OperationLogMapper operationLogMapper;
/**
* 异步保存日志
*/
@Async("operationLogExecutor")
public void saveAsync(OperationLog operationLog) {
try {
operationLogMapper.insert(operationLog);
} catch (Exception e) {
log.error("异步保存操作日志失败", e);
}
}
}
异步线程池配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("operationLogExecutor")
public Executor operationLogExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("operation-log-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
使用示例
import org.springframework.web.bind.annotation.*;
import com.example.demo.annotation.OperationLogAnnotation;
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/create")
@OperationLogAnnotation(
module = "用户管理",
operationType = "CREATE",
desc = "新增用户"
)
public Result createUser(@RequestBody User user) {
// 业务逻辑
return Result.success();
}
@PutMapping("/update/{id}")
@OperationLogAnnotation(
module = "用户管理",
operationType = "UPDATE",
desc = "修改用户信息",
saveResult = false // 可选择不保存返回结果
)
public Result updateUser(@PathVariable Long id, @RequestBody User user) {
// 业务逻辑
return Result.success();
}
@DeleteMapping("/delete/{id}")
@OperationLogAnnotation(
module = "用户管理",
operationType = "DELETE",
desc = "删除用户"
)
public Result deleteUser(@PathVariable Long id) {
// 业务逻辑
return Result.success();
}
@PostMapping("/batchDelete")
@OperationLogAnnotation(
module = "用户管理",
operationType = "BATCH_DELETE",
desc = "批量删除用户"
)
public Result batchDelete(@RequestBody List<Long> ids) {
// 业务逻辑
return Result.success();
}
}
查询接口
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/log")
public class OperationLogController {
@Autowired
private OperationLogMapper operationLogMapper;
/**
* 分页查询操作日志
*/
@GetMapping("/page")
public Result page(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String module,
@RequestParam(required = false) String operatorName,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
Page<OperationLog> pageParam = new Page<>(page, size);
QueryWrapper<OperationLog> queryWrapper = new QueryWrapper<>();
if (StringUtils.hasText(module)) {
queryWrapper.eq("module", module);
}
if (StringUtils.hasText(operatorName)) {
queryWrapper.like("operator_name", operatorName);
}
if (StringUtils.hasText(startTime)) {
queryWrapper.ge("operation_time", startTime);
}
if (StringUtils.hasText(endTime)) {
queryWrapper.le("operation_time", endTime);
}
queryWrapper.orderByDesc("operation_time");
Page<OperationLog> result = operationLogMapper.selectPage(pageParam, queryWrapper);
return Result.success(result);
}
}
application.yml配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
这个完整的操作日志系统实现了:
- 自动记录:通过AOP自动记录操作日志
- 灵活配置:通过注解灵活控制是否记录参数和返回结果
- 性能优化:使用异步方式保存日志,不影响主业务
- 查询方便:支持按模块、操作人、时间等条件查询
- 信息完整:记录操作人、IP、耗时、状态等完整信息