Browse Source

fix(db+cache): 迁移316-328移出seedGutFloraFingerprints早返回之后,getValue加unless保护防缓存null异常

Xiaogang Liao 2 days ago
parent
commit
469c75ab4b

+ 171 - 248
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -10869,6 +10869,177 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         // 迁移312: 为 gut_flora 补默认指纹规则(空表会导致指纹引擎永远 unknown)
         seedGutFloraFingerprints();
 
+        // 迁移316-329: 原误置于 seedGutFloraFingerprints 早返回(return)之后,gut_flora 指纹存在时永不执行;
+        // 现移至 runSystemMigrations 无条件执行(recommendation_rules / task_orchestration / onboarding_guide_config / 打卡表照片语音字段)
+        // 迁移316: 创建 recommendation_rules 表(统一推荐规则配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS recommendation_rules (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "rule_type TINYINT NOT NULL COMMENT '1-5', " +
+                    "rule_name VARCHAR(100) COMMENT '运营名称', " +
+                    "trigger_product_id BIGINT COMMENT '触发商品ID', " +
+                    "trigger_product_ids VARCHAR(1000) COMMENT '触发商品ID JSON 数组', " +
+                    "target_product_id BIGINT COMMENT '目标商品ID', " +
+                    "priority INT DEFAULT 10 COMMENT '优先级,越大越优', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '启用 1/0', " +
+                    "time_window_days INT COMMENT '回溯窗口(天)', " +
+                    "delay_days INT COMMENT '复购延迟天数', " +
+                    "scope VARCHAR(20) COMMENT 'family/individual', " +
+                    "scene_codes VARCHAR(500) COMMENT '生效场景 JSON 数组', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品推荐规则配置'");
+            log.info("迁移316: 已创建recommendation_rules表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移317: recommendation_rules 表索引
+        try {
+            jdbcTemplate.execute("CREATE INDEX idx_rules_enabled ON recommendation_rules(enabled)");
+            log.info("迁移317: 已创建idx_rules_enabled索引");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("CREATE INDEX idx_rules_scene ON recommendation_rules(scene_codes)");
+            log.info("迁移317: 已创建idx_rules_scene索引");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("CREATE INDEX idx_rules_priority ON recommendation_rules(priority)");
+            log.info("迁移317: 已创建idx_rules_priority索引");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_flows` (" +
+                    "`id` bigint NOT NULL AUTO_INCREMENT," +
+                    "`name` varchar(100) NOT NULL," +
+                    "`description` text," +
+                    "`creator_id` bigint NOT NULL," +
+                    "`family_id` bigint DEFAULT NULL," +
+                    "`version` int NOT NULL DEFAULT 1," +
+                    "`status` varchar(20) NOT NULL DEFAULT 'draft'," +
+                    "`schedule_cron` varchar(64) DEFAULT NULL," +
+                    "`config_json` json DEFAULT NULL," +
+                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "PRIMARY KEY (`id`)," +
+                    "KEY `idx_family_id` (`family_id`)," +
+                    "KEY `idx_status` (`status`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流定义'");
+            log.info("迁移329: 已创建task_orchestration_flows表");
+        } catch (Exception e) {
+            log.warn("迁移329: task_orchestration_flows 已存在或创建失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_edges` (" +
+                    "`id` bigint NOT NULL AUTO_INCREMENT," +
+                    "`flow_id` bigint NOT NULL," +
+                    "`from_node_id` varchar(64) NOT NULL," +
+                    "`to_node_id` varchar(64) NOT NULL," +
+                    "`edge_type` varchar(20) NOT NULL," +
+                    "`operator` varchar(10) NOT NULL DEFAULT 'AND'," +
+                    "`sort_order` int NOT NULL DEFAULT 0," +
+                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+                    "PRIMARY KEY (`id`)," +
+                    "KEY `idx_flow_id` (`flow_id`)," +
+                    "UNIQUE KEY `uk_flow_edge` (`flow_id`, `from_node_id`, `to_node_id`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流边定义'");
+            log.info("迁移329: 已创建task_orchestration_edges表");
+        } catch (Exception e) {
+            log.warn("迁移329: task_orchestration_edges 已存在或创建失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_executions` (" +
+                    "`id` bigint NOT NULL AUTO_INCREMENT," +
+                    "`flow_id` bigint NOT NULL," +
+                    "`flow_version` int NOT NULL DEFAULT 1," +
+                    "`family_id` bigint NOT NULL," +
+                    "`family_member_id` bigint NOT NULL," +
+                    "`trigger_source` varchar(20) NOT NULL DEFAULT 'manual'," +
+                    "`status` varchar(20) NOT NULL DEFAULT 'running'," +
+                    "`started_at` datetime NOT NULL," +
+                    "`finished_at` datetime DEFAULT NULL," +
+                    "`error_reason` varchar(255) DEFAULT NULL," +
+                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "PRIMARY KEY (`id`)," +
+                    "KEY `idx_execution_flow` (`flow_id`)," +
+                    "KEY `idx_execution_status` (`status`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流执行记录'");
+            log.info("迁移329: 已创建task_orchestration_executions表");
+        } catch (Exception e) {
+            log.warn("迁移329: task_orchestration_executions 已存在或创建失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_node_instances` (" +
+                    "`id` bigint NOT NULL AUTO_INCREMENT," +
+                    "`execution_id` bigint NOT NULL," +
+                    "`node_id` varchar(64) NOT NULL," +
+                    "`task_id` bigint DEFAULT NULL," +
+                    "`status` varchar(20) NOT NULL DEFAULT 'pending'," +
+                    "`generation` int NOT NULL DEFAULT 0," +
+                    "`condition_met_at` datetime DEFAULT NULL COMMENT '条件终止:外部API通知条件达成时间'," +
+                    "`loop_termination_reason` varchar(50) DEFAULT NULL," +
+                    "`started_at` datetime DEFAULT NULL," +
+                    "`completed_at` datetime DEFAULT NULL," +
+                    "`error_reason` varchar(255) DEFAULT NULL," +
+                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "PRIMARY KEY (`id`)," +
+                    "UNIQUE KEY `uk_node_instance` (`execution_id`, `node_id`, `generation`)," +
+                    "KEY `idx_execution_id` (`execution_id`)," +
+                    "KEY `idx_task_id` (`task_id`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流节点实例'");
+            log.info("迁移329: 已创建task_orchestration_node_instances表");
+        } catch (Exception e) {
+            log.warn("迁移329: task_orchestration_node_instances 已存在或创建失败: {}", e.getMessage());
+        }
+
+        // 迁移318: sys_config 预置首页引导浮层默认配置(onboarding_guide_config key)
+        try {
+            String defaultConfig = "{"
+                + "\"enabled\":true,"
+                + "\"version\":\"1.0\","
+                + "\"positions\":[\"home\",\"child\"],"
+                + "\"parent\":["
+                    + "{\"icon\":\"🔍\",\"title\":\"你真的了解孩子的状态吗?\",\"desc\":\"情绪低落没察觉、习惯变化没注意、能力短板没发现——很多问题等爆发了才看见。浠艾福帮你在问题发生前,看清孩子真实的身心状态。\",\"isIntro\":true},"
+                    + "{\"icon\":\"🌏\",\"title\":\"五维沙盘,看见全家\",\"desc\":\"身·心·智·行·富,一张全景图看清每个维度。孩子身体好不好、情绪怎么样、能力到哪了——不用猜,看得见.\"},"
+                    + "{\"icon\":\"📋\",\"title\":\"小任务,大改变\",\"desc\":\"布置每日小任务,完成就能攒星星。每天一点点积累,好习惯自然养成.\"},"
+                    + "{\"icon\":\"🎁\",\"title\":\"心愿墙,看得见努力\",\"desc\":\"孩子攒够星星就能兑换心愿。努力有回报,目标感和坚持力一起培养.\"}"
+                + "],"
+                + "\"child\":["
+                    + "{\"icon\":\"🔍\",\"title\":\"你的每一点成长,都被看见\",\"desc\":\"今天完成了几个任务?比昨天进步了多少?浠艾福帮你记录每一点努力,让成长看得见。\",\"isIntro\":true},"
+                    + "{\"icon\":\"🌟\",\"title\":\"赚星星\",\"desc\":\"完成爸爸妈妈布置的任务,就能获得星星积分,越努力越多!\"},"
+                    + "{\"icon\":\"🎮\",\"title\":\"玩中学\",\"desc\":\"完成任务后可以玩小游戏、兑换心愿,学习和快乐两不误!\"},"
+                    + "{\"icon\":\"🏆\",\"title\":\"升级挑战\",\"desc\":\"从星星宝宝到宇宙小英雄,一步步升级,看看你能走多远!\"}"
+                + "]"
+                + "}";
+            jdbcTemplate.update(
+                "INSERT IGNORE INTO sys_config (config_key, config_value, description) VALUES ('onboarding_guide_config', ?, '首页引导浮层配置')",
+                defaultConfig
+            );
+            log.info("迁移318: 已预置onboarding_guide_config默认配置");
+        } catch (Exception e) {
+            log.warn("预置onboarding_guide_config可能已存在: {}", e.getMessage());
+        }
+        // 迁移328: 饮食/运动打卡表补充照片与语音字段(前端已上传 media URL,补上持久化列)
+        try {
+            ensureColumn("health_meal_record", "photo_urls", "TEXT COMMENT '照片URL列表(逗号分隔)'");
+            ensureColumn("health_meal_record", "voice_url", "VARCHAR(500) COMMENT '语音URL'");
+            ensureColumn("health_meal_record", "voice_text", "TEXT COMMENT '语音转写文字'");
+            ensureColumn("exercise_record", "photo_urls", "TEXT COMMENT '照片URL列表(逗号分隔)'");
+            ensureColumn("exercise_record", "voice_url", "VARCHAR(500) COMMENT '语音URL'");
+            ensureColumn("exercise_record", "voice_text", "TEXT COMMENT '语音转写文字'");
+            log.info("迁移328: 已为health_meal_record/exercise_record补充照片语音字段");
+        } catch (Exception e) {
+            log.warn("迁移328: 补充照片语音字段失败: {}", e.getMessage());
+        }
+
+
         // 迁移323: 注册素质测评A2报告类型(dan家族)+ 默认指纹规则
         // 统一上传后指纹识别为 dan_a2 → ReportCollectService 按 report_type_registry.family=dan 分流走 DAN 采集管道
         try {
@@ -11278,254 +11449,6 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         } catch (Exception e) {
             log.warn("迁移312: 写入 gut_flora 指纹规则失败: {}", e.getMessage());
         }
-
-        // 迁移316: 创建 recommendation_rules 表(统一推荐规则配置)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS recommendation_rules (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "rule_type TINYINT NOT NULL COMMENT '1-5', " +
-                    "rule_name VARCHAR(100) COMMENT '运营名称', " +
-                    "trigger_product_id BIGINT COMMENT '触发商品ID', " +
-                    "trigger_product_ids VARCHAR(1000) COMMENT '触发商品ID JSON 数组', " +
-                    "target_product_id BIGINT COMMENT '目标商品ID', " +
-                    "priority INT DEFAULT 10 COMMENT '优先级,越大越优', " +
-                    "enabled TINYINT DEFAULT 1 COMMENT '启用 1/0', " +
-                    "time_window_days INT COMMENT '回溯窗口(天)', " +
-                    "delay_days INT COMMENT '复购延迟天数', " +
-                    "scope VARCHAR(20) COMMENT 'family/individual', " +
-                    "scene_codes VARCHAR(500) COMMENT '生效场景 JSON 数组', " +
-                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品推荐规则配置'");
-            log.info("迁移316: 已创建recommendation_rules表");
-        } catch (Exception e) {
-            // 表已存在,忽略错误
-        }
-
-        // 迁移317: recommendation_rules 表索引
-        try {
-            jdbcTemplate.execute("CREATE INDEX idx_rules_enabled ON recommendation_rules(enabled)");
-            log.info("迁移317: 已创建idx_rules_enabled索引");
-        } catch (Exception e) {
-            // 索引已存在,忽略错误
-        }
-        try {
-            jdbcTemplate.execute("CREATE INDEX idx_rules_scene ON recommendation_rules(scene_codes)");
-            log.info("迁移317: 已创建idx_rules_scene索引");
-        } catch (Exception e) {
-            // 索引已存在,忽略错误
-        }
-        try {
-            jdbcTemplate.execute("CREATE INDEX idx_rules_priority ON recommendation_rules(priority)");
-            log.info("迁移317: 已创建idx_rules_priority索引");
-        } catch (Exception e) {
-            // 索引已存在,忽略错误
-        }
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_flows` (" +
-                    "`id` bigint NOT NULL AUTO_INCREMENT," +
-                    "`name` varchar(100) NOT NULL," +
-                    "`description` text," +
-                    "`creator_id` bigint NOT NULL," +
-                    "`family_id` bigint DEFAULT NULL," +
-                    "`version` int NOT NULL DEFAULT 1," +
-                    "`status` varchar(20) NOT NULL DEFAULT 'draft'," +
-                    "`schedule_cron` varchar(64) DEFAULT NULL," +
-                    "`config_json` json DEFAULT NULL," +
-                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
-                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
-                    "PRIMARY KEY (`id`)," +
-                    "KEY `idx_family_id` (`family_id`)," +
-                    "KEY `idx_status` (`status`)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流定义'");
-            log.info("迁移329: 已创建task_orchestration_flows表");
-        } catch (Exception e) {
-            log.warn("迁移329: task_orchestration_flows 已存在或创建失败: {}", e.getMessage());
-        }
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_edges` (" +
-                    "`id` bigint NOT NULL AUTO_INCREMENT," +
-                    "`flow_id` bigint NOT NULL," +
-                    "`from_node_id` varchar(64) NOT NULL," +
-                    "`to_node_id` varchar(64) NOT NULL," +
-                    "`edge_type` varchar(20) NOT NULL," +
-                    "`operator` varchar(10) NOT NULL DEFAULT 'AND'," +
-                    "`sort_order` int NOT NULL DEFAULT 0," +
-                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
-                    "PRIMARY KEY (`id`)," +
-                    "KEY `idx_flow_id` (`flow_id`)," +
-                    "UNIQUE KEY `uk_flow_edge` (`flow_id`, `from_node_id`, `to_node_id`)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流边定义'");
-            log.info("迁移329: 已创建task_orchestration_edges表");
-        } catch (Exception e) {
-            log.warn("迁移329: task_orchestration_edges 已存在或创建失败: {}", e.getMessage());
-        }
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_executions` (" +
-                    "`id` bigint NOT NULL AUTO_INCREMENT," +
-                    "`flow_id` bigint NOT NULL," +
-                    "`flow_version` int NOT NULL DEFAULT 1," +
-                    "`family_id` bigint NOT NULL," +
-                    "`family_member_id` bigint NOT NULL," +
-                    "`trigger_source` varchar(20) NOT NULL DEFAULT 'manual'," +
-                    "`status` varchar(20) NOT NULL DEFAULT 'running'," +
-                    "`started_at` datetime NOT NULL," +
-                    "`finished_at` datetime DEFAULT NULL," +
-                    "`error_reason` varchar(255) DEFAULT NULL," +
-                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
-                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
-                    "PRIMARY KEY (`id`)," +
-                    "KEY `idx_execution_flow` (`flow_id`)," +
-                    "KEY `idx_execution_status` (`status`)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流执行记录'");
-            log.info("迁移329: 已创建task_orchestration_executions表");
-        } catch (Exception e) {
-            log.warn("迁移329: task_orchestration_executions 已存在或创建失败: {}", e.getMessage());
-        }
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `task_orchestration_node_instances` (" +
-                    "`id` bigint NOT NULL AUTO_INCREMENT," +
-                    "`execution_id` bigint NOT NULL," +
-                    "`node_id` varchar(64) NOT NULL," +
-                    "`task_id` bigint DEFAULT NULL," +
-                    "`status` varchar(20) NOT NULL DEFAULT 'pending'," +
-                    "`generation` int NOT NULL DEFAULT 0," +
-                    "`condition_met_at` datetime DEFAULT NULL COMMENT '条件终止:外部API通知条件达成时间'," +
-                    "`loop_termination_reason` varchar(50) DEFAULT NULL," +
-                    "`started_at` datetime DEFAULT NULL," +
-                    "`completed_at` datetime DEFAULT NULL," +
-                    "`error_reason` varchar(255) DEFAULT NULL," +
-                    "`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP," +
-                    "`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
-                    "PRIMARY KEY (`id`)," +
-                    "UNIQUE KEY `uk_node_instance` (`execution_id`, `node_id`, `generation`)," +
-                    "KEY `idx_execution_id` (`execution_id`)," +
-                    "KEY `idx_task_id` (`task_id`)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务编排流节点实例'");
-            log.info("迁移329: 已创建task_orchestration_node_instances表");
-        } catch (Exception e) {
-            log.warn("迁移329: task_orchestration_node_instances 已存在或创建失败: {}", e.getMessage());
-        }
-
-        // 迁移318: sys_config 预置首页引导浮层默认配置(onboarding_guide_config key)
-        try {
-            String defaultConfig = "{"
-                + "\"enabled\":true,"
-                + "\"version\":\"1.0\","
-                + "\"positions\":[\"home\",\"child\"],"
-                + "\"parent\":["
-                    + "{\"icon\":\"🔍\",\"title\":\"你真的了解孩子的状态吗?\",\"desc\":\"情绪低落没察觉、习惯变化没注意、能力短板没发现——很多问题等爆发了才看见。浠艾福帮你在问题发生前,看清孩子真实的身心状态。\",\"isIntro\":true},"
-                    + "{\"icon\":\"🌏\",\"title\":\"五维沙盘,看见全家\",\"desc\":\"身·心·智·行·富,一张全景图看清每个维度。孩子身体好不好、情绪怎么样、能力到哪了——不用猜,看得见.\"},"
-                    + "{\"icon\":\"📋\",\"title\":\"小任务,大改变\",\"desc\":\"布置每日小任务,完成就能攒星星。每天一点点积累,好习惯自然养成.\"},"
-                    + "{\"icon\":\"🎁\",\"title\":\"心愿墙,看得见努力\",\"desc\":\"孩子攒够星星就能兑换心愿。努力有回报,目标感和坚持力一起培养.\"}"
-                + "],"
-                + "\"child\":["
-                    + "{\"icon\":\"🔍\",\"title\":\"你的每一点成长,都被看见\",\"desc\":\"今天完成了几个任务?比昨天进步了多少?浠艾福帮你记录每一点努力,让成长看得见。\",\"isIntro\":true},"
-                    + "{\"icon\":\"🌟\",\"title\":\"赚星星\",\"desc\":\"完成爸爸妈妈布置的任务,就能获得星星积分,越努力越多!\"},"
-                    + "{\"icon\":\"🎮\",\"title\":\"玩中学\",\"desc\":\"完成任务后可以玩小游戏、兑换心愿,学习和快乐两不误!\"},"
-                    + "{\"icon\":\"🏆\",\"title\":\"升级挑战\",\"desc\":\"从星星宝宝到宇宙小英雄,一步步升级,看看你能走多远!\"}"
-                + "]"
-                + "}";
-            jdbcTemplate.update(
-                "INSERT IGNORE INTO sys_config (config_key, config_value, description) VALUES ('onboarding_guide_config', ?, '首页引导浮层配置')",
-                defaultConfig
-            );
-            log.info("迁移318: 已预置onboarding_guide_config默认配置");
-        } catch (Exception e) {
-            log.warn("预置onboarding_guide_config可能已存在: {}", e.getMessage());
-        }
-        // 迁移328: 饮食/运动打卡表补充照片与语音字段(前端已上传 media URL,补上持久化列)
-        try {
-            ensureColumn("health_meal_record", "photo_urls", "TEXT COMMENT '照片URL列表(逗号分隔)'");
-            ensureColumn("health_meal_record", "voice_url", "VARCHAR(500) COMMENT '语音URL'");
-            ensureColumn("health_meal_record", "voice_text", "TEXT COMMENT '语音转写文字'");
-            ensureColumn("exercise_record", "photo_urls", "TEXT COMMENT '照片URL列表(逗号分隔)'");
-            ensureColumn("exercise_record", "voice_url", "VARCHAR(500) COMMENT '语音URL'");
-            ensureColumn("exercise_record", "voice_text", "TEXT COMMENT '语音转写文字'");
-            log.info("迁移328: 已为health_meal_record/exercise_record补充照片语音字段");
-        } catch (Exception e) {
-            log.warn("迁移328: 补充照片语音字段失败: {}", e.getMessage());
-        }
-
-        // 迁移329: 创建 guide_popup 表(弹窗引导页配置化管理)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_popup (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-                    "slug VARCHAR(64) NOT NULL UNIQUE COMMENT '唯一标识', " +
-                    "title VARCHAR(128) NOT NULL DEFAULT '' COMMENT '弹窗标题', " +
-                    "description TEXT COMMENT '主描述文案', " +
-                    "icon VARCHAR(256) NOT NULL DEFAULT '' COMMENT '图标:emoji或图片URL', " +
-                    "icon_type TINYINT NOT NULL DEFAULT 0 COMMENT '图标类型:0=emoji 1=图片', " +
-                    "theme_color VARCHAR(20) NOT NULL DEFAULT '#F97316' COMMENT '主题色', " +
-                    "btn_text VARCHAR(64) NOT NULL DEFAULT '知道了' COMMENT '按钮文案', " +
-                    "btn_action VARCHAR(256) NOT NULL DEFAULT '' COMMENT '点击跳转路径,空=关闭', " +
-                    "status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=禁用 1=启用', " +
-                    "sort_order INT NOT NULL DEFAULT 0 COMMENT '排序优先级', " +
-                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
-                    "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
-                    "INDEX idx_status_sort (status, sort_order)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='引导弹窗配置表'");
-            log.info("迁移329: 已创建 guide_popup 表");
-        } catch (Exception e) {
-            log.warn("guide_popup 表可能已存在: {}", e.getMessage());
-        }
-
-        // 迁移330: 创建 guide_popup_step 表(引导弹窗步骤)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_popup_step (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-                    "popup_id BIGINT NOT NULL COMMENT '关联 guide_popup.id', " +
-                    "step_order INT NOT NULL DEFAULT 0 COMMENT '步骤顺序', " +
-                    "title VARCHAR(128) NOT NULL DEFAULT '' COMMENT '步骤标题', " +
-                    "desc TEXT COMMENT '步骤描述', " +
-                    "icon VARCHAR(256) NOT NULL DEFAULT '' COMMENT '步骤图标', " +
-                    "icon_type TINYINT NOT NULL DEFAULT 0 COMMENT '图标类型:0=emoji 1=图片', " +
-                    "is_intro TINYINT NOT NULL DEFAULT 0 COMMENT '是否为开场身份定义页', " +
-                    "btn_text VARCHAR(64) NOT NULL DEFAULT '下一步' COMMENT '本步骤按钮文案', " +
-                    "btn_action VARCHAR(256) NOT NULL DEFAULT '' COMMENT '最后一步跳转链接', " +
-                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
-                    "INDEX idx_popup_order (popup_id, step_order)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='引导弹窗步骤表'");
-            log.info("迁移330: 已创建 guide_popup_step 表");
-        } catch (Exception e) {
-            log.warn("guide_popup_step 表可能已存在: {}", e.getMessage());
-        }
-
-        // 迁移331: 创建 guide_popup_trigger 表(引导弹窗触发条件)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_popup_trigger (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-                    "popup_id BIGINT NOT NULL COMMENT '关联 guide_popup.id', " +
-                    "trigger_type VARCHAR(32) NOT NULL COMMENT '触发类型:page_enter/task_complete/assessment_done/first_login/daily_active/role_change', " +
-                    "target_page VARCHAR(128) DEFAULT '' COMMENT '目标页面路径', " +
-                    "target_action VARCHAR(128) DEFAULT '' COMMENT '触发行为标识', " +
-                    "min_role VARCHAR(32) DEFAULT 'parent' COMMENT '最低角色要求', " +
-                    "extra_config JSON DEFAULT NULL COMMENT '扩展条件', " +
-                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
-                    "INDEX idx_popup_trigger (popup_id)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='引导弹窗触发条件表'");
-            log.info("迁移331: 已创建 guide_popup_trigger 表");
-        } catch (Exception e) {
-            log.warn("guide_popup_trigger 表可能已存在: {}", e.getMessage());
-        }
-
-        // 迁移332: 创建 guide_popup_log 表(引导弹窗用户消费记录)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_popup_log (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-                    "popup_id BIGINT NOT NULL COMMENT '关联 guide_popup.id', " +
-                    "user_id BIGINT NOT NULL COMMENT '用户ID users.id', " +
-                    "member_id BIGINT DEFAULT NULL COMMENT '家庭成员ID family_members.id', " +
-                    "role VARCHAR(32) NOT NULL COMMENT '弹出时所在角色', " +
-                    "consumed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '消费时间', " +
-                    "button_clicked TINYINT NOT NULL DEFAULT 0 COMMENT '是否点击按钮', " +
-                    "INDEX idx_user_popup (user_id, popup_id), " +
-                    "INDEX idx_consume_time (consumed_at)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='引导弹窗用户消费记录表'");
-            log.info("迁移332: 已创建 guide_popup_log 表");
-        } catch (Exception e) {
-            log.warn("guide_popup_log 表可能已存在: {}", e.getMessage());
-        }
     }
 
     /**

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/SysConfigService.java

@@ -91,7 +91,7 @@ public class SysConfigService {
     /**
      * 内部方法:获取配置值(供其他 Service 调用)
      */
-    @Cacheable(value = "sysConfig", key = "#key")
+    @Cacheable(value = "sysConfig", key = "#key", unless = "#result == null")
     public String getValue(String key) {
         SysConfig config = configMapper.selectOne(
             new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, key));