From 8d51ec2d21e658ac84bb4faab364d2e36190db6e Mon Sep 17 00:00:00 2001 From: chenyuan Date: Mon, 3 Aug 2026 20:29:32 +0800 Subject: [PATCH] feat: add dev database profile without flyway --- README.md | 4 +- docs/DEVELOPMENT_GUIDE.md | 16 ++--- pom.xml | 8 --- src/main/resources/application-dev.yml | 14 ++++ src/main/resources/application-test.yml | 3 - src/main/resources/application.yml | 6 +- .../resources/db/migration/V0__baseline.sql | 1 - .../migration/V1__create_identity_tables.sql | 29 -------- ...eate_platform_identity_and_auth_tables.sql | 28 -------- .../V3__seed_tzs001_local_student.sql | 12 ---- src/main/resources/schema.sql | 69 +++++++++++++++++++ .../digitalrmb/ApplicationContextTest.java | 11 --- .../com/yau/digitalrmb/FlywayRemovalTest.java | 14 ++++ 13 files changed, 110 insertions(+), 105 deletions(-) delete mode 100644 src/main/resources/db/migration/V0__baseline.sql delete mode 100644 src/main/resources/db/migration/V1__create_identity_tables.sql delete mode 100644 src/main/resources/db/migration/V2__create_platform_identity_and_auth_tables.sql delete mode 100644 src/main/resources/db/migration/V3__seed_tzs001_local_student.sql create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/com/yau/digitalrmb/FlywayRemovalTest.java diff --git a/README.md b/README.md index cabb109..2ea4b1d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - 主平台单点登录:主平台跳转至 `GET /api/v1/auth/sso?token=...`。后端以主平台教师或学生档案的 `add_time` 动态 HMAC 密钥验证 Token,生成一次性 `code` 并跳转至前端回调地址。 - 独立访问:浏览器访问 `GET /api/v1/auth/cas/login`,跳转学校 CAS;回调校验成功后生成同样的一次性 `code`。 -- 本地账号密码:`POST /api/v1/auth/login` 仅校验本系统保存的 BCrypt 密码哈希。数据库迁移内置学生账号 `tzs001`;SSO/CAS 仅同步身份与角色,不读取主平台密码。 +- 本地账号密码:`POST /api/v1/auth/login` 仅校验本系统保存的 BCrypt 密码哈希。`schema.sql` 内置学生账号 `tzs001`;SSO/CAS 仅同步身份与角色,不读取主平台密码。 - 会话兑换:`POST /api/v1/auth/session/exchange`,请求体为 `{"code":"..."}`,返回本系统访问 JWT 和刷新令牌。 本系统只接受教师和学生两类身份。平台账号与角色只能通过内部投影写入;没有用户、角色或用户角色的管理接口。 @@ -45,4 +45,4 @@ mvn spring-boot:run -Dspring-boot.run.profiles=local -Dspring-boot.run.arguments 本地 Swagger: -首次启动时,Flyway V3 迁移会创建或更新 `tzs001`(平台用户 ID `487`、`STUDENT`)。迁移文件只保存 BCrypt 哈希,不含密码明文。新 SSO/CAS 用户会获得随机 BCrypt 密码;如需为其开通本地密码登录,必须通过后续受控的本地账号初始化流程完成。 +应用启动时会执行幂等的 `schema.sql`,创建缺失表并创建或更新 `tzs001`(平台用户 ID `487`、`STUDENT`)。脚本只保存 BCrypt 哈希,不含密码明文。开发环境可使用 `dev` 配置连接测试库:`mvn spring-boot:run -Dspring-boot.run.profiles=dev`。新 SSO/CAS 用户会获得随机 BCrypt 密码;如需为其开通本地密码登录,必须通过后续受控的本地账号初始化流程完成。 diff --git a/docs/DEVELOPMENT_GUIDE.md b/docs/DEVELOPMENT_GUIDE.md index 11532ee..4ec70d6 100644 --- a/docs/DEVELOPMENT_GUIDE.md +++ b/docs/DEVELOPMENT_GUIDE.md @@ -95,15 +95,15 @@ infrastructure → domain / application 定义的端口 - 状态流转由聚合方法表达,例如 `approve()`、`reject()`;不要在 Controller 或 Mapper 中直接改状态字段。 - 需要跨聚合通知时发布 `shared.domain.DomainEvent`,由应用层或基础设施层处理副作用。 -## 5. 数据库与 Flyway 规范 +## 5. 数据库与初始化脚本规范 -- 所有表结构和内置数据变化必须新增 `src/main/resources/db/migration/V<版本>__<英文描述>.sql`。 -- 已发布的迁移文件不得修改、删除或重命名;修复必须创建新版本迁移。 +- 基础表结构和内置数据维护在 `src/main/resources/schema.sql`,脚本必须可重复执行,建表使用 `IF NOT EXISTS`,内置数据使用幂等写法。 +- 新增或修改表结构时,先在测试库执行并验证,再同步更新 `schema.sql`;不得依赖 Flyway 或保留 `flyway_schema_history`。 - 表名、字段名使用 `snake_case`;Java 字段使用 `camelCase`,通过 MyBatis-Plus 显式映射差异字段。 - 新业务表必须有主键、必要索引,以及 `created_at`、`updated_at`、`created_by`、`updated_by`、`deleted` 审计字段;可继承 `AuditableEntity`。 - 金额使用 `DECIMAL`,禁止使用 `float`/`double`;时间使用 `TIMESTAMP`,统一以 UTC 语义处理。 -- 身份、角色和用户角色仅能由内部投影或迁移写入;不得新增对外 CRUD 接口。 -- 禁止将密码、Token、验证码或敏感凭据写入业务表、日志、迁移文件或测试快照。 +- 身份、角色和用户角色仅能由内部投影或初始化脚本写入;不得新增对外 CRUD 接口。 +- 禁止将密码、Token、验证码或敏感凭据写入业务表、日志、初始化脚本或测试快照。 ## 6. API 与异常规范 @@ -120,11 +120,11 @@ infrastructure → domain / application 定义的端口 - 当前用户从 Spring Security 的 `Jwt`/`Authentication` 获取,业务模块不得解析主平台 Token。 - 角色只允许 `TEACHER`、`STUDENT`;权限判断放在应用层或 Spring Security 配置中,不能由前端传入角色决定。 - `platformintegration` 是唯一可以处理主平台 Token、CAS Ticket 和主平台只读数据源的模块。 -- 主平台连接只授予 `SELECT`;本系统 MySQL 使用主数据源,主平台只读数据源不得被 Flyway 或业务写操作复用。 +- 主平台连接只授予 `SELECT`;本系统 MySQL 使用主数据源,主平台只读数据源不得被初始化脚本或业务写操作复用。 ## 8. 测试与提交规范 -- 新领域规则写单元测试;新增接口至少写 `MockMvc` 集成测试;新增迁移需覆盖启动迁移和关键读写。 +- 新领域规则写单元测试;新增接口至少写 `MockMvc` 集成测试;新增表结构需覆盖启动初始化和关键读写。 - 修复缺陷先增加可复现的失败测试,再实现修复。 - 提交前至少运行: @@ -141,7 +141,7 @@ mvn package -DskipTests -B - [ ] 已创建新的顶层模块目录,而非把业务代码放入 `shared`。 - [ ] 已区分 `domain`、`application`、`infrastructure`、`interfaces` 职责。 - [ ] Controller 未直接调用 Mapper,且未返回持久化实体。 -- [ ] 新增数据库变化使用新的 Flyway 版本迁移。 +- [ ] 新增数据库变化已同步到测试库和 `schema.sql`。 - [ ] 接口已校验参数、统一响应,并补充 Swagger 说明。 - [ ] 未记录或返回密码、Token、验证码等敏感信息。 - [ ] 已补齐单元/集成测试并通过 Maven 验证。 diff --git a/pom.xml b/pom.xml index d3257e6..918239d 100644 --- a/pom.xml +++ b/pom.xml @@ -46,14 +46,6 @@ org.springframework.boot spring-boot-starter-actuator - - org.flywaydb - flyway-core - - - org.flywaydb - flyway-mysql - com.baomidou mybatis-plus-boot-starter diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 3ecb264..6b2c878 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,3 +1,17 @@ +spring: + datasource: + url: jdbc:mysql://${DB_HOST:118.31.7.2}:${DB_PORT:3306}/${DB_NAME:digital_rmb}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&useInformationSchema=true&allowPublicKeyRetrieval=true&connectTimeout=10000&socketTimeout=30000 + username: ${DB_USER:root} + password: ${DB_PWD:sztzjy2017} + driver-class-name: com.mysql.cj.jdbc.Driver springdoc: swagger-ui: enabled: true +platform-integration: + datasource: + url: ${spring.datasource.url} + username: ${spring.datasource.username} + password: ${spring.datasource.password} +security: + jwt: + secret: 0123456789012345678901234567890123456789012345678901234567890123 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 3187193..487192c 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -4,9 +4,6 @@ spring: username: sa password: "" driver-class-name: org.h2.Driver - flyway: - enabled: true - locations: classpath:db/migration security: jwt: secret: 0123456789012345678901234567890123456789012345678901234567890123 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2352f7e..199be9a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,9 +6,9 @@ spring: username: ${DIGITAL_RMB_DB_USERNAME} password: ${DIGITAL_RMB_DB_PASSWORD} driver-class-name: com.mysql.cj.jdbc.Driver - flyway: - enabled: true - locations: classpath:db/migration + sql: + init: + mode: always security: jwt: secret: ${DIGITAL_RMB_JWT_SECRET} diff --git a/src/main/resources/db/migration/V0__baseline.sql b/src/main/resources/db/migration/V0__baseline.sql deleted file mode 100644 index 86431f0..0000000 --- a/src/main/resources/db/migration/V0__baseline.sql +++ /dev/null @@ -1 +0,0 @@ --- baseline diff --git a/src/main/resources/db/migration/V1__create_identity_tables.sql b/src/main/resources/db/migration/V1__create_identity_tables.sql deleted file mode 100644 index 6ed0fd3..0000000 --- a/src/main/resources/db/migration/V1__create_identity_tables.sql +++ /dev/null @@ -1,29 +0,0 @@ -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/main/resources/db/migration/V2__create_platform_identity_and_auth_tables.sql b/src/main/resources/db/migration/V2__create_platform_identity_and_auth_tables.sql deleted file mode 100644 index 582cc88..0000000 --- a/src/main/resources/db/migration/V2__create_platform_identity_and_auth_tables.sql +++ /dev/null @@ -1,28 +0,0 @@ -CREATE TABLE platform_user_snapshot ( - platform_user_id BIGINT PRIMARY KEY, - account VARCHAR(64) NOT NULL, - display_name VARCHAR(64) NOT NULL, - role_key VARCHAR(16) NOT NULL, - source_updated_at TIMESTAMP NOT NULL, - synced_at TIMESTAMP NOT NULL -); - -CREATE TABLE auth_login_exchange_code ( - code_hash CHAR(64) PRIMARY KEY, - platform_user_id BIGINT NOT NULL, - expires_at TIMESTAMP NOT NULL, - consumed_at TIMESTAMP NULL -); - -CREATE TABLE auth_refresh_token ( - token_hash CHAR(64) PRIMARY KEY, - platform_user_id BIGINT NOT NULL, - expires_at TIMESTAMP NOT NULL, - revoked_at TIMESTAMP NULL -); - -INSERT INTO sys_role (id, name, created_at, updated_at, created_by, updated_by, deleted) -VALUES (1001, 'TEACHER', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE); - -INSERT INTO sys_role (id, name, created_at, updated_at, created_by, updated_by, deleted) -VALUES (1002, 'STUDENT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE); diff --git a/src/main/resources/db/migration/V3__seed_tzs001_local_student.sql b/src/main/resources/db/migration/V3__seed_tzs001_local_student.sql deleted file mode 100644 index da21cd4..0000000 --- a/src/main/resources/db/migration/V3__seed_tzs001_local_student.sql +++ /dev/null @@ -1,12 +0,0 @@ -INSERT INTO sys_user (id, username, password_hash, enabled, created_at, updated_at, created_by, updated_by, deleted) -VALUES (487, 'tzs001', '$2a$10$ufcw5KFHtOmLzxAsV4C.MuIDOErMlw0iw5J5hc8OMzaTx0u9QYxG6', TRUE, - CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE) -ON DUPLICATE KEY UPDATE username = VALUES(username), password_hash = VALUES(password_hash), enabled = TRUE, - updated_at = CURRENT_TIMESTAMP, updated_by = 'SYSTEM', deleted = FALSE; - -INSERT INTO platform_user_snapshot (platform_user_id, account, display_name, role_key, source_updated_at, synced_at) -VALUES (487, 'tzs001', 'tzs001', 'STUDENT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) -ON DUPLICATE KEY UPDATE account = VALUES(account), display_name = VALUES(display_name), role_key = VALUES(role_key), - source_updated_at = CURRENT_TIMESTAMP, synced_at = CURRENT_TIMESTAMP; - -INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES (487, 1002); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..b749a73 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,69 @@ +CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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) +); + +CREATE TABLE IF NOT EXISTS platform_user_snapshot ( + platform_user_id BIGINT PRIMARY KEY, + account VARCHAR(64) NOT NULL, + display_name VARCHAR(64) NOT NULL, + role_key VARCHAR(16) NOT NULL, + source_updated_at TIMESTAMP NOT NULL, + synced_at TIMESTAMP NOT NULL +); + +CREATE TABLE IF NOT EXISTS auth_login_exchange_code ( + code_hash CHAR(64) PRIMARY KEY, + platform_user_id BIGINT NOT NULL, + expires_at TIMESTAMP NOT NULL, + consumed_at TIMESTAMP NULL +); + +CREATE TABLE IF NOT EXISTS auth_refresh_token ( + token_hash CHAR(64) PRIMARY KEY, + platform_user_id BIGINT NOT NULL, + expires_at TIMESTAMP NOT NULL, + revoked_at TIMESTAMP NULL +); + +INSERT IGNORE INTO sys_role (id, name, created_at, updated_at, created_by, updated_by, deleted) +VALUES (1001, 'TEACHER', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE), + (1002, 'STUDENT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE); + +INSERT INTO sys_user (id, username, password_hash, enabled, created_at, updated_at, created_by, updated_by, deleted) +VALUES (487, 'tzs001', '$2a$10$ufcw5KFHtOmLzxAsV4C.MuIDOErMlw0iw5J5hc8OMzaTx0u9QYxG6', TRUE, + CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'SYSTEM', 'SYSTEM', FALSE) +ON DUPLICATE KEY UPDATE username = VALUES(username), password_hash = VALUES(password_hash), enabled = TRUE, + updated_at = CURRENT_TIMESTAMP, updated_by = 'SYSTEM', deleted = FALSE; + +INSERT INTO platform_user_snapshot (platform_user_id, account, display_name, role_key, source_updated_at, synced_at) +VALUES (487, 'tzs001', 'tzs001', 'STUDENT', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) +ON DUPLICATE KEY UPDATE account = VALUES(account), display_name = VALUES(display_name), role_key = VALUES(role_key), + source_updated_at = CURRENT_TIMESTAMP, synced_at = CURRENT_TIMESTAMP; + +INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES (487, 1002); diff --git a/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java b/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java index 356c8a9..fb10e0a 100644 --- a/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java +++ b/src/test/java/com/yau/digitalrmb/ApplicationContextTest.java @@ -1,7 +1,6 @@ package com.yau.digitalrmb; import com.zaxxer.hikari.HikariDataSource; -import org.flywaydb.core.Flyway; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -14,9 +13,6 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class ApplicationContextTest { - @Autowired - private Flyway flyway; - @Autowired @Qualifier("dataSource") private HikariDataSource applicationDataSource; @@ -25,13 +21,6 @@ class ApplicationContextTest { @Qualifier("platformReadOnlyDataSource") private HikariDataSource platformReadOnlyDataSource; - @Test - void baselineMigrationIsApplied() { - assertThat(flyway.info().applied()) - .extracting(migration -> migration.getVersion().getVersion()) - .contains("0"); - } - @Test void applicationDatasourceIsSeparateAndWritable() { assertThat(applicationDataSource).isNotSameAs(platformReadOnlyDataSource); diff --git a/src/test/java/com/yau/digitalrmb/FlywayRemovalTest.java b/src/test/java/com/yau/digitalrmb/FlywayRemovalTest.java new file mode 100644 index 0000000..c344f4a --- /dev/null +++ b/src/test/java/com/yau/digitalrmb/FlywayRemovalTest.java @@ -0,0 +1,14 @@ +package com.yau.digitalrmb; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class FlywayRemovalTest { + + @Test + void runtimeClasspathDoesNotContainFlyway() { + assertThatThrownBy(() -> Class.forName("org.flywaydb.core.Flyway")) + .isInstanceOf(ClassNotFoundException.class); + } +}