|
|
@@ -8560,458 +8560,5 @@ private void runMigration100() {
|
|
|
|
|
|
// 迁移225: articles 添加 growth_category 列(成长分类筛选,Article 实体已含字段,生产库缺列导致 Unknown column 'growth_category')
|
|
|
ensureColumn("articles", "growth_category", "VARCHAR(50) DEFAULT NULL COMMENT '成长分类'");
|
|
|
-
|
|
|
- // 迁移226: products 添加 growth_category 列(成长分类筛选,Product 实体已含字段,生产库缺列导致 Unknown column 'growth_category')
|
|
|
- ensureColumn("products", "growth_category", "VARCHAR(50) DEFAULT NULL COMMENT '成长分类'");
|
|
|
-
|
|
|
- // 迁移227: product_skus.id 升级为 BIGINT(生产库为 INT,插入超出 INT 范围的 id 报 Out of range value for column 'id')
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("ALTER TABLE product_skus MODIFY COLUMN id BIGINT AUTO_INCREMENT");
|
|
|
- jdbcTemplate.execute("ALTER TABLE product_skus MODIFY COLUMN product_id BIGINT NOT NULL COMMENT '所属商品ID'");
|
|
|
- log.info("product_skus.id/product_id 已升级为 BIGINT");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("product_skus.id/product_id 升级失败(可能已修改): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移209: 创建 survey_templates 表(定期调研模板)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS survey_templates (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "title VARCHAR(100) NOT NULL COMMENT '调研标题', " +
|
|
|
- "dimension VARCHAR(20) COMMENT '适用维度(身/智/富/行/心)', " +
|
|
|
- "questions_json TEXT COMMENT '题目JSON(含选项)', " +
|
|
|
- "cycle_type VARCHAR(20) DEFAULT 'weekly' COMMENT '周期: weekly/biweekly/monthly', " +
|
|
|
- "ai_generated TINYINT(1) DEFAULT 0 COMMENT '是否AI生成', " +
|
|
|
- "enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用', " +
|
|
|
- "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定期调研模板'"
|
|
|
- );
|
|
|
- log.info("已创建survey_templates表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建survey_templates表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移210: 创建 survey_records 表(定期调研记录)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS survey_records (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
|
|
|
- "member_id BIGINT NOT NULL COMMENT '成员ID', " +
|
|
|
- "template_id BIGINT NOT NULL COMMENT '调研模板ID', " +
|
|
|
- "answers_json TEXT COMMENT '答案JSON', " +
|
|
|
- "submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '提交时间', " +
|
|
|
- "period_start DATE COMMENT '周期开始', " +
|
|
|
- "period_end DATE COMMENT '周期结束', " +
|
|
|
- "INDEX idx_member (member_id), " +
|
|
|
- "INDEX idx_template (template_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定期调研记录'"
|
|
|
- );
|
|
|
- log.info("已创建survey_records表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建survey_records表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移211: 创建 report_blocks 表(通用报告展示块,按报告类型存储 blocks JSON)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_blocks (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "report_type VARCHAR(32) NOT NULL COMMENT 'gut_flora/dan/physical_exam/tongue', " +
|
|
|
- "report_id BIGINT NOT NULL COMMENT '各类型报告主键ID', " +
|
|
|
- "blocks JSON NOT NULL COMMENT '块数组 [{type,title,items,extra}]', " +
|
|
|
- "version INT DEFAULT 1, " +
|
|
|
- "created_at DATETIME, " +
|
|
|
- "updated_at DATETIME, " +
|
|
|
- "UNIQUE KEY uk_report_type_id (report_type, report_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告通用展示块'");
|
|
|
- log.info("已创建report_blocks表");
|
|
|
- } catch (Exception e) {
|
|
|
- // 表已存在,忽略错误
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移212: users/consignees表添加省份/城市/区县/详细地址字段(个人信息所在地与订单联系人共享数据)
|
|
|
- ensureColumn("users", "province", "VARCHAR(50) DEFAULT NULL COMMENT '省份'");
|
|
|
- ensureColumn("users", "city", "VARCHAR(50) DEFAULT NULL COMMENT '城市'");
|
|
|
- ensureColumn("users", "district", "VARCHAR(50) DEFAULT NULL COMMENT '区县'");
|
|
|
- ensureColumn("users", "street", "VARCHAR(100) DEFAULT NULL COMMENT '详细地址'");
|
|
|
- ensureColumn("consignees", "province", "VARCHAR(50) DEFAULT NULL COMMENT '省份'");
|
|
|
- ensureColumn("consignees", "city", "VARCHAR(50) DEFAULT NULL COMMENT '城市'");
|
|
|
- ensureColumn("consignees", "district", "VARCHAR(50) DEFAULT NULL COMMENT '区县'");
|
|
|
- ensureColumn("consignees", "street", "VARCHAR(100) DEFAULT NULL COMMENT '详细地址'");
|
|
|
-
|
|
|
- // 迁移213: micro_actions 添加维度字段(微行动×五维自检联动:按短板维度推荐今日行动)
|
|
|
- ensureColumn("micro_actions", "dimension_code", "VARCHAR(32) DEFAULT NULL COMMENT '维度code: body/wisdom/wealth/action/mind'");
|
|
|
- // 为既有12个行动补充维度映射(身体类→body,情绪类→mind)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("UPDATE micro_actions SET dimension_code = CASE code " +
|
|
|
- "WHEN 'DRINK_WATER' THEN 'body' WHEN 'DEEP_BREATH' THEN 'mind' WHEN 'STRETCH' THEN 'body' " +
|
|
|
- "WHEN 'LOOK_FAR' THEN 'body' WHEN 'SMILE' THEN 'mind' WHEN 'NECK_ROLL' THEN 'body' " +
|
|
|
- "WHEN 'FIST_RELAX' THEN 'body' WHEN 'SIP_WATER' THEN 'body' WHEN 'BLINK' THEN 'body' " +
|
|
|
- "WHEN 'ACUPRESSURE' THEN 'body' WHEN 'TIPTOE' THEN 'body' WHEN 'SHRUG' THEN 'body' " +
|
|
|
- "ELSE dimension_code END WHERE dimension_code IS NULL OR dimension_code = ''");
|
|
|
- log.info("迁移213: 已为既有微行动补充维度映射");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("迁移213: 微行动维度映射更新失败: " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移214: 创建 nutrition_category 表(营养产品分类)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_category (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "name VARCHAR(50) NOT NULL COMMENT '分类名', " +
|
|
|
- "parent_id BIGINT DEFAULT NULL COMMENT '父分类ID', " +
|
|
|
- "sort_order INT DEFAULT 0 COMMENT '排序', " +
|
|
|
- "create_time DATETIME DEFAULT CURRENT_TIMESTAMP" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品分类'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_category表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_category表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移215: 创建 nutrition_brand 表(品牌)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_brand (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "name VARCHAR(100) NOT NULL COMMENT '品牌名', " +
|
|
|
- "logo_url VARCHAR(500) DEFAULT NULL COMMENT '品牌Logo', " +
|
|
|
- "description TEXT COMMENT '品牌简介', " +
|
|
|
- "country VARCHAR(50) DEFAULT NULL COMMENT '产地国', " +
|
|
|
- "create_time DATETIME DEFAULT CURRENT_TIMESTAMP" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品品牌'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_brand表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_brand表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移216: 创建 nutrition_product 表(营养产品主表)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_product (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "name VARCHAR(200) NOT NULL COMMENT '产品名称', " +
|
|
|
- "brand_id BIGINT DEFAULT NULL COMMENT '品牌ID', " +
|
|
|
- "category_id BIGINT DEFAULT NULL COMMENT '分类ID', " +
|
|
|
- "sub_category VARCHAR(50) DEFAULT NULL COMMENT '子分类', " +
|
|
|
- "form VARCHAR(30) DEFAULT NULL COMMENT '剂型', " +
|
|
|
- "net_content VARCHAR(50) DEFAULT NULL COMMENT '净含量', " +
|
|
|
- "shelf_life VARCHAR(50) DEFAULT NULL COMMENT '保质期', " +
|
|
|
- "suitable_for VARCHAR(200) DEFAULT NULL COMMENT '适用人群', " +
|
|
|
- "image_url VARCHAR(500) DEFAULT NULL COMMENT '产品图', " +
|
|
|
- "description TEXT COMMENT '产品描述', " +
|
|
|
- "status TINYINT DEFAULT 1 COMMENT '状态 0下架/1上架', " +
|
|
|
- "create_time DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
- "INDEX idx_brand (brand_id), " +
|
|
|
- "INDEX idx_category (category_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_product表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_product表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移217: 创建 nutrition_ingredient 表(成分明细)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_ingredient (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "product_id BIGINT NOT NULL COMMENT '产品ID', " +
|
|
|
- "ingredient_type VARCHAR(20) COMMENT '成分类型', " +
|
|
|
- "name VARCHAR(100) NOT NULL COMMENT '成分名', " +
|
|
|
- "strain VARCHAR(100) DEFAULT NULL COMMENT '菌株号', " +
|
|
|
- "amount_per_serving DECIMAL(10,2) DEFAULT NULL COMMENT '每份含量', " +
|
|
|
- "unit VARCHAR(20) DEFAULT NULL COMMENT '单位', " +
|
|
|
- "daily_value VARCHAR(50) DEFAULT NULL COMMENT '每日参考值%', " +
|
|
|
- "purpose VARCHAR(200) DEFAULT NULL COMMENT '功效说明', " +
|
|
|
- "create_time DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "INDEX idx_product (product_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品成分明细'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_ingredient表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_ingredient表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移218: 创建 nutrition_platform_listing 表(平台在售信息)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_platform_listing (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "product_id BIGINT NOT NULL COMMENT '产品ID', " +
|
|
|
- "platform VARCHAR(30) COMMENT '平台', " +
|
|
|
- "platform_url VARCHAR(500) DEFAULT NULL COMMENT '商品链接', " +
|
|
|
- "current_price DECIMAL(10,2) DEFAULT NULL COMMENT '当前价格', " +
|
|
|
- "original_price DECIMAL(10,2) DEFAULT NULL COMMENT '原价/划线价', " +
|
|
|
- "price_unit VARCHAR(30) DEFAULT NULL COMMENT '价格单位', " +
|
|
|
- "price_per_unit DECIMAL(10,2) DEFAULT NULL COMMENT '每单位价格', " +
|
|
|
- "sales_count INT DEFAULT NULL COMMENT '销量', " +
|
|
|
- "stock_status VARCHAR(20) DEFAULT NULL COMMENT '库存状态', " +
|
|
|
- "last_checked DATETIME DEFAULT NULL COMMENT '最后爬取时间', " +
|
|
|
- "create_time DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
- "INDEX idx_product (product_id), " +
|
|
|
- "INDEX idx_platform (platform)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品平台在售信息'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_platform_listing表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_platform_listing表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移219: 创建 nutrition_product_review 表(评价)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS nutrition_product_review (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "platform_listing_id BIGINT NOT NULL COMMENT '平台在售ID', " +
|
|
|
- "source VARCHAR(30) DEFAULT NULL COMMENT '来源平台', " +
|
|
|
- "rating DECIMAL(2,1) DEFAULT NULL COMMENT '评分', " +
|
|
|
- "review_count INT DEFAULT NULL COMMENT '评价数', " +
|
|
|
- "good_rate VARCHAR(10) DEFAULT NULL COMMENT '好评率', " +
|
|
|
- "last_updated DATETIME DEFAULT NULL COMMENT '最后更新时间', " +
|
|
|
- "INDEX idx_listing (platform_listing_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品评价'"
|
|
|
- );
|
|
|
- log.info("已创建nutrition_product_review表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建nutrition_product_review表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移220: 创建 file_record 表(文件去重+维护)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute(
|
|
|
- "CREATE TABLE IF NOT EXISTS file_record (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "hash VARCHAR(64) NOT NULL COMMENT 'SHA-256 文件哈希', " +
|
|
|
- "url VARCHAR(500) NOT NULL COMMENT '文件访问URL', " +
|
|
|
- "file_size BIGINT NOT NULL COMMENT '文件大小(字节)', " +
|
|
|
- "content_type VARCHAR(100) DEFAULT NULL COMMENT 'MIME类型', " +
|
|
|
- "original_filename VARCHAR(255) DEFAULT NULL COMMENT '原始文件名', " +
|
|
|
- "category VARCHAR(50) DEFAULT 'general' COMMENT '分类', " +
|
|
|
- "description VARCHAR(500) DEFAULT NULL COMMENT '描述', " +
|
|
|
- "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
- "UNIQUE KEY uk_hash (hash), " +
|
|
|
- "INDEX idx_category (category), " +
|
|
|
- "INDEX idx_created_at (created_at)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件记录表(去重&维护)'"
|
|
|
- );
|
|
|
- log.info("已创建file_record表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建file_record表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移221: nutrition_product 添加 local_product_id 字段(关联本平台商品)
|
|
|
- ensureColumn("nutrition_product", "local_product_id", "BIGINT DEFAULT NULL COMMENT '本平台商品ID,关联products.id'");
|
|
|
-
|
|
|
-// 迁移222: 创建 unlock_gates 表(通关关卡定义)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS unlock_gates (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "gate_type VARCHAR(32) NOT NULL COMMENT 'SELF_CHECK/INVITE_FAMILY/CREATE_FAMILY_MEMBER/PURCHASE/MEMBERSHIP/REPORT_UPLOAD/MICRO_ACTION/STREAK_DAYS', " +
|
|
|
- "name VARCHAR(50) NOT NULL COMMENT '关卡名称', " +
|
|
|
- "description VARCHAR(200) COMMENT '关卡说明', " +
|
|
|
- "icon VARCHAR(10) COMMENT '图标emoji', " +
|
|
|
- "sort_order INT DEFAULT 0 COMMENT '排序号', " +
|
|
|
- "enabled TINYINT DEFAULT 1 COMMENT '启用:1 禁用:0', " +
|
|
|
- "is_required TINYINT DEFAULT 1 COMMENT '主线:1 增值:0', " +
|
|
|
- "is_soft TINYINT DEFAULT 0 COMMENT '硬门禁:0 软引导:1', " +
|
|
|
- "unlock_target VARCHAR(32) COMMENT 'PERSONAL_SANDBOX/FAMILY_SANDBOX/CHILD_MICRO_ACTION/...', " +
|
|
|
- "condition_params VARCHAR(500) COMMENT '条件参数JSON', " +
|
|
|
- "reward_points INT DEFAULT 0 COMMENT '通关奖励积分', " +
|
|
|
- "reward_energy INT DEFAULT 0 COMMENT '通关奖励能量', " +
|
|
|
- "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
- "INDEX idx_sort (enabled, is_required, is_soft, sort_order)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通关解锁关卡定义'");
|
|
|
- log.info("已创建unlock_gates表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建unlock_gates表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移223: 创建 family_gate_progress 表(家庭通关进度)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_gate_progress (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
|
|
|
- "gate_id BIGINT NOT NULL COMMENT '关卡ID', " +
|
|
|
- "status VARCHAR(16) DEFAULT 'LOCKED' COMMENT 'LOCKED/UNLOCKED', " +
|
|
|
- "unlocked_by BIGINT COMMENT '解锁人user_id', " +
|
|
|
- "unlocked_at DATETIME COMMENT '解锁时间', " +
|
|
|
- "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "UNIQUE KEY uk_family_gate (family_id, gate_id)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭关卡通关进度'");
|
|
|
- log.info("已创建family_gate_progress表");
|
|
|
-
|
|
|
- // 默认关卡种子数据(幂等:INSERT IGNORE)
|
|
|
- jdbcTemplate.execute("INSERT IGNORE INTO unlock_gates (gate_type, name, description, icon, sort_order, enabled, is_required, is_soft, unlock_target, condition_params, reward_points, reward_energy) VALUES " +
|
|
|
- "('SELF_CHECK', '五维自检', '完成15题五维自检,看见自己的能量状态', '📝', 1, 1, 1, 0, 'PERSONAL_SANDBOX', '{\"minCount\":1}', 60, 10), " +
|
|
|
- "('INVITE_FAMILY', '邀请家人', '邀请家人加入,共同创建家庭数据', '👨👩👧👦', 2, 1, 1, 0, 'FAMILY_SANDBOX', '{}', 80, 15), " +
|
|
|
- "('CREATE_FAMILY_MEMBER', '创建家人', '为孩子创建账号,开启家庭互动', '👶', 3, 1, 1, 0, 'CHILD_MICRO_ACTION', '{}', 50, 10), " +
|
|
|
- "('MICRO_ACTION', '首次微行动', '完成一次微行动,养成每日小习惯', '🎯', 4, 1, 1, 1, 'MICRO_ACTION_DAILY', '{\"count\":1}', 50, 5), " +
|
|
|
- "('STREAK_DAYS', '连续打卡3天', '连续打卡3天,获得里程碑奖励', '🔥', 5, 1, 1, 1, 'BADGE_MILESTONE', '{\"days\":3}', 30, 5), " +
|
|
|
- "('SELF_CHECK', '复测对比', '完成第二次自检,看见前后变化', '📊', 6, 1, 1, 1, 'TREND_COMPARE', '{\"minCount\":2}', 60, 10), " +
|
|
|
- "('PURCHASE', '首次购物', '在商城完成首次购物,解锁深度分析', '🛒', 7, 0, 0, 0, 'PREMIUM_MODULES', '{}', 150, 30), " +
|
|
|
- "('MEMBERSHIP', '加入会员', '加入家庭会员,享受专属权益', '👑', 8, 0, 0, 0, 'PREMIUM_MODULES', '{}', 200, 50), " +
|
|
|
- "('REPORT_UPLOAD', '健康报告上传', '上传健康报告,获取AI解读与洞察', '📋', 9, 0, 0, 0, 'HEALTH_INSIGHTS', '{\"minCount\":1}', 100, 20)");
|
|
|
- log.info("已插入默认关卡种子数据");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建family_gate_progress表或插入种子数据失败: " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移222: 创建 microbiome_article 表(菌群知识库文章管理)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS microbiome_article (" +
|
|
|
- "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
|
|
|
- "title VARCHAR(255) NOT NULL COMMENT '文章标题', " +
|
|
|
- "content MEDIUMTEXT COMMENT '文章原文Markdown', " +
|
|
|
- "category VARCHAR(50) DEFAULT '科普' COMMENT '分类', " +
|
|
|
- "tags VARCHAR(500) COMMENT '逗号分隔标签', " +
|
|
|
- "source_url VARCHAR(500) COMMENT '来源URL', " +
|
|
|
- "pub_date VARCHAR(20) COMMENT '发布日期', " +
|
|
|
- "status TINYINT DEFAULT 1 COMMENT '1=启用 0=禁用', " +
|
|
|
- "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
|
|
|
- "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
|
|
|
- "INDEX idx_category (category), " +
|
|
|
- "INDEX idx_status (status), " +
|
|
|
- "FULLTEXT INDEX ft_content (title, content)" +
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菌群知识库文章'");
|
|
|
- log.info("已创建microbiome_article表");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建microbiome_article表失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移224: health_reports 添加 shannon_index 列(修复 Unknown column 错误)
|
|
|
- ensureColumn("health_reports", "shannon_index", "VARCHAR(32) DEFAULT NULL COMMENT '香农指数'");
|
|
|
-
|
|
|
- // 迁移225: nutrition_product 添加康美日记同步字段(价格/销量/来源/图片/规格)
|
|
|
- ensureColumn("nutrition_product", "price", "DECIMAL(10,2) DEFAULT 0 COMMENT '当前售价'");
|
|
|
- ensureColumn("nutrition_product", "original_price", "INT DEFAULT 0 COMMENT '原价(整数)'");
|
|
|
- ensureColumn("nutrition_product", "sold_num", "INT DEFAULT 0 COMMENT '已售数量'");
|
|
|
- ensureColumn("nutrition_product", "source_platform", "VARCHAR(50) DEFAULT '' COMMENT '来源平台'");
|
|
|
- ensureColumn("nutrition_product", "source_id", "VARCHAR(100) DEFAULT '' COMMENT '来源平台商品ID'");
|
|
|
- ensureColumn("nutrition_product", "source_url", "VARCHAR(500) DEFAULT '' COMMENT '来源平台商品链接'");
|
|
|
- ensureColumn("nutrition_product", "product_images", "TEXT COMMENT '商品图片URL(JSON数组)'");
|
|
|
- ensureColumn("nutrition_product", "spec", "VARCHAR(200) DEFAULT '' COMMENT '规格'");
|
|
|
- // 来源平台+商品ID 索引(防止重复导入)
|
|
|
- try {
|
|
|
- jdbcTemplate.execute("CREATE INDEX idx_source ON nutrition_product(source_platform, source_id)");
|
|
|
- log.info("已创建nutrition_product idx_source索引");
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("创建idx_source索引失败(可能已存在): " + e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 导入全量全国省市区数据到 streets 表
|
|
|
- * 读取 classpath:region_data.json(31省/369市/2813区)
|
|
|
- * 仅在 streets 表省份级数据不足(<20 条)时执行
|
|
|
- */
|
|
|
- private void seedFullRegionData() {
|
|
|
- try {
|
|
|
- Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM streets WHERE level = 1", Integer.class);
|
|
|
- if (count != null && count >= 20) {
|
|
|
- log.info("全国省市区数据已存在({}个省份),跳过导入", count);
|
|
|
- return;
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("检查省份数据失败,跳过全量导入: {}", e.getMessage());
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:region_data.json");
|
|
|
- if (!resource.exists()) {
|
|
|
- log.warn("region_data.json 不存在,跳过全量省市区数据导入");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- String content = new String(StreamUtils.copyToByteArray(resource.getInputStream()), StandardCharsets.UTF_8);
|
|
|
- com.alibaba.fastjson.JSONArray provinces = com.alibaba.fastjson.JSON.parseArray(content);
|
|
|
-
|
|
|
- for (int pi = 0; pi < provinces.size(); pi++) {
|
|
|
- com.alibaba.fastjson.JSONObject prov = provinces.getJSONObject(pi);
|
|
|
- String provName = prov.getString("name");
|
|
|
- String provCode = prov.getString("code");
|
|
|
-
|
|
|
- java.sql.Connection conn = jdbcTemplate.getDataSource().getConnection();
|
|
|
- java.sql.PreparedStatement ps = conn.prepareStatement(
|
|
|
- "INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES (?, ?, ?, 1, NULL, ?)",
|
|
|
- java.sql.Statement.RETURN_GENERATED_KEYS
|
|
|
- );
|
|
|
- ps.setString(1, provName);
|
|
|
- ps.setString(2, provCode);
|
|
|
- ps.setString(3, provName);
|
|
|
- ps.setInt(4, pi + 1);
|
|
|
- ps.executeUpdate();
|
|
|
- java.sql.ResultSet rs = ps.getGeneratedKeys();
|
|
|
- long provId = 0;
|
|
|
- if (rs.next()) provId = rs.getLong(1);
|
|
|
- rs.close();
|
|
|
- ps.close();
|
|
|
-
|
|
|
- com.alibaba.fastjson.JSONArray cities = prov.getJSONArray("cities");
|
|
|
- if (cities == null) continue;
|
|
|
-
|
|
|
- for (int ci = 0; ci < cities.size(); ci++) {
|
|
|
- com.alibaba.fastjson.JSONObject city = cities.getJSONObject(ci);
|
|
|
- String cityName = city.getString("name");
|
|
|
- String cityCode = city.getString("code");
|
|
|
-
|
|
|
- java.sql.PreparedStatement ps2 = conn.prepareStatement(
|
|
|
- "INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES (?, ?, ?, ?, ?, 2, ?, ?)",
|
|
|
- java.sql.Statement.RETURN_GENERATED_KEYS
|
|
|
- );
|
|
|
- ps2.setString(1, provName);
|
|
|
- ps2.setString(2, provCode);
|
|
|
- ps2.setString(3, cityName);
|
|
|
- ps2.setString(4, cityCode);
|
|
|
- ps2.setString(5, provName + cityName);
|
|
|
- ps2.setLong(6, provId);
|
|
|
- ps2.setInt(7, ci + 1);
|
|
|
- ps2.executeUpdate();
|
|
|
- java.sql.ResultSet rs2 = ps2.getGeneratedKeys();
|
|
|
- long cityId = 0;
|
|
|
- if (rs2.next()) cityId = rs2.getLong(1);
|
|
|
- rs2.close();
|
|
|
- ps2.close();
|
|
|
-
|
|
|
- com.alibaba.fastjson.JSONArray districts = city.getJSONArray("districts");
|
|
|
- if (districts == null) continue;
|
|
|
-
|
|
|
- for (int di = 0; di < districts.size(); di++) {
|
|
|
- com.alibaba.fastjson.JSONObject dist = districts.getJSONObject(di);
|
|
|
- String distName = dist.getString("name");
|
|
|
- String distCode = dist.getString("code");
|
|
|
-
|
|
|
- java.sql.PreparedStatement ps3 = conn.prepareStatement(
|
|
|
- "INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, 3, ?, ?)",
|
|
|
- java.sql.Statement.RETURN_GENERATED_KEYS
|
|
|
- );
|
|
|
- ps3.setString(1, provName);
|
|
|
- ps3.setString(2, provCode);
|
|
|
- ps3.setString(3, cityName);
|
|
|
- ps3.setString(4, cityCode);
|
|
|
- ps3.setString(5, distName);
|
|
|
- ps3.setString(6, distCode);
|
|
|
- ps3.setString(7, provName + cityName + distName);
|
|
|
- ps3.setLong(8, cityId);
|
|
|
- ps3.setInt(9, di + 1);
|
|
|
- ps3.executeUpdate();
|
|
|
- ps3.close();
|
|
|
- }
|
|
|
- }
|
|
|
- conn.close();
|
|
|
- }
|
|
|
- log.info("全国省市区数据导入完成:{}省/{}市/{}区", provinces.size(), 0, 0);
|
|
|
- } catch (Exception e) {
|
|
|
- log.warn("导入全国省市区数据失败: {}", e.getMessage());
|
|
|
- }
|
|
|
}
|
|
|
}
|