fix(relationship): Phase 12 复验 —— 修复实体↔生产迁移系统性漂移(生产故障族)/ 双路径 episode 与 NPE / 测试库缺表致首页零覆盖 / 计划项 2 未实现
KEEP-VERIFY 复验发现并修复 4 处真实缺陷: - D1(Critical,生产故障·同一族系统性缺陷)实体与 db/migration 系统性漂移:23 表 50 列在生产 迁移中不存在,而测试库 schema-h2.sql 手写补齐 → 全量绿=假绿。其中 11 表属"INSERT 必崩"级 (缺 id/created_at/updated_at,被 AutoFillHandler 强制填充),含 matches.episode、user_preferences 权重列、permissions/roles、consent_records、risk_events 等;真实 PG 上 Match 创建、择偶偏好保存、 权限/角色写入、同意记录、风险事件全部失败。修复:新增 V037(幂等 ADD COLUMN IF NOT EXISTS + 存量 episode 回填)。 - D2(High)RelationshipService.createMatch 不写 episode/lastActivityAt,与 MatchService 双路径 填充不一致;且 episode 为 null 时 m.getEpisode()+1 对 Integer 自动拆箱抛 NPE。修复:统一按 AGENTS.md #20 推导 + null 兜底。 - D3(High)测试库缺 user_identities 等 7 表 → GET /api/v1/home 在测试环境 BadSqlGrammar, 首页接口零回归覆盖。修复:schema-h2.sql 补齐 7 表。 - D4(Medium)Phase 12 计划项 2 实际未实现:getting_to_know 分支只产出 counseling。修复: HomeApplicationService 补 premarital 引导(与 counseling 并列)。 测试保真度: - 新增永久守卫 EntityMigrationParityTest(5 用例):把"实体字段 ⊆ 生产迁移累积列"与"测试库覆盖 关键业务表"固化为自动不变量,区分"必崩级/潜伏级";经两轮负向验证证明会变红(第一轮分级过窄 已修正:episode 由"潜伏级"纠正为"必崩级")。 - 新增 RelationshipProgressPhase12Test(14 用例,含 8 线程并发确认)与 HomePremaritalGuidancePhase12Test(5 用例)。 - 删除 2 个假测试类中的 assertTrue(true) 占位(AGENTS.md 明令禁止),改走真实服务方法验证。 Gate:单方确认不改变最终关系 ✅ / 双确认后才 successful ✅ / 历史 Episode 可追踪 ✅ / 新 Episode 不恢复旧聊天权限(conversations.match_id UNIQUE)✅ / 非成员不可代确认 ✅ 全量回归:371 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(71 类,5m07s)
This commit is contained in:
+165
@@ -0,0 +1,165 @@
|
||||
-- V037__align_entity_columns_with_migrations.sql
|
||||
--
|
||||
-- 用途:消除「实体字段 vs 生产 DDL」的架构漂移(schema drift)。
|
||||
--
|
||||
-- 背景(Phase 12 复验发现,属与 Phase 08/09/10/11 同族的潜伏缺陷):
|
||||
-- 测试库 backend/src/test/resources/schema-h2.sql 是**手写**的最小 schema,长期比生产
|
||||
-- migrations 多出一批列。于是:
|
||||
-- · 实体(@TableName + extends BaseEntity)声明了这些列;
|
||||
-- · AutoFillHandler.insertFill 对 id / createdAt / updatedAt 做**强制填充**,
|
||||
-- MyBatis-Plus 默认 FieldStrategy=NOT_NULL → 这些字段**必然出现在 INSERT 语句**;
|
||||
-- · 真实 PostgreSQL 上列不存在 → 任何 insert/updateById 直接
|
||||
-- `ERROR: column "xxx" of relation "yyy" does not exist`;
|
||||
-- · H2 测试库有列 → 全量测试绿 → **假绿**。
|
||||
--
|
||||
-- 本迁移把生产 DDL 补齐到与实体一致,是「测试保真度三层」第一层(生产约束下沉)的
|
||||
-- 反向修复:**生产补齐而非测试删列** —— 因为业务代码确实在写这些列,删列会丢功能。
|
||||
--
|
||||
-- 分级(V037 全量覆盖):
|
||||
-- A 级(缺 id / created_at / updated_at → INSERT 必崩,生产功能 100% 不可用)
|
||||
-- consent_records · local_eligibility_evidences · match_rule_configs ·
|
||||
-- matchmaker_followups · matchmaker_recommendations · permissions ·
|
||||
-- review_reasons · risk_events · roles · user_preferences · user_privacy_settings
|
||||
-- B 级(缺 deleted_at / 业务列 → 读为 null、软删失效、Episode 永远为 null)
|
||||
-- activity_capacity_groups · activity_mutual_connection_consents ·
|
||||
-- activity_organizations · matches · matchmaker_services ·
|
||||
-- organization_relationships · review_items · review_tasks ·
|
||||
-- service_program_members · user_identities · user_local_eligibilities · user_relations
|
||||
--
|
||||
-- 幂等:全部使用 ADD COLUMN IF NOT EXISTS,可安全重复执行。
|
||||
|
||||
-- ============================================================================
|
||||
-- A 级:补齐 BaseEntity 公共列(id / created_at / updated_at / deleted_at)
|
||||
-- ============================================================================
|
||||
|
||||
-- roles:缺 updated_at / deleted_at(已有 created_at)
|
||||
ALTER TABLE roles
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- permissions:缺 created_at / updated_at / deleted_at(仅 id/code/name/module/description)
|
||||
ALTER TABLE permissions
|
||||
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- consent_records:缺 created_at / updated_at(业务时间列是 agreed_at)
|
||||
ALTER TABLE consent_records
|
||||
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- risk_events:缺 updated_at(已有 created_at / deleted_at)
|
||||
ALTER TABLE risk_events
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
||||
|
||||
-- review_reasons:主键是 code(非 id),且缺 created_at / updated_at / deleted_at。
|
||||
-- 实体 ReviewReason extends BaseEntity → AutoFillHandler 强塞 id → 必须补 id 列以承接,
|
||||
-- 同时保留 code 为主键(业务以 code 引用)。id 上加唯一索引,保证与实体语义一致。
|
||||
ALTER TABLE review_reasons
|
||||
ADD COLUMN IF NOT EXISTS id UUID,
|
||||
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
UPDATE review_reasons SET id = gen_random_uuid() WHERE id IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_review_reasons_id ON review_reasons(id);
|
||||
|
||||
-- match_rule_configs:缺 updated_at / deleted_at(已有 created_at)
|
||||
ALTER TABLE match_rule_configs
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- matchmaker_followups:缺 updated_at / deleted_at
|
||||
ALTER TABLE matchmaker_followups
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- matchmaker_recommendations:缺 updated_at / deleted_at
|
||||
ALTER TABLE matchmaker_recommendations
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- ============================================================================
|
||||
-- A 级特例:主键为 user_id(无 id 列)的表
|
||||
-- 实体继承 BaseEntity → AutoFillHandler.insertFill 会 setValue("id", UUID),
|
||||
-- MyBatis-Plus 随即把 id 写进 INSERT 列清单 → 生产必崩。
|
||||
-- 补 id 列(可空 + 唯一索引)以承接实体语义,原 user_id 主键保持不变。
|
||||
-- ============================================================================
|
||||
|
||||
-- user_preferences:缺 id + 11 个匹配权重列 + deleted_at
|
||||
ALTER TABLE user_preferences
|
||||
ADD COLUMN IF NOT EXISTS id UUID,
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS age_weight DOUBLE PRECISION DEFAULT 0.15,
|
||||
ADD COLUMN IF NOT EXISTS region_weight DOUBLE PRECISION DEFAULT 0.20,
|
||||
ADD COLUMN IF NOT EXISTS marital_weight DOUBLE PRECISION DEFAULT 0.10,
|
||||
ADD COLUMN IF NOT EXISTS children_weight DOUBLE PRECISION DEFAULT 0.05,
|
||||
ADD COLUMN IF NOT EXISTS education_weight DOUBLE PRECISION DEFAULT 0.10,
|
||||
ADD COLUMN IF NOT EXISTS height_weight DOUBLE PRECISION DEFAULT 0.05,
|
||||
ADD COLUMN IF NOT EXISTS marriage_plan_weight DOUBLE PRECISION DEFAULT 0.15,
|
||||
ADD COLUMN IF NOT EXISTS lifestyle_weight DOUBLE PRECISION DEFAULT 0.10,
|
||||
ADD COLUMN IF NOT EXISTS settlement_weight DOUBLE PRECISION DEFAULT 0.05,
|
||||
ADD COLUMN IF NOT EXISTS interest_weight DOUBLE PRECISION DEFAULT 0.10;
|
||||
UPDATE user_preferences SET id = gen_random_uuid() WHERE id IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_user_preferences_id ON user_preferences(id);
|
||||
|
||||
-- user_privacy_settings:缺 id
|
||||
ALTER TABLE user_privacy_settings
|
||||
ADD COLUMN IF NOT EXISTS id UUID,
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
UPDATE user_privacy_settings SET id = gen_random_uuid() WHERE id IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_user_privacy_settings_id ON user_privacy_settings(id);
|
||||
|
||||
-- ============================================================================
|
||||
-- B 级:补齐 deleted_at(软删语义)与业务列
|
||||
-- ============================================================================
|
||||
|
||||
-- matches:缺 episode / source_id / last_activity_at / unmatched_by / deleted_at
|
||||
-- episode —— AGENTS.md #20:Prior Unmatch 后重新 Match 时 +1,历史 Episode 可追踪
|
||||
-- source_id —— 来源关联 ID(activity_mutual 时为 activity_id + pair_id)
|
||||
-- last_activity_at / unmatched_by —— 最近互动时间 / unmatch 发起方
|
||||
ALTER TABLE matches
|
||||
ADD COLUMN IF NOT EXISTS source_id VARCHAR(64),
|
||||
ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS unmatched_by UUID,
|
||||
ADD COLUMN IF NOT EXISTS episode INT NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
-- 存量 Match 的 episode 补齐为 1
|
||||
UPDATE matches SET episode = 1 WHERE episode IS NULL;
|
||||
|
||||
-- local_eligibility_evidences:缺 file_id / updated_at / deleted_at
|
||||
ALTER TABLE local_eligibility_evidences
|
||||
ADD COLUMN IF NOT EXISTS file_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- service_program_members / user_local_eligibilities / user_relations:缺 deleted_at
|
||||
ALTER TABLE service_program_members ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE user_local_eligibilities ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE user_relations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- user_identities:缺 deleted_at
|
||||
ALTER TABLE user_identities ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- organization_relationships:缺 deleted_at + 实体声明的 created_by
|
||||
ALTER TABLE organization_relationships
|
||||
ADD COLUMN IF NOT EXISTS created_by UUID,
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- 活动域:缺 deleted_at
|
||||
ALTER TABLE activity_capacity_groups ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE activity_mutual_connection_consents ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE activity_organizations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- matchmaker_services:缺 deleted_at
|
||||
ALTER TABLE matchmaker_services ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- review_items / review_tasks:缺 deleted_at
|
||||
ALTER TABLE review_items ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
ALTER TABLE review_tasks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
|
||||
|
||||
-- ============================================================================
|
||||
-- 索引:软删过滤常用 deleted_at IS NULL,为高频表补索引
|
||||
-- ============================================================================
|
||||
CREATE INDEX IF NOT EXISTS idx_matches_episode ON matches(user_a_id, user_b_id, episode);
|
||||
CREATE INDEX IF NOT EXISTS idx_matches_deleted ON matches(deleted_at) WHERE deleted_at IS NULL;
|
||||
Reference in New Issue
Block a user