init
This commit is contained in:
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
out/
|
||||
build/
|
||||
classes/
|
||||
target/
|
||||
session_data/
|
||||
logs/
|
||||
*.jar
|
||||
*.war
|
||||
*.class
|
||||
|
||||
#ide-eclipse
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
||||
.idea/
|
||||
*.iml
|
||||
*.eml
|
||||
*.log
|
||||
|
||||
#mac os
|
||||
.DS_Store
|
||||
.settings/
|
||||
node_modules/
|
||||
upload/
|
||||
package.json
|
||||
package-lock.json
|
||||
309
README.md
Normal file
309
README.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# operation-log-parent
|
||||
操作日志生成组件(操作日志又称系统变更日志、审计日志等)
|
||||
|
||||
# 背景
|
||||
不管是B端还是C端系统,在用户使用过程中,都会涉及到对相关资源进行更新或者删除的操作,如电商系统中商家修改商品售价,OA系统中管理员修改用户的权限等,数据库中一般记录的都是资源的最后修改时间和修改人。第一是可读性比较差,只能是程序员能够查询使用,第二是缺少修改前的值,无法对数据进行追溯。
|
||||
# 问题
|
||||
1. 如何生成可读性高的操作日志
|
||||
2. 操作日志内容包含修改前后的值,方便后期的数据追溯
|
||||
# 如何生成可读性高的操作日志
|
||||
一个可读性高的操作日志,应包含下面几部分
|
||||
- 操作人:张三
|
||||
- 操作时间:2022-03-23 19:00:00
|
||||
- 业务模块:商品
|
||||
- 业务标识号:100878(这里是操作的资源对象id,当前案例是商品id)
|
||||
- 操作内容:修改了商品价格,xxxx
|
||||
# 操作日志内容包含修改前后的值
|
||||
- 操作内容:修改了商品价格,从12¥调整到0.1¥
|
||||
- 操作内容:修改了商品价格,从14¥调整到0.01¥
|
||||
# 理想的操作日志列表展示
|
||||
| 操作人 | 操作时间 | 业务模块 | 业务标识号 | 操作内容 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 张三 | 2022-03-23 19:00:00 | 商品 | 100878 | 修改了商品价格,从12¥调整到0.1¥ |
|
||||
| 李四 | 2022-03-24 19:00:00 | 商品 | 200878 | 修改了商品价格,从14¥调整到0.01¥ |
|
||||
# 如何优雅的生成操作日志
|
||||
## 方案一:手动在业务代码中记录(不够优雅)
|
||||
所有的埋点在业务代码里面手动埋入,在变更事件触发之前,查询一次资源对应的状态并记录在内存中,变更之后再记录变更后资源的状态,最后将前后状态、操作人、变更时间一起写到数据库埋点的代码中。
|
||||
```
|
||||
public void updateApp(SmtApp newApp){
|
||||
|
||||
//操作日志实体对象
|
||||
OperateLog operateLog = new OperateLog();
|
||||
operateLog.setUserId("当前用户ID");
|
||||
//业务对象标识-这里是应用id
|
||||
operateLog.setBizNo(newApp.id);
|
||||
//操作发生时间
|
||||
operateLog.setCreatedTime(new Date);
|
||||
//操作类别,这里是应用变更
|
||||
operateLog.setCategory("应用变更");
|
||||
//操作日志详情,记录操作的业务对象的变更信息
|
||||
operateLog.setDetail("更新了应用信息,应用名称:oldName --> newName");
|
||||
//保存操作日志
|
||||
operateLogService.save(operateLog);
|
||||
|
||||
/******忽略后续的应用更新代码************/
|
||||
}
|
||||
```
|
||||
优点:
|
||||
- 方案简单,没有难度,堆人堆时间就能完成,简单明了
|
||||
|
||||
缺点:
|
||||
- 业务侵入性大,完全耦合正常的业务
|
||||
- 扩展性非常差,想增加其他维度的埋点时,需要修改所有埋点的业务代码
|
||||
|
||||
## 方案二:使用 Canal 监听数据库记录操作日志(不够优雅)
|
||||
canal 是一款基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费的开源组件,通过采用监听数据库 Binlog 的方式,这样可以从底层知道是哪些数据做了修改,然后根据更改的数据记录操作日志。
|
||||
|
||||
优点:
|
||||
- 和业务逻辑完全分离
|
||||
|
||||
缺点:
|
||||
- 难以记录操作前的内容
|
||||
- 只能针对数据库的更改做操作记录,如涉及到和外部交互的部分,无法记录,如发送邮件、短信、RPC调用
|
||||
- 记录的操作结果内容只适合开发人员看,无法给到产品和运营人员使用
|
||||
## 基于AOP方法注解实现操作日志
|
||||
为了解决上面几个方案所带来的问题,一般采用 AOP 的方式记录日志,让操作日志和业务逻辑解耦,接下来看一个简单的 AOP 日志的例子。伪代码如下:
|
||||
```
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{#oldName}改为{#newName}", bizNo = "#userId", category = "用户更改")
|
||||
public void updateNameById(Long userId, Sting oldName,String newName) {
|
||||
// do update action
|
||||
}
|
||||
```
|
||||
# 使用AOP方案优雅的记录操作日志
|
||||
结合上一节如何生成可读性高的操作日志,那么AOP注解接口需要包含以下几个关键属性:
|
||||
|
||||
```
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface LogRecordAnnotation {
|
||||
|
||||
String operator() default "";
|
||||
|
||||
String bizNo();
|
||||
|
||||
String category();
|
||||
|
||||
String detail() default "";
|
||||
|
||||
String condition() default "true";
|
||||
}
|
||||
```
|
||||
|
||||
| 属性 | 是否必须 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| operator | 否 | 操作人 |
|
||||
| bizNo | 是 | 操作的业务模块 |
|
||||
| category | 是 | 操作的业务资源标识 |
|
||||
| detail | 是 | 操作日志详情 |
|
||||
| condition | 否 | 操作日志记录条件,表达式返回boolean类型 |
|
||||
|
||||
> conditon属性我们稍后再展开说明
|
||||
|
||||
**operator**属性设置成非必填主要是为了减少冗余的赋值操作,正常的项目中,都会保存当前的用户信息到一个请求上下文中,如UserContext,操作日志组件提供统一的OperatorGetService接口,由使用方实现,返回当前用户信息
|
||||
```
|
||||
//组件提供接口
|
||||
public interface OperatorGetService {
|
||||
|
||||
Operator getUser();
|
||||
|
||||
@Data
|
||||
class Operator {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
}
|
||||
//使用方实现
|
||||
public static class MarketOperatorGetServiceImpl implements OperatorGetService {
|
||||
@Override
|
||||
public Operator getUser() {
|
||||
Long userId = UserContext.getUserId();
|
||||
String userName = UserContext.getRealName();
|
||||
return Operator.builder()
|
||||
.id(String.valueOf(userId))
|
||||
.name(userName)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面的讲解统一使用更新用户信息为例子展开
|
||||
```
|
||||
@Data
|
||||
public class User {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户所属部门id
|
||||
*/
|
||||
private Long departmentId;
|
||||
|
||||
/**
|
||||
* 用户名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 用户年龄
|
||||
*/
|
||||
private Integer age;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
* 0-禁用,1-启用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
private Date createdTime;
|
||||
|
||||
private Date updatedTime;
|
||||
}
|
||||
```
|
||||
## 更新用户单个属性(名称)
|
||||
常规的更新用户名称的业务方法如下
|
||||
```
|
||||
void updateNameById(Integer id, String newName) {
|
||||
//doUpdate
|
||||
}
|
||||
```
|
||||
在方法上面加上我们定义的注解
|
||||
```
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,改为{#newName}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameById(Integer id, String newName) {
|
||||
//doUpdate
|
||||
}
|
||||
```
|
||||
很明显,操作日志详情缺少旧的用户名,无法满足使用需要,解决方法有两种:
|
||||
|
||||
**第一种是让开发在方法参数中加上旧的用户名称**:
|
||||
```
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{#oldName}改为{#newName}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameById(Integer id,String oldName, String newName) {
|
||||
//doUpdate
|
||||
}
|
||||
```
|
||||
这种方式在原有方法上面强行增加了一个也业务无关的参数,既不符合相关设计原则,也无法说服带有强迫症的开发,毕竟这种方式违反开发常识了。
|
||||
|
||||
**第二种是使用自定义模板表达式,来获取旧的用户名称:**
|
||||
```
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{getUserNameById(#id)}改为{#newName}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameById(Integer id,String newName) {
|
||||
//doUpdate
|
||||
}
|
||||
|
||||
String getUserNameById(Integer id){
|
||||
User user =; //get user by select db
|
||||
return user.getName();
|
||||
}
|
||||
```
|
||||
- 模板表达式```{getUserNameById(#id)}```用来获取旧的用户名称,代表调用```getUserNameById(#id)``` 方法来获取用户名称,其中的参数使用`spel`表达式引用了原方法中的参数`id`(用户id)
|
||||
- 模板表达式`{#newName}` 用来获取新用户名称,`#newName`使用spel表达式引用了原方法中的参数`newName`
|
||||
- 外层使用大括号`{}`括起来是为了方便和`spel`表达式作区分
|
||||
> 自定义模板相关的详细说明将放在代码实现章节讲解
|
||||
|
||||
## 更新用户多个属性
|
||||
上一节我们讲解了在更新单个属性的时候,如何去记录操作日志,如果是多个属性的情况,我们又改如何去记录呢?如下面的业务方法:
|
||||
```
|
||||
/**
|
||||
* 通过用户id更新用户信息
|
||||
* @param userId 用户id
|
||||
* @param newUser 新的用户信息
|
||||
*/
|
||||
void updateById(Integer userId, User newUser) {
|
||||
//doUpdate
|
||||
}
|
||||
```
|
||||
用户信息的更新场景包含下面几种情形:
|
||||
- 只是更新了单个属性
|
||||
- 同时更新了多个属性
|
||||
- 不需要记录`updatedTime`属性的变更,因为操作日志本身包含了操作时间
|
||||
|
||||
那么我们如何在不修改原有业务代码的情况下实现上面3中情形呢?
|
||||
|
||||
**第一需要使用到对象的``diff``操作**
|
||||
>对象`diff`操作说明:对比两个同类型对象的属性值差异,并得出差异结果,如更改了用户的多个属性,那么期望得到的操作内容:name: yyq-old --> yyq-plus,age: 28 --> 29
|
||||
|
||||
我们将自定义模板中的diff声音定义如下:
|
||||
```
|
||||
diff({oldObject},{newObject})
|
||||
```
|
||||
使用约定大于配置的理论,将需要`diff`的两个对象使用`diff()`表达式包起来,其中的`{oldObject}`和`{newObject}`两个子表达式用法和上一节中的用法一样,可以调用方法,也可以直接引用方法参数
|
||||
|
||||
**第二需要定义一个注解来标识目标对象有哪些属性是需要进行diff操作:**
|
||||
```
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface OpLogField {
|
||||
|
||||
String fieldName() default "";
|
||||
|
||||
String fieldMapping() default "{}";
|
||||
|
||||
String dateFormat() default "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
String decimalFormat() default "";
|
||||
|
||||
//先讲关键属性,暂时跳过其他属性
|
||||
```
|
||||
|
||||
| 属性 | 是否必须 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| fieldName| 否 | diff结果中显示属性别名,起到易读作用,如name显示为名称 |
|
||||
| fieldMapping| 否 | diff结果中显示属性值别名,起到易读作用,json格式,如status可设置为{"0": "禁用", "1": "启用"} |
|
||||
| dateFormat| 否 | diff结果中的时间类型属性值格式化样式,起到易读作用 |
|
||||
| decimalFormat| 否 | diff结果中的数值类型格式化样式,默认不格式化,如设置为#,###.##则Double d = 554545.4545454的数值将被显示为 554,545.45|
|
||||
|
||||
使用该注解对User相关属性进行标识:
|
||||
```
|
||||
@Data
|
||||
private static class User {
|
||||
|
||||
private Integer id;
|
||||
|
||||
@OpLogField(fieldName="部门id")
|
||||
private Long departmentId;
|
||||
|
||||
@OpLogField(fieldName="名称")
|
||||
private String name;
|
||||
|
||||
@OpLogField(fieldName="年龄")
|
||||
private Integer age;
|
||||
|
||||
@OpLogField(fieldName="状态",="{"0": "禁用", "1": "启用"}")
|
||||
private Integer status;
|
||||
|
||||
private Date createdTime;
|
||||
|
||||
pricate Date updatedTime;
|
||||
}
|
||||
```
|
||||
接着使用操作日志注解标注该方法
|
||||
```
|
||||
/**
|
||||
* 通过用户id更新用户信息
|
||||
* @param userId 用户id
|
||||
* @param newUser 新的用户信息
|
||||
*/
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,diff({getUserById(#userId)},{#newUser})", bizNo = "#id", category = "用户更改")
|
||||
void updateById(Integer userId, User newUser) {
|
||||
//doUpdate
|
||||
}
|
||||
|
||||
String getUserById(Integer id){
|
||||
User user =; //get user by select db
|
||||
return user;
|
||||
}
|
||||
```
|
||||
假定`oldUser`是`(id=1, departmentId=1, name=yyq-old, age=28, status=0)`
|
||||
假定`newUser`是`(id=1, departmentId=1, name=yyq-plus, age=29, status=1)`
|
||||
执行该方法将得到的操作日志内容:
|
||||
> 更改用户信息,名称: yyq-old --> yyq-plus,年龄: 28 --> 29,状态:禁用 --> 启用
|
||||
|
||||
# 代码实现架构
|
||||
持续补充
|
||||
# 使用手册
|
||||
持续补充
|
||||
22
operation-log-starter/pom.xml
Normal file
22
operation-log-starter/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>operation-log-parent</artifactId>
|
||||
<groupId>com.water</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>operation-log-starter</artifactId>
|
||||
<version>1.0</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.water</groupId>
|
||||
<artifactId>operation-log</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
76
operation-log/pom.xml
Normal file
76
operation-log/pom.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>operation-log-parent</artifactId>
|
||||
<groupId>com.water</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>operation-log</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
|
||||
<properties>
|
||||
<lombok.version>1.18.20</lombok.version>
|
||||
<spring.expression.version>4.3.16.RELEASE</spring.expression.version>
|
||||
<fastjson.version>1.2.76</fastjson.version>
|
||||
<commons.lang3.version>3.12.0</commons.lang3.version>
|
||||
<common.collection4>4.4</common.collection4>
|
||||
<guava.version>31.0.1-jre</guava.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>asm</artifactId>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>${spring.expression.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons.lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
<version>${common.collection4}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.water.ad.operation.log.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* @author yyq
|
||||
**/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface LogRecordAnnotation {
|
||||
|
||||
String BEFORE_METHOD_PREFIX = "beforeExecute.";
|
||||
|
||||
/**
|
||||
* 操作日志的执行人
|
||||
*
|
||||
* @return operator
|
||||
*/
|
||||
String operator() default "";
|
||||
|
||||
/**
|
||||
* 业务标识号,支持spel表达式
|
||||
*
|
||||
* @return bizNo
|
||||
*/
|
||||
String bizNo();
|
||||
|
||||
/**
|
||||
* 操作日志种类,字符串常量
|
||||
*
|
||||
* @return category
|
||||
*/
|
||||
String category();
|
||||
|
||||
/**
|
||||
* 操作内容
|
||||
* 普通字符串模式:修改了名称,从{xxx}修改为{xxx}
|
||||
* diff模式:更新的应用,diff({xxx},{xxx})
|
||||
*
|
||||
* @return detail
|
||||
*/
|
||||
String detail() default "";
|
||||
|
||||
/**
|
||||
* diff操作时指定只对应特定的field,默认全部,逗号分割
|
||||
* field1,field2
|
||||
*
|
||||
* @return diffField
|
||||
*/
|
||||
String diffField() default "";
|
||||
|
||||
/**
|
||||
* 是否记录操作日志判断表达式,支持spel表达式,返回值必须是布尔类型
|
||||
*
|
||||
* @return condition boolean
|
||||
*/
|
||||
String condition() default "true";
|
||||
|
||||
/**
|
||||
* 普通模板(非diff情况)下的返回值映射
|
||||
* JSON string mapper<p>
|
||||
* A typical value should look like: <p>
|
||||
* ---- {"0": "disabled", "1": "enabled"} ----<p>
|
||||
* Map the field values like [0/1] to a human-readable string like [enabled/disabled] <p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String commonValueMapping() default "{}";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright 2020 Jasper J B Deng(djbing85@gmail.com)
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.water.ad.operation.log.annotation;
|
||||
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @author djbing85@gmail.com
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface OpLogField {
|
||||
|
||||
|
||||
/**
|
||||
* field name in a human-friendly way,
|
||||
* default/empty field name in a BO means to generate oplog with it's raw field name
|
||||
*
|
||||
* @return field name
|
||||
*/
|
||||
String fieldName() default "";
|
||||
|
||||
|
||||
/**
|
||||
* JSON string mapper<p>
|
||||
* A typical value should look like: <p>
|
||||
* ---- {"0": "disabled", "1": "enabled"} ----<p>
|
||||
* Map the field values like [0/1] to a human-readable string like [enabled/disabled] <p>
|
||||
* Oplog intercepter will take the field value as key,
|
||||
* "translate" it to the mapped value while generating the oplog automatically<p>
|
||||
* Default or empty means field value will be used directly<p>
|
||||
* A malformed JSON string will result to a false translate: that is, to use raw value.
|
||||
*
|
||||
* @return Default "{}"
|
||||
*/
|
||||
String fieldMapping() default "{}";
|
||||
|
||||
/**
|
||||
* Specify if the field should be ignored when generate oplog detail<p>
|
||||
* An ignore field will be skip when compare differences between two BO object<p>
|
||||
*
|
||||
* @return Default false
|
||||
*/
|
||||
boolean ignore() default false;
|
||||
|
||||
|
||||
/**
|
||||
* Date format will only apply on field type list below: <p>
|
||||
* java.util.Date<p>
|
||||
* java.util.Calendar<p>
|
||||
* java.time.LocalDate<p>
|
||||
* java.time.LocalTime<p>
|
||||
* java.time.LocalDateTime<p>
|
||||
* Invalid date format will result to a format failure,<p>
|
||||
*
|
||||
* @return Default "yyyy-MM-dd HH:mm:ss"
|
||||
*/
|
||||
String dateFormat() default "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/**
|
||||
* A valid decimalFormat should looks like: #,###.##<p>
|
||||
* Will try to format Double/Float/Long/Integer/BigDecimal fields <p>
|
||||
* Empty decimalFormat will be ignore<p>
|
||||
* <p>
|
||||
* For example: #,###.## <p>
|
||||
* Double d = 554545.4545454; <p>
|
||||
* Formatted String: 554,545.45; <p>
|
||||
* Long l = 1234567890;<p>
|
||||
* Formatted String: 1,234,567,890<p>
|
||||
*
|
||||
* @return Default ""
|
||||
*/
|
||||
String decimalFormat() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.water.ad.operation.log.aop;
|
||||
|
||||
import com.water.ad.operation.log.annotation.LogRecordAnnotation;
|
||||
import com.water.ad.operation.log.core.LogRecordContext;
|
||||
import com.water.ad.operation.log.core.OperatorGetService;
|
||||
import com.water.ad.operation.log.core.expression.LogRecordExpression;
|
||||
import com.water.ad.operation.log.core.parse.LogRecordExpressionEvaluator;
|
||||
import com.water.ad.operation.log.core.parse.LogRecordExpressionParser;
|
||||
import com.water.ad.operation.log.core.record.LogRecordService;
|
||||
import com.water.ad.operation.log.model.LogRecord;
|
||||
import com.water.ad.operation.log.model.TemplateContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.context.expression.AnnotatedElementKey;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
public class LogRecordPointcut {
|
||||
|
||||
|
||||
private LogRecordExpressionEvaluator logRecordExpressionEvaluator;
|
||||
private LogRecordService iLogRecordService;
|
||||
private OperatorGetService operatorGetService;
|
||||
|
||||
public LogRecordPointcut(LogRecordExpressionEvaluator logRecordExpressionEvaluator,
|
||||
LogRecordService logRecordService,
|
||||
OperatorGetService operatorGetService,
|
||||
LogRecordExpressionParser logRecordExpressionParser) {
|
||||
Assert.notNull(logRecordExpressionEvaluator, "logRecordExpressionEvaluator can not be null");
|
||||
Assert.notNull(logRecordService, "logRecordService can not be null");
|
||||
Assert.notNull(operatorGetService, "operatorGetService can not be null");
|
||||
this.logRecordExpressionEvaluator = logRecordExpressionEvaluator;
|
||||
this.iLogRecordService = logRecordService;
|
||||
this.operatorGetService = operatorGetService;
|
||||
}
|
||||
|
||||
@Around("@annotation(com.water.ad.operation.log.annotation.LogRecordAnnotation)")
|
||||
public Object record(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
|
||||
Object ret = null;
|
||||
MethodExecuteResult methodExecuteResult = new MethodExecuteResult();
|
||||
LogRecordAnnotation logRecordAnnotation = null;
|
||||
Object[] args = joinPoint.getArgs();
|
||||
Object target = joinPoint.getTarget();
|
||||
Class targetClass = target.getClass();
|
||||
TemplateContext templateContext = null;
|
||||
try {
|
||||
logRecordAnnotation = method.getAnnotation(LogRecordAnnotation.class);
|
||||
templateContext = parseTemplate(method, args, target, logRecordAnnotation);
|
||||
LogRecordContext.putVariable(LogRecordContext.TEMPLATE_CONTEXT_KEY, templateContext);
|
||||
processBeforeExecuteFunction(templateContext);
|
||||
} catch (Exception e) {
|
||||
methodExecuteResult.setSuccess(false);
|
||||
log.error("log record parse before function exception {}", e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
ret = joinPoint.proceed(args);
|
||||
} catch (Exception e) {
|
||||
methodExecuteResult = new MethodExecuteResult(false, e, e.getMessage());
|
||||
}
|
||||
try {
|
||||
if (logRecordAnnotation != null && templateContext != null && methodExecuteResult.isSuccess()) {
|
||||
recordExecute(ret, templateContext, logRecordAnnotation);
|
||||
}
|
||||
} catch (Exception t) {
|
||||
//记录日志错误不要影响业务
|
||||
log.error("log record parse exception", t);
|
||||
} finally {
|
||||
LogRecordContext.clear();
|
||||
}
|
||||
if (methodExecuteResult.getThrowable() != null) {
|
||||
throw methodExecuteResult.getThrowable();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 模板解析
|
||||
*
|
||||
* @param logRecordAnnotation logRecordAnnotation
|
||||
* @return TemplateContext
|
||||
*/
|
||||
private TemplateContext parseTemplate(Method method, Object[] args, Object targetObject,
|
||||
LogRecordAnnotation logRecordAnnotation) {
|
||||
String detail = logRecordAnnotation.detail();
|
||||
String bizNo = logRecordAnnotation.bizNo();
|
||||
String operator = logRecordAnnotation.operator();
|
||||
String category = logRecordAnnotation.category();
|
||||
String condition = logRecordAnnotation.condition();
|
||||
TemplateContext template = new TemplateContext();
|
||||
|
||||
if (StringUtils.isEmpty(bizNo)) {
|
||||
throw new ExpressionException("bizNo expression is empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(category)) {
|
||||
throw new ExpressionException("category expression is empty");
|
||||
}
|
||||
if (StringUtils.isEmpty(condition)) {
|
||||
throw new ExpressionException("condition expression is empty");
|
||||
}
|
||||
|
||||
//自定义模板解析
|
||||
EvaluationContext context = logRecordExpressionEvaluator.createEvaluationContext(method, args, targetObject);
|
||||
AnnotatedElementKey annotatedElementKey = new AnnotatedElementKey(method, targetObject.getClass());
|
||||
|
||||
LogRecordExpression detailExpression = logRecordExpressionEvaluator.parseExpression(detail, annotatedElementKey);
|
||||
Expression bizNoExpression = logRecordExpressionEvaluator.parseExpression(bizNo, annotatedElementKey);
|
||||
if (StringUtils.isNotEmpty(operator)) {
|
||||
Expression operatorExpression = logRecordExpressionEvaluator.parseExpression(operator, annotatedElementKey);
|
||||
template.setOperatorExpression(operatorExpression);
|
||||
|
||||
}
|
||||
Expression conditionExpression = logRecordExpressionEvaluator.parseExpression(condition, annotatedElementKey);
|
||||
template.setDetailExpression(detailExpression);
|
||||
template.setBiNoExpression(bizNoExpression);
|
||||
template.setConditionExpression(conditionExpression);
|
||||
template.setEvaluationContext(context);
|
||||
template.setTargetObject(targetObject);
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义函数-前置执行逻辑
|
||||
*
|
||||
* @param template template
|
||||
*/
|
||||
private void processBeforeExecuteFunction(TemplateContext template) {
|
||||
|
||||
Expression[] beforeExpressions = template.getDetailExpression().beforeExecute();
|
||||
if (beforeExpressions != null) {
|
||||
for (Expression beforeExpression : beforeExpressions) {
|
||||
beforeExpression.getValue(template.getEvaluationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ret target method return
|
||||
* @param template template
|
||||
* @param logRecordAnnotation logRecordAnnotation
|
||||
*/
|
||||
private void recordExecute(Object ret, TemplateContext template, LogRecordAnnotation
|
||||
logRecordAnnotation) {
|
||||
|
||||
EvaluationContext context = template.getEvaluationContext();
|
||||
//返回值也放到context中
|
||||
context.setVariable("ret", ret);
|
||||
//是否记录
|
||||
boolean condition = handlerCondition(template);
|
||||
if (!condition) {
|
||||
log.warn("condition expression [{}] is false ,skip record log", template.getConditionExpression().getExpressionString());
|
||||
return;
|
||||
}
|
||||
//指定所有操作内容
|
||||
String content = logRecordAnnotation.detail();
|
||||
if (LogRecordContext.getVariables().containsKey(LogRecordContext.CUSTOM_LOG_DETAIL_KEY)) {
|
||||
content = (String) LogRecordContext.getVariables().get(LogRecordContext.CUSTOM_LOG_DETAIL_KEY);
|
||||
} else {
|
||||
content = template.getDetailExpression().getValue(context, String.class);
|
||||
}
|
||||
//指定附加内容
|
||||
String appendContent;
|
||||
if (LogRecordContext.getVariables().containsKey(LogRecordContext.CUSTOM_LOG_APPEND_DETAIL_KEY)) {
|
||||
appendContent = (String) LogRecordContext.getVariables().get(LogRecordContext.CUSTOM_LOG_APPEND_DETAIL_KEY);
|
||||
content = content + appendContent;
|
||||
}
|
||||
String category = logRecordAnnotation.category();
|
||||
String bizNo = handlerBizNo(template);
|
||||
String operator = handlerOperator(template);
|
||||
iLogRecordService.record(LogRecord.builder()
|
||||
.time(new Date())
|
||||
.operatorId(operator)
|
||||
.category(category)
|
||||
.bizNo(bizNo)
|
||||
.detail(content).build());
|
||||
}
|
||||
|
||||
private String handlerBizNo(TemplateContext template) {
|
||||
String bizNo = template.getBiNoExpression().getValue(template.getEvaluationContext(), String.class);
|
||||
if (StringUtils.isEmpty(bizNo)) {
|
||||
throw new EvaluationException("bizNo is null");
|
||||
}
|
||||
return bizNo;
|
||||
}
|
||||
|
||||
private String handlerOperator(TemplateContext template) {
|
||||
String operator;
|
||||
if (template.getOperatorExpression() != null) {
|
||||
operator = template.getOperatorExpression().getValue(template.getEvaluationContext(), String.class);
|
||||
} else {
|
||||
if (operatorGetService.getUser() == null) {
|
||||
throw new EvaluationException("operatorGetService return null info");
|
||||
}
|
||||
operator = operatorGetService.getUser().getId();
|
||||
}
|
||||
if (StringUtils.isEmpty(operator)) {
|
||||
throw new EvaluationException("operator is null");
|
||||
}
|
||||
return operator;
|
||||
}
|
||||
|
||||
private boolean handlerCondition(TemplateContext template) {
|
||||
return template.getConditionExpression().getValue(template.getEvaluationContext(), Boolean.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.water.ad.operation.log.aop;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MethodExecuteResult {
|
||||
|
||||
private boolean success = true;
|
||||
private Throwable throwable;
|
||||
private String errorMsg;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.water.ad.operation.log.core;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public class DefaultOperatorGetServiceImpl implements OperatorGetService {
|
||||
|
||||
@Override
|
||||
public Operator getUser() {
|
||||
return Operator.builder().id("-1").name("unknown").build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.water.ad.operation.log.core;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
public class LogRecordContext {
|
||||
|
||||
/**
|
||||
* 使用栈结构,解决嵌套操作记录问题
|
||||
*/
|
||||
private static final InheritableThreadLocal<Stack<Map<String, Object>>> VARIABLE_MAP_STACK = new InheritableThreadLocal<Stack<Map<String, Object>>>() {
|
||||
@Override
|
||||
protected Stack<Map<String, Object>> initialValue() {
|
||||
Stack<Map<String, Object>> stack = new Stack<>();
|
||||
stack.push(new HashMap<>(16));
|
||||
return stack;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 自定义操作内容
|
||||
*/
|
||||
public static final String CUSTOM_LOG_DETAIL_KEY = "$$CUSTOM_LOG_DETAIL";
|
||||
|
||||
public static final String CUSTOM_LOG_APPEND_DETAIL_KEY = "$$CUSTOM_LOG_APPEND_DETAIL";
|
||||
|
||||
public static final String TEMPLATE_CONTEXT_KEY = "$$TEMPLATE_CONTEXT";
|
||||
|
||||
|
||||
public static void putVariable(String key, Object value) {
|
||||
Map<String, Object> map = getVariables();
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getVariables() {
|
||||
if (VARIABLE_MAP_STACK.get().empty()) {
|
||||
//压入一个新的
|
||||
VARIABLE_MAP_STACK.get().push(new HashMap<>(16));
|
||||
}
|
||||
return VARIABLE_MAP_STACK.get().peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户直接定义操作明细
|
||||
*
|
||||
* @param detail detail
|
||||
*/
|
||||
public static void putLogDetail(String detail) {
|
||||
putVariable(CUSTOM_LOG_DETAIL_KEY, detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户在切面解析的基础上,在原内容后面附加内容
|
||||
*
|
||||
* @param detailAppend detailAppend
|
||||
*/
|
||||
public static void putLogDetailAppend(String detailAppend) {
|
||||
putVariable(CUSTOM_LOG_APPEND_DETAIL_KEY, detailAppend);
|
||||
}
|
||||
|
||||
/**
|
||||
* 出栈释放
|
||||
*/
|
||||
public static void clear() {
|
||||
if (!VARIABLE_MAP_STACK.get().isEmpty()) {
|
||||
VARIABLE_MAP_STACK.get().pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.water.ad.operation.log.core;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public interface OperatorGetService {
|
||||
|
||||
|
||||
/**
|
||||
* get operator user
|
||||
*
|
||||
* @return operator user
|
||||
*/
|
||||
Operator getUser();
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
class Operator {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.water.ad.operation.log.core.diff;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DateFormat;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-23
|
||||
**/
|
||||
@Slf4j
|
||||
public abstract class AbstractObjectDiffHandler implements ObjectDiffHandler {
|
||||
|
||||
protected Object formatDateField(Class modelClass, String dateFormat, Object value, Field field) {
|
||||
if (StringUtils.isEmpty(dateFormat)) {
|
||||
return value;
|
||||
}
|
||||
if (value == null) {
|
||||
return value;
|
||||
}
|
||||
DateFormat sdf;
|
||||
if (value instanceof Date) {
|
||||
try {
|
||||
sdf = new SimpleDateFormat(dateFormat);
|
||||
value = sdf.format((Date) value);
|
||||
} catch (Exception e) {
|
||||
log.error("error format date {} with given dateFormat: {} in class: {}, field: {}", value, dateFormat, modelClass, field.getName(), e);
|
||||
}
|
||||
}
|
||||
if (value instanceof Calendar) {
|
||||
try {
|
||||
sdf = new SimpleDateFormat(dateFormat);
|
||||
value = sdf.format(((Calendar) value).getTime());
|
||||
} catch (Exception e) {
|
||||
log.error("error format Calendar {} with given dateFormat: {} in class: {}, field: {}", value, dateFormat, modelClass, field.getName(), e);
|
||||
|
||||
}
|
||||
}
|
||||
if (value instanceof LocalDate) {
|
||||
try {
|
||||
LocalDate tempLocalDate = (LocalDate) value;
|
||||
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern(dateFormat);
|
||||
value = formatter1.format(tempLocalDate);
|
||||
} catch (Exception e) {
|
||||
log.error("error format LocalDate {} with given dateFormat: {} in class: {}, field: {}", value, dateFormat, modelClass, field.getName(), e);
|
||||
}
|
||||
}
|
||||
if (value instanceof LocalTime) {
|
||||
try {
|
||||
LocalTime tempLocalTime = (LocalTime) value;
|
||||
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern(dateFormat);
|
||||
value = formatter1.format(tempLocalTime);
|
||||
} catch (Exception e) {
|
||||
log.error("error format LocalTime {} with given dateFormat: {} in class: {}, field: {}", value, dateFormat, modelClass, field.getName(), e);
|
||||
}
|
||||
}
|
||||
if (value instanceof LocalDateTime) {
|
||||
try {
|
||||
LocalDateTime tempLocalDateTime = (LocalDateTime) value;
|
||||
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern(dateFormat);
|
||||
value = formatter1.format(tempLocalDateTime);
|
||||
} catch (Exception e) {
|
||||
log.error("error format LocalDateTime {} with given dateFormat: {} in class: {}, field: {}", value, dateFormat, modelClass, field.getName(), e);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected JSONObject parseFieldMapping(Class modelClass, String fieldMapping, Field field) {
|
||||
JSONObject fieldMap;
|
||||
try {
|
||||
if (!StringUtils.isEmpty(fieldMapping)) {
|
||||
fieldMap = JSONObject.parseObject(fieldMapping);
|
||||
} else {
|
||||
fieldMap = new JSONObject();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("fieldMapping found wrong json format: {} in class: {}, field: {}", fieldMapping, modelClass, field.getName(), e);
|
||||
fieldMap = new JSONObject();
|
||||
}
|
||||
return fieldMap;
|
||||
}
|
||||
|
||||
protected Object doFieldMapping(JSONObject fieldMap, Object value) {
|
||||
if (fieldMap != null && fieldMap.size() > 0) {
|
||||
Object mapVal = fieldMap.get(value);
|
||||
if (mapVal != null) {
|
||||
value = mapVal;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected Object formatDecimal(Class modelClass, String decimalFormat, Object value, Field field) {
|
||||
if (!StringUtils.isEmpty(decimalFormat)) {
|
||||
if (value instanceof BigDecimal || value instanceof Double ||
|
||||
value instanceof Float || value instanceof Long ||
|
||||
value instanceof Integer) {
|
||||
try {
|
||||
DecimalFormat df = new DecimalFormat(decimalFormat);
|
||||
value = df.format(value);
|
||||
} catch (Exception e) {
|
||||
log.error("error format digit with given decimalFormat:{} in class: {}, field: {}", decimalFormat, modelClass, field.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected boolean objectEquals(Object valuePre, Object valuePost) {
|
||||
if (valuePre instanceof BigDecimal && valuePost instanceof BigDecimal) {
|
||||
return ((BigDecimal) valuePre).stripTrailingZeros().equals(((BigDecimal) valuePost).stripTrailingZeros());
|
||||
}
|
||||
return valuePre.equals(valuePost);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.water.ad.operation.log.core.diff;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.water.ad.operation.log.annotation.OpLogField;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-23
|
||||
**/
|
||||
@Slf4j
|
||||
public class DefaultObjectDiffHandler extends AbstractObjectDiffHandler {
|
||||
|
||||
/**
|
||||
* 4 space
|
||||
*/
|
||||
private final static String INDENT_APPENDER = " ";
|
||||
|
||||
@Override
|
||||
public String diff(Object pre, Object post, List<String> specificFieldList) {
|
||||
|
||||
Assert.notNull(pre, "diff pre Object can not be null");
|
||||
Assert.notNull(post, "diff post Object can not be null");
|
||||
|
||||
if (pre.getClass() != post.getClass()) {
|
||||
throw new EvaluationException(String.format("diff object must be the same type pre [%s] , post [%s]", pre.getClass(), post.getClass()));
|
||||
}
|
||||
String indent = "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
OpLogField opLogFieldAnnotation;
|
||||
Field[] fields;
|
||||
fields = pre.getClass().getDeclaredFields();
|
||||
List<Field> fieldList;
|
||||
if (CollectionUtils.isNotEmpty(specificFieldList)) {
|
||||
fieldList = new ArrayList<Field>();
|
||||
for (Field field : fields) {
|
||||
if (specificFieldList.contains(field.getName())) {
|
||||
fieldList.add(field);
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(specificFieldList)) {
|
||||
throw new EvaluationException("specified field list not found when diff " + JSON.toJSONString(specificFieldList));
|
||||
}
|
||||
} else {
|
||||
fieldList = Arrays.asList(fields);
|
||||
}
|
||||
Class modelClass = pre.getClass();
|
||||
String fieldName;
|
||||
String fieldMapping;
|
||||
String dateFormat;
|
||||
String decimalFormat;
|
||||
JSONObject fieldMap;
|
||||
Object valuePre;
|
||||
Object valuePost;
|
||||
indent = indent + INDENT_APPENDER;
|
||||
try {
|
||||
for (Field field : fieldList) {
|
||||
boolean hasAnnotation = field.isAnnotationPresent(OpLogField.class);
|
||||
if (!hasAnnotation) {
|
||||
log.warn("field without OpLogField annotation,skip diff field name {}", field.getName());
|
||||
continue;
|
||||
}
|
||||
opLogFieldAnnotation = field.getAnnotation(OpLogField.class);
|
||||
boolean ignore = opLogFieldAnnotation.ignore();
|
||||
if (ignore) {
|
||||
//skip the ignore fields
|
||||
continue;
|
||||
}
|
||||
fieldName = opLogFieldAnnotation.fieldName();
|
||||
if (StringUtils.isEmpty(fieldName)) {
|
||||
fieldName = field.getName();
|
||||
}
|
||||
|
||||
dateFormat = opLogFieldAnnotation.dateFormat();
|
||||
decimalFormat = opLogFieldAnnotation.decimalFormat();
|
||||
fieldMapping = opLogFieldAnnotation.fieldMapping();
|
||||
|
||||
valuePre = FieldUtils.readDeclaredField(pre, field.getName(), true);
|
||||
valuePost = FieldUtils.readDeclaredField(post, field.getName(), true);
|
||||
|
||||
if (valuePre == null && valuePost == null) {
|
||||
continue;
|
||||
}
|
||||
//OpLogModel should have it's own equals method.
|
||||
if (valuePre != null && valuePost != null) {
|
||||
if (objectEquals(valuePre, valuePost)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
valuePre = formatDateField(modelClass, dateFormat, valuePre, field);
|
||||
valuePost = formatDateField(modelClass, dateFormat, valuePost, field);
|
||||
|
||||
valuePre = formatDecimal(modelClass, decimalFormat, valuePre, field);
|
||||
valuePost = formatDecimal(modelClass, decimalFormat, valuePost, field);
|
||||
|
||||
fieldMap = parseFieldMapping(modelClass, fieldMapping, field);
|
||||
|
||||
valuePre = doFieldMapping(fieldMap, valuePre);
|
||||
valuePost = doFieldMapping(fieldMap, valuePost);
|
||||
|
||||
Class subModelClz = null;
|
||||
if (valuePre != null) {
|
||||
subModelClz = valuePre.getClass();
|
||||
}
|
||||
if (valuePost != null) {
|
||||
subModelClz = valuePost.getClass();
|
||||
}
|
||||
sb.append(fieldName).append(": ")
|
||||
.append(valuePre).append(" --> ").append(valuePost).append("\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new EvaluationException("diff Object error " + e.getMessage(), e);
|
||||
}
|
||||
if (sb.length() > 1 && sb.toString().endsWith("\n")) {
|
||||
return sb.substring(0, sb.length() - 1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.water.ad.operation.log.core.diff;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-23
|
||||
**/
|
||||
public interface ObjectDiffHandler {
|
||||
|
||||
/**
|
||||
* 对象diff
|
||||
*
|
||||
* @param pre pre object
|
||||
* @param post post Object
|
||||
* @param specificFieldList 只对比特定的field
|
||||
* @return diff差异结果
|
||||
*/
|
||||
String diff(Object pre, Object post, List<String> specificFieldList);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.water.ad.operation.log.core.expression;
|
||||
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.*;
|
||||
import org.springframework.expression.common.ExpressionUtils;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-03-09
|
||||
**/
|
||||
public abstract class BaseLogRecordExpression implements Expression {
|
||||
|
||||
static SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
|
||||
|
||||
|
||||
String expressionString;
|
||||
|
||||
public BaseLogRecordExpression(String expressionString) {
|
||||
Assert.notNull(expressionString, "expressionString is null");
|
||||
this.expressionString = expressionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* 目标方法前置执行
|
||||
*
|
||||
* @return expression array execute before target method
|
||||
*/
|
||||
abstract Expression[] beforeExecute();
|
||||
|
||||
|
||||
@Override
|
||||
public String getExpressionString() {
|
||||
return expressionString;
|
||||
}
|
||||
|
||||
/**
|
||||
* get expression value
|
||||
*
|
||||
* @param context context
|
||||
* @return value
|
||||
* @throws EvaluationException
|
||||
*/
|
||||
@Override
|
||||
public abstract Object getValue(EvaluationContext context) throws EvaluationException;
|
||||
|
||||
@Override
|
||||
public <T> T getValue(EvaluationContext context, Class<T> expectedResultType)
|
||||
throws EvaluationException {
|
||||
Object value = getValue(context);
|
||||
return ExpressionUtils.convertTypedValue(context, new TypedValue(value), expectedResultType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue() on a BaseEmptyExpression ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueType(EvaluationContext context) {
|
||||
Assert.notNull(context, "EvaluationContext is required");
|
||||
TypeDescriptor typeDescriptor = new TypedValue(getValue(context)).getTypeDescriptor();
|
||||
return (typeDescriptor != null ? typeDescriptor.getType() : null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue(Class<T> expectedResultType) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(Object rootObject) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue(Object rootObject) on a BaseEmptyExpression");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue(Object rootObject, Class<T> desiredResultType) on a BaseEmptyExpression");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue(EvaluationContext context, Object rootObject) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
|
||||
throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueType() {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueType() on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueType(Object rootObject) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueType(Object rootObject) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueType(EvaluationContext context, Object rootObject) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDescriptor getValueTypeDescriptor() {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueTypeDescriptor() on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueTypeDescriptor(Object rootObject) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context) {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueTypeDescriptor(EvaluationContext context) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject)
|
||||
throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call getValueTypeDescriptor(EvaluationContext context, Object rootObject) on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(Object rootObject) throws EvaluationException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(EvaluationContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(Object rootObject, Object value) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call setValue on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call setValue on a BaseEmptyExpression");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
|
||||
throw new EvaluationException(this.expressionString, "Cannot call setValue on a BaseEmptyExpression");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.water.ad.operation.log.core.expression;
|
||||
|
||||
import com.water.ad.operation.log.core.function.FunctionService;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.expression.*;
|
||||
import org.springframework.expression.common.CompositeStringExpression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 自定义方法表达式
|
||||
* <p>
|
||||
* NOTE:不支持方法没有参数的情况
|
||||
* <p>
|
||||
* { method(#p1) } 单参数
|
||||
* <p>
|
||||
* { method(#p1,#p2) } 多参数
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-03-09
|
||||
**/
|
||||
public class CustomMethodExpression extends BaseLogRecordExpression {
|
||||
|
||||
private static final String BEFORE_EXECUTE_PREFIX = "$$before.";
|
||||
private static final MethodArgsExpressionParser METHOD_ARGS_EXPRESSION_PARSER = new MethodArgsExpressionParser();
|
||||
|
||||
/**
|
||||
* 目标方法之前执行
|
||||
*/
|
||||
private boolean beforeExecute;
|
||||
|
||||
private String fullNameMethod;
|
||||
|
||||
/**
|
||||
* 涉及到前置执行,这里需要保存执行结果
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 方法参数(spel表达式,这里无效再拆分)
|
||||
*/
|
||||
private Expression[] argsExpressions;
|
||||
|
||||
private FunctionService functionService;
|
||||
|
||||
public CustomMethodExpression(String expressionString, FunctionService functionService) {
|
||||
super(expressionString);
|
||||
Assert.notNull(functionService, "function service is null");
|
||||
this.functionService = functionService;
|
||||
//解析当前表达式
|
||||
parse();
|
||||
}
|
||||
|
||||
@Override
|
||||
Expression[] beforeExecute() {
|
||||
if (beforeExecute) {
|
||||
return new Expression[]{this};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(EvaluationContext context) throws EvaluationException {
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
Object[] args = getArgsValue(context);
|
||||
value = functionService.apply(context.getRootObject().getValue(), fullNameMethod, args);
|
||||
return value;
|
||||
}
|
||||
|
||||
private void parse() {
|
||||
//调用目标方法前执行
|
||||
if (expressionString.startsWith(BEFORE_EXECUTE_PREFIX)) {
|
||||
beforeExecute = true;
|
||||
}
|
||||
//这里使用自定义的解析器,默认的遇到method(#p1,#p2多参数会报错
|
||||
Expression expression = METHOD_ARGS_EXPRESSION_PARSER.parseExpression(expressionString, new TemplateParserContext("(", ")"));
|
||||
if (!(expression instanceof CompositeStringExpression)) {
|
||||
throw new ExpressionException(String.format("expressionString [{%s}] incongruity for CustomMethod expression", expression));
|
||||
}
|
||||
CompositeStringExpression compositeStringExpression = (CompositeStringExpression) expression;
|
||||
Expression[] expressions = compositeStringExpression.getExpressions();
|
||||
if (expressions.length != 2) {
|
||||
throw new ExpressionException(String.format("expressionString [{%s}] incongruity for CustomMethod expression", expression));
|
||||
}
|
||||
//方法全名
|
||||
fullNameMethod = expressions[0].getExpressionString().replace(BEFORE_EXECUTE_PREFIX, "");
|
||||
//参数
|
||||
String argsExpression = expressions[1].getExpressionString();
|
||||
String[] argsExpressionString = argsExpression.split(",");
|
||||
argsExpressions = new Expression[argsExpressionString.length];
|
||||
for (int i = 0; i < argsExpressionString.length; i++) {
|
||||
argsExpressions[i] = spelExpressionParser.parseExpression(argsExpressionString[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 方法参数解析
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
private Object[] getArgsValue(EvaluationContext context) {
|
||||
if (ArrayUtils.isEmpty(argsExpressions)) {
|
||||
return null;
|
||||
}
|
||||
Object[] args = new Object[argsExpressions.length];
|
||||
for (int i = 0; i < argsExpressions.length; i++) {
|
||||
Expression expression = argsExpressions[i];
|
||||
Object arg = argsExpressions[i].getValue(context);
|
||||
if (arg == null) {
|
||||
throw new EvaluationException(expression.getExpressionString(), String.format("Cannot call get value for [%s] expression",
|
||||
expression.getExpressionString()));
|
||||
}
|
||||
args[i] = arg;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public boolean isBeforeExecute() {
|
||||
return beforeExecute;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 方法参数解析器
|
||||
*/
|
||||
static class MethodArgsExpressionParser extends TemplateAwareExpressionParser {
|
||||
@Override
|
||||
protected Expression doParseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||
return new LiteralExpression(expressionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.water.ad.operation.log.core.expression;
|
||||
|
||||
import com.water.ad.operation.log.core.function.FunctionService;
|
||||
import com.water.ad.operation.log.core.diff.ObjectDiffHandler;
|
||||
import com.water.ad.operation.log.util.TemplateUtil;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.expression.common.CompositeStringExpression;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 对象diff表达式
|
||||
* <p>
|
||||
* diff( {methodA(#p1)},{methodB(#p2)} ) preObject引用方法,postObject引用方法
|
||||
* <p>
|
||||
* diff( {methodA(#p1)},{#objB} ) preObject引用方法,postObject引用参数对象
|
||||
* <p>
|
||||
* diff( {#objA},{#methodB(#p2)} ) preObject引用参数对象,postObject引用方法
|
||||
* <p>
|
||||
* diff( {#objA},{#objB} ) preObject引用参数对象,postObject引用参数对象
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-03-09
|
||||
**/
|
||||
public class DiffExpression extends BaseLogRecordExpression {
|
||||
|
||||
private Expression preExpression;
|
||||
private Expression postExpression;
|
||||
|
||||
private ObjectDiffHandler objectDiffHandler;
|
||||
private FunctionService functionService;
|
||||
|
||||
|
||||
public DiffExpression(String expressionString, ObjectDiffHandler objectDiffHandler, FunctionService functionService) {
|
||||
super(expressionString);
|
||||
Assert.notNull(objectDiffHandler, "objectDiffHandler is null");
|
||||
Assert.notNull(functionService, "functionService is null");
|
||||
this.expressionString = expressionString;
|
||||
this.objectDiffHandler = objectDiffHandler;
|
||||
this.functionService = functionService;
|
||||
|
||||
parse();
|
||||
}
|
||||
|
||||
@Override
|
||||
Expression[] beforeExecute() {
|
||||
List<Expression> expressions = new ArrayList<>();
|
||||
if (preExpression instanceof CustomMethodExpression) {
|
||||
expressions.add(preExpression);
|
||||
}
|
||||
if (postExpression instanceof CustomMethodExpression) {
|
||||
expressions.add(postExpression);
|
||||
}
|
||||
if (expressions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return expressions.toArray(new Expression[0]);
|
||||
}
|
||||
|
||||
private void parse() {
|
||||
Expression expression = spelExpressionParser.parseExpression(expressionString,
|
||||
new TemplateParserContext("{", "}"));
|
||||
if (!(expression instanceof CompositeStringExpression)) {
|
||||
throw new ExpressionException(String.format("expressionString [diff(%s)] incongruity for diff expression", expressionString));
|
||||
}
|
||||
CompositeStringExpression compositeStringExpression = (CompositeStringExpression) expression;
|
||||
Expression[] expressions = compositeStringExpression.getExpressions();
|
||||
if (expressions.length != 3) {
|
||||
throw new ExpressionException(String.format("expressionString [diff(%s)] incongruity for diff expression", expressionString));
|
||||
}
|
||||
//中间的逗号,跳过
|
||||
preExpression = transferExpression(expressions[0]);
|
||||
postExpression = transferExpression(expressions[2]);
|
||||
|
||||
}
|
||||
|
||||
private Expression transferExpression(Expression expression) {
|
||||
if (TemplateUtil.isSourceSpelExpression(expression.getExpressionString())) {
|
||||
//引用对象
|
||||
return expression;
|
||||
} else {
|
||||
//引用方法
|
||||
return new CustomMethodExpression(expression.getExpressionString(), functionService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValue(EvaluationContext context) throws EvaluationException {
|
||||
Object pre = preExpression.getValue(context);
|
||||
Object post = postExpression.getValue(context);
|
||||
return objectDiffHandler.diff(pre, post, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.water.ad.operation.log.core.expression;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.CompositeStringExpression;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 操作日志表达式
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-03-09
|
||||
**/
|
||||
public class LogRecordExpression extends BaseLogRecordExpression {
|
||||
|
||||
|
||||
private Expression expression;
|
||||
|
||||
public LogRecordExpression(String expressionString, Expression expression) {
|
||||
super(expressionString);
|
||||
Assert.notNull(expression, "expression is null");
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(EvaluationContext context) throws EvaluationException {
|
||||
return expression.getValue(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression[] beforeExecute() {
|
||||
List<Expression> beforeExpression = new ArrayList<>();
|
||||
if (expression instanceof CompositeStringExpression) {
|
||||
Expression[] expressions = ((CompositeStringExpression) expression).getExpressions();
|
||||
for (Expression exp : expressions) {
|
||||
if (exp instanceof BaseLogRecordExpression) {
|
||||
Expression[] bex = ((BaseLogRecordExpression) exp).beforeExecute();
|
||||
if (bex != null) {
|
||||
beforeExpression.addAll(Arrays.asList(bex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expression instanceof BaseLogRecordExpression) {
|
||||
Expression[] bex = ((BaseLogRecordExpression) expression).beforeExecute();
|
||||
if (bex != null) {
|
||||
beforeExpression.addAll(Arrays.asList(bex));
|
||||
}
|
||||
}
|
||||
return beforeExpression.toArray(new Expression[]{});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public class DefaultFunctionServiceImpl implements FunctionService {
|
||||
|
||||
private final ParseFunctionFactory parseFunctionFactory;
|
||||
|
||||
public DefaultFunctionServiceImpl(ParseFunctionFactory parseFunctionFactory) {
|
||||
this.parseFunctionFactory = parseFunctionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义函数执行
|
||||
*
|
||||
* @param functionName 自定义函数名称
|
||||
* @param args method args
|
||||
* @return method execute result
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object targetObject, String functionName, Object[] args) {
|
||||
|
||||
ParseFunction function = parseFunctionFactory.getFunction(functionName);
|
||||
if (function == null) {
|
||||
//这里原值返回还是null返回好呢
|
||||
return null;
|
||||
}
|
||||
return function.apply(args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
/**
|
||||
* 自定义方法执行接口
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public interface FunctionService {
|
||||
|
||||
|
||||
/**
|
||||
* @param targetObject 目标方法所属对象
|
||||
* @param functionName 自定义函数名称
|
||||
* @param args 参数
|
||||
* @return function execute result
|
||||
*/
|
||||
Object apply(Object targetObject, String functionName, Object[] args);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-03-12
|
||||
**/
|
||||
public class MethodResolver {
|
||||
public Method resolve(Object targetObject, String functionName, Object[] args) {
|
||||
Class<?> targetClass = targetObject.getClass();
|
||||
Method method;
|
||||
Class[] paramsType;
|
||||
paramsType = new Class[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
paramsType[i] = args[i].getClass();
|
||||
}
|
||||
method = ReflectionUtils.findMethod(targetClass, functionName, paramsType);
|
||||
if (method == null) {
|
||||
throw new EvaluationException(String.format("not found method [%s] param %s for class [%s]", functionName, JSON.toJSONString(paramsType), targetObject.getClass().getName()));
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public interface ParseFunction {
|
||||
|
||||
/**
|
||||
* 方法名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String functionName();
|
||||
|
||||
/**
|
||||
* 方法执行
|
||||
*
|
||||
* @param args
|
||||
* @return
|
||||
*/
|
||||
Object apply(Object[] args);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public class ParseFunctionFactory {
|
||||
|
||||
private Map<String, ParseFunction> allFunctionMap = new HashMap<>(16);
|
||||
|
||||
public ParseFunctionFactory(List<ParseFunction> parseFunctions) {
|
||||
if (CollectionUtils.isEmpty(parseFunctions)) {
|
||||
return;
|
||||
}
|
||||
for (ParseFunction parseFunction : parseFunctions) {
|
||||
if (StringUtils.isEmpty(parseFunction.functionName())) {
|
||||
continue;
|
||||
}
|
||||
allFunctionMap.put(parseFunction.functionName(), parseFunction);
|
||||
}
|
||||
}
|
||||
|
||||
public ParseFunction getFunction(String functionName) {
|
||||
return allFunctionMap.get(functionName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 按照加入 {@link PrioritizedFunctionServiceImpl#functionServices} 集合中的顺序作为优先级执行
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-21
|
||||
**/
|
||||
public class PrioritizedFunctionServiceImpl implements FunctionService {
|
||||
|
||||
private List<FunctionService> functionServices;
|
||||
|
||||
public PrioritizedFunctionServiceImpl(List<FunctionService> functionServices) {
|
||||
if (functionServices == null) {
|
||||
throw new IllegalArgumentException("functionServices can not be null");
|
||||
}
|
||||
this.functionServices = functionServices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(Object targetObject, String functionName, Object[] args) {
|
||||
for (FunctionService functionService : functionServices) {
|
||||
Object value = functionService.apply(targetObject, functionName, args);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 基于spring容器中bean的方法调用
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-18
|
||||
**/
|
||||
@Slf4j
|
||||
public class SpringContextBeanFunctionServiceImpl extends MethodResolver implements FunctionService {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private static final String FULL_NAME_SPIL = ".";
|
||||
|
||||
public SpringContextBeanFunctionServiceImpl(BeanFactory beanFactory) {
|
||||
if (beanFactory == null) {
|
||||
throw new IllegalArgumentException("beanFactory can not be null");
|
||||
}
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 这里的自定义名称代表方法全名称,如fm.lizhi.ad.operation.log.handler.function.SpringContextFunctionServiceImpl.apply
|
||||
*
|
||||
* @param functionName 自定义函数名称
|
||||
* @param args 参数
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object targetObject, String functionName, Object[] args) {
|
||||
if (!functionName.contains(FULL_NAME_SPIL)) {
|
||||
return null;
|
||||
}
|
||||
int indexOf = functionName.lastIndexOf(FULL_NAME_SPIL);
|
||||
String classFullName = functionName.substring(0, indexOf);
|
||||
String methodName = functionName.substring(indexOf + 1);
|
||||
Object object;
|
||||
Class<?> targetClass;
|
||||
try {
|
||||
targetClass = Class.forName(classFullName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
String errorMsg = String.format("not found class [%s]", classFullName);
|
||||
log.error(errorMsg, e);
|
||||
throw new EvaluationException(errorMsg, e);
|
||||
}
|
||||
object = beanFactory.getBean(targetClass);
|
||||
Method method = resolve(object, methodName, args);
|
||||
return ReflectionUtils.invokeMethod(method, object, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.water.ad.operation.log.core.function;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 基于切面代理的目标对象所属方法调用
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-18
|
||||
**/
|
||||
@Slf4j
|
||||
public class TargetObjectFunctionServiceImpl extends MethodResolver implements FunctionService {
|
||||
|
||||
private static final String FULL_NAME_SPIL = ".";
|
||||
|
||||
/**
|
||||
* @param targetObject 目标方法所属对象
|
||||
* @param functionName 自定义函数名称
|
||||
* @param args 参数
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object apply(Object targetObject, String functionName, Object[] args) {
|
||||
if (functionName.contains(FULL_NAME_SPIL)) {
|
||||
return null;
|
||||
}
|
||||
Method method = resolve(targetObject, functionName, args);
|
||||
return ReflectionUtils.invokeMethod(method, targetObject, args);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.water.ad.operation.log.core.parse;
|
||||
|
||||
import org.springframework.context.expression.AnnotatedElementKey;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* copy from {@link org.springframework.context.expression.CachedExpressionEvaluator}
|
||||
* <p>
|
||||
* 修改 getParser的返回类型
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-03-10
|
||||
**/
|
||||
public class CachedExpressionEvaluator {
|
||||
private final ExpressionParser parser;
|
||||
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance with the specified {@link ExpressionParser}.
|
||||
*/
|
||||
protected CachedExpressionEvaluator(ExpressionParser parser) {
|
||||
Assert.notNull(parser, "LogRecordExpressionParser must not be null");
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected ExpressionParser getParser() {
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a shared parameter name discoverer which caches data internally.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
protected ParameterNameDiscoverer getParameterNameDiscoverer() {
|
||||
return this.parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@link Expression} for the specified SpEL value
|
||||
* <p>Parse the expression if it hasn't been already.
|
||||
*
|
||||
* @param cache the cache to use
|
||||
* @param elementKey the element on which the expression is defined
|
||||
* @param expression the expression to parse
|
||||
*/
|
||||
protected Expression getExpression(Map<ExpressionKey, Expression> cache,
|
||||
AnnotatedElementKey elementKey, String expression) {
|
||||
|
||||
ExpressionKey expressionKey = createKey(elementKey, expression);
|
||||
Expression expr = cache.get(expressionKey);
|
||||
if (expr == null) {
|
||||
expr = getParser().parseExpression(expression);
|
||||
cache.put(expressionKey, expr);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private ExpressionKey createKey(AnnotatedElementKey elementKey, String expression) {
|
||||
return new ExpressionKey(elementKey, expression);
|
||||
}
|
||||
|
||||
|
||||
protected static class ExpressionKey implements Comparable<ExpressionKey> {
|
||||
|
||||
private final AnnotatedElementKey element;
|
||||
|
||||
private final String expression;
|
||||
|
||||
protected ExpressionKey(AnnotatedElementKey element, String expression) {
|
||||
Assert.notNull(element, "AnnotatedElementKey must not be null");
|
||||
Assert.notNull(expression, "Expression must not be null");
|
||||
this.element = element;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof ExpressionKey)) {
|
||||
return false;
|
||||
}
|
||||
ExpressionKey otherKey = (ExpressionKey) other;
|
||||
return (this.element.equals(otherKey.element) &&
|
||||
ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.element.hashCode() * 29 + this.expression.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.element + " with expression \"" + this.expression + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExpressionKey other) {
|
||||
int result = this.element.toString().compareTo(other.element.toString());
|
||||
if (result == 0) {
|
||||
result = this.expression.compareTo(other.expression);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.water.ad.operation.log.core.parse;
|
||||
|
||||
import com.water.ad.operation.log.core.LogRecordContext;
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public class LogRecordEvaluationContext extends MethodBasedEvaluationContext {
|
||||
|
||||
public LogRecordEvaluationContext(Object rootObject, Method method, Object[] arguments, ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
super(rootObject, method, arguments, parameterNameDiscoverer);
|
||||
|
||||
//把 LogRecordContext 中的变量都放到 RootObject 中
|
||||
Map<String, Object> variables = LogRecordContext.getVariables();
|
||||
if (!CollectionUtils.isEmpty(variables)) {
|
||||
for (Map.Entry<String, Object> entry : variables.entrySet()) {
|
||||
setVariable(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.water.ad.operation.log.core.parse;
|
||||
|
||||
import com.water.ad.operation.log.core.expression.LogRecordExpression;
|
||||
import org.springframework.context.expression.AnnotatedElementKey;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 表达式执行器
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public class LogRecordExpressionEvaluator extends CachedExpressionEvaluator {
|
||||
|
||||
private Map<ExpressionKey, Expression> expressionCache = new ConcurrentHashMap<>(64);
|
||||
|
||||
/**
|
||||
* Create a new instance with the specified {@link ExpressionParser}.
|
||||
*
|
||||
* @param parser
|
||||
*/
|
||||
public LogRecordExpressionEvaluator(ExpressionParser parser) {
|
||||
super(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* expression 解析
|
||||
*/
|
||||
public LogRecordExpression parseExpression(String conditionExpression, AnnotatedElementKey methodKey) {
|
||||
return (LogRecordExpression) getExpression(this.expressionCache, methodKey, conditionExpression);
|
||||
}
|
||||
|
||||
public EvaluationContext createEvaluationContext(Method method, Object[] args, Object targetObject) {
|
||||
return new LogRecordEvaluationContext(targetObject, method, args, this.getParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.water.ad.operation.log.core.parse;
|
||||
|
||||
import com.water.ad.operation.log.core.expression.CustomMethodExpression;
|
||||
import com.water.ad.operation.log.core.expression.DiffExpression;
|
||||
import com.water.ad.operation.log.core.expression.LogRecordExpression;
|
||||
import com.water.ad.operation.log.core.function.FunctionService;
|
||||
import com.water.ad.operation.log.core.diff.ObjectDiffHandler;
|
||||
import com.water.ad.operation.log.util.RegexpUtil;
|
||||
import com.water.ad.operation.log.util.TemplateUtil;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-03-09
|
||||
**/
|
||||
public class LogRecordExpressionParser extends TemplateAwareExpressionParser {
|
||||
|
||||
private static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* diff模板解析
|
||||
*/
|
||||
private static final ParserContext DIFF_PARSE_CONTEXT = new DiffParserContext();
|
||||
/**
|
||||
* 普通模板解析
|
||||
*/
|
||||
private static final ParserContext COMMON_PARSER_CONTEXT = new CommonParserContext();
|
||||
private static final String DIFF_TEMPLATE_REG = "diff\\(.+\\)";
|
||||
private static final String CUSTOM_METHOD_REG = "\\(.+\\)";
|
||||
private static final String COMMON_TEMPLATE_REG = "\\{.+\\}";
|
||||
|
||||
private ObjectDiffHandler objectDiffHandler;
|
||||
private FunctionService functionService;
|
||||
|
||||
public LogRecordExpressionParser(FunctionService functionService, ObjectDiffHandler objectDiffHandler) {
|
||||
Assert.notNull(objectDiffHandler, "objectDiffHandler is null");
|
||||
Assert.notNull(objectDiffHandler, "functionService is null");
|
||||
this.objectDiffHandler = objectDiffHandler;
|
||||
this.functionService = functionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expressionString 表达式
|
||||
* @return TemplateExpression
|
||||
* @throws ParseException
|
||||
*/
|
||||
@Override
|
||||
public LogRecordExpression parseExpression(String expressionString) throws ParseException {
|
||||
|
||||
Expression expression;
|
||||
//diff expression contain diff()
|
||||
if (RegexpUtil.hasStr(expressionString, DIFF_TEMPLATE_REG)) {
|
||||
expression = super.parseExpression(expressionString, DIFF_PARSE_CONTEXT);
|
||||
} else if (RegexpUtil.hasStr(expressionString, COMMON_TEMPLATE_REG)) {
|
||||
//common expression,contain {}
|
||||
expression = super.parseExpression(expressionString, COMMON_PARSER_CONTEXT);
|
||||
} else if (TemplateUtil.isSourceSpelExpression(expressionString)) {
|
||||
//spel表达式
|
||||
expression = super.parseExpression(expressionString);
|
||||
} else {
|
||||
//常量
|
||||
expression = new LiteralExpression(expressionString);
|
||||
}
|
||||
return new LogRecordExpression(expressionString, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Expression doParseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||
Expression expression;
|
||||
if (context instanceof DiffParserContext) {
|
||||
// diff expression
|
||||
expression = new DiffExpression(expressionString, objectDiffHandler, functionService);
|
||||
} else if (RegexpUtil.hasStr(expressionString, CUSTOM_METHOD_REG)) {
|
||||
//method expression
|
||||
expression = new CustomMethodExpression(expressionString, functionService);
|
||||
} else {
|
||||
//spel expression
|
||||
expression = SPEL_EXPRESSION_PARSER.parseExpression(expressionString);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
|
||||
public static class DiffParserContext implements ParserContext {
|
||||
@Override
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "diff(";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionSuffix() {
|
||||
return ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommonParserContext implements ParserContext {
|
||||
@Override
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "{";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionSuffix() {
|
||||
return "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.water.ad.operation.log.core.record;
|
||||
|
||||
import com.water.ad.operation.log.model.LogRecord;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
@Slf4j
|
||||
public class DefaultLogRecordServiceImpl implements LogRecordService {
|
||||
|
||||
@Override
|
||||
public void record(LogRecord logRecord) {
|
||||
|
||||
log.info("【logRecord】log={}", logRecord);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.water.ad.operation.log.core.record;
|
||||
|
||||
import com.water.ad.operation.log.model.LogRecord;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
public interface LogRecordService {
|
||||
|
||||
/**
|
||||
* 保存log
|
||||
*
|
||||
* @param logRecord 日志实体
|
||||
*/
|
||||
void record(LogRecord logRecord);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.water.ad.operation.log.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义函数
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-21
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CustomMethod {
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 方法参数
|
||||
*/
|
||||
private Object[] args;
|
||||
|
||||
/**
|
||||
* 方法返回值
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 原始模板 {m1{#p1}}
|
||||
*/
|
||||
private String sourceTemplate;
|
||||
|
||||
/**
|
||||
* 原始的方法参数spel表达式(通过sourceTemplate解析出来的)
|
||||
*/
|
||||
private List<String> argsSpelExpression;
|
||||
|
||||
/**
|
||||
* 是否是前置执行
|
||||
*/
|
||||
private boolean beforeMethod;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.water.ad.operation.log.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LogRecord {
|
||||
|
||||
private Date time;
|
||||
|
||||
private String operatorId;
|
||||
|
||||
private String bizNo;
|
||||
|
||||
private String detail;
|
||||
|
||||
/**
|
||||
* 类别
|
||||
*/
|
||||
private String category;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.water.ad.operation.log.model;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 单独的表达式
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-22
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class StandaloneSpel {
|
||||
|
||||
|
||||
private String template;
|
||||
|
||||
private Object value;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.water.ad.operation.log.model;
|
||||
|
||||
import com.water.ad.operation.log.core.expression.LogRecordExpression;
|
||||
import lombok.*;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
|
||||
|
||||
/**
|
||||
* 模板上下文
|
||||
*
|
||||
* @author yyq
|
||||
* @create 2022-02-21
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TemplateContext {
|
||||
|
||||
private LogRecordExpression detailExpression;
|
||||
/**
|
||||
* 业务标识号,使用spel表达式
|
||||
*/
|
||||
private Expression biNoExpression;
|
||||
/**
|
||||
* 操作人,使用spel表达式
|
||||
*/
|
||||
private Expression operatorExpression;
|
||||
|
||||
/**
|
||||
* 记录条件,使用spel表达式
|
||||
*/
|
||||
private Expression conditionExpression;
|
||||
|
||||
/**
|
||||
* 操作日志类别,常量字符串
|
||||
*/
|
||||
private String category;
|
||||
|
||||
private EvaluationContext evaluationContext;
|
||||
|
||||
/**
|
||||
* 切面拦截的目标对象
|
||||
*/
|
||||
private Object targetObject;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.water.ad.operation.log.util;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
*/
|
||||
public class RegexpUtil {
|
||||
|
||||
/**
|
||||
* 判断给定字符串中是否含有制定字符串
|
||||
*
|
||||
* @param src
|
||||
* @param reg
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasStr(String src, String reg) {
|
||||
Pattern p = Pattern.compile(reg);
|
||||
Matcher m = p.matcher(src);
|
||||
return m.find();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.water.ad.operation.log.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-03-12
|
||||
**/
|
||||
public class TemplateUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否是原始的spel表达式
|
||||
*
|
||||
* @param expression
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSourceSpelExpression(String expression) {
|
||||
return StringUtils.isNotEmpty(expression) &&
|
||||
expression.startsWith("#") ||
|
||||
expression.startsWith("@");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
package com.water.ad.operation.log.test;
|
||||
|
||||
import com.water.ad.operation.log.annotation.LogRecordAnnotation;
|
||||
import com.water.ad.operation.log.annotation.OpLogField;
|
||||
import com.water.ad.operation.log.aop.LogRecordPointcut;
|
||||
import com.water.ad.operation.log.core.LogRecordContext;
|
||||
import com.water.ad.operation.log.core.record.LogRecordService;
|
||||
import com.water.ad.operation.log.model.LogRecord;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-17
|
||||
**/
|
||||
@SuppressWarnings("")
|
||||
public class LogRecordPointcutTest {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private UserService userServiceProxy;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
applicationContext = new AnnotationConfigApplicationContext(LogRecordPointcutTestConfig.class);
|
||||
UserService userService = applicationContext.getBean(UserService.class);
|
||||
LogRecordPointcut logRecordPointcut = applicationContext.getBean(LogRecordPointcut.class);
|
||||
Assert.assertNotNull(userService);
|
||||
AspectJProxyFactory factory = new AspectJProxyFactory(userService);
|
||||
factory.addAspect(logRecordPointcut);
|
||||
userServiceProxy = factory.getProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* pre方法调用(单参数),post引用方法参数
|
||||
*/
|
||||
@Test
|
||||
public void testPreMethodSingleParamPostQuote() {
|
||||
userServiceProxy.updateNameById(1, "yyq-plus");
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更新了用户名称,从yyq-old改为yyq-plus", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* pre方法调用(多参数),post引用方法参数
|
||||
*/
|
||||
@Test
|
||||
public void testPreMethodMultiParamPostQuote() {
|
||||
userServiceProxy.updateNameById(2L, 1, "yyq-plus");
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更新了用户名称,从yyq-old改为yyq-plus", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* pre引用方法参数,post方法调用(单参数)
|
||||
*/
|
||||
@Test
|
||||
public void testPreQuotePostMethodSigleParam() {
|
||||
userServiceProxy.updateNameByIdWithOldName(1, "yyq-old");
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更新了用户名称,从yyq-old改为yyq-plus", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* pre引用方法参数,post方法调用(多参数)
|
||||
*/
|
||||
@Test
|
||||
public void testPreQuotePostMethodMultiParam() {
|
||||
userServiceProxy.updateNameByIdWithOldName(2L, 1, "yyq-old");
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更新了用户名称,从yyq-old改为yyq-plus", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* diff比较
|
||||
* pre方法调用(单参数),post引用方法参数
|
||||
*/
|
||||
@Test
|
||||
public void testDiffPreMethodSingleParamPostQuote() {
|
||||
userServiceProxy.updateUser(User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* diff比较
|
||||
* pre方法调用(多参数),post引用方法参数
|
||||
*/
|
||||
@Test
|
||||
public void testDiffPreMethodMultiParamPostQuote() {
|
||||
userServiceProxy.updateUserWithCompanyId(2L, User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* diff比较
|
||||
* pre引用方法参数,post方法调用(单参数)
|
||||
*/
|
||||
@Test
|
||||
public void testDiffPreQuotePostMethodSingleParam() {
|
||||
userServiceProxy.updateUserWithOldUser(User.builder().id(1).name("yyq-old").age(28).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* diff比较
|
||||
* pre引用方法参数,post方法调用(单参数)
|
||||
*/
|
||||
@Test
|
||||
public void testDiffPreQuotePostMethodMultiParam() {
|
||||
userServiceProxy.updateUserWithOldUser(2L, User.builder().id(1).name("yyq-old").age(28).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* diff比较
|
||||
* pre引用方法参数,post引用方法参数
|
||||
*/
|
||||
@Test
|
||||
public void testDiffPreQuotePostQuote() {
|
||||
userServiceProxy.updateUser(User.builder().id(1).name("yyq-old").age(28).build(), User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户直接指定操作内容
|
||||
*/
|
||||
@Test
|
||||
public void testCustomContent() {
|
||||
userServiceProxy.updateUserWithCustomContent(User.builder().id(1).name("yyq-old").age(28).build(), User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("直接指定操作内容,更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户附加操作内容
|
||||
*/
|
||||
@Test
|
||||
public void testCustomAppendContent() {
|
||||
userServiceProxy.updateUserWithOldUserAppend(User.builder().id(1).name("yyq-old").age(28).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("更改用户信息,名称: yyq-old --> yyq-plus\n" +
|
||||
"年龄: 28 --> 29 我是附加的内容", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
/**
|
||||
* condition false
|
||||
*/
|
||||
@Test
|
||||
public void testConditionFalse() {
|
||||
userServiceProxy.updateUserWithConditionFalse(User.builder().id(1).name("yyq-old").age(28).build(), User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNull(logRecordService.record());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* condition false
|
||||
*/
|
||||
@Test
|
||||
public void testConditionFalseUserRet() {
|
||||
User user = userServiceProxy.updateUserWithConditionFalseUseRet(User.builder().id(1).name("yyq-old").age(28).build(), User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNull(user);
|
||||
Assert.assertNull(logRecordService.record());
|
||||
}
|
||||
|
||||
/**
|
||||
* condition false
|
||||
*/
|
||||
@Test
|
||||
public void testConditionFalseTrueRet() {
|
||||
User user = userServiceProxy.updateUserWithConditionTrueUseRet(User.builder().id(1).name("yyq-old").age(28).build(), User.builder().id(1).name("yyq-plus").age(29).build());
|
||||
TestLogRecordRecordService logRecordService = applicationContext.getBean(TestLogRecordRecordService.class);
|
||||
Assert.assertNotNull(logRecordService);
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertNotNull(logRecordService.record());
|
||||
Assert.assertEquals("updateUserWithConditionTrueUseRet", logRecordService.record().getDetail());
|
||||
}
|
||||
|
||||
|
||||
@Slf4j
|
||||
public static class UserService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id 用户id
|
||||
* @param newName 新用户名
|
||||
*/
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{$$before.getNameById(#id)}改为{#newName}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameById(Integer id, String newName) {
|
||||
log.info("######### updateNameById id {} newName {} #####", id, newName);
|
||||
}
|
||||
|
||||
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{$$before.getNameById(#departmentId,#id)}改为{#newName}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameById(Long departmentId, Integer id, String newName) {
|
||||
log.info("######### updateNameById departmentId {},id {},newName {}#####", departmentId, id, newName);
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{#oldName}改为{getNameByIdNew(#id)}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameByIdWithOldName(Integer id, String oldName) {
|
||||
log.info("######### updateNameById id {},oldName {} #####", id, oldName);
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更新了用户名称,从{#oldName}改为{getNameByIdNew(#departmentId,#id)}", bizNo = "#id", category = "用户更改")
|
||||
void updateNameByIdWithOldName(Long departmentId, Integer id, String oldName) {
|
||||
log.info("######### updateNameById departmentId {},id {},oldName {} ##### ", departmentId, id, oldName);
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({$$before.getUserById(#newUser.id)},{#newUser})", bizNo = "#newUser.id", category = "用户更改")
|
||||
void updateUser(User newUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({#oldUser},{#newUser})", bizNo = "#oldUser.id", category = "用户更改")
|
||||
void updateUser(User oldUser, User newUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({$$before.getUserById(#departmentId,#newUser.id)},{#newUser})", bizNo = "#newUser.id", category = "用户更改")
|
||||
void updateUserWithCompanyId(Long departmentId, User newUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({#oldUser},{getUserByIdNew(#oldUser.id)})", bizNo = "#oldUser.id", category = "用户更改")
|
||||
void updateUserWithOldUser(User oldUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({#oldUser},{$$before.getUserByIdNew(#departmentId,#oldUser.id)})", bizNo = "#oldUser.id", category = "用户更改")
|
||||
void updateUserWithOldUser(Long departmentId, User oldUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(detail = "更改用户信息,diff({#oldUser},{getUserByIdNew(#oldUser.id)})", bizNo = "#oldUser.id", category = "用户更改")
|
||||
void updateUserWithOldUserAppend(User oldUser) {
|
||||
log.info("######### update user #####");
|
||||
LogRecordContext.putLogDetailAppend(" 我是附加的内容");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(bizNo = "#oldUser.id", category = "用户更改")
|
||||
void updateUserWithCustomContent(User oldUser, User newUser) {
|
||||
LogRecordContext.putLogDetail(String.format("直接指定操作内容,更改用户信息,名称: %s --> %s\n" +
|
||||
"年龄: %s --> %s", oldUser.getName(), newUser.getName(), oldUser.getAge(), newUser.getAge()));
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(bizNo = "#oldUser.id", category = "用户更改", condition = "false")
|
||||
void updateUserWithConditionFalse(User oldUser, User newUser) {
|
||||
log.info("######### update user #####");
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(bizNo = "#oldUser.id", category = "用户更改", condition = "#ret!=null")
|
||||
User updateUserWithConditionFalseUseRet(User oldUser, User newUser) {
|
||||
log.info("######### update user #####");
|
||||
return null;
|
||||
}
|
||||
|
||||
@LogRecordAnnotation(bizNo = "#oldUser.id", detail = "updateUserWithConditionTrueUseRet", category = "用户更改", condition = "#ret!=null")
|
||||
User updateUserWithConditionTrueUseRet(User oldUser, User newUser) {
|
||||
log.info("######### update user #####");
|
||||
return newUser;
|
||||
}
|
||||
|
||||
public String getNameById(Integer id) {
|
||||
return getUserById(id).getName();
|
||||
}
|
||||
|
||||
public String getNameByIdNew(Integer id) {
|
||||
return getUserByIdNew(id).getName();
|
||||
}
|
||||
|
||||
public String getNameById(Long departmentId, Integer id) {
|
||||
return getUserById(departmentId, id).getName();
|
||||
}
|
||||
|
||||
public String getNameByIdNew(Long departmentId, Integer id) {
|
||||
return getUserByIdNew(departmentId, id).getName();
|
||||
|
||||
}
|
||||
|
||||
public User getUserById(Integer id) {
|
||||
return User.builder().id(id).name("yyq-old").age(28).build();
|
||||
}
|
||||
|
||||
public User getUserByIdNew(Integer id) {
|
||||
return User.builder().id(id).name("yyq-plus").age(29).build();
|
||||
}
|
||||
|
||||
public User getUserById(Long departmentId, Integer id) {
|
||||
return User.builder().id(id).departmentId(departmentId).name("yyq-old").age(28).build();
|
||||
}
|
||||
|
||||
public User getUserByIdNew(Long departmentId, Integer id) {
|
||||
return User.builder().id(id).departmentId(departmentId).name("yyq-plus").age(29).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
private static class User {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户所属部门id
|
||||
*/
|
||||
private Long departmentId;
|
||||
|
||||
@OpLogField(fieldName = "名称")
|
||||
private String name;
|
||||
|
||||
@OpLogField(fieldName = "年龄")
|
||||
private Integer age;
|
||||
}
|
||||
|
||||
@Slf4j
|
||||
public static class TestLogRecordRecordService implements LogRecordService {
|
||||
|
||||
static ThreadLocal<LogRecord> threadLocal = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public void record(LogRecord logRecord) {
|
||||
threadLocal.set(logRecord);
|
||||
log.info("【logRecord】log={}", logRecord);
|
||||
}
|
||||
|
||||
LogRecord record() {
|
||||
return threadLocal.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.water.ad.operation.log.test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.water.ad.operation.log.aop.LogRecordPointcut;
|
||||
import com.water.ad.operation.log.core.DefaultOperatorGetServiceImpl;
|
||||
import com.water.ad.operation.log.core.OperatorGetService;
|
||||
import com.water.ad.operation.log.core.diff.DefaultObjectDiffHandler;
|
||||
import com.water.ad.operation.log.core.diff.ObjectDiffHandler;
|
||||
import com.water.ad.operation.log.core.function.*;
|
||||
import com.water.ad.operation.log.core.parse.LogRecordExpressionEvaluator;
|
||||
import com.water.ad.operation.log.core.parse.LogRecordExpressionParser;
|
||||
import com.water.ad.operation.log.core.record.LogRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yyq
|
||||
* @create 2022-02-22
|
||||
**/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class LogRecordPointcutTestConfig {
|
||||
|
||||
|
||||
@Bean
|
||||
public LogRecordPointcutTest.UserService testService() {
|
||||
return new LogRecordPointcutTest.UserService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LogRecordPointcut logRecordInterceptor(LogRecordExpressionEvaluator logRecordExpressionEvaluator,
|
||||
LogRecordService logRecordService,
|
||||
OperatorGetService operatorGetService,
|
||||
LogRecordExpressionParser logRecordExpressionParser) {
|
||||
return new LogRecordPointcut(logRecordExpressionEvaluator, logRecordService, operatorGetService, logRecordExpressionParser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LogRecordExpressionEvaluator.class)
|
||||
public LogRecordExpressionEvaluator logRecordValueParser(LogRecordExpressionParser logRecordExpressionParser, FunctionService functionService) {
|
||||
return new LogRecordExpressionEvaluator(logRecordExpressionParser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OperatorGetService.class)
|
||||
public OperatorGetService operatorGetService() {
|
||||
return new DefaultOperatorGetServiceImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FunctionService.class)
|
||||
@Primary
|
||||
public FunctionService functionService(ParseFunctionFactory parseFunctionFactory, BeanFactory beanFactory) {
|
||||
DefaultFunctionServiceImpl defaultFunctionService = new DefaultFunctionServiceImpl(parseFunctionFactory);
|
||||
SpringContextBeanFunctionServiceImpl contextFunctionService = new SpringContextBeanFunctionServiceImpl(beanFactory);
|
||||
TargetObjectFunctionServiceImpl objectFunctionService = new TargetObjectFunctionServiceImpl();
|
||||
return new PrioritizedFunctionServiceImpl(Lists.newArrayList(defaultFunctionService, contextFunctionService, objectFunctionService));
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public ParseFunctionFactory parseFunctionFactory(@Autowired(required = false) List<ParseFunction> parseFunctions) {
|
||||
return new ParseFunctionFactory(parseFunctions);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LogRecordService.class)
|
||||
public LogRecordService recordService() {
|
||||
return new LogRecordPointcutTest.TestLogRecordRecordService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ObjectDiffHandler.class)
|
||||
public ObjectDiffHandler objectDiffHandler() {
|
||||
return new DefaultObjectDiffHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LogRecordExpressionParser logRecordExpressionParser(FunctionService functionService,
|
||||
ObjectDiffHandler objectDiffHandler) {
|
||||
return new LogRecordExpressionParser(functionService, objectDiffHandler);
|
||||
}
|
||||
|
||||
}
|
||||
48
pom.xml
Normal file
48
pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.water</groupId>
|
||||
<artifactId>operation-log-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.0</version>
|
||||
<modules>
|
||||
<module>operation-log</module>
|
||||
<module>operation-log-starter</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-boot.version>2.0.9.RELEASE</spring-boot.version>
|
||||
<maven.test.skip>true</maven.test.skip>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user