在这篇博客中,我们将演示如何为你的Spring Boot应用程序添加基本审计。
1.0 简介
在之前的博客中,我们演示了如何创建Spring Boot应用程序,添加Flyway迁移,以及实现认证/授权。
在这篇博客中,我们将探讨如何为应用程序添加审计功能。
所以问题是 "什么是审计,为什么它很重要?"
在任何应用程序中,审计意味着我们跟踪和记录所有业务对象的每一个变化,即跟踪每个插入、更新和删除操作。
基本上,它涉及跟踪三件事
- 执行了什么操作?
- 谁做的?
- 什么时候做的?
审计帮助我们维护历史记录,这可以帮助我们跟踪用户活动。
你可以按照本博客中的步骤,使用任何Spring Boot应用程序。然而,在这篇博客中,我们将从我们在上一篇博客中建立的基础应用程序开始,该应用程序中已经建立了flyway集成和认证。
你可以在github.com/chatterjees…中找到本博客的起始基础代码。
我们的应用程序中已经有了一个Customer
实体,我们将为这个实体添加审计。
2.0 JPA审计
这是最简单的审计形式。
在这里,我们不跟踪所做的改变,而只是跟踪谁创建或修改了一个业务实体,以及何时完成的。
具体来说,我们将跟踪每个业务对象的以下额外字段
- 创建时间
- 创建者
- 修改时
- 修改时间
2.1 在数据库中添加新的审计列
让我们先把创建/修改列添加到数据库的Customer
表中。
ALTER TABLE `customer`
ADD COLUMN `created_by` VARCHAR(50) NOT NULL,
ADD COLUMN `updated_by` VARCHAR(50) NULL,
ADD COLUMN `created_on` DATETIME NOT NULL,
ADD COLUMN `updated_on` DATETIME NULL;
你可以直接在你的数据库上运行下面的脚本,或者你可以把它添加到你的Flyway/Liquibase脚本中(如果你正在使用它们或任何其他类似的数据库版本管理工具)。
2.2 在实体类中添加审计字段
现在我们已经将字段添加到数据库中,让我们将字段添加到我们的Customer
实体中。
添加以下代码。
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "updated_by")
@LastModifiedBy
private String updatedBy;
@Column(name = "created_on")
@CreatedDate
private Date createdOn;
@Column(name = "updated_on")
@LastModifiedDate
private Date updatedOn;
-
注释。 我们已经为所有的审计字段添加了来自
org.springframework.data.annotation
的注解-
@CreatedBy
/@LastModifiedBy
:将一个字段声明为代表最近创建/修改包含该字段的实体的委托人的字段 -
@CreatedDate
/@LastModifiedDate
:宣布一个字段为代表包含该字段的实体被创建/最近被修改的日期。
-
-
日期字段
- 在我们的例子中,我们使用了
java.util.Date
- 该字段可以是以下任何一种类型 -
Joda-Time
,DateTime
, legacy JavaDate
和Calendar
, JDK8日期和时间类型,以及long
或Long
。
- 在我们的例子中,我们使用了
如果你不想在你的每个实体类上添加这些字段和注解,你的实体也可以从以下方面扩展你的实体
org.springframework.data.jpa.domain.AbstractAuditable
2.3 填充登录的用户信息
创建和修改日期将由JPA自动填充。
然而我们必须在created_by
和updated_by
列中提供要坚持的细节。
2.3.1 实现AuditAware[T]。
我们要做的第一件事是实现AuditAware<T>
,其中T
是CreatedBy/ModifiedBy字段的类型(对我们来说是String
)。
- 创建一个新的类
AuditAwareImpl
,实现AuditAware<String>
- 实现方法 -
public Optional getCurrentAuditor()
- 在方法
getCurrentAuditor()
中添加逻辑,以返回当前登录用户的用户名- 我们可以获取当前的
SecurityContext
,从中获取当前的Principal
,然后从其中获取用户的详细信息。
- 我们可以获取当前的
public class AuditAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername);
}
}
2.3.2 启用JPA审计
接下来我们将创建一个Configuration
,启用JPA审计,同时为AuditAware<String>
Bean提供一个实现。
- 创建一个新的配置类
- 为这个类添加注解
@EnableJpaAuditing
- 创建一个方法来返回
AuditorAware<String>
的Bean - 并返回一个我们在上一步创建的AuditAwareImpl
类的新实例。
@Configuration
@EnableJpaAuditing(auditorAwareRef = "customAuditProvider")
public class JpaConfig {
@Bean
public AuditorAware<String> customAuditProvider() {
return new AuditAwareImpl();
}
}
最后一个配置是将一个EntityListener
类与你的审计实体联系起来。Spring已经为我们提供了一个这样的监听器----。AuditingEntityListener
@Entity
@EntityListeners(AuditingEntityListener.class) // add this line
public class Customer {
....
....
}
2.4 测试JPA的审计
现在我们已经添加了所有需要的实现,是时候测试它了。
我们将使用Rest客户端测试curl
。
所用的例子.或测试你已经在spring boot应用程序中配置了认证/授权,并且它在数据库中有两个用户
- admin01@tw.com(密码:admin01@123#)
- admin02@tw.com(密码:admin02@123#)然而,这些用户并不是强制性的。
你可以通过改变下面例子中的curl语句,用你自己的用户组进行测试。
以用户身份*(admin01@tw.com)*登录,并使用以下命令创建一个客户
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
现在以另一个用户*(admin02@tw.com)*的身份登录,更新同一个客户,并改变姓氏。(改变PUT url末尾的id,以匹配创建的客户的id)
curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
转到数据库并运行sql --select * from customer where email_address = 'johndoe@gmail.com'
你可以看到审计细节被填充到客户表中。
3.0 Hibernate Envers审计
尽管JPA审计为基本的审计提供了简单的配置,但它只提供以下信息
- 实体是什么时候创建的,谁创建了它。
- 实体最后一次修改是什么时候,谁修改了它。
它并没有为你提供一个实体的所有修改/更新的细节,例如一个客户实体可能被修改了5次。通过JPA审计,我没有办法找出在这5次更新中,实体的哪些地方被修改了,以及谁做了这些修改。
因此,Hibernate Envers
,它为一个实体提供完整的 审计历史为一个实体提供完整的信息。
3.1 Hibernate Envers如何在数据库中存储审计信息
- Envers创建了一个
REVINFO
表- 这个表在应用程序的所有实体中都是通用的。
- 它包含了每个实体的修订时间以及由谁完成的细节。
- 默认情况下,它包含两列
REV
- 主键列 - 修订版ID/修订版号REVTSTMP
- 修订版创建的时间戳。- 此外,我们还可以在这个表中以自定义列的形式存储用户信息。
- 基本上,Envers为每个需要审计的实体创建一个'AUDIT'表。
- 例如,如果我们想审计表的变化,Envers将创建一个存储在数据库中的
CUSTOMER
表的变化,Envers将创建审计历史记录存储在一个CUSTOMER_AUD
表。(表的名字后缀为_AUD
) - 这个
*_AUD
该表拥有与主实体表相同的所有列。 - 此外,它还有另外两列
REV
: Revision ID - Foriegn key 参照的是REVINFO.REV
REVTYPE
: 有三个可能的数字值来表示对实体的操作类型 -0
(添加),1
(更新), 和2
(删除)
- 例如,如果我们想审计表的变化,Envers将创建一个存储在数据库中的
Hibernate为Enver表和列的名称提供了一些自定义功能。我们将在我们的应用程序中进行以下定制,以提高表和列名称的可读性。
- 表的后缀
_AUD
将被改成_AUDIT
...例如,CUSTOMER
的审计表现在将是CUSTOMER_AUDIT
.所有审计表的以下列也将被重新命名REV
列将被重命名为REVISION_ID
REVTYPE
列将被重命名为REVISION_TYPE
REVINFO
表将被重新命名为REVISION_INFO
.此外,修订表中的以下列将被重新命名REV
列将被重命名为REVISION_ID
REVTSTMP
将被重命名为REV_TIMESTAMP
总结一下,这就是AUDIT表的ER图的模样。
你可能已经注意到,在ID列中没有从审计表到主实体表的外来键引用。
这样做的原因是,在某些情况下,我们可能希望在审计表中保留删除的记录。在这种情况下,由于主实体表中的记录已经被删除,所以外国键引用将不起作用。
3.2 在数据库中添加审计表
我们已经看到了审计表和修订信息表的模样。让我们在数据库中创建这些表。
你可以直接在你的数据库中运行下面的脚本,也可以把它添加到你的Flyway/Liquibase脚本中(如果你正在使用它们或者其他类似的数据库版本管理工具)。
CREATE TABLE `revision_info` (
`revision_id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`rev_timestamp` BIGINT(20) NOT NULL,
`user` VARCHAR(50) NOT NULL
);
CREATE TABLE `customer_audit` (
`revision_id` INTEGER NOT NULL,
`id` BIGINT(20) NOT NULL,
`revision_type` TINYINT NOT NULL,
`email_address` VARCHAR(255),
`first_name` VARCHAR(255),
`last_name` VARCHAR(255),
`created_by` VARCHAR(50),
`updated_by` VARCHAR(50),
`created_on` DATETIME,
`updated_on` DATETIME,
PRIMARY KEY (`revision_id`, `id`),
CONSTRAINT `idfk_customer_revinfo_rev_id`
FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);
CREATE TABLE `address_audit` (
`revision_id` INTEGER NOT NULL,
`id` BIGINT(20) NOT NULL,
`revision_type` TINYINT NOT NULL,
`city` VARCHAR(255),
`country` VARCHAR(255),
`customer_id` BIGINT(20),
`state_code` VARCHAR(255),
`street_address` VARCHAR(255),
`zip_code` VARCHAR(255),
PRIMARY KEY (`revision_id`, `id`),
CONSTRAINT `idfk_address_revinfo_rev_id`
FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);
3.3 启用审计
在本节中,我们将通过失败的第一种方法来添加审计。
这意味着我们将一步一步地添加配置/代码,之后启动应用程序,看看应用程序是启动还是失败。如果失败了,我们将修复它并再次重复循环,直到我们得到一个工作的应用程序。
这将帮助我们了解为什么需要一些配置/代码,以及如果我们错过了这些配置/代码可能会出现什么错误。
3.3.1 在build.gradle中添加依赖性
第一个要做的配置是在build.gradle中添加Hibernate Envers
的依赖性。build.gradle
compile('org.hibernate:hibernate-envers:5.4.25.Final')
3.3.2 添加@Audited注解
现在将@Audited
注解添加到所有你想审计的实体中。
@Audited
public class Customer {
....
}
@Audited
public class Address {
....
}
3.3.3 在application.yaml中添加配置
让我们尝试启动该应用程序。
你会得到以下错误
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [address_aud]
OR
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [customer_aud]
这是因为Hibernate Envers期望审计表为 *_AUD
表。
然而,正如你所看到的,我们已经将我们的表后缀自定义为 _AUDIT
table。这意味着Envers正在搜索 customer_aud table
, address_aud
表,但我们有 customer_audit
和 address_audit
表。此外,我们还定制了一些列 -- REV
, REVTSMTP
和 REVTYPE
.
失败的原因是我们需要告诉Hibernate Envers关于这些定制的内容。我们可以通过添加以下属性来做到这一点application.yaml
spring:
jpa:
properties:
org:
hibernate:
envers:
audit_table_suffix: _AUDIT
revision_field_name: REVISION_ID
revision_type_field_name: REVISION_TYPE
现在,如果你再次尝试启动应用程序,你会得到下面的错误
Schema-validation: missing table [customer_address_audit]
为了修复这个错误,在 实体类中的字段,添加一个 @NotAudited
注释到Customer
实体类中的字段addresses
。
@NotAudited
@OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, targetEntity = Address.class, cascade = CascadeType.ALL)
@JoinColumn(name = "customer_id")
private List<Address> addresses;
3.3.4 配置一个RevisionEntity
让我们再次尝试启动该应用程序。
这一次你会得到以下错误。
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [revinfo]
我们已经创建了一个表 REVISION_INFO
而不是 REVINFO
.
但是我们不能通过application.yaml
中的配置来改变表的名称。要做到这一点,我们需要创建一个RevisionEntity
类。
Envers
已经提供了一个类 - ,我们将扩展这个类。org.hibernate.envers.DefaultRevisionEntity
@Entity
@Table(name = "revision_info")
@RevisionEntity
public class AuditRevisionEntity extends DefaultRevisionEntity {
@Column(name = "user")
private String user;
}
- 我们创建了一个类
AuditRevisionEntity
延伸自DefaultRevisionEntity
. - 添加
@Table
注解,并提供表的名称。revision_info
- 添加注解
@RevisionEntity
,这表明这是Hibernate Envers的RevisionInfo
实体。 - 添加一个字段为
user
的字段,我们已经在revison_info
表中添加了这个字段。
让我们尝试再次启动应用程序。这次我们会得到以下错误。
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [hibernate_sequence]
- Hibernate要求这个ID列有以下注释------。
@GeneratedValue(strategy = GenerationType.***AUTO***)
- 解决这个问题的最简单方法是将生成策略从
GenerationType.***AUTO***
改为GenerationType.***IDENTITY***
- 然而,这个错误是由于
org.hibernate.envers.DefaultRevisionEntity
中的ID字段,我们扩展了这个字段。
由于我们不能改变DefaultRevisionEntity类,我们将在application.yaml中添加以下配置来解决这个问题。
spring:
jpa:
hibernate:
use-new-id-generator-mappings: false
让我们再次尝试启动应用程序。
这次你会得到以下错误,因为Envers希望在修订信息表中有id
和timestamp
列。
Schema-validation: missing column [id] in table [revision_info]
由于我们已经将列重命名为revision_id
和rev_timestamp
,我们需要在我们的RevisionEntity
类中添加配置。
只需将以下注解添加到 AuditRevisionEntity
类。
@AttributeOverrides({
@AttributeOverride(name = "timestamp", column = @Column(name = "rev_timestamp")),
@AttributeOverride(name = "id", column = @Column(name = "revision_id"))
})
public class AuditRevisionEntity extends DefaultRevisionEntity {
...
...
}
现在你应该可以成功启动你的应用程序了。
现在你知道了在配置Envers审计时可能出现的各种错误,以及如何解决它们。
注意:不需要将
spring-data-envers
库到你的项目中。Hibernate Envers库已经足够了。
3.4 在Revision表中填入登录用户信息
现在我们已经设置好了一切,我们的应用程序也在运行。
让我们试着创建一个新的客户。
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
你会得到以下错误
Caused by: java.sql.SQLException: Field 'user' doesn't have a default value
这是因为Envers试图将数据审核到revision_info
表中,并将null
放到user
列中。
这是因为Envers不知道如何获取登录用户的信息并填充到这个user
列中。
让我们添加代码来添加登录的用户信息。
- 创建一个实现以下功能的类
org.hibernate.envers.RevisionListener
- 在新的类中实现方法------。
public void newRevision(Object revisionEntity)
- 在这个方法中,从Spring的
SecurityContext
中获取当前用户(类似于我们在AuditorAwareImpl
中的做法)。 - 并将这个用户设置在
AuditRevisionEntity
- 在这个方法中,从Spring的
public class AuditRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User");
AuditRevisionEntity audit = (AuditRevisionEntity) revisionEntity;
audit.setUser(currentUser);
}
}
接下来在RevisionEntity
类中添加以下注解----。 AuditRevisionEntity
@RevisionEntity(AuditRevisionListener.class)
3.5 测试Hibernate Envers审计
现在让我们测试一下这个应用程序,看看审计工作是否正常。
以用户身份*(admin01@tw.com)*登录,使用下面的命令创建一个客户
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}, {"streetAddress":"2240 Bridge Parkway","city":"San Francisco","stateCode":"CA","zipCode":"94065","country":"USA"}]}'
现在以另一个用户*(admin02@tw.com)*的身份登录,更新同一个客户,并改变姓氏和删除其中一个地址。(改变PUT url末尾的id,以匹配创建的客户的id)
curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"id":1, "streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
现在让我们到数据库中去验证一下审计历史是否被正确填入。
运行sql -select * from revision_info;
这将给你提供关于有多少次修订和由谁创建的信息。
接下来看看客户审计表 -select * from customer_audit
你可以看到,在修订1中,客户John Doe被创建,而修订2中,它的姓氏被更新。
最后让我们看一下地址审计表------。select * from address_audit;
我们可以从上面看到,在修订版1中,有两个地址被添加。而在修订版2中,其中一个地址被删除。
被删除的条目作为
NULL
值存储在审计表中。
如果你想保留旧的值,而不是NULL
,那么请将以下属性设置为 "true"。application.yaml
org.hibernate.envers.store_data_at_delete: true
3.6 通过REST API从应用程序中获取审计信息
3.6.1 创建一个AuditReader的bean
Hibernate Envers提供了两个方法来获取一个实体的审计信息。
forRevisionsOfEntity
- 这个方法用于获取一个实体所经历的所有修订变化。forRevisionsOfEntityWithChanges
- 这是为了获取修订变化,以及每个修订中哪些字段和数据被改变的信息。
你将需要一个类的实例 org.hibernate.envers.AuditReader
的实例,以创建 AuditQuery
来获取审计信息
添加以下配置类来创建一个Bean的 org.hibernate.envers.AuditReader
@Configurationpublic class AuditConfiguration { private final EntityManagerFactory entityManagerFactory; AuditConfiguration(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } @Bean AuditReader auditReader() { return AuditReaderFactory.get(entityManagerFactory.createEntityManager()); }}
3.6.2 在客户服务中创建一个方法来获取客户的审计信息
现在我们已经创建了一个AuditReader
的bean,继续将AuditReader的依赖关系自动连接到CustomerService
类中。
public class CustomerService {
...
...
@Autowired
private AuditReader auditReader;
...
}
现在在CustomerService
,添加一个方法来获取客户实体的审计修订细节
public List<?> getRevisions(Long id, boolean fetchChanges) {
AuditQuery auditQuery = null;
if(fetchChanges) {
auditQuery = auditReader.createQuery()
.forRevisionsOfEntityWithChanges(Customer.class, true);
}
else {
auditQuery = auditReader.createQuery()
.forRevisionsOfEntity(Customer.class, true);
}
auditQuery.add(AuditEntity.id().eq(id));
return auditQuery.getResultList();
}
如果fetchChanges
boolean为false,这个方法将只获取修订信息,否则它将返回所有的修订信息以及被修改的细节。
3.6.3 为客户审计暴露一个Rest API
像这样公开一个Rest API
@GetMapping(path = "/{id}/revisions", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getRevisions(@PathVariable(name = "id") String customerId,
@RequestParam(value = "fetchChanges", required = false) boolean fetchChanges) {
List results = customerService.getRevisions(Long.valueOf(customerId), fetchChanges);
return ResponseEntity.ok(results);
}
现在让我们点击API来获取审计信息。点击下面的URL来获取客户身份2的审计修订。
curl -i -u admin02@tw.com:admin02@123# \
-X GET -H "Content-Type: application/json" \
http://localhost:8080/customer/2/revisions
你会得到下面的错误,因为应用程序不能序列化。AuditRevisionEntity
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
为了解决这个问题,在AuditRevisionEntity类中添加以下注解
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
....
}
现在,如果你再次点击相同的URL,你应该得到id=2的客户的修订列表(如下所示)。
[
{
"id": 1,
"timestamp": 1608084027455,
"user": "admin01@tw.com",
"revisionDate": "2020-12-16T02:00:27.455+0000"
},
{
"id": 2,
"timestamp": 1608084069397,
"user": "admin02@tw.com",
"revisionDate": "2020-12-16T02:01:09.397+0000"
}
]
3.6.4 获取带有修改细节的审计报告
在上面的例子中,我们只得到了关于对象何时被改变的信息。我们没有得到谁改变了它的信息。
要做到这一点,请点击带有查询参数的相同URLfetchChanges=true
curl -i -u admin02@tw.com:admin02@123# \
-X GET -H "Content-Type: application/json" \
http://localhost:8080/customer/2/revisions\?fetchChanges\=true
不幸的是,你会得到以下错误
{
"statusCode": 400,
"errorMessage": "The specified entity [com.dev.springdemo.customer.model.Customer] does not support or use modified flags."
}
这是因为我们没有配置我们的客户实体来跟踪哪些字段被改变。
修改Customer
实体上的@Audited
注解,并添加属性withModifiedFlag = true
@Audited(withModifiedFlag = true)
public class Customer implements Serializable {
...
}
现在,如果你重新运行代码,你将再次得到错误(类似于下面的内容)。
Schema-validation: missing column [email_address_mod] in table [customer_audit]
Envers可以通过为*
_audit
表中的每个实体字段添加_mod
列来跟踪字段的变化。因此,对于客户的每一个字段,我们需要在
customer_audit
表中添加一个相应的boolean
列,并在列名的后面加上_mod
例如,对于customer
表中的email_address
列,在customer_audit
表中添加一个email_address_mod
字段。
因此,为了在customer_audit
表中添加所有的*_mod
列,运行下面的 sql 脚本。
ALTER TABLE `customer_audit`
ADD COLUMN `created_by_mod` TINYINT(1) NULL,
ADD COLUMN `created_on_mod` TINYINT(1) NULL ,
ADD COLUMN `email_address_mod` boolean NULL,
ADD COLUMN `first_name_mod` boolean NULL,
ADD COLUMN `last_name_mod` boolean NULL,
ADD COLUMN `updated_by_mod` boolean NULL,
ADD COLUMN `updated_on_mod` boolean NULL;
现在我们终于可以测试它了。通过创建和更新客户,创建一些新的审计条目。
现在,如果你用chagnes获取修订版,你会得到一个类似这样的输出。
[
[
{
"id": 4,
"firstName": "Clark",
"lastName": "Kent",
"emailAddress": "superman@gmail.com",
"addresses": null,
"createdBy": "admin01@tw.com",
"updatedBy": "admin01@tw.com",
"createdOn": "2020-12-16T03:25:01.000+0000",
"updatedOn": "2020-12-16T03:25:01.000+0000"
},
{
"id": 3,
"timestamp": 1608089100811,
"user": "admin01@tw.com",
"revisionDate": "2020-12-16T03:25:00.811+0000"
},
"ADD",
[]
],
[
{
"id": 4,
"firstName": "Super",
"lastName": "Man",
"emailAddress": "superman@gmail.com",
"addresses": null,
"createdBy": "admin01@tw.com",
"updatedBy": "admin02@tw.com",
"createdOn": "2020-12-16T03:25:01.000+0000",
"updatedOn": "2020-12-16T03:25:49.000+0000"
},
{
"id": 4,
"timestamp": 1608089148899,
"user": "admin02@tw.com",
"revisionDate": "2020-12-16T03:25:48.899+0000"
},
"MOD",
[
"firstName",
"lastName",
"updatedBy",
"updatedOn"
]
]
]
这个项目的全部代码可以在github.com/chatterjees…。
要检查这个发布标签,请运行以下命令。
git clonegithub.com/chatterjees…
cd spring-boot-app
git checkout tags/v4.0 -b v4.0-with-auditing