feat: add dev database profile without flyway

master
chenyuan 4 weeks ago
parent 2888d4cd0f
commit 8d51ec2d21

@ -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<http://localhost:8081/swagger-ui/index.html>
首次启动时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 密码;如需为其开通本地密码登录,必须通过后续受控的本地账号初始化流程完成。

@ -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 验证。

@ -46,14 +46,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>

@ -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

@ -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

@ -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}

@ -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)
);

@ -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);

@ -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);

@ -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);

@ -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);

@ -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);
}
}
Loading…
Cancel
Save