通过Annotation将用户操作记录到数据库表功能实现

一、背景

        在用户对我们所开发的系统访问的时候,需要我们的系统具有强大的健壮性,使得给与用户的体验感十足。在业务开发的过程中,我们通过将几个相关的操作绑定成一个事件,使得安全性以及数据的前后一致性得到提高。但是在溯源方面,我们往往没有很好的解决方案,我们无法得知错误的具体信息,这给后期的debug带来了一定的负担,那么我们如果将用户的操作具体信息可以记录到数据库中,那么是不是就可以溯源了?

二、任务:将一个员工管理系统中的增删改相关接口的操作日志记录到数据库中。

三、实现

3.1 我们需要插入一个数据表:log代表着插入数据的格式(数据表中不需要有任何数据):

-- 操作日志表
create table operate_log(id int unsigned primary key auto_increment comment 'ID',operate_user int unsigned comment '操作人ID',operate_time datetime comment '操作时间',class_name varchar(100) comment '操作的类名',method_name varchar(100) comment '操作的方法名',method_params varchar(1000) comment '方法参数',return_value varchar(2000) comment '返回值',cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

除此之外,我们需要在pom文件中引入fastjson和aop的依赖:

<!--        aop依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency>

3.2 目录结构如下所示:

 3.3 首先我们需要定义一个操作类OperateLog

package com.bytedance.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {private Integer id; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作时间private String className; //操作类名private String methodName; //操作方法名private String methodParams; //操作方法参数private String returnValue; //操作方法返回值private Long costTime; //操作耗时
}

3.4 其次我们需要定义一个注解文件Log

package com.bytedance.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) // 当前注解什么时候生效
@Target(ElementType.METHOD) // 注解有效的地方//定义一个注解
public @interface Log {
}

3.5 接着我们定义一个LogAspect的切面,这一部分是将这些代码从主干(controller)中抽离出来,以便于复用和改动,如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。

package com.bytedance.aop;
import com.alibaba.fastjson.JSONObject;
import com.bytedance.mapper.OperateLogMapper;
import com.bytedance.pojo.OperateLog;
import com.bytedance.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.util.Arrays;// 定义切面类
@Component
@Aspect // 这是一个切面类
@Slf4j
public class LogAspect {// 注入OperateLogMapper的bean对象@Autowiredprivate OperateLogMapper operateLogMapper;@Autowiredprivate HttpServletRequest request;// 定义一个通知方法@Around("@annotation(com.bytedance.anno.Log)") // 匹配的是方法上的加有Log注解的方法public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {// 获取操作人id - 当前登陆的员工的id 即获得请求头中的jwt令牌,解析String jwt = request.getHeader("token");Claims claims = JwtUtils.parseJWT(jwt);Integer operateUserId = (Integer) claims.get("id");// 操作时间LocalDateTime operateTime = LocalDateTime.now();// 操作类名String className = joinPoint.getTarget().getClass().getName();// 操作方法名String methodName = joinPoint.getSignature().getName();// 操作方法参数
//        String methodParams = joinPoint.getArgs().toString();注意不能这么写Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);// 方法开始时间long start = System.currentTimeMillis();// 调用原始方法运行Object res = joinPoint.proceed();// 方法返回值String returnValue = JSONObject.toJSONString(res);// 方法结束时间long end = System.currentTimeMillis();// 操作耗时long costTime = end - start;// 记录操作日志 需要调用OperateLogMapper中的insert方法,所以得注入bean对象OperateLog operateLog = new OperateLog(null , operateUserId,operateTime,className,methodName,methodParams,returnValue,costTime);operateLogMapper.insert(operateLog);log.info("AOP记录操作日志:{}",operateLog);return res;}
}

3.6 OperateLogMapper负责将改动的数据写入数据库中

package com.bytedance.mapper;import com.bytedance.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OperateLogMapper {//插入日志数据@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")public void insert(OperateLog log);}

3.7 最后将EmpController中需要记录日志的操作上打上@Log的标注即可。

package com.bytedance.controller;import com.bytedance.anno.Log;
import com.bytedance.pojo.Emp;
import com.bytedance.pojo.PageBean;
import com.bytedance.pojo.Result;
import com.bytedance.service.DeptService;
import com.bytedance.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;import java.time.LocalDate;
import java.util.List;@RestController
@Slf4j
@RequestMapping("/emps")
public class EmpController {@Autowiredprivate EmpService empService;@GetMappingpublic Result page(@RequestParam(defaultValue = "1") Integer page ,@RequestParam(defaultValue = "10") Integer pageSize,String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin , @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){log.info("分页参数为:{},{},{},{},{},{}",page,pageSize,name,gender,begin,end);PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end);return Result.success(pageBean);}// 批量删除员工信息@Log@DeleteMapping("/{ids}")public Result delete(@PathVariable List<Integer> ids){log.info("批量删除员工参数为:{}",ids);empService.delete(ids);return Result.success();}// 添加员工信息
//    @PostMapping
//    public Result add(@RequestBody Emp emp){
//        log.info("新增员工信息为:{emp}",emp);
//        empService.add(emp);
//        return Result.success();
//    }// 根据id查询员工信息@GetMapping("/{id}")public Result getById(@PathVariable Integer id){log.info("根据ID查询员工信息:{}",id);Emp emp = empService.getById(id);return Result.success(emp);}// 修改员工信息@Log@PutMappingpublic Result update(@RequestBody Emp emp){log.info("前端传过来的员工信息:{}",emp);empService.update(emp);return Result.success();}}

尝试一下,成功! 

注:1、记得将引入的包名换成自己的名字。

2、如果在LogAspect切面中报错,可能你没有jwt的实现类

package com.bytedance.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;public class JwtUtils {private static String signKey = "itheima";private static Long expire = 432000000L; // 240小时候过期/*** 生成JWT令牌* @param claims JWT第二部分负载 payload 中存储的内容* @return*/public static String generateJwt(Map<String, Object> claims){String jwt = Jwts.builder().addClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();return jwt;}/*** 解析JWT令牌* @param jwt JWT令牌* @return JWT第二部分负载 payload 中存储的内容*/public static Claims parseJWT(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();return claims;}
}

3、如果这里的params对象是这样的,那可能是切面中获取params的方法写的不对:

 // 操作方法参数
//        String methodParams = joinPoint.getArgs().toString();注意不能这么写Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/732882.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

未来城市:探索数字孪生在智慧城市中的实际应用与价值

目录 一、引言 二、数字孪生与智慧城市的融合 三、数字孪生在智慧城市中的实际应用 1、智慧交通管理 2、智慧能源管理 3、智慧建筑管理 4、智慧城市管理 四、数字孪生在智慧城市中的价值 五、挑战与展望 六、结论 一、引言 随着科技的飞速发展&#xff0c;智慧城市已…

linux 日志轮转

前言: 在Linux系统中&#xff0c;日志轮转是一种重要的管理机制&#xff0c;它可以帮助管理日志文件的大小、数量以及保持系统的性能稳定。通过日志轮转&#xff0c;可以定期对日志文件进行归档、压缩或清理&#xff0c;确保系统的日志记录不会无限增长而占用过多的磁盘空间…

TQTT X310 软件无线电设备的FLASH固件更新方法--WIN和UBUNTU环境

TQTT X310 除了PCIE口全部兼容USRP 官方的X310&#xff0c;并配备两块UBX160射频子板以及GPSDO。TQTT X310可以直接使用官方的固件&#xff0c;但是不支持官方的固件升级命令。这篇BLOG提供烧写刷新FLASH的方法。 这里分别给出WIN下和UBUNTU下升级的软件和方法 WIN环境下烧写…

Java代码审计安全篇-常见Java SQL注入

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全java代码审计&#xff0c;记录自己的学习过程&#xff0c;还希望…

使用Git将代码上传至代码托管平台GitCode

使用像GitLbi、GitHub、Gitee等代码托管平台用于版本控制非常滴方便&#xff0c;能够跟踪代码的变化和历史记录&#xff0c;方便管理和回滚&#xff0c;还允许多个开发者同时在一个项目上进行开发和协作&#xff0c;提高团队协作效率。 这些平台的代码托管和上传方式都大同小异…

Ainx的消息封装

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于Ainx系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列…

186基于matlab的信号盲源分离算法

基于matlab的信号盲源分离算法&#xff0c;包括变步长盲源分离&#xff08;EASI&#xff09;,RLS(自然梯度和普通梯度)&#xff0c;并将三种方法分离结果进行对比。程序已调通&#xff0c;可直接运行。 186 信号盲源分离算法 变步长盲源分离 (xiaohongshu.com)

智能革新:2024年AI辅助研发的挑战、机遇与未来展望

引言 在进入2024年的门槛时&#xff0c;我们站在了一个科技飞速发展的新纪元&#xff0c;其中&#xff0c;人工智能&#xff08;AI&#xff09;的持续进步和应用扩展无疑是推动这一变革的强大动力。AI辅助研发&#xff0c;作为将人工智能技术应用于科研和产品开发过程的一种模…

第三百九十一回

文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容&#xff0c;本章回中将介绍如何通过相机获取视频文件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. …

windows11编译FFmpeg源码完整步骤

1.安装MSYS2 下载并安装MSYS2 安装GCC GCC安装成功 克隆FFmpeg源码 打开MSYS2终端并进入ffmpeg文件夹,然后输入./configure回车开始生成makefile

「蓝桥·算法双周赛」第七场分级赛——小白入门赛

题目列表 说明 好久没打蓝桥杯的比赛&#xff0c;回来试试水&#xff0c;就开了第1、2、3一共三个题&#xff0c;第4题可惜了。1.thanks,mom【算法赛】 思路&#xff1a; 没什么好说的&#xff0c;但是当时比赛刚开始服务器有问题&#xff0c;基本提交的全WA了。#include <…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…

【Azure 架构师学习笔记】- Azure Private Endpoint

本文属于【Azure 架构师学习笔记】系列。 前言 公有云的其中一个特点是默认允许公网访问&#xff0c; 这就对企业环境带来风险&#xff0c;也是很多年前企业对公有云抵触的其中一个原因&#xff0c;现在这类问题已经很少&#xff0c;因为有了很多技术来确保云上的资源被安全地…

HTML5:七天学会基础动画网页10

继续介绍3D转换: 3D转换:rotate3d 方法与说明 rrotateX(angle)otate3d(x,y,z,angle[角度]) 3D转换&#xff0c;正常取值0/1&#xff0c;0代表当前轴线不进行旋转&#xff0c;1反之&#xff0c;例:rotate3d(1,1,1,30deg)&#xff0c;代表三个轴线都要旋转30度 rotate3d(0…

论文目录3:大模型时代(2023+)

1 instruction tuning & in context learning 论文名称来源主要内容Finetuned Language Models Are Zero-Shot Learners2021 机器学习笔记&#xff1a;李宏毅ChatGPT Finetune VS Prompt_UQI-LIUWJ的博客-CSDN博客 早期做instruction tuning的work MetaICL: Learning to …

HTML 学习笔记(九)颜色值和长度单位

一、颜色 1.通过RGB值来设置颜色 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>table</title&…

⎣模型⎤Claude3——给奥特曼一点点压力!

微信公众号|人工智能技术派 作 者|hws Claude系列模型是由美国创业公司Anthropic基于transformer架构研发&#xff0c;该公司是一群在2021年OpenAI离职员工(包括前首席科学家Ilya Sutskever和Dario Amodei)创建的。Anthropic先前已发布Claude1和Claude2&#xff0c;效果相当不错…

【数据分享】2000-2022年全国1km分辨率的逐日PM10栅格数据

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年全国范围逐日的PM2.5栅格数据和2013-2022年全国范围逐日SO2栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;。 本次我们给大家带来的是2000-2022年全国范围的逐日的PM10栅…

【论文阅读】关于智能合约的漏洞检测

两篇论文&#xff0c;都是关于智能合约漏洞检测的综述文章 [1]崔展齐,杨慧文,陈翔等.智能合约安全漏洞检测研究进展[J/OL].软件学报:1-33[2024-03-05].https://doi.org/10.13328/j.cnki.jos.007046. [2]王丹,黄松,王兴亚.以太坊智能合约测试研究综述[J].信息技术与信息化,2023(…

算法设计与分析(超详解!) 第一节 算法概述

1.算法的定义 算法的非形式化定义&#xff1a;算法是规则的有限集合&#xff0c;是为解决特定问题而规定的一系列操作。 可以理解为&#xff1a;算法&#xff08;algorithm&#xff09;是指在解决问题时&#xff0c;按照某种机械的步骤一定可以得到问题的结果&#xff08;有的…