SpringBoot通过注解形式实现系统操作日志

介绍

我们在日常开发工作中,肯定逃不开与日志接触,一些比较严谨的后台管理系统里面会涉及到一些比较重要的资料,有些公司为了知道有哪些人登录了系统,是谁在什么时候修改了用户信息或者资料,所以就有了操作日志这么个需求。

此文章介绍的是SpringBoot下如何通过注解的形式实现操作日志,仅供学习参考,不喜勿喷。

具体实现

pom依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--aspectj依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.25.RELEASE</version></dependency><!--swagger依赖--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.8.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.8.0</version></dependency><!--工具依赖--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.32</version></dependency>

代码实现

  • 系统日志注解
/**
* 系统日志注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperateLog {//用户操作String value();//保存入参boolean isSaveParams() default true;//保存返回结果boolean isSaveResult() default false;//异常信息boolean isRecordError() default false;//忽略属性String [] ignoreFields() default {};}
  • 切面处理类
/**
* 系统日志,切面处理类
*/
@Component
@Aspect
public class SysLogAspect {private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);@Resourceprivate SysLogService sysLogService;// 定义切点,被@SysOperateLog注解标注的方法作为切点@Pointcut("@annotation(com.ou.common.annotation.SysOperateLog)")public void logPointCut() {}@Around("logPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {long beginTime = System.currentTimeMillis();//执行方法Object result = null;try {result = point.proceed();} catch (Throwable throwable) {//异常日志记录MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();SysOperateLog syslog = method.getAnnotation(SysOperateLog.class);if(syslog != null && syslog.isRecordError()){//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;result = "异常返回," + throwable.getMessage();sysLogService.aopTransactionalSysLog(point,"error!!", time);}throw  throwable;}//执行时长(毫秒)long time = System.currentTimeMillis() - beginTime;//保存日志sysLogService.aopSysLog(point,result, time);return result;}}
  • 过滤器
public class FastJsonByteArrayValueFilter implements ValueFilter {public FastJsonByteArrayValueFilter() {}public Object process(Object object, String name, Object value) {try {if (value == null) {return value;}Field declaredField = object.getClass().getDeclaredField(name);Class<?> type = declaredField.getType();if (type.toString().equals("class [B")) {return null;}} catch (NoSuchFieldException var6) {}return value;}
}
  • 日志实体类
/*** 系统操作日志实体类*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "SysLog对象", description = "系统操作日志")
public class SysLog implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "日志主键ID")private String id;@ApiModelProperty(value = "用户ID")private String userId;private String userName;@ApiModelProperty(value = "用户操作")private String operation;@ApiModelProperty(value = "请求方法")private String method;private String params;private String result;@ApiModelProperty(value = "执行时长")private Long time;@ApiModelProperty(value = "IP地址")private String ip;@ApiModelProperty(value = "逻辑删除标志,0:正常,1:删除")private Integer delFlag;@ApiModelProperty(value = "创建人")private String createUser;@ApiModelProperty(value = "创建时间")private Date createTime;@ApiModelProperty(value = "更新人")private String updateUser;@ApiModelProperty(value = "更新时间")private Date updateTime;}
  • 业务层接口
/**
* 系统操作日志
*/
public interface SysLogService{void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time);void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time);
}
  • 业务层实现类
/**
* 系统操作日志 服务实现类
*/
@Service
public class SysLogServiceImpl implements SysLogService {private static final Logger logger = LoggerFactory.getLogger(SysLogServiceImpl.class);//MediumTextprivate static final int MAX_BTYE_LENGTH = 16777215;//@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)@Overridepublic void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time){aopSysLog(joinPoint,result,time);}/*** ProceedingJoinPoint继承了JoinPoint* JoinPoint.getSignature() = 获取到了方法的【修饰符 + 包名 + 组件名(类名) +方法】*/@Overridepublic void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();SysLog sysLog = new SysLog();//这里我是用UUID来模仿ID,可自行设置//sysLog.setId(UUIDCreater.create());SysOperateLog sysOperateLog = method.getAnnotation(SysOperateLog.class);boolean isSaveResult = false;boolean isSaveParams = false;if(sysOperateLog != null){//注解上的描述sysLog.setOperation(sysOperateLog.value());isSaveResult = sysOperateLog.isSaveResult();isSaveParams = sysOperateLog.isSaveParams();}//请求的方法名String className = joinPoint.getTarget().getClass().getName();String methodName = signature.getName();String[] parameterNames = signature.getParameterNames();// 设置 com.ou.controller.SysLogController.testParam(map)sysLog.setMethod(className + "." + methodName + "("+ StringUtils.join(parameterNames,",")+")");try{//保存参数信息if(isSaveParams){Object[] args = joinPoint.getArgs();StringBuffer params = new StringBuffer();for(int i = 0 ; i < args.length; i++){if(args[i] instanceof ServletResponse){continue;}else if(args[i] instanceof Class){continue;}else  if(args[i] instanceof byte[]){continue;} else  if(args[i] instanceof ServletRequest){ServletRequest request = (ServletRequest)args[i];params.append(JSON.toJSONString(request.getParameterMap()));}else {Object json = JSON.toJSON(args[i]);String jsonString = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});params.append(jsonString);}if(i < args.length - 1){params.append("; ");}}byte[] paramsBytes = params.toString().getBytes();if(paramsBytes.length > MAX_BTYE_LENGTH){byte[] subBytes = new byte[MAX_BTYE_LENGTH];System.arraycopy(paramsBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);sysLog.setParams(new String(subBytes));}else{sysLog.setParams(params.toString());}}//保存返回结果if(isSaveResult && result != null){Object json = JSON.toJSON(result);String[] ignoreFields = sysOperateLog.ignoreFields();if(ignoreFields.length > 0){if(json instanceof JSONObject){JSONObject jsonObject = (JSONObject) json;for(String field : ignoreFields){jsonObject.remove(field);}}else if(json instanceof JSONArray){JSONArray jsonArray = (JSONArray) json;for(Object obj : jsonArray){if(obj instanceof JSONObject){JSONObject jsonObject = (JSONObject) obj;for(String field : ignoreFields){jsonObject.remove(field);}}}}}String transferResult = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});byte[] transferResultBytes = transferResult.toString().getBytes();if(transferResultBytes.length > MAX_BTYE_LENGTH){byte[] subBytes = new byte[MAX_BTYE_LENGTH];System.arraycopy(transferResultBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);sysLog.setResult(new String(subBytes));}else{sysLog.setResult(transferResult);}}try {//获取requestHttpServletRequest request = HttpContextUtils.getHttpServletRequest();//设置IP地址sysLog.setIp(IPUtils.getIpAddr(request));} catch (Exception e) {}sysLog.setTime(time);//保存系统日志--这里是打印logger.info("系统日志:{}",JSONObject.toJSONString(sysLog));}catch (Exception e){logger.error("系统日志记录失败:",e);}}
}
  • 工具类
public class HttpContextUtils {public static HttpServletRequest getHttpServletRequest() {return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();}
}/**
* 获取IP地址的工具类
*/
public class IPUtils {private static Logger logger = LoggerFactory.getLogger(IPUtils.class);/*** 获取IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = null;try {ip = request.getHeader("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}} catch (Exception e) {logger.error("IPUtils ERROR ", e);}return ip;}}
  • Controller接口

@RestController
@RequestMapping("/sys/log")
public class SysLogController {/*** 触发操作日志*/@SysOperateLog("触发-操作日志接口-不带参数")@RequestMapping("/test")public String test() {return "OK";}/*** 触发操作日志*/@SysOperateLog("触发-操作日志接口-带参数")@RequestMapping("/testParam")public String testParam(@RequestBody Map map) {return "OK";}
}

测试

在这里插入图片描述
控制台输出:

 系统日志:{"id":"9e088286efe64183aa5992f30a8aa34b","ip":"127.0.0.1","method":"com.ou.controller.SysLogController.testParam(map)","operation":"触发-操作日志接口-带参数","params":"{\"sex\":\"男\",\"name\":\"张三\",\"age\":18,\"desc\":\"描述啊啊啊\"}","time":10}

我这里是直接输出日志,具体怎么存库还是由各自的业务来进行修改。

--------END

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

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

相关文章

1.4 安全服务

思维导图&#xff1a; 1.4 安全服务 定义&#xff1a;在通信开放系统中&#xff0c;为系统或数据传输提供足够安全的协议层服务。 RFC4949 定义&#xff1a;由系统提供的对系统资源进行特殊保护的处理或通信服务。安全服务通过安全机制来实现安全策略。 分类&#xff1a;X.800 …

Flink将数据写入MySQL(JDBC)

一、写在前面 在实际的生产环境中&#xff0c;我们经常会把Flink处理的数据写入MySQL、Doris等数据库中&#xff0c;下面以MySQL为例&#xff0c;使用JDBC的方式将Flink的数据实时数据写入MySQL。 二、代码示例 2.1 版本说明 <flink.version>1.14.6</flink.version…

故障诊断模型 | Maltab实现BiLSTM双向长短期记忆神经网络故障诊断

文章目录 效果一览文章概述模型描述源码设计参考资料效果一览 文章概述 故障诊断模型 | Maltab实现BiLSTM双向长短期记忆神经网络故障诊断 模型描述 利用各种检查和测试方法,发现系统和设备是否存在故障的过程是故障检测;而进一步确定故障所在大致部位的过程是故障定位。故障…

Linux网络编程二(TCP三次握手、四次挥手、TCP滑动窗口、MSS、TCP状态转换、多进程/多线程服务器实现)

TCP三次握手 TCP三次握手(TCP three-way handshake)是TCP协议建立可靠连接的过程&#xff0c;确保客户端和服务器之间可以进行可靠的通信。下面是TCP三次握手的详细过程&#xff1a; 假设客户端为A&#xff0c;服务器为B 1、第一次握手&#xff08;SYN1&#xff0c;seq500&…

03_Flutter自定义下拉菜单

03_Flutter自定义下拉菜单 在Flutter的内置api中&#xff0c;可以使用showMenu实现类似下拉菜单的效果&#xff0c;或者使用PopupMenuButton组件&#xff0c;PopupMenuButton内部也是使用了showMenu这个api&#xff0c;但是使用showMenu时&#xff0c;下拉面板的显示已经被约定…

List的add(int index,E element)陷阱,不得不防

项目场景&#xff1a; 项目中有两个List列表&#xff0c;一个是List1用来存储一个标识&#xff0c;后续会根据这个标识去重。 一个List2是用来返回对象的&#xff0c;其中对象里也有一个属性List3。现需要将重复的标识数据追加到List3 我想到的两个方案&#xff1a; 尽量不动…

吴恩达《机器学习》2-5->2-7:梯度下降算法与理解

一、梯度下降算法 梯度下降算法的目标是通过反复迭代来更新模型参数&#xff0c;以便最小化代价函数。代价函数通常用于衡量模型的性能&#xff0c;我们希望找到使代价函数最小的参数值。这个过程通常分为以下几个步骤&#xff1a; 初始化参数&#xff1a; 随机或设定初始参数…

项目资源管理-考试重点

1. 冲突管理的5种方法 ① 撤退/回避 ② 缓和/包容 ③ 妥协/调解 ④ 强迫/命令 ⑤ 合作/解决问题 2. 虚拟团队的优缺点 优点&#xff1a; ① 能够利用不在同一地理区域的专家的专业技术 ② 将在家办公的员工纳入团队 ③ 以及将行动不便者或残疾人纳入团队 缺点&#…

【图像分类】基于计算机视觉的坑洼道路检测和识别(ResNet网络,附代码和数据集)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 本篇博文,我们将使用PyTorch深度学习框架搭建ResNet实现钢轨缺陷识别,附完整的项目代码和数据集,可以说是全网…

C++ 自引用指针this(整理)

使用例子&#xff1a; #include <iostream> #include <Windows.h> using namespace std; class A { public:A(int x1){x x1;}void disp(){cout<<"this"<<this<<" when x"<<this->x<<endl;} private:int x;…

Node学习笔记之user用户API模块

1、获取用户的基本信息 步骤 获取登录会话存储的session中用户的id判断是否获取到id根据用户id查询数据库中的个人信息检查指定 id 的用户是否存在将密码设置为空将数据返回给前端 // 获取用户信息数据 exports.userinfo (req, res) > {(async function () {// 1. 获取…

关于Spring和SpringBoot中对配置文件的读取

Spring读取xml文件 具体流程见网址Spring源码分析2 — spring XML配置文件的解析流程 - 知乎 (zhihu.com) 我这里只做一下总结和自己的理解&#xff1a; &#xff08;1&#xff09;通过getConfigLocations方法, 从web.xml文件中读取配置文件地址&#xff0c;如果web.xml中读取…

合肥中科深谷嵌入式项目实战——人工智能与机械臂(四)

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 作者&#xff1a;爱吃饼干的小白鼠。Python领域优质创作者&#xff0c;2022年度博客新星top100入围&#xff0c;荣获多家平台专家称号。…

对象补充-原型和函数原型-创建对象

defineProperties可以定义多个属性描述符 var obj {// 私有属性&#xff08;js里面是没有严格意义的私有属性&#xff09;_age: 18,_eat: function() {} }Object.defineProperties(obj, {name: {configurable: true,enumerable: true,writable: true,value: "why"}…

Failed to prepare the device for development

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

创新领航 | 竹云参编《基于区块链的数据资产评估实施指南》正式发布!

10月25日&#xff0c;由深圳数宝数据服务股份有限公司和深圳职业技术大学提出&#xff0c;中国科学院深圳先进技术研究院、中国电子技术标准化研究院、中国&#xff08;天津&#xff09;自由贸易试验区政策与产业创新发展局、网络空间治理与数字经济法治&#xff08;长三角&…

论文阅读——ELECTRA

论文下载&#xff1a;https://openreview.net/pdf?idr1xMH1BtvB 另一篇分析文章&#xff1a;ELECTRA 详解 - 知乎 一、概述 对BERT的token mask 做了改进。结合了GAN生成对抗模型的思路&#xff0c;但是和GAN不同。 不是对选择的token直接用mask替代&#xff0c;而是替换为…

喜讯!合合信息顺利通过CMMI3级评估

近日&#xff0c;在擎标顾问团的咨询辅导下&#xff0c;上海合合信息科技股份有限公司&#xff08;简称“合合信息”&#xff09;顺利通过了CMMI3级评估。CMMI是国际上最流行、最实用的一种软件生产过程标准和软件企业成熟度等级认证的标准&#xff0c;通过该认证表明企业在开发…

【多线程面试题十四】、说一说synchronized的底层实现原理

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说一说synchronized的底…

常见面试题-MySQL专栏(二)

了解索引扫描吗&#xff1f; 答&#xff1a; MySQL有两种方法生成有序结果&#xff1a; 通过排序操作按照索引顺序扫描 如果 explain 出来的 type 列值为 “index” 的话&#xff0c;说明是按照索引扫描了。 索引扫描本身的速度是很快的。但是如果索引不能覆盖查询所需的全…