从0到1带你实现一个业务日志组件(一)需求分析篇

✨这里是第七人格的博客✨小七,欢迎您的到来~✨

🍅系列专栏:【架构思想】🍅

✈️本篇内容: 从0到1带你实现一个业务日志组件✈️

🍱本篇收录完整代码地址:gitee.com/diqirenge/b...

楔子

前段时间小七接到一个任务,要为某些后台操作添加业务操作日志,并且需要将日志存储在数据库中。看了看当前项目中的代码,很*很暴力,直接就在对应的地方写的一条插入语句就ok了,抛开技术不谈,这样的代码还是很优雅的!

但是小七想起了以前看过的美团的一篇技术博客------《如何优雅地记录操作日志?》 ,正好有2天时间完成这个需求,时间充足,还等什么?开干

分析需求

业务方需求很简单,总结起来就是 把什么人在什么时间对什么东西做了什么操作 给记录下来,当然再做好一点的话,可以将两次操作的不同记录下来。我们这一次就完成基础需求就好了。

确认方案

首先我们来思考一下可行的几种方案:

  1. 直接持久化,在需要保存日志的地方写sql就好了。

  2. 将保存操作抽象提取为工具类,在需要保存日志的地方,调用工具类。

  3. 使用AOP生成操作日志。

  4. 使用canal监听数据库变化,记录操作日志。

针对第一点,优势在于不用动脑子;针对第二点,优势在于比第一点动了一点脑子。这两种方案小七都是不推荐的,耦合性太高,拓展性太低。

针对第三点,也是很多公司采取的方案,但是大家一般都只是,简单的拼接了一下出入参,结合美团的博客------《如何优雅地记录操作日志?》 我们可以考虑使用SpEL来做动态模版。这种方案可行且优雅。

针对第四点,只能根据数据库的更改做日志记录,局限太大,不考虑。

综上所述,我们最后选择使用AOP来生成动态的操作日志。

基础架构

方案确定了,接下来,我们需要定义一下我们的基础架构。因为是根据AOP来玩的,所以我们需要2个东西,注解和切面。

分支名称

231013-52javaee.com-DemandAnalysis

仓库地址

gitee.com/diqirenge/b...

分支描述

根据需求分析,完成注解设计。

代码实现

注解一:业务日志注解 BizLog

根据需求分析,我们可以抽象出我们需要的日志属性

需求 翻译
什么人 操作者
什么时间 操作时间(这个不需要传入,以系统计算为准)
什么东西 xx系统,xx模块,xx业务id
什么操作 操作类型

以上属性按道理就可以完成我们的需求了,但是为了拓展性,我们可以再添加以下属性

拓展 翻译
操作结果 1、成功模板 2、失败模版
记录条件 根据某些条件,判断是否需要记录日志。比如:证件类型为身份证的才记录
详情 拓展字段,存放其他不好定义的信息

具体代码如下:

java 复制代码
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(BizLogs.class)
public @interface BizLog {
    /**
     * 操作者,必填
     */
    String operator();

    /**
     * 成功模板,必填
     */
    String success();

    /**
     * 系统,默认取spring-application-name
     */
    String system() default "";

    /**
     * 模块
     */
    String module() default "";

    /**
     * 操作类型:比如增删改查
     */
    String type() default "";

    /**
     * 关联的业务id
     */
    String bizNo() default "";

    /**
     * 失败模板
     */
    String fail() default "";


    /**
     * 拓展字段
     * 记录更详细的其他信息
     */
    String detail() default "";

    /**
     * 记录条件 默认 true
     * true代表要记录,false代表不记录
     */
    String condition() default "";

}

注解解析:

java 复制代码
// 用于指示将被注解的元素包含在生成的Java文档中
@Documented
java 复制代码
// 表明这个注解可以修饰(作用)在方法上
@Target(ElementType.METHOD)
java 复制代码
// 对应Java代码的加载和运行顺序
// 范围大小:java源文件<.class文件<内存字节码
@Retention(RetentionPolicy.RUNTIME)
java 复制代码
// 表明这个注解可以被子类继承
@Inherited
java 复制代码
// 表示这个注解可以被重复使用
@Repeatable(BizLogs.class)

注解二: BizLogs

该注解里面只有一个BizLog注解的集合

java 复制代码
BizLog[] value();

方便有多种不同日志记录需求的时候,可以写成:

java 复制代码
@BizLogs({
        @BizLog(.....),
        @BizLog(.....)
})

切面

java 复制代码
@Aspect
@Component
public class BizLogAspect {

    /**
     * 系统日志记录器
     */
    public static Logger log = LoggerFactory.getLogger(BizLogAspect.class);


    /**
     * 定义切点
     * 切入包含BizLog和BizLogs注解的方法
     */
    @Pointcut("@annotation(com.run2code.log.annotation.BizLog) || @annotation(com.run2code.log.annotation.BizLogs)")
    public void pointCut() {
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        // todo 待实现
        return null;
    }

}

正则表达式测试类

因为我们最终是通过解析SpEL,来完成动态模版的写入的,所以需要定义我们的数据结构,哪些能被我们的组件解析,哪些不能解析,通过文章指导,我们有2种结构需要解析:

1、直接将类的属性给到Spring去解析

比如:#request.activityId

2、将方法给到Spring去解析

比如:getActivityNameById#id

通过以上分析,我们定义以下结构:

1、类属性的结构

java 复制代码
{{#request.activityId}}

2、自定义方法的结构

java 复制代码
{getNameById{#id}}

通过正则表达式,我们可以很好的解析以上结构

java 复制代码
    /**
     * 这个正则表达式的含义为:
     * 匹配一个包含在花括号中的字符串,其中花括号中可以包含任意数量的空白字符(包括空格、制表符、换行符等),
     * 并且花括号中至少包含一个单词字符(字母、数字或下划线)。
     * =================================================
     * 具体来说,该正则表达式由两部分组成:
     * {s*(\w*)\s*}:表示匹配一个左花括号,后面跟随零个或多个空白字符,然后是一个单词字符(字母、数字或下划线)零个或多个空白字符,最后是一个右花括号。这部分用括号括起来,以便提取匹配到的内容。
     * (.*?):表示匹配任意数量的任意字符,但尽可能少地匹配。这部分用括号括起来,以便提取匹配到的内容。
     * =================================================
     * 因此,整个正则表达式的意思是:
     * 匹配一个包含在花括号中的字符串,
     * 其中花括号中可以包含任意数量的空白字符(包括空格、制表符、换行符等),
     * 并且花括号中至少包含一个单词字符(字母、数字或下划线),并提取出花括号中的内容。
     * =================================================
     */
    private static final Pattern PATTERN = Pattern.compile("\\{\\s*(\\w*)\\s*\\{(.*?)}}");;

测试方法

java 复制代码
@Test
public void test() {
    // 1、参数解析
    String template = "{{#request.activityId}}";
    Matcher matcher = PATTERN.matcher(template);
    while (matcher.find()) {
        String paramName = matcher.group(2);
        System.out.println("paramName:" + paramName);
    }
    // 2、自定义函数解析
    String customFunctionTemplate = "{getNameById{#id}}";
    Matcher customFunctionMatcher = PATTERN.matcher(customFunctionTemplate);
    while (customFunctionMatcher.find()) {
        String paramName = customFunctionMatcher.group(2);
        String funcName = customFunctionMatcher.group(1);
        System.out.println("paramName:" + paramName);
        System.out.println("funcName:" + funcName);
    }

}

测试结果:

java 复制代码
paramName:#request.activityId
paramName:#id
funcName:getNameById
相关推荐
会说法语的猪1 小时前
springboot实现图片上传、下载功能
java·spring boot·后端
凡人的AI工具箱1 小时前
每天40分玩转Django:实操多语言博客
人工智能·后端·python·django·sqlite
Cachel wood1 小时前
Django REST framework (DRF)中的api_view和APIView权限控制
javascript·vue.js·后端·python·ui·django·前端框架
m0_748234082 小时前
Spring Boot教程之三十一:入门 Web
前端·spring boot·后端
想成为高手4992 小时前
国产之光--仓颉编程语言的实战案例分析
后端
编码浪子2 小时前
构建一个rust生产应用读书笔记7-确认邮件2
开发语言·后端·rust
向上的车轮3 小时前
云边端架构的优势是什么?面临哪些挑战?
架构·云边端
昙鱼3 小时前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
FHYAAAX3 小时前
灾备方案和架构类型、跨区域
架构·华为云
白宇横流学长3 小时前
基于SpringBoot的停车场管理系统设计与实现【源码+文档+部署讲解】
java·spring boot·后端