我们如何实现业务操作日志功能?_系统日志功能实现-程序员宅基地

技术标签: java  安全  后台  服务器  

1. 需求

我们经常会有这样的需求,需要对关键的业务功能做操作日志记录,也就是用户在指定的时间操作了哪个功能,操作前后的数据记录,必要的时候可以一键回退,今天我就为大家实现这个的功能,让大家可以直接拿来用。

1.1 日志分类

  1. 系统日志:指的是程序执行过程中的关键步骤记录,根据实际输出的debug、info、warn、error等级别的执行记录信息,主要用来记录核心参数以及返回值,同时为了在出现问题的时候能够快速排查问题
  2. 操作日志:它是用户实际业务操作行为的记录,主要是为了分析用户的行为偏好,一方面用来对业务进行分析,开发人员也可以针对访问的频率去提高特定接口的性能。

2. 设计思路

首先我们得要分析具体要实现这个功能,都需要记录哪些信息,然后怎么要去实现这个功能?
具体功能点:

  1. 记录用户的业务操作行为,具体字段有:操作人、操作时间、操作功能、日志类型、操作内容描述、操作内容、操作前内容。
  2. 需要对外提供页面和管理功能,以便查询追溯和数据回滚。

我们首先要想到,要实现这个功能,最好的办法就是需要和业务逻辑进行解耦,因为它是一种业务辅助功能,同时是对所有业务的一种横向操作,那我们使用什么技术,是不是就呼之欲出了?最容易想到的就是AOP切面+自定义注解

2.1 实现步骤

1、首先定义操作日志注解,注解内定义一些属性,如操作功能名称、描述等;

2、将自定义注解标记在需要进行业务操作记录的方法上,一般查询不需要。

3、定义切入点,编写切面:切入点就是标记业务操作日志注解的目标方法;切面就是保存业务操作日志信息。

对了,在这里我想给大家说一下咱们经常用到的几个技术的区别,过滤器,拦截器,SpringAOP,这个也是我经常的困惑。

2.2 SpringAOP、过滤器、拦截器对比

在匹配中同一目标时,过滤器、拦截器、SpringAOP的执行优先级是:过滤器>拦截器>SpringAOP,执行顺序是先进后出,主要有如下区别:

  1. 过滤器(Filter)
    过滤器,就是起到过滤筛选作用的一种事物,只不过这里的过滤器过滤的对象是客户端访问的web资源。也可以理解为一种预处理手段,对资源进行拦截后,将其中我们认为的杂质(用户自己定义的)过滤,符合条件的放行,不符合的则拦截下来。
    过滤器常见的使用场景:统一设置编码,过滤敏感字符,登录校验,URL级别的访问权限控制,数据压缩。
    在这里插入图片描述
    2.拦截器(Interceptor)
    拦截器是springmvc提供的,类似于过滤器。主要用于拦截用户请求并作相应的处理。
    拦截器的使用场景: 日志记录,权限校验,登录校验,性能检测,经常会用在网关处。
    在这里插入图片描述
    3:AOP
    AOP拦截的是类的元数据(包、类、方法名、参数等),AOP针对具体的代码,能够实现更加复杂的业务逻辑。
    使用的场景:日志记录,性能统计,安全控制,事务处理,异常处理。

3. 具体实现

3.1 引入依赖

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

3.2 数据库表设计

create table if not exists bus_log
(
   id bigint auto_increment comment '自增id'
      primary key,
   bus_name varchar(100) null comment '业务名称',
   bus_descrip varchar(255) null comment '业务操作描述',
   oper_person varchar(100) null comment '操作人',
   oper_time datetime null comment '操作时间',
   ip_from varchar(50) null comment '操作来源ip',
   param_file varchar(255) null comment '操作参数报文文件'
)
comment '业务操作日志' default charset ='utf8';

3.3 代码实现

  1. 定义日志注解
/**
 * 业务日志注解
 * 可以作用在控制器或其他业务类上,用于描述当前类的功能;
 * 也可以用于方法上,用于描述当前方法的作用;
 */
@Target({
    ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BusLog {
    
 
 
    /**
     * 功能名称
     * @return
     */
    String name() default "";
 
    /**
     * 功能描述
     * @return
     */
    String descrip() default "";
 
}
  1. 编写切面

主要步骤是:在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息;

@Component
@Aspect
@Slf4j
public class BusLogAop implements Ordered {
    
    @Autowired
    private BusLogDao busLogDao;
 
    /**
     * 定义BusLogAop的切入点为标记@BusLog注解的方法
     */
    @Pointcut(value = "@annotation(com.wuk.BusLog)")
    public void pointcut() {
    
    }
 
    /**
     * 业务操作环绕通知
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
    
        log.info("----BusAop 环绕通知 start");
        //执行目标方法
        Object result = null;
        try {
    
            result = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
    
            throwable.printStackTrace();
        }
        //目标方法执行完成后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述
        Object target = proceedingJoinPoint.getTarget();
        Object[] args = proceedingJoinPoint.getArgs();
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        BusLog anno1 = target.getClass().getAnnotation(BusLog.class);
        BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class);
        BusLogBean busLogBean = new BusLogBean();
        String logName = anno1.name();
        String logDescrip = anno2.descrip();
        busLogBean.setBusName(logName);
        busLogBean.setBusDescrip(logDescrip);
        busLogBean.setOperPerson("wuk");
        busLogBean.setOperTime(new Date());
        JsonMapper jsonMapper = new JsonMapper();
        String json = null;
        try {
    
            json = jsonMapper.writeValueAsString(args);
        } catch (JsonProcessingException e) {
    
            e.printStackTrace();
        }
        //把参数报文写入到文件中
        OutputStream outputStream = null;
        try {
    
            String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log";
            outputStream = new FileOutputStream(paramFilePath);
            outputStream.write(json.getBytes(StandardCharsets.UTF_8));
            busLogBean.setParamFile(paramFilePath);
        } catch (FileNotFoundException e) {
    
            e.printStackTrace();
        } catch (IOException e) {
    
            e.printStackTrace();
        } finally {
    
            if (outputStream != null) {
    
                try {
    
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
    
                    e.printStackTrace();
                }
 
            }
        }
        //保存业务操作日志信息
        this.busLogDao.insert(busLogBean);
        log.info("----BusAop 环绕通知 end");
        return result;
    }
 
    @Override
    public int getOrder() {
    
        return 1;
    }
}
  1. 业务接口添加注解
@RestController
@Slf4j
@RequestMapping("/person")
public class PersonController {
    
    @Autowired
    private IPersonService personService;
 
    @PostMapping
    @BusLog(name="添加人员信息",descrip = "添加人员信息")
    public Person add(@RequestBody Person person) {
    
        Person result = this.personService.registe(person);
        log.info("//增加person执行完成");
        return result;
    }
    
    @PutMapping
    @BusLog(name="修改人员信息",descrip = "修改人员信息")
    public void edit(@RequestBody Person person) {
    
         this.personService.update(person);
    }
    
    @DeleteMapping
    @BusLog(name="删除人员信息", descrip = "删除人员信息")
    public void delete(@PathVariable(name = "id") Integer id) {
    
         this.personService.delete(id);
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wu2374633583/article/details/131219500

智能推荐

Python中常用的内置函数(不断更新中)_python内置函数-程序员宅基地

文章浏览阅读2w次,点赞20次,收藏204次。在Python中有非常多的内置函数,在这里列出来一些经常使用到的内置函数,在编程时如果恰当地使用这些函数会达到事半功倍的效果!本篇博文内容会经常更新,建议收藏_python内置函数

计算机实习学习总结报告10篇_实习工作总结经验csdn-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏10次。计算机实习学习总结报告篇1一、实习单位:来到实习单位后主要是在门市从事产品的销售和商家间渠道工作。在工作的过程中了解计算机相关行业的发展现状及趋势;熟悉计算机硬件组装、计算机系统及软件安装、局域网搭建;掌握典型计算机网络工程的安装与维护;了解网站建设与网站营销、网站美工设计、基于java的动态网站建设并且了解单位营运方式、项目分工、如何进行管理等。虽然整个实习时间较短,但应该说让咱们每个人都还是学到了不少知识和东西,见识到了不少平时课堂中、校园里无法见识到的方方面面也基本圆满完成所有的实习任务。二、实习总结_实习工作总结经验csdn

C++ QT调用python脚本并将软件打包发布_c++ 调用python 打包-程序员宅基地

文章浏览阅读3.2k次,点赞7次,收藏48次。怎么调用python脚本就不详细说了,网上有很多教程,对于我来说主要就是打包的问题比较难解决,弄了一个下午都没解决,不知道是minconda的问题,还是Qt更新的原因,网上的很多解决方法都不行,经过我的一项一项排查,最后发现就是少导了一个文件夹,怕自己后面忘记,所以发个帖子记录一下。_c++ 调用python 打包

Langchain+本地大语言模型进行数据库操作的实战代码_langchain执行sql-程序员宅基地

文章浏览阅读8.1w次,点赞61次,收藏79次。本文讲解了Langchain+本地大语言模型进行数据库操作的实战代码,希望能对尝试使用开源大语言模型进行SQL操作的同学们有所帮助。文章目录1. 前言2. 代码思路剖析3. 实战代码_langchain执行sql

最近做项目所积累的一些小知识(一)-程序员宅基地

文章浏览阅读95次。CSS篇1.如何让一个页面有背景图片,并且背景图片铺满整个屏幕? 可以用body,的background属性来设置!例如:background: url("123.jpg") no-repeat fixed center top / 100% 500px transparent;  那么现在把这个属性拆开来解释下。1.background-imag..._java做完项目后的技能与知识积累怎么写 site:blog.csdn.net

AD 7192 ---- 基于寄存器_ad7192 id寄存器-程序员宅基地

文章浏览阅读1.7k次,点赞5次,收藏9次。结合数据手册,分析寄存器_ad7192 id寄存器

随便推点

Android ScrollView与RecyclerView滑动冲突问题_android recyclerview scrollview-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏2次。在我们日常开发中经常会用到ScrollView与RecyclerView的组合,但是这种组合有时会出现滑动不流畅的问题,也就是卡顿现象布局如下:<ScrollView ="http://schemas.android.com/apk/res/android"android:layout_width="match_parent" androi......_android recyclerview scrollview

Matlab实现的数学模型(2020新整理)_matlab数学模型-程序员宅基地

文章浏览阅读1.3w次,点赞84次,收藏384次。文章目录1、(精)matlab&lingo已编好的程序2、《MATLAB 神经网络30个案例分析》源程序 数据3、《MATLAB神经网络原理与实例精解》随书附带源程序4、《MATLAB图像处理》源文件5、《基于MATLAB的高等数学问题求解》 随书附带源程序6、28个实际问题建模MATLAB源程序代码.rar7、AHP层次分析法8、CellularAutomata元胞向量机9、FuzzyM..._matlab数学模型

《MySQL是怎么运行的:从根儿上理解MySQL》(8-10)学习总结_mysql的sort_union使用了bitmap-程序员宅基地

文章浏览阅读354次。说明文章的图片来源《MySQL是怎么运行的:从根儿上理解MySQL》,本篇文章只是个人学习总结,欢迎大家买一本看看,对于mysql是由浅入深的讲解非常细致目录说明8.MySQL 的数据目录数据库和文件系统的关系Mysql的数据目录数据目录和安装目录的区别如何确定mysql的数据目录数据目录的结构数据库在文件系统的表示表在文件系统的表示Innodb如何存储表数据系统表空间独立表空间MyISAM是怎么存储数据的视图在文件系统的表示其它文件文件系统对数据库的影响Mysql系统数据库简介总结9.InnoDB的表_mysql的sort_union使用了bitmap

ThreadPoolTaskExecutor的提交方法execute和submit_threadpooltaskexecutor submit execute-程序员宅基地

文章浏览阅读956次。前面提到了线程池提交任务有两种方法:无返回值的任务使用public void execute(Runnable command) 方法提交;有返回值的任务使用public <T> Future<T> submit(Callable) 方法提交。下面具体来看下两者的应用以及区别。一、与主线程执行顺序的区别:..._threadpooltaskexecutor submit execute

Debug之路——跟踪算法ECO的配置(ECO: Efficient Convolution Operators for Tracking)_eco跟踪算法-程序员宅基地

文章浏览阅读2.7k次,点赞2次,收藏11次。昨天琢磨着 跑一下ECO,苦于这个电脑没有GPU,只能跑CPU版的了,就是run_demo_ECO,配置过程其实不难,主要是把要准备的包都准备好,然后mex -setup对,接下来就顺利了,下面介绍流程:Win7+VS2013+Matlab2016a+Matconvnet-1.0-beta23 直接参考下面这个博客,简单粗暴的教程,步骤简洁详细,没有冗余: http..._eco跟踪算法

MacOS 系统成功安装 tensorflow 步骤_mac装tensorflow-程序员宅基地

文章浏览阅读1.3k次,点赞21次,收藏18次。tensorflow 2 mac os 系统安装 步骤_mac装tensorflow

推荐文章

热门文章

相关标签