springboot实现用户操作日志记录

springboot实现用户操作日志记录

简介:之前写了《aop实现日志持久化记录》一文,主要介绍自定义aop标注方法上,通过切面方法对用户操作插入mysql。思路正确但是实际操作上存在一些小问题,本文将从项目出发,对细节进行补充。

另外值得一提的是,因为是基于AOP对controller方法做环绕通知实现的日志持久化记录,所以如果请求在Filter或者Interceptor中被拦截,则不会进入环绕通知,也就无法记录日志

1. 创建日志数据库表

数据库表结构大致如下

image-20231231171416729.png

建表语句(基于MySQL 5.7)

CREATE TABLE `admin_log` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '日志id',
  `ip` VARCHAR(20) DEFAULT NULL COMMENT '操作ip',
  `uri` VARCHAR(100) DEFAULT NULL COMMENT '请求URI',
  `method_type` VARCHAR(10) DEFAULT NULL COMMENT '请求类型(GET,POST)',
  `method_name` VARCHAR(100) DEFAULT NULL COMMENT '目标方法名',
  `method_desc` VARCHAR(20) DEFAULT NULL COMMENT '接口介绍',
  `request_param` TEXT COMMENT '请求参数',
  `status` VARCHAR(20) DEFAULT NULL COMMENT '请求状态',
  `result` TEXT COMMENT '返回结果',
  `user_id` VARCHAR(20) DEFAULT NULL COMMENT '操作者id',
  `execution_time` BIGINT(20) DEFAULT NULL COMMENT '方法耗时(ms)',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

2. 引入maven依赖

		<!-- 其他依赖在此不列出,springboot,mybatis等等	……  -->

        <!-- aop依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3. 创建数据库表对应实体类

@Data
public class AdminLog {

    private Integer id;
    private String userId;          // 用户id
    private String ip;              // 操作者ip
    private String uri;             // 请求URI
    private String methodType;      // 请求类型【GET,POST】
    private String methodName;      // 方法名称
    private String methodDesc;      // 接口简介
    private String requestParam;    // post请求参数
    private String status;          // 方法执行最终状态
    private String result;          // 返回结果
    private Long executionTime;     // 方法耗时
    private String createTime;      // 执行时间
    
}

4. 创建自定义注解

自定义注解,标注在要保存用户操作日志的controller方法上,被标注的方法会通过下面写的环绕通知进行日志记录

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteLog {

    /**
     * 方法描述(描述目标方法的作用)
     */
    String value();

}

5. 创建上下文对象

从上面数据库表可以看出,日志类需要的ip、uri、methodType等参数需要在请求的request参数中获取,为记录这些参数信息,通过ThreadLocal设置上下文对象,方便获取。

先创建需要的请求信息的实体类

@Data
public class RequestBaseInfo {

    private String ip;
    private String methodType;  // 请求类型,如【GET,POST,PUT,DELETE】
    private String uri;
    
    public RequestBaseInfo(){}
    public RequestBaseInfo(String ip, String methodType, String uri){
        this.ip = ip;
        this.methodType = methodType;
        this.uri = uri;
    }

}

然后创建上下文对象,保存该类的对象

public class RequestContextHolder {

    private static final ThreadLocal<RequestBaseInfo> ipThreadLocal = new ThreadLocal<>();

    public static void setRequestBaseInfo(RequestBaseInfo requestBaseInfo) {
        ipThreadLocal.set(requestBaseInfo);
    }

    public static RequestBaseInfo getRequestBaseInfo() {
        return ipThreadLocal.get();
    }

    public static void clear() {
        ipThreadLocal.remove();
    }

}

6. 创建拦截器

创建拦截器,为每个线程保存上下文对象

/**
 * 该拦截器主要为线程保存请求的基本信息,例如来源ip,请求uri,请求方法等
 */
@Slf4j
@Component
public class SaveRequestBaseInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 将请求基本信息存到ThreadLocal,[ip,method,uri]
        RequestBaseInfo rbi = new RequestBaseInfo(request.getRemoteAddr(),request.getMethod(),request.getRequestURI());
        RequestContextHolder.setRequestBaseInfo(rbi);
        // 另外自己的框架里是否可以获取用户已登录的ip信息,没有的话这里还可以多设置一个获取登录用户的id
        // 因为我们的数据库日志表中有userId字段,如果没办法在后续的aop切面方法中获取,亦可以在这里拦截器中获取
        // 例如我项目中的spring security可以在aop切面中获取登录主体,拦截器就不需要获取了
        // 具体思路就是设置多一个上下文对象或者在RequestBaseInfo中设置多一个userId字段
        // 然后在这里获取请求token,然后在缓存中获取登录信息,获取登录者id
        // 当然实现的方式有多种,根据实际项目配置,或者不记录userId也可以
        // ……todo
        return true;
    }
}

将拦截器注册生效(配置进WebMvcConfigurer)

@Configuration
public class WebConfig  implements WebMvcConfigurer {

    @Autowired
    SaveRequestBaseInterceptor saveRequestBaseInterceptor;

    /**
     * 添加自定义拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(saveRequestBaseInterceptor).addPathPatterns("/**");
    }

}

7. 创建AOP切面方法

在创建切面方法前还需要创建AdminLog表dao操作的相关代码,例如具体插入的service文件,mapper文件,这里就省略不说了,很基础的东西

aop实现针对以上自定义注解@WriteLog标注切面的环绕通知

@Aspect
@Component
public class WriteLogAspect {

    @Autowired
    AdminLogService adminLogService;

    // com.jankin.inoteadmin.system.annotation.WriteLog 是我定义接口的文件路径
    @Pointcut("@annotation(com.jankin.inoteadmin.system.annotation.WriteLog)")
    public void writeLogAspect() {}

    /**
     * 返回通知切面方法
     * @param joinPoint 切点,就是被注解的目标方法
     */
    @Around("writeLogAspect()")
    public Object logPostMapping(ProceedingJoinPoint joinPoint) throws Throwable {
        String userId = null; // 获取操作用户Id
    //  //我这里用的是SpringSecurity框架,这样获取UserId
    //	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    //	if (authentication!=null){
    //  	// SecurityUser是自定义的UserDetails类,其中包含了UserId
    //    	SecurityUser principal = (SecurityUser)authentication.getPrincipal();
    //    	userId = principal.getUserId();
    //	}
        String status = "ERROR";
        String resultStr = "";
        Object result = null;
        long startTime = System.currentTimeMillis();    // 执行前时间
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            resultStr = e.getMessage();
            throw e;
        }finally {
            long finishTime = System.currentTimeMillis();   // 目标方法执行后时间
            if (result instanceof Result) {
                resultStr = result.toString();   // 返回结果字符串
                status = ((Result) result).getCode()==200? "SUCCESS":"EXCEPTION";
            }
            // 其他to do ……
            AdminLog sysLog = new AdminLog();
            sysLog.setUserId(userId);
            RequestBaseInfo rbi = RequestContextHolder.getRequestBaseInfo();
            sysLog.setIp(rbi.getIp());
            sysLog.setUri(rbi.getUri());
            sysLog.setMethodType(rbi.getMethodType());
            sysLog.setMethodName(joinPoint.getSignature().toShortString());
            // 获取注解上的方法描述
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            WriteLog annotation = signature.getMethod().getAnnotation(WriteLog.class);
            sysLog.setMethodDesc(annotation.value());
            sysLog.setRequestParam(Arrays.toString(joinPoint.getArgs()));
            sysLog.setStatus(status);
            sysLog.setResult(resultStr);
            sysLog.setExecutionTime(finishTime-startTime);
            adminLogService.addLog(sysLog);
        }
        return result;
    }

}

8. 应用

在接口(controller方法)上标注自定义注解(@WriteLog),即可完成接口日志的插入

    @WriteLog("测试接口Get")
    @GetMapping
    public Result get(){
        return Result.success("测试成功");
    }

    @WriteLog("测试接口Post")
    @PostMapping("post")
    public Result post(TestDto testDto){
        return Result.success("测试成功");
    }

测试结果

image-20240101002407422.png

至此,全篇结束

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用POI库实现Excel文件的导入导出,也可以使用CSV文件进行导入导出。在Spring Boot中,可以使用Spring Batch来实现大批量数据的导入导出。另外,也可以使用第三方库,比如EasyExcel等来简化导入导出的操作。 ### 回答2: Spring Boot 提供了丰富的功能和工具来实现导入和导出数据的功能。在导入数据方面,我们可以使用 Spring Boot 提供的 Apache POI 或 EasyExcel 等库来解析 Excel 文件,并将解析后的数据存储到数据库中。 我们首先需要在项目的依赖中添加 Apache POI 或 EasyExcel 相关的库。然后,我们可以创建一个控制器类来处理上传文件的请求,并在该类中编写逻辑来解析 Excel 文件。 在导入文件的控制器方法中,我们可以使用 POI 或 EasyExcel 提供的 API 来打开 Excel 文件,逐行读取数据,并将数据插入到数据库中。在处理 Excel 文件的过程中,我们还可以根据业务需求进行一些数据校验或转换操作。 除了 Excel 文件外,Spring Boot 还支持导入其他格式的文件,比如 CSV 文件。对于导入 CSV 文件,我们可以使用 Spring Boot 提供的 CSV 库或 OpenCSV 库来解析文件,并将解析后的数据存储到数据库中。 在导出数据方面,Spring Boot 内置了 Thymeleaf 和 FreeMarker 等模板引擎,我们可以使用这些模板引擎来生成导出的文件,比如 Excel 或 PDF 文件。 我们可以创建一个控制器类来处理导出数据的请求,并在该类中编写逻辑来生成导出的文件。在生成文件的控制器方法中,我们可以使用模板引擎来渲染导出数据的模板,并将渲染后的结果写入到文件中。 在导出文件的过程中,我们还可以通过配置相关的响应头信息,来指定文件的类型、名称和下载方式等。这样用户在访问导出链接时,会自动下载生成的文件。 总而言之,Spring Boot 提供了丰富的工具和库来实现数据的导入和导出。我们可以根据需要选择合适的库和技术来处理文件,比如 Apache POI 或 EasyExcel 等用于导入 Excel 文件,Thymeleaf 或 FreeMarker 等用于导出文件,并结合控制器和服务层逻辑编写实现。 ### 回答3: Spring Boot是一个开源的Java开发框架,可以快速搭建应用程序。对于实现导入导出的功能,我们可以使用Spring Boot结合相关的库来实现。 首先,我们需要在pom.xml文件中添加相关的依赖。对于导入导出功能,常用的库有Apache POI、EasyExcel等。你可以根据具体需求选择合适的库。 然后,我们可以创建一个Controller类来处理导入导出的请求。对于导出功能,我们可以在Controller中定义一个接口,当用户请求导出时,调用相关的库来生成Excel文件并返回给用户下载。 对于导入功能,我们可以在Controller中定义另一个接口,用户可以通过该接口上传Excel文件。然后,我们可以使用相关的库来读取Excel文件中的数据,并进行处理。例如,我们可以将数据存储到数据库中,或者对数据进行一定的业务操作。 在处理导入导出功能时,我们还需要注意处理异常情况。例如,对于导入功能,我们需要检查上传文件的格式和内容是否符合要求。对于导出功能,我们需要确保生成的Excel文件能够正确地下载到用户的电脑上。 在实现导入导出功能时,我们还可以结合其他的Spring Boot特性。例如,我们可以使用Spring Security来进行权限控制,只允许有权限的用户进行导入导出操作。我们也可以使用Spring AOP来实现日志记录功能,方便后续的排查和调试。 总之,使用Spring Boot结合相关的库,我们可以方便地实现导入导出功能。通过合理的结构设计和细致的异常处理,可以保证导入导出功能的稳定性和安全性。同时,我们还可以利用其他Spring Boot的特性来增强导入导出功能的实用性和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值