本文目录导读:

我将为您提供一个完整的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、耗时、状态等完整信息
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播