开源:如何优雅的实现一个操作日志组件
日志几乎存在于所有系统中,开发调试日志的记录我们有log4j,logback等来实现,但对于要展示给用户看的日志,我并没有发现一个简单通用的实现方案。所以决定为之后的开发项目提供一个通用的操作日志组件。
2. 系统日志和操作日志所有系统都会有日志,但我们区分了 系统日志 和 操作日志
系统日志:主要用于开发者调试排查系统问题的,不要求固定格式和可读性操作日志:主要面向用户的,要求简单易懂,反映出用户所做的动作。通过操作日志可追溯到 某人在某时干了某事情,如:
租户 | 操作人 | 时间 | 操作 | 内容 |
---|---|---|---|---|
A租户 | 小明 | 2022/2/27 20:15:00 | 新增 | 新增了一个用户:Mr.Wang |
B租户 | 大米 | 2022/2/28 10:35:00 | 更新 | 修改订单 [xxxxxx] 价格为 xx 元 |
C租户 | 老王 | 2022/2/28 22:55:00 | 查询 | 查询了名为: [xx] 的所有交易 |
基于以上两点,我们想想如何实现。
spingboot快速接入,需要我们来自定义spring boot starter;
业务入侵性低,首先想到了AOP,一般操作日志都是在增删改查的方法中,所以我们可以使用注解在这些方法上,通过AOP拦截这些方法。
3.3 待实现:因此,我们需要实现以下功能:
自定义spring boot starter定义日志注解AOP拦截日志注解方法定义日志动态内容模板模板中又需要实现:
动态模板表达式解析:用强大的SpEL来解析表达式自定义函数:支持目标方法前置/后置的自定义函数3.4 展现所以我们最终期望的大概是这样:
@EasyLog(module="用户模块",type="新增",content="测试{functionName{#userDto.name}}",condition="#userDto.name=="easylog"")publicStringtest(UserDtouserDto){return"test";}4. 实现步骤4.1 定义日志注解
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceEasyLog{Stringtenant()default"";Stringoperator()default"";Stringmodule()default"";Stringtype()default"";StringbizNo()default"";Stringcontent();Stringfail()default"";Stringdetail()default"";Stringcondition()default"";}
字段 | 意义 | 支持SpEl表达式 | 必填 |
---|---|---|---|
tenant | 租户,SAAS系统中区分不同租户 | 是 | 否 |
operator | 操作者 | 是 | 否 |
module | 模块,区分不同业务模块 | 否 | 否 |
type | 操作类型,形如:增删改查 | 否 | 否 |
bizNo | 业务编号,便于查询 | 是 | 否 |
content | 日志模板内容 | 是 | 是 |
fail | 操作失败时的模板内容 | 是 | 否 |
detail | 额外的记录信息 | 是 | 否 |
condition | 是否记录的条件 (默认:true 记录) | 是 | 否 |
这里的自定义函数,并不是指SpEL中的自定义函数,因为SpEL中的自定义函数必须是静态方法才可以注册到其中,因为静态方法使用中并没有我们自己定义方法来的方便,所以这里的自定义函数仅仅指代我们定义的一个普通方法。
publicinterfaceICustomFunction{/***目标方法执行前执行自定义函数*@return是否是前置函数*/booleanexecuteBefore();/***自定义函数名*@return自定义函数名*/StringfunctionName();/***自定义函数*@paramparam参数*@return执行结果*/Stringapply(Stringparam);}
我们定义好自定义函数接口,实现交给使用者。使用者将实现类交给Spring容器管理,我们解析的时候从Spring容器中获取即可。
4.3 SpEL表达式解析主要牵涉下面几个核心类:
解析器ExpressionParser,用于将字符串表达式转换为Expression表达式对象。表达式Expression,最后通过它的getValute方法对表达式进行计算取值。上下文EvaluationContext,通过上下文对象结合表达式来计算最后的结果。ExpressionParserparser=newSpelExpressionParser();//创建一个表达式解析器StandardEvaluationContextex=newStandardEvaluationContext();//创建上下文ex.setVariables("name","easylog");//将自定义参数添加到上下文Expressionexp=parser.parseExpression(""欢迎你!"+#name");//模板解析Stringval=exp.getValue(ex,String.class);//获取值
我们只需要拿到日志注解中的动态模板即可通过SpEL来解析。
4.4 自定义函数的解析我们采用 { functionName { param }}的形式在模板中展示自定义函数,解析整个模板前,我们先来解析下自定义函数,将解析后的值替换掉模板中的字符串即可。
if(template.contains("{")){Matchermatcher=PATTERN.matcher(template);while(matcher.find()){StringfuncName=matcher.group(1);Stringparam=matcher.group(2);if(customFunctionService.executeBefore(funcName)){Stringapply=customFunctionService.apply(funcName,param);}}}4.5 获取操作者信息
一般我们都是将登录者信息存入应用上下文中,所以我们不必每次都在日志注解中指出,我们可统一设置,定义一个获取操作者接口,由使用者实现。
publicinterfaceIOperatorService{//获取当前操作者StringgetOperator();//当前租户StringgetTenant();}4.6 定义日志内容接收
我们要将解析完成后的日志内容实体信息发送给我们的使用者,所以我们需要定义一个日志接收的接口,具体的实现交给使用者来实现,无论他接收到日志存储在数据库,MQ还是哪里,让使用者来决定。
publicinterfaceILogRecordService{/***保存log*@parameasyLogInfo日志实体*/voidrecord(EasyLogInfoeasyLogInfo);}4.7 定义AOP拦截
@Aspect@Component@AllArgsConstructorpublicclassEasyLogAspect{@Pointcut("@annotation(**.EasyLog)")publicvoidpointCut(){}//环绕通知@Around("pointCut()&&@annotation(easyLog)")publicObjectaround(ProceedingJoinPointjoinPoint,EasyLogeasyLog)throwsThrowable{//前置自定义函数解析try{result=joinPoint.proceed();}catch(Throwablee){}//SpEL解析//后置自定义函数解析returnresult;}}4.8 自定义 spring boot starter
创建自动配置类,将定义的一些来交给Spring容器管理:
@Configuration@ComponentScan("**")publicclassEasyLogAutoConfiguration{@Bean@ConditionalOnMissingBean(ICustomFunction.class)@Role(BeanDefinition.ROLE_APPLICATION)publicICustomFunctioncustomFunction(){returnnewDefaultCustomFunction();}@Bean@ConditionalOnMissingBean(IOperatorService.class)@Role(BeanDefinition.ROLE_APPLICATION)publicIOperatorServiceoperatorGetService(){returnnewDefaultOperatorServiceImpl();}@Bean@ConditionalOnMissingBean(ILogRecordService.class)@Role(BeanDefinition.ROLE_APPLICATION)publicILogRecordServicerecordService(){returnnewDefaultLogRecordServiceImpl();}}
上一篇我已经完整的介绍了如何自定义 spring boot starter ,可去参考:
开源:如何自定义spring boot starter
5. 我们可以学到什么?你可以拉取easy-log源码,用于学习,通过easy-log你可以学到:
注解的定义及使用AOP的应用SpEL表达式的解析自定义 Spring boot starter设计模式6. 源码GitHub: https://github.com/flyhero/easy-logGitee: https://gitee.com/flyhero/easy-log相关阅读
-
世界热推荐:今晚7:00直播丨下一个突破...
今晚19:00,Cocos视频号直播马上点击【预约】啦↓↓↓在运营了三年... -
NFT周刊|Magic Eden宣布支持Polygon网...
Block-986在NFT这样的市场,每周都会有相当多项目起起伏伏。在过去... -
环球今亮点!头条观察 | DeFi的兴衰与...
在比特币得到机构关注之后,许多财务专家预测世界将因为加密货币的... -
重新审视合作,体育Crypto的可靠关系才能双赢
Block-987即使在体育Crypto领域,人们的目光仍然集中在FTX上。随着... -
简讯:前端单元测试,更进一步
前端测试@2022如果从2014年Jest的第一个版本发布开始计算,前端开发... -
焦点热讯:刘强东这波操作秀
近日,刘强东发布京东全员信,信中提到:自2023年1月1日起,逐步为...