From e6381ebd22a00971fe0b8f42ce74ff7bfa7f4238 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Mon, 3 Aug 2026 11:06:19 +0800 Subject: [PATCH] feat: add identity persistence scaffold --- pom.xml | 5 ++ .../persistence/entity/RoleEntity.java | 13 +++++ .../persistence/entity/UserEntity.java | 19 +++++++ .../persistence/mapper/RoleMapper.java | 9 ++++ .../persistence/mapper/UserMapper.java | 9 ++++ .../shared/config/MybatisPlusConfig.java | 50 +++++++++++++++++++ .../persistence/AuditableEntity.java | 33 ++++++++++++ .../migration/V1__create_identity_tables.sql | 29 +++++++++++ .../digitalrmb/ApplicationContextTest.java | 4 +- .../identity/IdentityPersistenceTest.java | 32 ++++++++++++ 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/RoleEntity.java create mode 100644 src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/UserEntity.java create mode 100644 src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/RoleMapper.java create mode 100644 src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/UserMapper.java create mode 100644 src/main/java/com/yau/digitalrmb/shared/config/MybatisPlusConfig.java create mode 100644 src/main/java/com/yau/digitalrmb/shared/infrastructure/persistence/AuditableEntity.java create mode 100644 src/main/resources/db/migration/V1__create_identity_tables.sql create mode 100644 src/test/java/com/yau/digitalrmb/identity/IdentityPersistenceTest.java diff --git a/pom.xml b/pom.xml index d170367..605b83f 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,11 @@ mybatis-plus-spring-boot4-starter ${mybatis-plus.version} + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + com.mysql mysql-connector-j diff --git a/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/RoleEntity.java b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/RoleEntity.java new file mode 100644 index 0000000..ed8ce43 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/RoleEntity.java @@ -0,0 +1,13 @@ +package com.yau.digitalrmb.identity.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.yau.digitalrmb.shared.infrastructure.persistence.AuditableEntity; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("sys_role") +public class RoleEntity extends AuditableEntity { + private String name; +} diff --git a/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/UserEntity.java b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/UserEntity.java new file mode 100644 index 0000000..fbde89b --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/entity/UserEntity.java @@ -0,0 +1,19 @@ +package com.yau.digitalrmb.identity.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.yau.digitalrmb.shared.infrastructure.persistence.AuditableEntity; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("sys_user") +public class UserEntity extends AuditableEntity { + private String username; + + @TableField("password_hash") + private String passwordHash; + + private Boolean enabled; +} diff --git a/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/RoleMapper.java b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/RoleMapper.java new file mode 100644 index 0000000..433032b --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/RoleMapper.java @@ -0,0 +1,9 @@ +package com.yau.digitalrmb.identity.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yau.digitalrmb.identity.infrastructure.persistence.entity.RoleEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface RoleMapper extends BaseMapper { +} diff --git a/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/UserMapper.java b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/UserMapper.java new file mode 100644 index 0000000..4d5e01e --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/identity/infrastructure/persistence/mapper/UserMapper.java @@ -0,0 +1,9 @@ +package com.yau.digitalrmb.identity.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yau.digitalrmb.identity.infrastructure.persistence.entity.UserEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserMapper extends BaseMapper { +} diff --git a/src/main/java/com/yau/digitalrmb/shared/config/MybatisPlusConfig.java b/src/main/java/com/yau/digitalrmb/shared/config/MybatisPlusConfig.java new file mode 100644 index 0000000..7dfd431 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/shared/config/MybatisPlusConfig.java @@ -0,0 +1,50 @@ +package com.yau.digitalrmb.shared.config; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.annotation.DbType; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.time.LocalDateTime; + +@Configuration +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + @Bean + public MetaObjectHandler metaObjectHandler() { + return new MetaObjectHandler() { + @Override + public void insertFill(MetaObject metaObject) { + LocalDateTime now = LocalDateTime.now(); + String actor = currentActor(); + strictInsertFill(metaObject, "createdAt", LocalDateTime.class, now); + strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, now); + strictInsertFill(metaObject, "createdBy", String.class, actor); + strictInsertFill(metaObject, "updatedBy", String.class, actor); + } + + @Override + public void updateFill(MetaObject metaObject) { + strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now()); + strictUpdateFill(metaObject, "updatedBy", String.class, currentActor()); + } + }; + } + + private static String currentActor() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication == null || !authentication.isAuthenticated() ? "SYSTEM" : authentication.getName(); + } +} diff --git a/src/main/java/com/yau/digitalrmb/shared/infrastructure/persistence/AuditableEntity.java b/src/main/java/com/yau/digitalrmb/shared/infrastructure/persistence/AuditableEntity.java new file mode 100644 index 0000000..8f5c31b --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/shared/infrastructure/persistence/AuditableEntity.java @@ -0,0 +1,33 @@ +package com.yau.digitalrmb.shared.infrastructure.persistence; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +public abstract class AuditableEntity { + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @TableField(value = "created_at", fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableField(value = "created_by", fill = FieldFill.INSERT) + private String createdBy; + + @TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE) + private String updatedBy; + + @TableLogic + private Boolean deleted; +} diff --git a/src/main/resources/db/migration/V1__create_identity_tables.sql b/src/main/resources/db/migration/V1__create_identity_tables.sql new file mode 100644 index 0000000..6ed0fd3 --- /dev/null +++ b/src/main/resources/db/migration/V1__create_identity_tables.sql @@ -0,0 +1,29 @@ +CREATE TABLE sys_user ( + id BIGINT PRIMARY KEY, + username VARCHAR(64) NOT NULL UNIQUE, + password_hash VARCHAR(100) NOT NULL, + enabled BOOLEAN NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + created_by VARCHAR(64) NOT NULL, + updated_by VARCHAR(64) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE sys_role ( + id BIGINT PRIMARY KEY, + name VARCHAR(64) NOT NULL UNIQUE, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + created_by VARCHAR(64) NOT NULL, + updated_by VARCHAR(64) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE sys_user_role ( + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + PRIMARY KEY (user_id, role_id), + CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user(id), + CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role(id) +); diff --git a/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java b/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java index 488d509..952a65c 100644 --- a/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java +++ b/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java @@ -17,6 +17,8 @@ class ApplicationContextTest { @Test void baselineMigrationIsApplied() { - assertThat(flyway.info().applied()).hasSize(1); + assertThat(flyway.info().applied()) + .extracting(migration -> migration.getVersion().getVersion()) + .contains("0"); } } diff --git a/src/test/java/com/yau/digitalrmb/identity/IdentityPersistenceTest.java b/src/test/java/com/yau/digitalrmb/identity/IdentityPersistenceTest.java new file mode 100644 index 0000000..9e90465 --- /dev/null +++ b/src/test/java/com/yau/digitalrmb/identity/IdentityPersistenceTest.java @@ -0,0 +1,32 @@ +package com.yau.digitalrmb.identity; + +import com.yau.digitalrmb.identity.infrastructure.persistence.entity.UserEntity; +import com.yau.digitalrmb.identity.infrastructure.persistence.mapper.UserMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class IdentityPersistenceTest { + @Autowired + private UserMapper userMapper; + + @Test + void mapperPersistsAuditedUser() { + UserEntity user = new UserEntity(); + user.setUsername("operator"); + user.setPasswordHash("hash"); + user.setEnabled(true); + + userMapper.insert(user); + + assertThat(user.getId()).isNotNull(); + assertThat(userMapper.selectById(user.getId()).getUsername()).isEqualTo("operator"); + assertThat(user.getCreatedAt()).isNotNull(); + assertThat(user.getCreatedBy()).isEqualTo("SYSTEM"); + } +}