feat(flags): PUT /operator/feature-flags 真持久化(V048)
此前 updateFlag 只改 FeatureFlagConfig 内存单例的字段:重启即回默认值、 多实例互不一致,表现为「后台显示已关闭,一次发布后功能又回来了」。 - V048 新建 feature_flag_overrides(flag_key 主键;值存字符串、回读按字段 声明类型解析;updated_by 记审计)。updated_by 必须建 uuid 而非 varchar, 否则 UUIDStringTypeHandler 以 uuid 发参数会在 PG 抛 operator does not exist: character varying = uuid(同 V046 的 actor_id)。 - FeatureFlagOverride/Mapper/Service:upsert 走「先 UPDATE、0 行再 INSERT」, 不用 PG 的 ON CONFLICT(H2 测试库不认,同 IdempotencyMapper 取舍)。 - FeatureFlagConfig.reloadFromPersistence() 挂 @PostConstruct:该时机晚于 @ConfigurationProperties 绑定,故 DB 覆盖压得住 yml 默认值。 - PUT 先改内存再落库(内存改动失败即 400,不写库);新增 DELETE 恢复默认。 - 覆盖值无法解析时启动 fail-fast,不静默回退默认(否则「配了不生效」)。 守卫 FeatureFlagPersistenceGuardTest 11 例,核心是「另起实例重新加载后改动仍在」 (重启等价路径),并自检 @PostConstruct 未被摘掉。 负向验证:注入不落库 + 摘钩子 -> 恰好 5 例变红,其余不受影响;还原 md5 一致。 全量回归 700 / 0 / 0 / 3(起点 689,净 +11);UI 契约门禁 18/18。
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
-- V048: Feature Flag 运行期覆盖持久化
|
||||
--
|
||||
-- 背景(Phase 23 收尾遗留项):PUT /api/v1/operator/feature-flags/{flagName}
|
||||
-- 此前只修改 FeatureFlagConfig 这个内存单例的字段 —— 重启即回默认值,
|
||||
-- 且多实例部署时各实例互不一致。运维在后台关掉某个开关后,一次发布就把它改回来了,
|
||||
-- 而后台仍然显示"已关闭",是典型的「显示与事实不符」。
|
||||
--
|
||||
-- 本表让「运维改过的开关」成为持久事实:
|
||||
-- * 存在一行 = 该 flag 被覆盖,启动时由 FeatureFlagConfig 加载并覆盖默认配置;
|
||||
-- * 不存在 = 沿用 yml / Java 字段默认值。
|
||||
-- DELETE /{flagName} 删除该行即恢复默认。
|
||||
--
|
||||
-- flag_value 一律存字符串,回读时按 FeatureFlagConfig 字段的**声明类型**解析
|
||||
-- (value_type 仅作记录与自检用)——避免为每个类型各开一列。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS feature_flag_overrides (
|
||||
flag_key VARCHAR(64) PRIMARY KEY,
|
||||
flag_value VARCHAR(255) NOT NULL,
|
||||
value_type VARCHAR(16) NOT NULL,
|
||||
-- ⚠️ 必须是 uuid(不是 varchar):项目全局注册了 UUIDStringTypeHandler,
|
||||
-- 它会把合法 UUID 形式的 String 以 Types.OTHER(uuid)发送。建成 varchar 会抛
|
||||
-- "operator does not exist: character varying = uuid"(同 V046 修的 actor_id)。
|
||||
updated_by UUID,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT chk_flag_value_type CHECK (value_type IN ('boolean', 'int', 'string'))
|
||||
);
|
||||
|
||||
COMMENT ON TABLE feature_flag_overrides
|
||||
IS 'Feature Flag 运行期覆盖值;存在即覆盖默认配置,删除即恢复默认';
|
||||
COMMENT ON COLUMN feature_flag_overrides.updated_by
|
||||
IS '最后一次修改该 flag 的运营人员 ID;null 表示非经 API 写入';
|
||||
Reference in New Issue
Block a user