Java实现操作日志案例

wen java案例 1

本文目录导读:

Java实现操作日志案例

  1. 数据库表设计
  2. Maven依赖配置
  3. 实体类
  4. Mapper接口
  5. 自定义注解
  6. AOP切面实现
  7. 异步保存日志(优化方案)
  8. 异步线程池配置
  9. 使用示例
  10. 查询接口
  11. application.yml配置

我将为您提供一个完整的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

这个完整的操作日志系统实现了:

  1. 自动记录:通过AOP自动记录操作日志
  2. 灵活配置:通过注解灵活控制是否记录参数和返回结果
  3. 性能优化:使用异步方式保存日志,不影响主业务
  4. 查询方便:支持按模块、操作人、时间等条件查询
  5. 信息完整:记录操作人、IP、耗时、状态等完整信息

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