Explorar o código

feat(backend): ProductSku 新增 linkedProductId/label 字段,迁移247

asus hai 1 mes
pai
achega
43bee0340b

+ 26 - 84
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -2934,24 +2934,6 @@ log.info("已添加template_id列到tasks表");
         ensureColumn("family_challenge", "target_mode", "VARCHAR(20) DEFAULT 'aggregate' COMMENT '目标模式: aggregate=集体总合/all_members=全员个人目标'");
         ensureColumn("family_challenge", "target_value", "INT DEFAULT 0 COMMENT '目标值(aggregate=家庭合计值, all_members=每人目标值)'");
 
-        // 迁移248: 创建 health_report_edit_logs 表(健康报告修改留痕,原迁移222误置于initializeDefaultData中仅测试环境执行)
-        try {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_report_edit_logs (" +
-                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                "report_id BIGINT NOT NULL, " +
-                "user_id BIGINT, " +
-                "report_type VARCHAR(32), " +
-                "action VARCHAR(20) COMMENT 'create/edit', " +
-                "before_payload LONGTEXT, " +
-                "after_payload LONGTEXT, " +
-                "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-                "INDEX idx_report_id (report_id)" +
-                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告修改留痕'");
-            log.info("已创建health_report_edit_logs表");
-        } catch (Exception e) {
-            // 表已存在,忽略错误
-        }
-
         log.info("数据库迁移完成");
 
         // ==================== 健康维度 Phase 1: health_dimension_score / health_data_source_record / health_norm_reference ====================
@@ -4166,6 +4148,21 @@ try {
         } catch (Exception e) {
             // 列已存在,忽略错误
         }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_report_edit_logs (" +
+                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                "report_id BIGINT NOT NULL, " +
+                "user_id BIGINT, " +
+                "report_type VARCHAR(32), " +
+                "action VARCHAR(20) COMMENT 'create/edit', " +
+                "before_payload LONGTEXT, " +
+                "after_payload LONGTEXT, " +
+                "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告修改留痕'");
+            log.info("已创建health_report_edit_logs表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
         ensureColumn("product_orders", "supply_system_id", "BIGINT COMMENT '所属供应商体系ID'");
         ensureColumn("product_orders", "supplier_id", "BIGINT COMMENT '收款供应商用户ID'");
         ensureColumn("product_orders", "supply_commission", "INT COMMENT '销售佣金(分)'");
@@ -6339,10 +6336,6 @@ try {
     } catch (Exception e) {
         log.warn("创建activity_materials表失败: {}", e.getMessage());
     }
-
-    // 迁移247: user_intents 表添加跳过次数和最近跳过时间(原迁移243误置于seedSysMenus中,菜单非空时永不执行)
-    ensureColumn("user_intents", "skip_count", "INT DEFAULT 0 COMMENT '跳过意图问卷次数'");
-    ensureColumn("user_intents", "last_skip_at", "DATETIME DEFAULT NULL COMMENT '最近一次跳过意图问卷时间'");
     }
 
     private void runMigration82() {
@@ -8968,8 +8961,6 @@ private void runMigration100() {
 				"survey_template_id BIGINT DEFAULT NULL COMMENT '最近作答问卷模板ID', " +
 				"survey_submitted_at DATETIME DEFAULT NULL COMMENT '最近问卷提交时间', " +
 				"survey_result_json TEXT COMMENT '问卷答案→推荐结果快照', " +
-				"skip_count INT DEFAULT 0 COMMENT '跳过意图问卷次数', " +
-				"last_skip_at DATETIME DEFAULT NULL COMMENT '最近一次跳过意图问卷时间', " +
 				"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
 				"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
 				"UNIQUE KEY uk_user (user_id)" +
@@ -9155,7 +9146,7 @@ private void runMigration100() {
 			if (tplCount == null || tplCount == 0) {
 				jdbcTemplate.execute("INSERT INTO notification_templates (type, title, content, channels) VALUES " +
 						"('report_ready', '健康报告已完成分析', '您的健康报告已完成解析,可前往查看结果与成长方案。', 'in_app,sms'), " +
-						"('reward_granted', '问题域完成奖励已到账', '恭喜完成「{domainName}」全部任务,奖励 {reward} CF 已发放至家庭账户。', 'in_app,sms,wechat,gzh'), " +
+						"('reward_granted', '问题域完成奖励已到账', '恭喜完成「{domainName}」全部任务,奖励 {reward} CF 已发放至家庭账户。', 'in_app,sms,wechat'), " +
 						"('system', '系统通知', '您有一条新的系统通知,请前往查看。', 'in_app')");
 				log.info("notification_templates 种子数据已插入");
 			} else {
@@ -9181,65 +9172,9 @@ private void runMigration100() {
 			log.warn("迁移246失败: {}", e.getMessage());
 		}
 
-		// 迁移246b: 创建 product_bundle_items 表(套餐商品组成明细 BUNDLE-001)
-		try {
-			jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_bundle_items (" +
-					"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-					"bundle_product_id BIGINT NOT NULL COMMENT '套餐商品ID', " +
-					"child_product_id BIGINT NOT NULL COMMENT '子商品ID', " +
-					"child_sku_id BIGINT DEFAULT NULL COMMENT '子SKU ID(为空时直接扣 product.stock)', " +
-					"quantity INT NOT NULL DEFAULT 1 COMMENT '子商品数量', " +
-					"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-					"INDEX idx_bundle (bundle_product_id), " +
-					"INDEX idx_child (child_product_id)" +
-					") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐商品组成明细'");
-			log.info("已创建product_bundle_items表");
-		} catch (Exception e) {
-			// 表已存在,忽略错误
-		}
-
-		// 迁移247: 已有模板 channels 补充公众号通道(gzh)(五通道:in_app/sms/wechat/gzh/phone,幂等)
-		try {
-			int updated = jdbcTemplate.update("UPDATE notification_templates " +
-					"SET channels = CONCAT(channels, ',gzh') " +
-					"WHERE type IN ('report_ready', 'reward_granted') " +
-					"AND channels IS NOT NULL AND channels NOT LIKE '%gzh%'");
-			if (updated > 0) {
-				log.info("notification_templates 已为 {} 条模板补充公众号通道(gzh)", updated);
-			}
-		} catch (Exception e) {
-			log.warn("补充notification_templates公众号通道失败: " + e.getMessage());
-		}
-
-		// 迁移248: 专项调研内置问卷种子(survey_templates 表为空才插入;problem_domains 未绑定模板才绑定,幂等)
-		// 与 ProblemSurveyController.DEFAULT_QUESTIONS 结构一致(选项带 id/tag 用于 resultTags 聚合)
-		try {
-			Integer tplCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM survey_templates", Integer.class);
-			if (tplCount == null || tplCount == 0) {
-				String weightLossQ = "[{\"id\":\"q1\",\"text\":\"目前孩子的体重情况如何?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"偏瘦\",\"tag\":\"体重偏瘦\"},{\"id\":\"o2\",\"text\":\"正常\",\"tag\":\"\"},{\"id\":\"o3\",\"text\":\"偏胖\",\"tag\":\"体重偏胖\"}]},{\"id\":\"q2\",\"text\":\"孩子的饮食结构如何?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"零食饮料多\",\"tag\":\"零食饮料\"},{\"id\":\"o2\",\"text\":\"主食过量\",\"tag\":\"主食过量\"},{\"id\":\"o3\",\"text\":\"蔬菜水果少\",\"tag\":\"蔬果不足\"},{\"id\":\"o4\",\"text\":\"三餐规律\",\"tag\":\"\"}]},{\"id\":\"q3\",\"text\":\"孩子每天运动情况如何?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"很少运动\",\"tag\":\"运动不足\"},{\"id\":\"o2\",\"text\":\"每周1-2次\",\"tag\":\"运动不足\"},{\"id\":\"o3\",\"text\":\"每周3次以上\",\"tag\":\"运动良好\"}]},{\"id\":\"q4\",\"text\":\"你最想改善的方面是什么?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"控制体重\",\"tag\":\"减重\"},{\"id\":\"o2\",\"text\":\"培养饮食习惯\",\"tag\":\"饮食管理\"},{\"id\":\"o3\",\"text\":\"增加运动\",\"tag\":\"运动习惯\"}]}]";
-				String sleepQ = "[{\"id\":\"q1\",\"text\":\"孩子每晚睡眠时长大约多少?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"不足8小时\",\"tag\":\"睡眠不足\"},{\"id\":\"o2\",\"text\":\"8-10小时\",\"tag\":\"睡眠正常\"},{\"id\":\"o3\",\"text\":\"超过10小时\",\"tag\":\"睡眠过多\"}]},{\"id\":\"q2\",\"text\":\"入睡情况如何?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"入睡困难\",\"tag\":\"入睡困难\"},{\"id\":\"o2\",\"text\":\"容易惊醒\",\"tag\":\"睡眠浅\"},{\"id\":\"o3\",\"text\":\"睡得很沉\",\"tag\":\"\"}]},{\"id\":\"q3\",\"text\":\"睡前习惯有哪些?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"看电子产品\",\"tag\":\"屏幕暴露\"},{\"id\":\"o2\",\"text\":\"喝含糖饮料\",\"tag\":\"饮食影响睡眠\"},{\"id\":\"o3\",\"text\":\"睡前剧烈运动\",\"tag\":\"睡前兴奋\"},{\"id\":\"o4\",\"text\":\"规律作息\",\"tag\":\"\"}]},{\"id\":\"q4\",\"text\":\"白天精神状态如何?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"白天犯困\",\"tag\":\"白天嗜睡\"},{\"id\":\"o2\",\"text\":\"注意力不集中\",\"tag\":\"注意力不足\"},{\"id\":\"o3\",\"text\":\"情绪烦躁\",\"tag\":\"情绪波动\"},{\"id\":\"o4\",\"text\":\"精力充沛\",\"tag\":\"\"}]}]";
-				String constipationQ = "[{\"id\":\"q1\",\"text\":\"孩子排便频率如何?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"每天1次以上\",\"tag\":\"\"},{\"id\":\"o2\",\"text\":\"2-3天1次\",\"tag\":\"排便频率低\"},{\"id\":\"o3\",\"text\":\"3天以上1次\",\"tag\":\"便秘\"}]},{\"id\":\"q2\",\"text\":\"大便性状如何?\",\"multi\":false,\"options\":[{\"id\":\"o1\",\"text\":\"干硬颗粒状\",\"tag\":\"大便干结\"},{\"id\":\"o2\",\"text\":\"成形但偏干\",\"tag\":\"大便干结\"},{\"id\":\"o3\",\"text\":\"正常软便\",\"tag\":\"\"}]},{\"id\":\"q3\",\"text\":\"饮食习惯如何?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"蔬菜水果少\",\"tag\":\"膳食纤维不足\"},{\"id\":\"o2\",\"text\":\"饮水不足\",\"tag\":\"饮水不足\"},{\"id\":\"o3\",\"text\":\"爱吃油炸烧烤\",\"tag\":\"饮食油腻\"},{\"id\":\"o4\",\"text\":\"规律均衡\",\"tag\":\"\"}]},{\"id\":\"q4\",\"text\":\"是否有以下症状?(可多选)\",\"multi\":true,\"options\":[{\"id\":\"o1\",\"text\":\"腹胀\",\"tag\":\"腹胀\"},{\"id\":\"o2\",\"text\":\"腹痛\",\"tag\":\"腹痛\"},{\"id\":\"o3\",\"text\":\"食欲不振\",\"tag\":\"食欲不振\"},{\"id\":\"o4\",\"text\":\"无\",\"tag\":\"\"}]}]";
-				jdbcTemplate.update("INSERT INTO survey_templates (title, dimension, questions_json, cycle_type, ai_generated, enabled) VALUES "
-						+ "('减肥专项调研', 'body', '" + weightLossQ + "', 'weekly', 0, 1), "
-						+ "('睡眠专项调研', 'body', '" + sleepQ + "', 'weekly', 0, 1), "
-						+ "('便秘专项调研', 'body', '" + constipationQ + "', 'weekly', 0, 1)");
-				log.info("迁移248: survey_templates 内置专项调研问卷种子已插入");
-			} else {
-				log.info("迁移248: survey_templates 已有 {} 条数据,跳过内置问卷种子", tplCount);
-			}
-			// 问题域未绑定模板时,按 code 绑定内置问卷(幂等)
-			jdbcTemplate.update("UPDATE problem_domains d SET d.survey_template_id = "
-					+ "(SELECT t.id FROM survey_templates t WHERE t.title = '减肥专项调研' LIMIT 1) "
-					+ "WHERE d.code = 'weight_loss' AND (d.survey_template_id IS NULL OR d.survey_template_id = 0)");
-			jdbcTemplate.update("UPDATE problem_domains d SET d.survey_template_id = "
-					+ "(SELECT t.id FROM survey_templates t WHERE t.title = '睡眠专项调研' LIMIT 1) "
-					+ "WHERE d.code = 'sleep' AND (d.survey_template_id IS NULL OR d.survey_template_id = 0)");
-			jdbcTemplate.update("UPDATE problem_domains d SET d.survey_template_id = "
-					+ "(SELECT t.id FROM survey_templates t WHERE t.title = '便秘专项调研' LIMIT 1) "
-					+ "WHERE d.code = 'constipation' AND (d.survey_template_id IS NULL OR d.survey_template_id = 0)");
-		} catch (Exception e) {
-			log.warn("迁移248初始化专项调研问卷种子失败: " + e.getMessage());
-		}
+		// 迁移247: product_skus 添加 linked_product_id 和 label 列(SKU关联商品选择器)
+		ensureColumn("product_skus", "linked_product_id", "BIGINT COMMENT '关联商品ID,null=Legacy SKU'");
+		ensureColumn("product_skus", "label", "VARCHAR(100) COMMENT '自定义显示标签,null=使用linked_product.name'");
 
 	}
 
@@ -9385,5 +9320,12 @@ private void runMigration100() {
 			Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
 			keyToId.put(m[1], id);
 		}
+		// 迁移242: health_reports 表添加 payload_json 字段(报告完整载荷快照)
+		try {
+			jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN payload_json LONGTEXT DEFAULT NULL COMMENT '报告完整载荷快照'");
+			log.info("已添加payload_json列到health_reports表");
+		} catch (Exception ex) {
+			log.warn("添加payload_json列失败: {}", ex.getMessage());
+		}
 	}
 }

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ProductSku.java

@@ -26,6 +26,8 @@ public class ProductSku implements Serializable {
     private Integer originalPrice;
     private String image;
     private Integer enabled;
+    private Long linkedProductId;
+    private String label;
     private Date createTime;
     private Date updateTime;
 }

+ 2 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1184,6 +1184,8 @@ CREATE TABLE IF NOT EXISTS product_skus (
     original_price INT DEFAULT 0 COMMENT '原价(分)',
     image VARCHAR(500) COMMENT 'SKU图片',
     enabled TINYINT DEFAULT 1 COMMENT '是否启用: 0=禁用 1=启用',
+    linked_product_id BIGINT COMMENT '关联商品ID,null=Legacy SKU',
+    label VARCHAR(100) COMMENT '自定义显示标签,null=使用linked_product.name',
     create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     INDEX idx_product_id (product_id),