Browse Source

chore: resolve merge conflicts with remote cfclub (DatabaseInitializer/ProductRecommendationService/schema.sql)

liaoxg 1 week ago
parent
commit
9ebf9366a
100 changed files with 19763 additions and 1243 deletions
  1. 12338 1
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 40 39
      cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java
  3. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/config/WebConfig.java
  4. 48 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthExerciseController.java
  5. 57 5
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthMealController.java
  6. 3 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java
  7. 50 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthSleepController.java
  8. 3 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminArticleController.java
  9. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleCommentController.java
  10. 25 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleController.java
  11. 18 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/nutrition/NutritionProductController.java
  12. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/dto/CreateTaskDTO.java
  13. 34 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java
  14. 35 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionAgeDosage.java
  15. 15 1
      cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionProduct.java
  16. 45 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionUlLimit.java
  17. 4 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java
  18. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java
  19. 6 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/NutritionAgeDosageMapper.java
  20. 7 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/NutritionUlLimitMapper.java
  21. 4 5
      cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java
  22. 6 1
      cfc-backend/src/main/java/com/etotem/cfc/service/EmiReportService.java
  23. 13 2
      cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java
  24. 180 0
      cfc-backend/src/main/java/com/etotem/cfc/service/NutritionProductService.java
  25. 1 820
      cfc-backend/src/main/java/com/etotem/cfc/service/ProductRecommendationService.java
  26. 80 9
      cfc-backend/src/main/java/com/etotem/cfc/service/ReportCollectService.java
  27. 75 0
      cfc-backend/src/main/java/com/etotem/cfc/task/RepeatTaskGenerator.java
  28. 6148 0
      cfc-backend/src/main/resources/schema.sql
  29. 1 0
      cfc-frontend/pages/activity/activity-detail/activity-detail.vue
  30. 1 0
      cfc-frontend/pages/activity/index.vue
  31. 63 2
      cfc-frontend/pages/article-center/article-detail.vue
  32. 5 2
      cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
  33. 4 1
      cfc-frontend/pages/growth/diet-checkin.vue
  34. 5 2
      cfc-frontend/pages/growth/exercise-checkin.vue
  35. 5 2
      cfc-frontend/pages/growth/sleep-checkin.vue
  36. 7 12
      cfc-frontend/pages/growth/upload.vue
  37. 7 12
      cfc-frontend/pages/guide/activities/detail.vue
  38. 11 16
      cfc-frontend/pages/guide/activities/index.vue
  39. 7 12
      cfc-frontend/pages/guide/packages/share.vue
  40. 7 12
      cfc-frontend/pages/meal/recipe-detail.vue
  41. 7 12
      cfc-frontend/pages/mind-detail/articles.vue
  42. 21 0
      cfc-frontend/pages/mind-detail/index.vue
  43. 2 2
      cfc-frontend/pages/profile-extra/family-members.vue
  44. 7 12
      cfc-frontend/pages/promotion/material.vue
  45. 7 12
      cfc-frontend/pages/shop/checkout/checkout.vue
  46. 7 12
      cfc-frontend/pages/shop/order-detail/order-detail.vue
  47. 7 12
      cfc-frontend/pages/shop/order-list/order-list.vue
  48. 9 0
      cfc-frontend/pages/tasks/tasks.vue
  49. 1 0
      cfc-frontend/utils/api.js
  50. 1 1
      cfc-web/.last_build_commit
  51. 2 2
      cfc-web/package-lock.json
  52. 1 1
      cfc-web/package.json
  53. 116 0
      cfc-web/public/CHANGELOG-v1.0.md
  54. 117 1
      cfc-web/public/CHANGELOG.md
  55. 8 0
      cfc-web/src/api/admin.js
  56. 3 1
      cfc-web/src/main.js
  57. 11 17
      cfc-web/src/router/index.js
  58. 24 0
      cfc-web/src/utils/format.js
  59. 1 1
      cfc-web/src/utils/permissions.js
  60. 1 5
      cfc-web/src/views/Children.vue
  61. 2 8
      cfc-web/src/views/Families.vue
  62. 1 4
      cfc-web/src/views/GuideFamilyTask.vue
  63. 1 4
      cfc-web/src/views/GuidePackages.vue
  64. 2 3
      cfc-web/src/views/Layout.vue
  65. 1 4
      cfc-web/src/views/OperationLogs.vue
  66. 1 5
      cfc-web/src/views/Points.vue
  67. 1 5
      cfc-web/src/views/Rewards.vue
  68. 1 5
      cfc-web/src/views/TaskTemplates.vue
  69. 1 5
      cfc-web/src/views/Tasks.vue
  70. 1 4
      cfc-web/src/views/TeacherConsult.vue
  71. 1 4
      cfc-web/src/views/Users.vue
  72. 1 5
      cfc-web/src/views/Wishes.vue
  73. 1 4
      cfc-web/src/views/admin/Activities.vue
  74. 1 4
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  75. 1 1
      cfc-web/src/views/admin/ArticleEdit.vue
  76. 3 5
      cfc-web/src/views/admin/ArticleManage.vue
  77. 1 4
      cfc-web/src/views/admin/AssessmentOrders.vue
  78. 1 4
      cfc-web/src/views/admin/ButlerAssignments.vue
  79. 1 4
      cfc-web/src/views/admin/DanExecution.vue
  80. 1 4
      cfc-web/src/views/admin/DanSettlement.vue
  81. 1 4
      cfc-web/src/views/admin/Dashboard.vue
  82. 1 4
      cfc-web/src/views/admin/DiscountRuleManagement.vue
  83. 1 4
      cfc-web/src/views/admin/EmotionAlertManage.vue
  84. 1 4
      cfc-web/src/views/admin/FamilyEarnings.vue
  85. 1 4
      cfc-web/src/views/admin/FamilyEarningsWithdraw.vue
  86. 2 10
      cfc-web/src/views/admin/HealthCheckinManage.vue
  87. 1 4
      cfc-web/src/views/admin/HealthPlanManage.vue
  88. 1 1
      cfc-web/src/views/admin/HealthReportEdit.vue
  89. 1 3
      cfc-web/src/views/admin/HealthReportReassign.vue
  90. 2 20
      cfc-web/src/views/admin/HomeBannerManage.vue
  91. 1 3
      cfc-web/src/views/admin/IndicatorCorrelations.vue
  92. 1 3
      cfc-web/src/views/admin/IndicatorMappings.vue
  93. 1 4
      cfc-web/src/views/admin/MembershipCenter.vue
  94. 1 5
      cfc-web/src/views/admin/NoticeManage.vue
  95. 1 4
      cfc-web/src/views/admin/OrderDetail.vue
  96. 1 4
      cfc-web/src/views/admin/OrderManage.vue
  97. 1 7
      cfc-web/src/views/admin/PendingRefund.vue
  98. 1 4
      cfc-web/src/views/admin/PointsLog.vue
  99. 1 4
      cfc-web/src/views/admin/ProductManage.vue
  100. 2 7
      cfc-web/src/views/admin/ProfileManagement.vue

+ 12338 - 1
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -21,5 +21,12342 @@
             log.info("已添加 user_content_weights 表到数据库");
         } catch (Exception e) {
             // 表已存在,忽略错误
+<<<<<<< HEAD
             log.warn("user_content_weights 表可能已存在,跳过创建", e);
-        }
+        }
+=======
+        }
+
+        // 迁移7: families表添加地址字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+            log.info("已添加street_id列到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN city VARCHAR(50) COMMENT '城市'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN district VARCHAR(50) COMMENT '区县'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street VARCHAR(100) COMMENT '街道'");
+            log.info("已添加地址字段到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_street_id (street_id)");
+            log.info("已添加idx_street_id索引到families表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        // 迁移7b: families表添加teacher_id和creator_id字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN teacher_id BIGINT COMMENT '绑定的成长规划师ID'");
+            log.info("已添加teacher_id列到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN creator_id BIGINT COMMENT '家庭创建者ID'");
+            log.info("已添加creator_id列到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移8: teachers表添加地址字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+            log.info("已添加street_id列到teachers表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN city VARCHAR(50) COMMENT '城市'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN district VARCHAR(50) COMMENT '区县'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street VARCHAR(100) COMMENT '街道'");
+            log.info("已添加地址字段到teachers表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD INDEX idx_street_id (street_id)");
+            log.info("已添加idx_street_id索引到teachers表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        // 迁移9: users表添加成长规划师相关字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_no VARCHAR(50) COMMENT '成长规划师证书号'");
+            log.info("已添加teacher_no列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_photo VARCHAR(500) COMMENT '成长规划师照片'");
+            log.info("已添加teacher_photo列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN certificate_image VARCHAR(500) COMMENT '成长规划师证书照片'");
+            log.info("已添加certificate_image列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_status VARCHAR(20) DEFAULT 'pending' COMMENT '成长规划师审核状态'");
+            log.info("已添加teacher_status列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_teacher_status (teacher_status)");
+            log.info("已添加idx_teacher_status索引到users表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        // 迁移10: users表添加teacherFamilyIds字段(成长规划师关联的家庭ID列表)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_family_ids VARCHAR(500) COMMENT '成长规划师关联的家庭ID列表(逗号分隔)'");
+            log.info("已添加teacher_family_ids列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移11: families表添加teacherId字段(绑定的成长规划师ID)
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN teacher_id BIGINT COMMENT '绑定的成长规划师ID'");
+            log.info("已添加teacher_id列到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_teacher_id (teacher_id)");
+            log.info("已添加idx_teacher_id索引到families表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        // 迁移12: 创建 teacher_messages 表(家长咨询成长规划师)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS teacher_messages (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "parent_id BIGINT NOT NULL COMMENT '家长用户ID', " +
+                    "teacher_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
+                    "content TEXT NOT NULL COMMENT '消息内容', " +
+                    "reply_content TEXT COMMENT '回复内容', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending(待回复)/replied(已回复)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "replied_at DATETIME COMMENT '回复时间', " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_teacher_id (teacher_id), " +
+                    "INDEX idx_parent_id (parent_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建teacher_messages表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+// 迁移13: admin_task_templates表添加teacher_id字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN teacher_id BIGINT COMMENT '成长规划师ID'");
+            log.info("已添加teacher_id列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移13b: admin_task_templates表添加新字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
+            log.info("已添加complete_types列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN duration INT COMMENT '预计完成时长(分钟)'");
+            log.info("已添加duration列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
+            log.info("已添加review_type列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
+            log.info("已添加task_type列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
+            log.info("已添加frequency列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
+            log.info("已添加max_frequency列到admin_task_templates表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移14: rewards表child_id字段允许NULL(模板奖励不需要child_id)
+        try {
+            jdbcTemplate.execute("ALTER TABLE rewards MODIFY COLUMN child_id BIGINT NULL COMMENT '孩子ID,模板奖励为NULL'");
+            log.info("已修改rewards.child_id字段允许NULL");
+        } catch (Exception e) {
+            // 修改失败,忽略错误
+        }
+
+        // 迁移15: admins表添加缺失列
+        try {
+            jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN nickname VARCHAR(50) DEFAULT NULL AFTER password");
+            log.info("已添加nickname列到admins表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN email VARCHAR(100) DEFAULT NULL AFTER phone");
+            log.info("已添加email列到admins表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE admins MODIFY COLUMN status VARCHAR(20) DEFAULT 'active'");
+            log.info("已修改admins.status字段类型");
+        } catch (Exception e) {
+            // 修改失败,忽略错误
+        }
+
+        // 迁移16: 创建用户操作日志表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_operation_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT \'用户ID\', " +
+                    "nickname VARCHAR(50) COMMENT \'用户昵称\', " +
+                    "operation_type VARCHAR(20) NOT NULL COMMENT \'操作类型: login/logout/create/update/delete/query等\', " +
+                    "description VARCHAR(500) COMMENT \'操作描述\', " +
+                    "request_path VARCHAR(200) COMMENT \'请求路径\', " +
+                    "request_method VARCHAR(10) COMMENT \'请求方法\', " +
+                    "request_params TEXT COMMENT \'请求参数(脱敏)\', " +
+                    "response_status INT COMMENT \'响应状态码\', " +
+                    "client_ip VARCHAR(50) COMMENT \'客户端IP\', " +
+                    "user_agent VARCHAR(500) COMMENT \'User-Agent\', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_operation_type (operation_type), " +
+                    "INDEX idx_created_at (created_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建user_operation_logs表");
+        } catch (Exception e) {
+// 表已存在,忽略错误
+        }
+
+// 迁移16: tasks表添加bonus_rule字段(POINT-002: 超额积分规则)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN bonus_rule VARCHAR(500) COMMENT \'超额积分规则JSON\'");
+            log.info("已添加bonus_rule列到tasks表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移17: tasks表添加points_expire_days字段(POINT-009: 积分有效期)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN points_expire_days INT DEFAULT 0 COMMENT \'积分有效期天数,0表示永久有效\'");
+            log.info("已添加points_expire_days列到tasks表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移18: points_log表添加expire_at字段(POINT-009: 积分有效期)
+        try {
+            jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expire_at DATE COMMENT \'积分过期日期\'");
+            log.info("已添加expire_at列到points_log表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移19: points_log表添加expired字段(POINT-009: 过期状态标记)
+        try {
+            jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expired TINYINT DEFAULT 0 COMMENT \'是否已过期: 0否1是\'");
+            log.info("已添加expired列到points_log表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+// 迁移20: tasks表添加template_id字段(TASK-004: 重复任务模板追踪)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN template_id BIGINT COMMENT \'模板任务ID\'");
+            log.info("已添加template_id列到tasks表");
+        } catch (Exception e) {
+// 列已存在,忽略错误
+        }
+
+        // 迁移21: tasks表添加is_template字段(TASK-004: 标记模板任务)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN is_template TINYINT DEFAULT 0 COMMENT '是否为模板任务'");
+            log.info("已添加is_template列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移22: 修复tasks表child_id和deadline允许NULL(支持多孩子和家长任务)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN child_id BIGINT NULL COMMENT '执行者ID'");
+            log.info("已修改tasks.child_id允许NULL");
+        } catch (Exception e) {
+            // 修改失败,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN deadline DATETIME NULL COMMENT '截止时间'");
+            log.info("已修改tasks.deadline允许NULL");
+        } catch (Exception e) {
+            // 修改失败,忽略错误
+        }
+
+        // 迁移23: tasks表添加新字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
+            log.info("已添加task_type列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移24: tasks表添加parent_task_id字段(购买任务→每日使用子任务链;子任务在父任务完成前不显示)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN parent_task_id BIGINT COMMENT '父任务ID(购买任务→每日使用子任务)'");
+            log.info("已添加parent_task_id列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
+            log.info("已添加frequency列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
+            log.info("已添加max_frequency列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
+            log.info("已添加complete_types列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
+            log.info("已添加review_type列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN earliest_start DATETIME COMMENT '最早开始时间'");
+            log.info("已添加earliest_start列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN latest_end DATETIME COMMENT '最晚结束时间'");
+            log.info("已添加latest_end列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移24: tasks表添加minigame_code字段(小游戏任务)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN minigame_code VARCHAR(50) DEFAULT NULL COMMENT '小游戏代码: schulte/1a2b/sudoku'");
+            log.info("已添加minigame_code列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移25: tasks表添加duration字段(TASK-011: 任务预计时长)
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN duration INT DEFAULT 0 COMMENT '预计时长(分钟)'");
+            log.info("已添加duration列到tasks表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_minigame (minigame_code) WHERE minigame_code IS NOT NULL");
+            log.info("已添加idx_minigame索引到tasks表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS trial_memberships (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "start_date DATETIME NOT NULL COMMENT '试用开始时间', " +
+                    "end_date DATETIME NOT NULL COMMENT '试用结束时间', " +
+                    "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/ACTIVE/EXPIRED/CONVERTED', " +
+                    "trial_days INT DEFAULT 7 COMMENT '试用天数', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_end_date (end_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建trial_memberships表");
+        } catch (Exception e) {
+            log.warn("创建trial_memberships表可能已存在: {}", e.getMessage());
+        }
+
+        // assessment_appointments 新字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN user_id BIGINT COMMENT '用户ID'");
+        } catch (Exception e) {
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID'");
+        } catch (Exception e) {
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN package_id BIGINT COMMENT '套餐ID'");
+        } catch (Exception e) {
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN remark VARCHAR(500) COMMENT '备注'");
+        } catch (Exception e) {
+        }
+
+        // assessment_orders 表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_orders (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_no VARCHAR(64) NOT NULL UNIQUE, " +
+                    "family_id BIGINT NOT NULL, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "child_id BIGINT, " +
+                    "guide_id BIGINT NOT NULL, " +
+                    "package_id BIGINT NOT NULL, " +
+                    "guide_name VARCHAR(50), " +
+                    "package_name VARCHAR(100), " +
+                    "total_price BIGINT DEFAULT 0, " +
+                    "discount_amount BIGINT DEFAULT 0, " +
+                    "actual_price BIGINT DEFAULT 0, " +
+                    "status VARCHAR(20) DEFAULT 'pending', " +
+                    "pay_time VARCHAR(50), " +
+                    "pay_type VARCHAR(20), " +
+                    "transaction_id VARCHAR(64), " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family (family_id), " +
+                    "INDEX idx_guide (guide_id), " +
+                    "INDEX idx_status (status)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表'");
+        } catch (Exception e) {
+        }
+
+        // 迁移26: guide_applications表添加address和bank_card字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN address VARCHAR(500) COMMENT '地址'");
+            log.info("已添加address列到guide_applications表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_card VARCHAR(50) COMMENT '银行卡号'");
+            log.info("已添加bank_card列到guide_applications表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_name VARCHAR(100) COMMENT '开户行'");
+            log.info("已添加bank_name列到guide_applications表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移27: users表添加address字段(成长规划师地址)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN address VARCHAR(500) COMMENT '地址'");
+            log.info("已添加address列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移28: 创建guide_application_records表(申请过程记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_application_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "application_id BIGINT NOT NULL COMMENT '关联申请ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '操作人用户ID', " +
+                    "action VARCHAR(50) NOT NULL COMMENT '操作: submit/approve/reject/withdraw/resubmit', " +
+                    "from_status VARCHAR(20) COMMENT '操作前状态', " +
+                    "to_status VARCHAR(20) NOT NULL COMMENT '操作后状态', " +
+                    "remark VARCHAR(500) COMMENT '备注/原因', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_application_id (application_id), " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_action (action)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请过程记录表'");
+            log.info("已创建guide_application_records表");
+} catch (Exception e) {
+
+        }
+
+
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'points_log' AND COLUMN_NAME = 'category'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task任务积分, system系统积分'");
+                log.info("已添加category列到points_log表");
+            } else {
+                log.info("category列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加category列失败: {}", e.getMessage());
+        }
+
+        // 迁移22: 创建invite_card表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS invite_card (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "type VARCHAR(20) NOT NULL COMMENT 'child/family/guide', " +
+                    "code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码', " +
+                    "ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)', " +
+                    "creator_id BIGINT NOT NULL COMMENT '创建人', " +
+                    "expires_at DATETIME NOT NULL, " +
+                    "used TINYINT DEFAULT 0, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_code (code), " +
+                    "INDEX idx_ref_type (ref_id, type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请卡片'");
+            log.info("已创建invite_card表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移23: 创建wishes表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS wishes (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "child_id BIGINT COMMENT '目标孩子ID', " +
+                    "created_by BIGINT COMMENT '创建者用户ID', " +
+                    "creator_type VARCHAR(20) COMMENT '创建者类型: child/parent', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '心愿标题', " +
+                    "description VARCHAR(500) COMMENT '心愿描述', " +
+                    "category VARCHAR(32) COMMENT '心愿分类', " +
+                    "points_required INT COMMENT '所需积分', " +
+                    "points_set_by BIGINT COMMENT '定价者ID', " +
+                    "points_set_at DATETIME COMMENT '定价时间', " +
+                    "status VARCHAR(32) DEFAULT 'active' COMMENT '状态: draft/active/pending_exchange/approved/exchanging/fulfilled/cancelled/rejected', " +
+                    "exchange_requested_at DATETIME COMMENT '申请兑换时间', " +
+                    "exchange_approved_by BIGINT COMMENT '批准者ID', " +
+                    "exchange_approved_at DATETIME COMMENT '批准时间', " +
+                    "points_deducted_at DATETIME COMMENT '扣积分时间', " +
+                    "fulfilled_by BIGINT COMMENT '兑现者ID', " +
+                    "fulfilled_at DATETIME COMMENT '兑现时间', " +
+                    "child_confirmed_at DATETIME COMMENT '孩子确认收货时间', " +
+                    "rejected_by BIGINT COMMENT '拒绝者ID', " +
+                    "rejected_at DATETIME COMMENT '拒绝时间', " +
+                    "reject_reason VARCHAR(500) COMMENT '拒绝原因', " +
+                    "reject_count INT DEFAULT 0 COMMENT '被拒绝次数', " +
+                    "cancelled_by BIGINT COMMENT '取消者ID', " +
+                    "cancelled_at DATETIME COMMENT '取消时间', " +
+                    "cancel_reason VARCHAR(500) COMMENT '取消原因', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_child_id (child_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿单表'");
+            log.info("已创建wishes表");
+        } catch (Exception e) {
+            log.warn("创建wishes表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移24: users表添加个人信息补充字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'ethnicity'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users " +
+                        "ADD COLUMN ethnicity VARCHAR(32) DEFAULT NULL COMMENT '民族', " +
+                        "ADD COLUMN blood_type VARCHAR(16) DEFAULT NULL COMMENT '血型', " +
+                        "ADD COLUMN highest_education VARCHAR(32) DEFAULT NULL COMMENT '最高学历', " +
+                        "ADD COLUMN marital_status VARCHAR(16) DEFAULT NULL COMMENT '婚姻状态', " +
+                        "ADD COLUMN family_role VARCHAR(16) DEFAULT NULL COMMENT '家长身份: 爸爸/妈妈/爷爷/奶奶/姥爷/姥姥/其他'");
+                log.info("已添加个人信息补充字段到users表");
+            } else {
+                log.info("个人信息补充字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加个人信息补充字段失败: {}", e.getMessage());
+        }
+
+        // Phase 0: 浠艾福俱乐部 — 服务商字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'vendor_type'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_type VARCHAR(32) DEFAULT NULL COMMENT '服务商类型: planner/activity_provider/product_supplier'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_status VARCHAR(16) DEFAULT NULL COMMENT '服务商审核状态: pending/approved/rejected'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_reject_reason VARCHAR(500) DEFAULT NULL COMMENT '审核拒绝原因'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN is_family_admin TINYINT DEFAULT 0 COMMENT '是否家庭管理员'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_info VARCHAR(2000) DEFAULT NULL COMMENT '服务商资质信息(JSON)'");
+                log.info("已添加浠艾福俱乐部服务商字段到users表");
+            } else {
+                log.info("浠艾福俱乐部服务商字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加服务商字段失败: {}", e.getMessage());
+        }
+
+        // Phase 0: 迁移已有规划师到服务商体系
+        try {
+            int updated = jdbcTemplate.update(
+                    "UPDATE users SET vendor_type = 'planner', vendor_status = 'approved' WHERE teacher_status = 'approved' AND vendor_type IS NULL"
+            );
+            if (updated > 0) {
+                log.info("已迁移 {} 名规划师到服务商体系", updated);
+            }
+        } catch (Exception e) {
+            log.warn("迁移规划师到服务商体系失败: {}", e.getMessage());
+        }
+
+        // ==================== Phase 1: 浠艾福俱乐部商品系统 ======        // 商品表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS products (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                            "  name VARCHAR(200) NOT NULL," +
+                            "  description VARCHAR(1000)," +
+                            "  intro TEXT," +
+                            "  cover_image VARCHAR(500)," +
+                            "  images TEXT," +
+                            "  price INT NOT NULL DEFAULT 0 COMMENT '价格(分)'," +
+                            "  stock INT NOT NULL DEFAULT 0," +
+                            "  sales_count INT NOT NULL DEFAULT 0," +
+                            "  product_type VARCHAR(32) NOT NULL COMMENT 'activity/course/physical/digital/service'," +
+                            "  vendor_id BIGINT," +
+                            "  vendor_name VARCHAR(100)," +
+                            "  status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT 'draft/pending/approved/rejected/on_shelf/off_shelf'," +
+                            "  reject_reason VARCHAR(500)," +
+                            "  member_price INT COMMENT '会员价(分)'," +
+                            "  profit_rate INT DEFAULT 0 COMMENT '平台利润率(千分比)'," +
+                            "  member_eligible VARCHAR(32) DEFAULT 'none' COMMENT 'all/budget/none'," +
+                            "  domain VARCHAR(32) COMMENT 'action/mind/body/wisdom/wealth'," +
+                            "  external_source VARCHAR(64)," +
+                            "  external_id VARCHAR(128)," +
+                            "  external_data TEXT," +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                            ")"
+            );
+        } catch (Exception e) {
+            log.warn("创建products表失败: {}", e.getMessage());
+        }
+
+        // 商品SKU表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS product_skus (" +
+                            "  id INT NOT NULL AUTO_INCREMENT," +
+                            "  product_id INT NOT NULL COMMENT '商品ID'," +
+                            "  sku_code VARCHAR(64) NOT NULL COMMENT 'SKU编码'," +
+                            "  specs JSON NOT NULL COMMENT '规格JSON'," +
+                            "  price DECIMAL(10,2) NOT NULL COMMENT 'SKU价格'," +
+                            "  stock INT NOT NULL DEFAULT 0 COMMENT '库存'," +
+                            "  original_price DECIMAL(10,2) COMMENT '原价/划线价'," +
+                            "  image VARCHAR(512) COMMENT 'SKU图片'," +
+                            "  enabled TINYINT(1) NOT NULL DEFAULT 1," +
+                            "  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP," +
+                            "  update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                            "  PRIMARY KEY (id)," +
+                            "  KEY idx_product_id (product_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU'"
+            );
+            log.info("已创建product_skus表");
+        } catch (Exception e) {
+            log.warn("创建product_skus表失败: {}", e.getMessage());
+        }
+
+        // 订单表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS product_orders (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                            "  order_no VARCHAR(64) NOT NULL UNIQUE," +
+                            "  product_id BIGINT NOT NULL," +
+                            "  product_name VARCHAR(200)," +
+                            "  product_type VARCHAR(32)," +
+                            "  buyer_id BIGINT NOT NULL," +
+                            "  family_id BIGINT," +
+                            "  quantity INT NOT NULL DEFAULT 1," +
+                            "  unit_price INT NOT NULL COMMENT '单价(分)'," +
+                            "  total_amount INT NOT NULL COMMENT '总金额(分)'," +
+                            "  status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/completed/cancelled/refunded'," +
+                            "  payment_method VARCHAR(32) COMMENT 'wechat/points/membership'," +
+                            "  remark VARCHAR(500)," +
+                            "  paid_at DATETIME," +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                            ")"
+            );
+        } catch (Exception e) {
+            log.warn("创建product_orders表失败: {}", e.getMessage());
+        }
+
+        // 系统配置表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS sys_config (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                            "  config_key VARCHAR(64) NOT NULL UNIQUE," +
+                            "  config_value TEXT," +
+                            "  description VARCHAR(500)," +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                            ")"
+            );
+        } catch (Exception e) {
+            log.warn("创建sys_config表失败: {}", e.getMessage());
+        }
+
+        // Migration: product_orders add logistics fields
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_no VARCHAR(64) COMMENT '物流单号'");
+            log.info("已添加logistics_no列到product_orders表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_company VARCHAR(100) COMMENT '物流公司'");
+            log.info("已添加logistics_company列到product_orders表");
+        } catch (Exception e) {
+        }
+
+        // SysConfig 种子数据
+        try {
+            insertSysConfigSeed("points_exchange_rate", "1", "积分兑换汇率(1积分=1分,即0.01元)");
+            insertSysConfigSeed("points_deduct_cap", "0.5", "积分抵扣上限(50%)");
+            insertSysConfigSeed("member_fee_standard", "1314", "标准会员年费(元)");
+            insertSysConfigSeed("member_fee_premium", "1316800", "高级家庭会员年费(分)");
+            insertSysConfigSeed("member_eligible_budget", "1314", "等值商品选择预算上限(元)");
+            insertSysConfigSeed("renewal_discount_rate", "0.85", "续费折扣率");
+            insertSysConfigSeed("member_discount_standard", "0.9", "标准会员折扣率");
+            insertSysConfigSeed("member_discount_premium", "8000", "高级家庭会员商品折扣(bps)");
+            insertSysConfigSeed("member_fee_family", "131400", "家庭会员年费(分)");
+            insertSysConfigSeed("member_fee_family_yearly", "131400", "家庭会员年费(分)");
+            insertSysConfigSeed("member_fee_family_monthly", "13140", "家庭会员月费(分)");
+            insertSysConfigSeed("member_fee_family_quarterly", "32850", "家庭会员季费(分)");
+            insertSysConfigSeed("member_upgrade_threshold", "131400", "自动升级家庭会员累计消费门槛(分)");
+            insertSysConfigSeed("member_discount_family", "9000", "家庭会员商品折扣(bps)");
+            insertSysConfigSeed("member_fee_premium_monthly", "131400", "高级家庭会员月费(分)");
+            insertSysConfigSeed("member_fee_family_original_yearly", "199900", "家庭会员年费原价(分)");
+            insertSysConfigSeed("member_fee_family_original", "199900", "家庭会员原价兜底(分)");
+            insertSysConfigSeed("member_fee_premium_original", "1599900", "高级会员年费原价(分)");
+            insertSysConfigSeed("member_fee_premium_original_monthly", "159900", "高级会员月费原价(分)");
+            insertSysConfigSeed("commission_member_l1_rate", "2000", "推荐家庭会员L1佣金率(bps)");
+            insertSysConfigSeed("commission_member_l2_rate", "500", "推荐家庭会员L2佣金率(bps)");
+            insertSysConfigSeed("commission_product_profit_share", "1000", "下线商品购买利润分成(bps)");
+            insertSysConfigSeed("commission_service_rate", "1000", "下线服务购买佣金率(bps)");
+            insertSysConfigSeed("commission_event_fixed", "2000", "推荐新用户报名活动固定佣金(分)");
+            insertSysConfigSeed("withdraw_min_amount", "10000", "最低提现金额(分)");
+            insertSysConfigSeed("withdraw_tax_rate", "2000", "佣金税率(bps)");
+            insertSysConfigSeed("withdraw_fee_free_threshold", "80000", "免税额度(分)");
+            insertSysConfigSeed("article_energy_rate_normal", "1", "普通文章能量汇率(每100字)");
+            insertSysConfigSeed("article_energy_rate_premium", "2", "优质文章能量汇率(每100字)");
+            insertSysConfigSeed("article_energy_rate_original", "3", "原创文章能量汇率(每100字)");
+            insertSysConfigSeed("article_reading_min_seconds", "60", "文章阅读达标最少秒数");
+            insertSysConfigSeed("activity_checkin_default_points", "5", "活动签到默认积分");
+            insertSysConfigSeed("health_checkin_points", "5", "健康打卡积分");
+            insertSysConfigSeed("finance_checkin_points", "5", "理财打卡积分");
+            insertSysConfigSeed("trial_enabled", "true", "是否启用会员试用");
+            insertSysConfigSeed("trial_days", "7", "试用天数");
+            insertSysConfigSeed("trial_features", "[\"free_activities\",\"free_courses\",\"teacher_consultation\"]", "试用期间可用功能");
+            insertSysConfigSeed("member_pay_virtual_enabled", "1", "会员支付是否使用虚拟支付通道(1=虚拟,0=普通微信)");
+insertSysConfigSeed("gift_membership_threshold", "198000", "购买送会员商品累计达到该金额(分)赠送会员");
+insertSysConfigSeed("gift_membership_level", "FAMILY", "达到送会员额度后赠送的会员等级");
+insertSysConfigSeed("gift_membership_original_threshold", "1000000", "送会员商品原价达标阈值(分),达到此金额后送会员商品部分实付一口价");
+insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达标后一口实付价(分)");
+            insertSysConfigSeed("health_dimension_weights", "{\"growth\":0.20,\"sleep\":0.15,\"vision\":0.10,\"immunity\":0.15,\"nutrition\":0.15,\"gut\":0.10,\"exercise\":0.15}", "7维健康→身能量权重配置");
+            insertSysConfigSeed("health_dimension_sync_factor", "0.5", "7维健康同步至身能量系数");
+            log.info("SysConfig 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("SysConfig 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // Product 种子数据(5 个示例商品,覆盖全部 5 种 productType)
+        try {
+            insertProductSeed("亲子关系工作坊", "线下半日工作坊,重建家庭对话模式", "activity", 38800, "action", "all");
+            insertProductSeed("情绪管理21天训练营", "21天视频课程,识别并管理情绪", "course", 19900, "mind", "all");
+            insertProductSeed("肠道菌群检测套装", "家用检测 + 报告解读", "physical", 59900, "body", "budget");
+            insertProductSeed("儿童多模态认知测评", "在线测评 + 个性化建议", "digital", 9900, "wisdom", "none");
+            insertProductSeed("成长规划师1对1咨询", "60分钟专业规划", "service", 50000, "wealth", "budget");
+            log.info("Product 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("Product 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // 活动种子数据(3 个示例活动,覆盖身/心/智三个维度)
+        try {
+            insertActivitySeed("亲子户外徒步探险", "周末亲子徒步活动,在自然中锻炼体魄、增进亲子关系,全程约5公里。",
+                    "body", "offline", "published",
+                    "2026-08-15 09:00:00", "2026-08-15 16:00:00", "北京奥林匹克森林公园", 50, 0, 0,
+                    "{\"body\":80,\"mind\":0,\"wisdom\":0,\"action\":20,\"wealth\":0}");
+            insertActivitySeed("家庭正念静心工作坊", "专业导师带领的正念冥想体验,学习情绪调节和家庭沟通技巧。",
+                    "mind", "offline", "published",
+                    "2026-08-22 14:00:00", "2026-08-22 17:00:00", "北京市朝阳区心理成长中心", 30, 9900, 7900,
+                    "{\"body\":0,\"mind\":80,\"wisdom\":10,\"action\":10,\"wealth\":0}");
+            insertActivitySeed("少儿科学思维探索营", "通过动手实验激发孩子对科学的兴趣,培养逻辑思维与创造力。",
+                    "wisdom", "online", "published",
+                    "2026-09-05 10:00:00", "2026-09-05 12:00:00", "线上直播", 200, 19900, 15900,
+                    "{\"body\":0,\"mind\":0,\"wisdom\":70,\"action\":30,\"wealth\":0}");
+            log.info("Activity 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("Activity 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // 方案规则种子数据(主链路 P0-3:模板包 + 方案生成规则)
+        try {
+            Long pkgFocus = ensureTemplatePackage("专注力提升方案模板",
+                "针对注意力维度低分(0-60)的14天专注力提升训练", 14, "专注力");
+            Long pkgKeep = ensureTemplatePackage("注意力巩固方案模板",
+                "针对注意力维度正常(61-100)的14天巩固训练", 14, "注意力");
+            ensureTemplateItems(pkgFocus, new String[][]{
+                {"舒尔特方格训练", "每日5分钟舒尔特方格,提升视觉专注", "5", "daily"},
+                {"静坐冥想", "每日10分钟静坐冥想,训练抗干扰能力", "3", "daily"},
+                {"番茄钟作业法", "使用番茄钟完成当日作业,25分钟一节", "5", "daily"}
+            });
+            ensureTemplateItems(pkgKeep, new String[][]{
+                {"专注力保持打卡", "每日记录专注时长,保持良好习惯", "3", "daily"},
+                {"优质睡眠打卡", "每日21:30前入睡,保障注意力恢复", "3", "daily"},
+                {"亲子共读", "每周3次亲子共读,培养持续专注", "5", "weekly"}
+            });
+            ensurePlanRule("attention", 0, 60, pkgFocus, 1);
+            ensurePlanRule("attention", 61, 100, pkgKeep, 2);
+            log.info("方案规则种子数据已加载: 模板包 focus={}, keep={}", pkgFocus, pkgKeep);
+        } catch (Exception e) {
+            log.warn("方案规则种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // 报告解析中台:注册菌群检测报告类型(主链路 P0-4)
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM report_type_registry WHERE type_id = ?", Integer.class, "gut_flora");
+            if (count == null || count == 0) {
+                jdbcTemplate.update(
+                    "INSERT INTO report_type_registry (type_id, family, display_name, description, parser_path, " +
+                    "min_confidence, is_active, source, review_status, version, created_at, updated_at) " +
+                    "VALUES ('gut_flora', 'gut_flora', '菌群检测', '募极生物/北京肠道菌群检测报告', " +
+                    "'gut_flora/extract_gut_flora.py', 30, 1, 'prebuilt', 'approved', '1.0', NOW(), NOW())");
+                log.info("已注册报告类型 gut_flora");
+            } else {
+                log.info("报告类型 gut_flora 已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("报告类型注册失败: {}", e.getMessage());
+        }
+
+        // 文章种子数据(3 个示例文章,覆盖学习力/情绪管理/亲子教育分类)
+        try {
+            insertArticleSeed("如何培养孩子的自主学习力",
+                    "从内在动机出发,帮助孩子建立自主学习习惯的实用方法指南。",
+                    "article", 4L, "wisdom", "published", "曹老师",
+                    "normal", 5, 1800,
+                    "{\"body\":0,\"mind\":0,\"wisdom\":60,\"action\":40,\"wealth\":0}");
+            insertArticleSeed("儿童情绪管理的五个关键步骤",
+                    "当孩子情绪爆发时,家长应该如何应对?本文提供一套可操作的情绪管理框架。",
+                    "article", 2L, "mind", "published", "李老师",
+                    "premium", 4, 2200,
+                    "{\"body\":0,\"mind\":80,\"wisdom\":10,\"action\":10,\"wealth\":0}");
+            insertArticleSeed("亲子运动的正确打开方式",
+                    "不同年龄段孩子的运动建议,让全家人在快乐中收获健康。",
+                    "article", 3L, "action", "published", "王老师",
+                    "normal", 3, 1500,
+                    "{\"body\":40,\"mind\":0,\"wisdom\":0,\"action\":60,\"wealth\":0}");
+            log.info("Article 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("Article 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // 补充商品种子数据(2 个,丰富商品库)
+        try {
+            insertProductSeed("少儿专注力训练教具盒", "含舒尔特方格、记忆卡片等8种专注力训练工具,适合5-12岁儿童。", "physical", 16800, "wisdom", "all");
+            insertProductSeed("家庭年度VIP成长会员", "一年内畅享全部线上课程、活动折扣及规划师咨询服务。", "digital", 298000, "action", "all");
+            log.info("补充 Product 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("补充 Product 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // ==================== Phase 2: 内容区块可见性配置 ======        // 内容区块配置表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS content_sections (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                            "  page_key VARCHAR(50) NOT NULL," +
+                            "  section_key VARCHAR(50) NOT NULL," +
+                            "  title VARCHAR(100)," +
+                            "  allowed_roles VARCHAR(255) DEFAULT '[\"anonymous\"]'," +
+                            "  sort_order INT DEFAULT 0," +
+                            "  status VARCHAR(20) DEFAULT 'active'," +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                            ")"
+            );
+            log.info("content_sections 表已创建");
+        } catch (Exception e) {
+            log.warn("创建 content_sections 表失败: {}", e.getMessage());
+        }
+
+        // ContentSection 种子数据
+        try {
+            insertContentSectionSeed("mind", "func_entries", "功能入口", "[\"anonymous\"]", 1);
+            insertContentSectionSeed("mind", "daily_tip", "每日心理", "[\"anonymous\"]", 2);
+            insertContentSectionSeed("mind", "recommended_reading", "推荐阅读", "[\"anonymous\"]", 3);
+            insertContentSectionSeed("mind", "premium_content", "会员专属内容", "[\"parent\",\"teacher\"]", 4);
+            insertContentSectionSeed("mind", "mind_tasks", "心智任务", "[\"parent\",\"child\"]", 5);
+            insertContentSectionSeed("mind", "related_activities", "相关活动", "[\"parent\",\"child\"]", 6);
+            insertContentSectionSeed("mind", "purchased_courses", "已购课程", "[\"parent\",\"child\"]", 7);
+            insertContentSectionSeed("body", "func_entries", "功能入口", "[\"anonymous\"]", 1);
+            insertContentSectionSeed("body", "daily_stats", "今日数据", "[\"anonymous\"]", 2);
+            insertContentSectionSeed("body", "health_tips", "健康小贴士", "[\"anonymous\"]", 3);
+            insertContentSectionSeed("body", "teacher_tips", "规划师推荐", "[\"teacher\"]", 4);
+            log.info("ContentSection 种子数据已加载");
+        } catch (Exception e) {
+            log.warn("ContentSection 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // ==================== 文章内容系统种子数据 ======        // 文章分类种子数据
+        try {
+            Integer catCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM article_categories WHERE id IN (1,2,3,4,5)", Integer.class);
+            if (catCount == null || catCount == 0) {
+                jdbcTemplate.execute("INSERT IGNORE INTO article_categories (id, name, icon, color, sort_order, status) VALUES " +
+                        "(1, '心理健康', '🧠', '#5B9BD5', 1, 1), " +
+                        "(2, '情绪管理', '💖', '#FF6B35', 2, 1), " +
+                        "(3, '亲子教育', '👨‍👩‍👧‍👦', '#4CAF50', 3, 1), " +
+                        "(4, '学习力', '📚', '#FFD700', 4, 1), " +
+                        "(5, '专注力', '🎯', '#8D6E63', 5, 1)");
+                log.info("文章分类种子数据已加载");
+            }
+        } catch (Exception e) {
+            log.warn("文章分类种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // ==================== 佣金系统 ======        // 迁移: users表添加推荐人字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'referrer_id'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN referrer_id BIGINT DEFAULT NULL COMMENT '推荐人ID'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN referral_code VARCHAR(32) DEFAULT NULL COMMENT '个人邀请码'");
+                jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_referral_code (referral_code)");
+                log.info("已添加佣金字段到users表");
+            } else {
+                log.info("佣金字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加佣金字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: products表添加利润率字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'products' AND COLUMN_NAME = 'profit_rate'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE products ADD COLUMN profit_rate INT DEFAULT 0 COMMENT '平台利润率(bps,100=1%)'");
+                log.info("已添加profit_rate列到products表");
+            } else {
+                log.info("profit_rate列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加profit_rate列失败: {}", e.getMessage());
+        }
+
+        // 新表: commission_records (佣金记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS commission_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_id BIGINT NOT NULL COMMENT '订单ID', " +
+                    "order_type VARCHAR(32) NOT NULL COMMENT '订单类型: membership/product/assessment/package', " +
+                    "referrer_id BIGINT NOT NULL COMMENT '推荐人ID', " +
+                    "buyer_id BIGINT NOT NULL COMMENT '购买人ID', " +
+                    "commission_type VARCHAR(16) NOT NULL COMMENT '佣金类型: member/profit', " +
+                    "order_amount INT NOT NULL COMMENT '订单金额(分)', " +
+                    "profit_rate INT DEFAULT 0 COMMENT '利润率(bps,100=1%)', " +
+                    "commission_amount INT NOT NULL COMMENT '佣金金额(分)', " +
+                    "status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled', " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_referrer (referrer_id), " +
+                    "INDEX idx_buyer (buyer_id), " +
+                    "INDEX idx_order (order_id, order_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建commission_records表");
+        } catch (Exception e) {
+            log.warn("创建commission_records表失败: {}", e.getMessage());
+        }
+
+        // 新表: withdrawal_requests (提现申请)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS withdrawal_requests (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "amount INT NOT NULL COMMENT '提现金额(分)', " +
+                    "account_info TEXT COMMENT '账户信息(JSON)', " +
+                    "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/approved/rejected', " +
+                    "audit_by BIGINT DEFAULT NULL COMMENT '审核人ID', " +
+                    "audit_at DATETIME DEFAULT NULL COMMENT '审核时间', " +
+                    "remark VARCHAR(255) DEFAULT NULL COMMENT '审核备注', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建withdrawal_requests表");
+        } catch (Exception e) {
+            log.warn("创建withdrawal_requests表失败: {}", e.getMessage());
+        }
+
+        // ==================== 会员体系 & 佣金扩展 ======        // 迁移: users表添加会员字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'member_level'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_level VARCHAR(20) DEFAULT 'FREE' COMMENT '会员等级: FREE/FAMILY/PROVIDER'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_consumption BIGINT DEFAULT 0 COMMENT '累计消费金额(分)'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN upgrade_from_consumption TINYINT DEFAULT 0 COMMENT '是否通过累计消费自动升级'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN direct_count INT DEFAULT 0 COMMENT '直接推荐人数'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN indirect_count INT DEFAULT 0 COMMENT '间接推荐人数'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_commission_earned BIGINT DEFAULT 0 COMMENT '累计获得佣金(分)'");
+                log.info("已添加会员字段到users表");
+            } else {
+                log.info("会员字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加会员字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: commission_records表添加扩展字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'commission_records' AND COLUMN_NAME = 'level'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN level INT DEFAULT 1 COMMENT '佣金层级: 1=直接推荐, 2=间接推荐'");
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN settled_at DATETIME DEFAULT NULL COMMENT '结算时间'");
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间'");
+                log.info("已添加扩展字段到commission_records表");
+            } else {
+                log.info("commission_records扩展字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加commission_records扩展字段失败: {}", e.getMessage());
+        }
+
+        // 新表: member_upgrade_records (会员升级记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_upgrade_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "from_level VARCHAR(20) NOT NULL COMMENT '升级前等级', " +
+                    "to_level VARCHAR(20) NOT NULL COMMENT '升级后等级', " +
+                    "upgrade_type VARCHAR(20) NOT NULL COMMENT '升级类型: pay/consumption/admin', " +
+                    "order_id BIGINT DEFAULT NULL COMMENT '关联支付订单ID', " +
+                    "consumption_amount BIGINT DEFAULT NULL COMMENT '触发升级的累计消费金额', " +
+                    "expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_order (order_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员升级记录'");
+            log.info("已创建member_upgrade_records表");
+        } catch (Exception e) {
+            log.warn("创建member_upgrade_records表失败: {}", e.getMessage());
+        }
+
+        // ==================== 文章内容系统 ======        // 文章分类表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_categories (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(50) NOT NULL COMMENT '分类名', " +
+                    "icon VARCHAR(20) DEFAULT '' COMMENT '图标emoji', " +
+                    "color VARCHAR(20) DEFAULT '#5B9BD5' COMMENT '标识色', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "status TINYINT DEFAULT 1 COMMENT '1启用/0禁用'" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类'");
+            log.info("已创建article_categories表");
+        } catch (Exception e) {
+            log.warn("创建article_categories表失败: {}", e.getMessage());
+        }
+
+        // 文章表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS articles (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "category_id BIGINT DEFAULT 0 COMMENT '所属分类ID', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '标题', " +
+                    "summary VARCHAR(500) DEFAULT '' COMMENT '摘要', " +
+                    "cover_image VARCHAR(500) DEFAULT '' COMMENT '封面图URL', " +
+                    "content LONGTEXT COMMENT '富文本内容', " +
+                    "tags VARCHAR(200) DEFAULT '' COMMENT '标签JSON数组', " +
+                    "author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者', " +
+                    "read_time INT DEFAULT 0 COMMENT '预计阅读分钟数', " +
+                    "related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维JSON数组', " +
+                    "visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private', " +
+                    "visible_to TEXT COMMENT '私密指定人群JSON', " +
+                    "status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/published/archived', " +
+                    "is_featured TINYINT DEFAULT 0 COMMENT '1精选/0普通', " +
+                    "published_at DATETIME DEFAULT NULL COMMENT '发布时间', " +
+                    "view_count INT DEFAULT 0 COMMENT '浏览次数', " +
+                    "created_by BIGINT DEFAULT 0 COMMENT '发布人adminID', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章'");
+            log.info("已创建articles表");
+        } catch (Exception e) {
+            log.warn("创建articles表失败: {}", e.getMessage());
+        }
+
+        // 迁移: articles表补充新字段(兼容旧表)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'visibility'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private' AFTER related_dimensions");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN visible_to TEXT COMMENT '私密指定人群JSON' AFTER visibility");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN tags VARCHAR(200) DEFAULT '' COMMENT '标签JSON数组' AFTER content");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维JSON数组' AFTER read_time");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN is_featured TINYINT DEFAULT 0 COMMENT '1精选/0普通' AFTER status");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN published_at DATETIME DEFAULT NULL COMMENT '发布时间' AFTER is_featured");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN view_count INT DEFAULT 0 COMMENT '浏览次数' AFTER published_at");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN created_by BIGINT DEFAULT 0 COMMENT '发布人adminID' AFTER view_count");
+                log.info("已迁移articles表补充字段");
+            } else {
+                log.info("articles表字段已完整,跳过迁移");
+            }
+        } catch (Exception e) {
+            log.warn("articles表字段迁移失败: {}", e.getMessage());
+        }
+
+        // 迁移: articles表补充知识中心新字段(content_type/difficulty_level/dimension_ids)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'content_type'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN content_type VARCHAR(20) DEFAULT 'article' COMMENT '内容类型: article/knowledge/course/tip' AFTER article_type");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN difficulty_level TINYINT DEFAULT 0 COMMENT '难度等级 1-5' AFTER content_type");
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN dimension_ids TEXT COMMENT '关联维度ID列表JSON' AFTER related_dimensions");
+                log.info("已迁移articles表知识中心新字段");
+            } else {
+                log.info("articles表知识中心字段已完整,跳过迁移");
+            }
+        } catch (Exception e) {
+            log.warn("articles表知识中心字段迁移失败: {}", e.getMessage());
+        }
+
+        // 文章标签关联表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_tags (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "article_id BIGINT NOT NULL COMMENT '文章ID', " +
+                    "tag_id BIGINT NOT NULL COMMENT '标签ID(关联dan_knowledge_tag.id)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_article_tag (article_id, tag_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章-标签关联表'");
+            log.info("已创建article_tags表");
+        } catch (Exception e) {
+            log.warn("创建article_tags表失败: {}", e.getMessage());
+        }
+
+        // ==================== 五维能量系统(账本模式) ======        // 维度定义表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS energy_dimension (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "code VARCHAR(16) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth', " +
+                            "name VARCHAR(20) NOT NULL COMMENT '维度名称: 身/心/智/行/富', " +
+                            "icon VARCHAR(16) COMMENT '图标emoji', " +
+                            "element VARCHAR(10) COMMENT '五行元素: 土/火/金/木/水', " +
+                            "sort_order INT DEFAULT 0 COMMENT '显示顺序', " +
+                            "status TINYINT DEFAULT 1 COMMENT '1启用/0禁用', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量维度定义表'"
+            );
+            log.info("已创建energy_dimension表");
+        } catch (Exception e) {
+            log.warn("创建energy_dimension表失败: {}", e.getMessage());
+        }
+
+        // 服务-维度比例配置表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS energy_source_config (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "source_type VARCHAR(32) NOT NULL COMMENT '来源类型: task/activity/course/product/consultation', " +
+                            "source_id BIGINT NOT NULL COMMENT '来源业务表ID', " +
+                            "source_name VARCHAR(200) COMMENT '冗余名称', " +
+                            "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
+                            "ratio INT NOT NULL COMMENT '占比(bps,10000=100%)', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "INDEX idx_source (source_type, source_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务-维度比例配置表'"
+            );
+            log.info("已创建energy_source_config表");
+        } catch (Exception e) {
+            log.warn("创建energy_source_config表失败: {}", e.getMessage());
+        }
+
+        // 购物车
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS cart_items (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID'," +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID'," +
+                    "quantity INT DEFAULT 1 COMMENT '数量'," +
+                    "selected TINYINT(1) DEFAULT 1 COMMENT '是否选中'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_user (user_id)," +
+                    "INDEX idx_product (product_id)," +
+                    "UNIQUE KEY uk_user_product (user_id, product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车'");
+            log.info("已创建cart_items表");
+        } catch (Exception e) {
+            log.warn("创建cart_items表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS shop_categories (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "name VARCHAR(64) NOT NULL COMMENT '分类名称'," +
+                    "parent_id BIGINT DEFAULT 0 COMMENT '父分类ID(0=根节点)'," +
+                    "level INT DEFAULT 0 COMMENT '层级(0/1/2/3)'," +
+                    "sort INT DEFAULT 0 COMMENT '排序'," +
+                    "image VARCHAR(512) COMMENT '分类图标'," +
+                    "enabled TINYINT(1) DEFAULT 1 COMMENT '启用'," +
+                    "dimension_codes VARCHAR(128) COMMENT '维度编码(逗号分隔: body,mind,heart)'," +
+                    "product_types VARCHAR(128) COMMENT '商品类型(逗号分隔: PHYSICAL,COURSE,ASSESSMENT)'," +
+                    "min_visible_level VARCHAR(50) COMMENT '最低可见会员级别编码(NULL=全部可见)'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_parent (parent_id)," +
+                    "INDEX idx_sort (sort)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品类目'");
+            log.info("已创建shop_categories表");
+        } catch (Exception e) {
+            log.warn("创建shop_categories表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS distribution_systems (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "name VARCHAR(64) NOT NULL COMMENT '体系名称'," +
+                    "description VARCHAR(255) COMMENT '描述'," +
+                    "owner_id BIGINT COMMENT '创建者ID'," +
+                    "profit_rate INT DEFAULT 0 COMMENT '平台利润率(千分比)'," +
+                    "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("已创建distribution_systems表");
+        } catch (Exception e) {
+            log.warn("创建distribution_systems表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS distribution_relations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "system_id BIGINT NOT NULL COMMENT '体系ID'," +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID'," +
+                    "parent_id BIGINT COMMENT '上级用户ID(null=根)'," +
+                    "depth INT DEFAULT 0 COMMENT '层级深度'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "INDEX idx_system (system_id)," +
+                    "INDEX idx_user (user_id)," +
+                    "INDEX idx_parent (parent_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售关系'");
+            log.info("已创建distribution_relations表");
+        } catch (Exception e) {
+            log.warn("创建distribution_relations表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS after_sales_requests (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "order_id BIGINT NOT NULL COMMENT '订单ID'," +
+                    "order_no VARCHAR(32) NOT NULL COMMENT '订单号'," +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID'," +
+                    "type VARCHAR(32) NOT NULL COMMENT '类型: REFUND/RETURN_REFUND'," +
+                    "reason VARCHAR(255) COMMENT '原因'," +
+                    "description TEXT COMMENT '描述'," +
+                    "images TEXT COMMENT '凭证图片(JSON数组)'," +
+                    "refund_amount INT DEFAULT 0 COMMENT '退款金额(分)'," +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected/completed'," +
+                    "reject_reason VARCHAR(255) COMMENT '驳回原因'," +
+                    "handled_by BIGINT COMMENT '处理人ID'," +
+                    "handled_at DATETIME COMMENT '处理时间'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_order (order_id)," +
+                    "INDEX idx_user (user_id)," +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='售后申请'");
+            log.info("已创建after_sales_requests表");
+        } catch (Exception e) {
+            log.warn("创建after_sales_requests表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS shop_discount_rules (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "name VARCHAR(64) NOT NULL COMMENT '规则名称'," +
+                    "type VARCHAR(32) NOT NULL COMMENT '类型: PERCENT/FIXED/THRESHOLD'," +
+                    "value INT NOT NULL COMMENT '折扣值(PERCENT=百分比数字, FIXED/THRESHOLD=金额分)'," +
+                    "threshold_amount INT COMMENT '满减门槛(分,仅THRESHOLD)'," +
+                    "category_id BIGINT COMMENT '适用类目ID(null=全部)'," +
+                    "distribution_system_id BIGINT COMMENT '适用体系ID(null=全部)'," +
+                    "product_ids TEXT COMMENT '适用商品ID(逗号分隔,null=全部)'," +
+                    "start_time DATETIME COMMENT '开始时间'," +
+                    "end_time DATETIME COMMENT '结束时间'," +
+                    "max_uses INT DEFAULT 0 COMMENT '最大使用次数(0=不限)'," +
+                    "used_count INT DEFAULT 0 COMMENT '已使用次数'," +
+                    "enabled TINYINT(1) DEFAULT 1 COMMENT '启用'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_category (category_id)," +
+                    "INDEX idx_enabled (enabled)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='折扣规则'");
+            log.info("已创建shop_discount_rules表");
+        } catch (Exception e) {
+            log.warn("创建shop_discount_rules表失败: {}", e.getMessage());
+        }
+
+        // 电商扩展字段 - products表
+        try {
+            jdbcTemplate.execute("ALTER TABLE products ADD COLUMN category_id BIGINT COMMENT '商品类目ID'");
+            log.info("已添加category_id列到products表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE products ADD COLUMN distribution_system_id BIGINT COMMENT '销售体系ID'");
+            log.info("已添加distribution_system_id列到products表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 电商扩展字段 - product_orders表
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN discount_amount INT DEFAULT 0 COMMENT '优惠金额(分)'");
+            log.info("已添加discount_amount列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN coupon_id BIGINT COMMENT '优惠券ID'");
+            log.info("已添加coupon_id列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN cancel_at DATETIME COMMENT '取消时间'");
+            log.info("已添加cancel_at列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN transaction_id VARCHAR(64) COMMENT '微信支付交易号'");
+            log.info("已添加transaction_id列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN cover_image VARCHAR(512) COMMENT '商品封面图快照'");
+            log.info("已添加cover_image列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN address_snapshot TEXT COMMENT '收货地址快照(JSON)'");
+            log.info("已添加address_snapshot列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 能量流水表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS energy_log (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                            "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
+                            "amount INT NOT NULL COMMENT '变动量(正=获得,负=消耗)', " +
+                            "balance_after INT NOT NULL COMMENT '变动后余额', " +
+                            "source_type VARCHAR(32) COMMENT '来源类型: task/product/medical_report/daily_record', " +
+                            "source_id BIGINT COMMENT '来源ID', " +
+                            "ref_id BIGINT COMMENT '关联旧流水ID(覆盖时标记)', " +
+                            "expires_at DATETIME COMMENT '过期时间(空=永不过期)', " +
+                            "description VARCHAR(500) COMMENT '描述', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "INDEX idx_child_dim (child_id, dimension_id), " +
+                            "INDEX idx_created (created_at), " +
+                            "INDEX idx_source (source_type, source_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量流水表'"
+            );
+            log.info("已创建energy_log表");
+        } catch (Exception e) {
+            log.warn("创建energy_log表失败: {}", e.getMessage());
+        }
+
+        // 维度余额表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS energy_balance (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                            "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
+                            "balance INT DEFAULT 0 COMMENT '当前能量值(可为负)', " +
+                            "total_earned INT DEFAULT 0 COMMENT '累计获得', " +
+                            "total_spent INT DEFAULT 0 COMMENT '累计消耗', " +
+                            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "UNIQUE KEY uk_child_dim (child_id, dimension_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度余额表'"
+            );
+            log.info("已创建energy_balance表");
+        } catch (Exception e) {
+            log.warn("创建energy_balance表失败: {}", e.getMessage());
+        }
+
+        // 维度种子数据(按相生链: 智→富→行→心→身)
+        try {
+            Integer dimCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM energy_dimension", Integer.class);
+            if (dimCount == null || dimCount == 0) {
+                jdbcTemplate.execute("INSERT INTO energy_dimension (code, name, icon, element, sort_order, status) VALUES " +
+                        "('mind', '心', '🔥', '火', 4, 1), " +
+                        "('body', '身', '🌏', '土', 5, 1), " +
+                        "('wisdom', '智', '⚔️', '金', 1, 1), " +
+                        "('action', '行', '🌿', '木', 3, 1), " +
+                        "('wealth', '富', '💧', '水', 2, 1)");
+                log.info("能量维度种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化能量维度种子数据失败: {}", e.getMessage());
+        }
+
+        // 为已有商品插入默认比例配置(与Product.domain一致)
+        try {
+            Integer productConfigCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM energy_source_config WHERE source_type = 'product'", Integer.class);
+            if (productConfigCount == null || productConfigCount == 0) {
+                String[][] domainMapping = {
+                        {"action", "action"}, {"mind", "mind"}, {"body", "body"},
+                        {"wisdom", "wisdom"}, {"wealth", "wealth"}
+                };
+                for (String[] mapping : domainMapping) {
+                    jdbcTemplate.execute(
+                            "INSERT INTO energy_source_config (source_type, source_id, source_name, dimension_id, ratio) " +
+                                    "SELECT 'product', p.id, p.name, d.id, 10000 " +
+                                    "FROM products p " +
+                                    "JOIN energy_dimension d ON d.code = '" + mapping[1] + "' " +
+                                    "WHERE p.domain = '" + mapping[0] + "' " +
+                                    "AND NOT EXISTS (" +
+                                    "  SELECT 1 FROM energy_source_config esc " +
+                                    "  WHERE esc.source_type = 'product' AND esc.source_id = p.id" +
+                                    ")"
+                    );
+                }
+                log.info("已有商品能量比例配置已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化商品能量比例配置失败: {}", e.getMessage());
+        }
+
+        // ==================== 精准营养 P0: health_report_survey =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_report_survey (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                    "survey_data JSON COMMENT '调研问卷结果JSON', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_report (report_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告调研问卷'");
+            log.info("已创建 health_report_survey 表");
+        } catch (Exception e) {
+            log.warn("创建 health_report_survey 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 精准营养 P0: health_gut_flora + health_disease_risk =============
+        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_gut_flora (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
+                    "bacteria_name VARCHAR(100) NULL COMMENT '菌种名称', " +
+                    "bacteria_value VARCHAR(50) COMMENT '菌种丰度值', " +
+                    "normal_range VARCHAR(100) COMMENT '正常范围', " +
+                    "status VARCHAR(20) COMMENT '状态: 正常/偏低/偏高', " +
+                    "category VARCHAR(50) COMMENT '分类: 有益菌/有害菌/中性菌', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_report (report_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菌群检测专项数据'");
+            log.info("已创建 health_gut_flora 表");
+        } catch (Exception e) {
+            log.warn("创建 health_gut_flora 表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_disease_risk (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
+                    "disease_name VARCHAR(100) NOT NULL COMMENT '疾病名称', " +
+                    "risk_value VARCHAR(50) COMMENT '风险值(0-1)', " +
+                    "risk_level VARCHAR(20) COMMENT '风险等级: 高风险/需注意/低风险', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_report (report_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疾病风险评估数据'");
+            log.info("已创建 health_disease_risk 表");
+        } catch (Exception e) {
+            log.warn("创建 health_disease_risk 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: health_reports 表添加新字段 =============        
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN balance_score INT DEFAULT NULL COMMENT '菌群平衡分'");
+            log.info("已添加balance_score列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN diversity_score INT DEFAULT NULL COMMENT '菌群多样性分'");
+            log.info("已添加diversity_score列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN beneficial_score INT DEFAULT NULL COMMENT '有益菌评分'");
+            log.info("已添加beneficial_score列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN harmful_score INT DEFAULT NULL COMMENT '有害菌评分'");
+            log.info("已添加harmful_score列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN core_genus_score INT DEFAULT NULL COMMENT '核心菌属评分'");
+            log.info("已添加core_genus_score列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移: health_reports 表添加报告编号和姓名字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN report_number VARCHAR(50) DEFAULT NULL COMMENT '报告编号'");
+            log.info("已添加report_number列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN person_name VARCHAR(50) DEFAULT NULL COMMENT '被检测人姓名'");
+            log.info("已添加person_name列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // ==================== 迁移: health_gut_flora 表添加新字段 =============        
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN population_level VARCHAR(50) DEFAULT NULL COMMENT '处于人群%水平'");
+            log.info("已添加population_level列到health_gut_flora表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN detection_rate VARCHAR(50) DEFAULT NULL COMMENT '%正常人有检出'");
+            log.info("已添加detection_rate列到health_gut_flora表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN description TEXT DEFAULT NULL COMMENT '菌种说明'");
+            log.info("已添加description列到health_gut_flora表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN `level` VARCHAR(20) DEFAULT 'GENUS' COMMENT '级别: GENUS/SPECIES'");
+            log.info("已添加level列到health_gut_flora表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // ==================== 迁移: guides 表统一 ======        // 迁移: guides 添加 type 字段(区分服务商类型)
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN type VARCHAR(32) DEFAULT 'teacher' COMMENT '服务商类型: teacher/planner/activity_provider/product_supplier'");
+            log.info("已添加type列到guides表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移: guides 添加 teachers 合并字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN teacher_no VARCHAR(20) COMMENT '服务商编号'");
+            log.info("已添加teacher_no列到guides表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN title VARCHAR(50) COMMENT '职称/称号'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN specialty VARCHAR(200) COMMENT '专长领域'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN bio TEXT COMMENT '简介'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN experience_years TINYINT DEFAULT 0 COMMENT '从业年限'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN rating INT DEFAULT 500 COMMENT '评分(百分位,500=5.00)'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN total_appointments INT DEFAULT 0 COMMENT '总服务次数'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN city VARCHAR(50) COMMENT '城市'");
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN district VARCHAR(50) COMMENT '区县'");
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street VARCHAR(100) COMMENT '街道'");
+            log.info("已添加地址字段到guides表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN verified_at DATETIME COMMENT '审核通过时间'");
+        } catch (Exception e) {
+        }
+
+        // 迁移: 将 teachers 表数据合并到 guides(type='teacher')
+        try {
+            int migrated = jdbcTemplate.update(
+                    "INSERT IGNORE INTO guides (user_id, type, teacher_no, title, specialty, bio, experience_years, rating, total_appointments, " +
+                            "street_id, province, city, district, street, verified_at, status, level, created_at, updated_at) " +
+                            "SELECT t.user_id, 'teacher', t.teacher_no, t.title, t.specialty, t.bio, t.experience_years, t.rating, t.total_appointments, " +
+                            "t.street_id, t.province, t.city, t.district, t.street, t.verified_at, " +
+                            "CASE WHEN t.status = 'active' THEN 'active' ELSE 'inactive' END, " +
+                            "CASE WHEN t.verification_status = 'approved' THEN 'senior' WHEN t.verification_status = 'pending' THEN 'junior' ELSE 'junior' END, " +
+                            "t.created_at, t.updated_at " +
+                            "FROM teachers t " +
+                            "LEFT JOIN guides g ON g.user_id = t.user_id " +
+                            "WHERE g.id IS NULL"
+            );
+            if (migrated > 0) {
+                log.info("已从 teachers 表迁移 {} 条数据到 guides(type=teacher)", migrated);
+            }
+        } catch (Exception e) {
+            log.warn("迁移 teachers → guides 数据失败: {}", e.getMessage());
+        }
+
+        // 迁移: dan_assessment_results 表添加认知维度字段(6维认知体系扩展)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'perception_score'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
+                        "ADD COLUMN perception_score INT COMMENT '感知得分', " +
+                        "ADD COLUMN spatial_score INT COMMENT '空间思维得分', " +
+                        "ADD COLUMN processing_speed_score INT COMMENT '加工速度得分'");
+                log.info("已为 dan_assessment_results 表添加认知维度字段");
+            } else {
+                log.info("dan_assessment_results 认知维度字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 dan_assessment_results 认知维度字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: dan_assessment_results 表添加 EMI 维度字段(v1.2 心智模型重构)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'emotion_management_score'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
+                        "ADD COLUMN emotion_management_score INT COMMENT '情绪管理得分(EMI)', " +
+                        "ADD COLUMN empathy_score INT COMMENT '同理心得分(EMI)', " +
+                        "ADD COLUMN social_adaptability_score INT COMMENT '社会适应得分(EMI)', " +
+                        "ADD COLUMN self_motivation_score INT COMMENT '自我激励得分(EMI)'");
+                log.info("已为 dan_assessment_results 表添加 EMI 维度字段");
+            } else {
+                log.info("dan_assessment_results EMI 维度字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 dan_assessment_results EMI 维度字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: dan_assessment_results 表添加 EMI 心理子维度字段
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'emotion_score'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
+                        "ADD COLUMN emotion_score INT COMMENT '情绪商数(0-100)', " +
+                        "ADD COLUMN resilience_score INT COMMENT '心理韧性(0-100)', " +
+                        "ADD COLUMN stress_coping_score INT COMMENT '压力应对(0-100)', " +
+                        "ADD COLUMN self_awareness_score INT COMMENT '自我认知(0-100)'");
+                log.info("已为 dan_assessment_results 表添加 EMI 心理子维度字段");
+            } else {
+                log.info("dan_assessment_results EMI 心理子维度字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 dan_assessment_results EMI 心理子维度字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: dan_assessment_results 表添加大五人格维度字段
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'openness_score'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
+                        "ADD COLUMN openness_score INT COMMENT '开放性(0-100)', " +
+                        "ADD COLUMN conscientiousness_score INT COMMENT '尽责性(0-100)', " +
+                        "ADD COLUMN extraversion_score INT COMMENT '外向性(0-100)', " +
+                        "ADD COLUMN agreeableness_score INT COMMENT '宜人性(0-100)', " +
+                        "ADD COLUMN neuroticism_score INT COMMENT '神经质(0-100)', " +
+                        "ADD COLUMN big_five_overall_score INT COMMENT '大五人格综合分'");
+                log.info("已为 dan_assessment_results 表添加大五人格维度字段");
+            } else {
+                log.info("dan_assessment_results 大五人格维度字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 dan_assessment_results 大五人格维度字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: users 表添加出生时辰字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN birth_hour VARCHAR(10) COMMENT '出生时辰: 子丑寅卯辰巳午未申酉戌亥'");
+            log.info("已添加birth_hour列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移: 重建 family_member_attributes 表结构(v1.2 心智模型)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'family_member_attributes' AND COLUMN_NAME = 'member_id'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                // 旧表存在但结构不同,先删除旧表重建
+                jdbcTemplate.execute("DROP TABLE IF EXISTS family_member_attributes_old");
+                jdbcTemplate.execute("RENAME TABLE family_member_attributes TO family_member_attributes_old");
+                jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_member_attributes (" +
+                        "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                        "member_id BIGINT NOT NULL COMMENT '家庭成员ID(children表或users表)', " +
+                        "member_type VARCHAR(10) NOT NULL COMMENT '成员类型: child/parent', " +
+                        "birth_datetime DATETIME COMMENT '出生日期(精确到分钟,用于八字排盘)', " +
+                        "eight_characters JSON COMMENT '八字四柱JSON', " +
+                        "wuxing_elements JSON COMMENT '五行星盘元素JSON', " +
+                        "zodiac VARCHAR(16) COMMENT '生肖: rat/ox/tiger/rabbit/dragon/snake/horse/sheep/monkey/rooster/dog/pig', " +
+                        "blood_type VARCHAR(5) COMMENT '血型: A/B/AB/O', " +
+                        "mind_base_score INT DEFAULT 0 COMMENT '心维度基础分(百分位,500=5.00)', " +
+                        "wisdom_base_score INT DEFAULT 0 COMMENT '智维度基础分(百分位,500=5.00)', " +
+                        "behavior_modifier JSON COMMENT '行为修正因子JSON', " +
+                        "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                        "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                        "UNIQUE KEY uk_member (member_id, member_type)" +
+                        ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员先天属性表'");
+                log.info("已重建 family_member_attributes 表(v1.2 心智模型)");
+            } else {
+                log.info("family_member_attributes 表已为 v1.2 结构,跳过");
+            }
+} catch (Exception e) {
+            log.warn("重建 family_member_attributes 表失败: {}", e.getMessage());
+        }
+
+
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'show_to_family'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN show_to_family TINYINT DEFAULT 1 COMMENT '是否对家庭成员可见: 1=是, 0=否'");
+                log.info("已为 users 表添加 show_to_family 列");
+            } else {
+                log.info("users 表 show_to_family 列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 users 表 show_to_family 列失败: {}", e.getMessage());
+        }
+
+        // 迁移: tasks 表添加 family_member_id 列(FamilyMember 迁移)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tasks' AND COLUMN_NAME = 'family_member_id'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN family_member_id BIGINT COMMENT '家庭成员ID(关联 family_members.id)'");
+                log.info("已为 tasks 表添加 family_member_id 列");
+            } else {
+                log.info("tasks 表 family_member_id 列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 tasks 表 family_member_id 列失败: {}", e.getMessage());
+        }
+
+        // 迁移: family_members 表添加 role_override 列(FamilyMember 迁移)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'family_members' AND COLUMN_NAME = 'role_override'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN role_override VARCHAR(20) DEFAULT 'auto' COMMENT '角色覆盖: auto/parent/child/elderly,空=auto'");
+                log.info("已为 family_members 表添加 role_override 列");
+            } else {
+                log.info("family_members 表 role_override 列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 family_members 表 role_override 列失败: {}", e.getMessage());
+        }
+
+        // 迁移: family_members 表添加 id_card 列(孩子身份证号)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'family_members' AND COLUMN_NAME = 'id_card'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN id_card VARCHAR(18) DEFAULT NULL COMMENT '身份证号(孩子)'");
+                log.info("已为 family_members 表添加 id_card 列");
+            } else {
+                log.info("family_members 表 id_card 列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 family_members 表 id_card 列失败: {}", e.getMessage());
+        }
+
+        // 迁移: family_members 表添加孩子专属字段(原children表合并)
+        String[][] childColumns = {
+                {"penalty_enabled", "TINYINT DEFAULT 1 COMMENT '是否扣减积分(0否1是)'"},
+                {"theme", "VARCHAR(32) DEFAULT 'default' COMMENT '主题'"},
+                {"system_points", "INT DEFAULT 0 COMMENT '系统积分(干预/平台奖励)'"},
+                {"streak_days", "INT DEFAULT 0 COMMENT '连续打卡天数'"},
+                {"last_task_date", "DATE COMMENT '上次任务日期'"},
+                {"focus_max_daily", "TINYINT DEFAULT 3 COMMENT '每日专注上限'"},
+                {"focus_remaining", "TINYINT DEFAULT 3 COMMENT '今日剩余专注次数'"},
+                {"focus_reset_date", "DATE COMMENT '专注次数重置日期'"}
+        };
+        for (String[] col : childColumns) {
+            try {
+                Integer colExists = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'family_members' AND COLUMN_NAME = ?",
+                        Integer.class, col[0]);
+                if (colExists == null || colExists == 0) {
+                    jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN " + col[0] + " " + col[1]);
+                    log.info("已为 family_members 表添加 {} 列", col[0]);
+                }
+            } catch (Exception e) {
+                log.warn("检查/添加 family_members 表 {} 列失败: {}", col[0], e.getMessage());
+            }
+        }
+
+        // 家庭成员表添加 role_override 列(替换旧 capability_role)
+        ensureColumn("family_members", "role_override", "VARCHAR(20) DEFAULT 'auto' COMMENT '角色覆盖: auto/parent/child/elderly'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members DROP COLUMN capability_role");
+            log.info("已删除 family_members.capability_role 旧列,已由 role_override 替代");
+        } catch (Exception e) {
+            // 列不存在或已删除,忽略错误
+        }
+
+        // activities 表添加报名/签到相关字段
+        ensureColumn("activities", "require_registration", "TINYINT DEFAULT 1 COMMENT '是否需要报名: 1=需要, 0=直接参与'");
+        ensureColumn("activities", "require_checkin_review", "TINYINT DEFAULT 1 COMMENT '打卡是否需要审核: 1=需要, 0=直接通过'");
+        ensureColumn("activities", "energy_penalty_unregistered", "INT DEFAULT 5 COMMENT '未报名打卡的行能量扣减值'");
+
+        // family_members 表添加关系评分字段
+        ensureColumn("family_members", "trust_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '信任度 0-100'");
+        ensureColumn("family_members", "intimacy_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '亲密度 0-100'");
+        ensureColumn("family_members", "communication_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '沟通质量 0-100'");
+        ensureColumn("family_members", "last_interaction_at", "DATETIME COMMENT '最近互动时间'");
+
+        // 创建购物车表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS cart (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID', " +
+                    "quantity INT NOT NULL DEFAULT 1 COMMENT '数量', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_product (user_id, product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车'");
+            log.info("已创建 cart 表");
+        } catch (Exception e) {
+            log.warn("创建 cart 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: activities表添加visible_scope列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'activities' AND COLUMN_NAME = 'visible_scope'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN visible_scope VARCHAR(20) DEFAULT NULL COMMENT '可见范围: family(家庭)/role(角色)/user(用户)' AFTER visibility");
+                log.info("已添加visible_scope列到activities表");
+            } else {
+                log.info("activities表visible_scope列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加activities表visible_scope列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: articles表添加article_type列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'article_type'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN article_type VARCHAR(20) DEFAULT 'normal' COMMENT '文章类型: normal(普通)/premium(优质)/original(原创)' AFTER read_time");
+                log.info("已添加article_type列到articles表");
+            } else {
+                log.info("articles表article_type列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加articles表article_type列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: articles表添加word_count列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'word_count'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN word_count INT DEFAULT 0 COMMENT '文章字数' AFTER article_type");
+                log.info("已添加word_count列到articles表");
+            } else {
+                log.info("articles表word_count列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加articles表word_count列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: article_reading_records表添加task_id列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'article_reading_records' AND COLUMN_NAME = 'task_id'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE article_reading_records ADD COLUMN task_id BIGINT DEFAULT NULL COMMENT '关联的任务ID' AFTER duration_seconds");
+                log.info("已添加task_id列到article_reading_records表");
+            } else {
+                log.info("article_reading_records表task_id列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加article_reading_records表task_id列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: tasks表添加source_type列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tasks' AND COLUMN_NAME = 'source_type'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN source_type VARCHAR(20) DEFAULT NULL COMMENT '来源类型: article/activity/manual' AFTER minigame_code");
+                log.info("已添加source_type列到tasks表");
+            } else {
+                log.info("tasks表source_type列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加tasks表source_type列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: tasks表添加source_id列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tasks' AND COLUMN_NAME = 'source_id'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN source_id BIGINT DEFAULT NULL COMMENT '来源ID' AFTER source_type");
+                log.info("已添加source_id列到tasks表");
+            } else {
+                log.info("tasks表source_id列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加tasks表source_id列失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移: activities表添加checkin_points列 =============        
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'activities' AND COLUMN_NAME = 'checkin_points'",
+                    Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN checkin_points INT DEFAULT 0 COMMENT '签到积分(0=使用系统默认)' AFTER price");
+                log.info("已添加checkin_points列到activities表");
+            } else {
+                log.info("activities表checkin_points列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加activities表checkin_points列失败: {}", e.getMessage());
+        }
+
+        // 迁移: 创建family_invitations表(家庭邀请令牌)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_invitations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "token VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请令牌(UUID)', " +
+                    "created_by BIGINT NOT NULL COMMENT '创建人用户ID', " +
+                    "status VARCHAR(16) DEFAULT 'active' COMMENT '状态: active/used/expired/revoked', " +
+                    "expires_at DATETIME COMMENT '过期时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_token (token), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭邀请令牌'");
+            log.info("已创建family_invitations表");
+        } catch (Exception e) {
+            log.warn("创建family_invitations表失败: {}", e.getMessage());
+        }
+
+        // ==================== P0 营养师: nutrition_indicator_mapping =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS nutrition_indicator_mapping (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "indicator_name_pattern VARCHAR(200) NOT NULL COMMENT '指标名匹配模式(SQL LIKE语法,%为通配符)'," +
+                    "nutrient VARCHAR(50) NOT NULL COMMENT '关联营养素名'," +
+                    "severity_weight DECIMAL(3,2) DEFAULT 1.00 COMMENT '权重(0-1)'," +
+                    "deficiency_symptoms TEXT COMMENT '缺乏症状描述'," +
+                    "excess_symptoms TEXT COMMENT '过量症状描述'," +
+                    "food_suggestions TEXT COMMENT '推荐食物(逗号分隔)'," +
+                    "sort_order INT DEFAULT 0 COMMENT '排序(大值优先匹配)'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_nutrient (nutrient)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指标-营养素映射表'");
+            log.info("已创建 nutrition_indicator_mapping 表");
+        } catch (Exception e) {
+            log.warn("创建 nutrition_indicator_mapping 表失败: {}", e.getMessage());
+        }
+
+        // P0 营养师: nutrition_deficiency_record
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS nutrition_deficiency_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "child_id BIGINT NOT NULL COMMENT '孩子ID'," +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID'," +
+                    "nutrient VARCHAR(50) NOT NULL COMMENT '营养素名'," +
+                    "deficiency_type VARCHAR(20) NOT NULL COMMENT 'deficiency/excess/borderline/unclassified'," +
+                    "severity DECIMAL(5,2) DEFAULT 0 COMMENT '严重程度(0-1)'," +
+                    "indicator_name VARCHAR(100) COMMENT '关联指标名'," +
+                    "indicator_value VARCHAR(50) COMMENT '指标检测值'," +
+                    "ref_range VARCHAR(100) COMMENT '参考范围'," +
+                    "symptoms TEXT COMMENT '症状描述'," +
+                    "food_suggestions TEXT COMMENT '推荐食物'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "INDEX idx_child (child_id)," +
+                    "INDEX idx_report (report_id)," +
+                    "INDEX idx_nutrient (nutrient)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养缺乏分析记录'");
+            log.info("已创建 nutrition_deficiency_record 表");
+        } catch (Exception e) {
+            log.warn("创建 nutrition_deficiency_record 表失败: {}", e.getMessage());
+        }
+
+        // P0 营养师: 指标-营养素映射 种子数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM nutrition_indicator_mapping", Integer.class);
+            if (count == null || count == 0) {
+                String[][] seedData = {
+                        {"铁蛋白", "铁", "1.00", "乏力、面色苍白、注意力不集中", "铁过量可能损伤肝脏", "动物肝脏,红肉,菠菜,黑木耳", "10"},
+                        {"25-羟基维生素D", "维生素D", "1.00", "影响钙吸收、骨骼发育", "维生素D过量可致高钙血症", "三文鱼,蛋黄,蘑菇,强化牛奶", "20"},
+                        {"维生素D", "维生素D", "1.00", "影响钙吸收、骨骼发育、免疫力下降", "维生素D过量可致高钙血症", "三文鱼,蛋黄,蘑菇,强化牛奶", "15"},
+                        {"锌", "锌", "0.80", "食欲减退、味觉异常、免疫力降低", "锌过量影响铜吸收", "牡蛎,牛肉,南瓜子,扁豆", "10"},
+                        {"钙", "钙", "0.80", "骨骼发育迟缓、牙齿问题", "高钙血症、肾结石", "牛奶,豆腐,虾皮,芝麻酱", "10"},
+                        {"维生素B12", "维生素B12", "1.00", "贫血、神经发育迟缓", "一般不易过量", "牛肉,鸡蛋,牛奶,鱼肉", "10"},
+                        {"维生素C", "维生素C", "0.60", "免疫力下降、牙龈出血", "腹泻、肾结石风险", "猕猴桃,橙子,草莓,青椒", "10"},
+                        {"白蛋白", "蛋白质", "0.90", "发育迟缓、肌肉萎缩", "肾脏负担", "鸡蛋,鱼肉,牛奶,豆制品", "10"},
+                        {"总蛋白", "蛋白质", "0.90", "营养不良、免疫力下降", "血液粘稠", "鸡蛋,鱼肉,牛奶,豆制品", "9"},
+                        {"膳食纤维", "膳食纤维", "0.50", "便秘、肠道菌群失衡", "腹胀、排便过多", "全谷物,蔬菜,豆类,水果", "10"},
+                        {"叶酸", "叶酸", "0.70", "贫血、发育迟缓", "一般不易过量", "菠菜,动物肝脏,豆类,柑橘", "10"},
+                        {"维生素A", "维生素A", "0.70", "视力问题、皮肤干燥、免疫力下降", "头痛、肝脏损伤", "胡萝卜,动物肝脏,南瓜,菠菜", "10"},
+                        {"硒", "硒", "0.50", "免疫力下降、甲状腺功能异常", "脱发、指甲脆裂", "巴西坚果,海鲜,蘑菇", "10"},
+                        {"镁", "镁", "0.40", "肌肉痉挛、睡眠问题", "腹泻、低血压", "坚果,全谷物,深绿蔬菜", "10"},
+                        {"益生菌%", "益生菌", "0.60", "肠道菌群失衡、消化问题", "一般不易过量", "酸奶,泡菜,味噌,康普茶", "10"}
+                };
+                for (String[] row : seedData) {
+                    jdbcTemplate.update("INSERT IGNORE INTO nutrition_indicator_mapping " +
+                                    "(indicator_name_pattern, nutrient, severity_weight, deficiency_symptoms, excess_symptoms, food_suggestions, sort_order) " +
+                                    "VALUES (?, ?, ?, ?, ?, ?, ?)",
+                            row[0], row[1], new java.math.BigDecimal(row[2]), row[3], row[4], row[5], Integer.parseInt(row[6]));
+                }
+                log.info("nutrition_indicator_mapping 种子数据已加载 (15条)");
+            } else {
+                log.info("nutrition_indicator_mapping 种子数据已存在 ({}条), 跳过", count);
+            }
+        } catch (Exception e) {
+            log.warn("nutrition_indicator_mapping 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // ==================== 心版块 Phase 1: emotion_checkin 表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS emotion_checkin (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '打卡人用户ID', " +
+                    "emotion_type INT NOT NULL COMMENT '情绪类型: 1快乐/2平和平静/3平静/4有点不舒服/5难受/6烦躁/7生气/8焦虑/9恐惧', " +
+                    "emotion_name VARCHAR(20) COMMENT '情绪名称: happy/calm/peaceful/uncomfortable/sad/irritable/angry/anxious/fearful', " +
+                    "energy_level INT NOT NULL DEFAULT 3 COMMENT '能量状态: 1很低/2低/3中等/4高/5很高', " +
+                    "note TEXT COMMENT '打卡备注', " +
+                    "photo_url VARCHAR(500) COMMENT '照片URL', " +
+                    "favorite_thing VARCHAR(500) COMMENT '今日最喜欢的一件事', " +
+                    "points_awarded INT DEFAULT 0 COMMENT '奖励积分', " +
+                    "parent_visible TINYINT DEFAULT 1 COMMENT '家长可见: 1可见/0不可见', " +
+                    "checkin_date DATE NOT NULL COMMENT '打卡日期', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_id (child_id), " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_checkin_date (checkin_date), " +
+                    "INDEX idx_child_date (child_id, checkin_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='情绪打卡表'");
+            log.info("已创建 emotion_checkin 情绪打卡表");
+        } catch (Exception e) {
+            log.warn("创建 emotion_checkin 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 1a-2: DAN Schema 修复 =============        // v1.2 EMI 心理子维度
+        String[] danEmiFields = {"emotion_score", "resilience_score", "stress_coping_score", "self_awareness_score"};
+        for (String field : danEmiFields) {
+            try {
+                Integer exists = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = ?",
+                        Integer.class, field);
+                if (exists == null || exists == 0) {
+                    jdbcTemplate.execute("ALTER TABLE dan_assessment_results ADD COLUMN " + field + " INT DEFAULT NULL COMMENT 'EMI子维度 0-100'");
+                    log.info("已添加 {} 列到 dan_assessment_results 表", field);
+                }
+            } catch (Exception e) {
+                log.warn("检查/添加 {} 列失败: {}", field, e.getMessage());
+            }
+        }
+        // 大五人格维度
+        String[] danBigFiveFields = {"openness_score", "conscientiousness_score", "extraversion_score", "agreeableness_score", "neuroticism_score", "big_five_overall_score"};
+        for (String field : danBigFiveFields) {
+            try {
+                Integer exists = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = ?",
+                        Integer.class, field);
+                if (exists == null || exists == 0) {
+                    jdbcTemplate.execute("ALTER TABLE dan_assessment_results ADD COLUMN " + field + " INT DEFAULT NULL COMMENT '大五人格 0-100'");
+                    log.info("已添加 {} 列到 dan_assessment_results 表", field);
+                }
+            } catch (Exception e) {
+                log.warn("检查/添加 {} 列失败: {}", field, e.getMessage());
+            }
+        }
+        // EMI 维度(情绪商数评估字段)
+        String[] danEmiDimFields = {"emotion_management_score", "empathy_score", "social_adaptability_score", "self_motivation_score"};
+        for (String field : danEmiDimFields) {
+            try {
+                Integer exists = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = ?",
+                        Integer.class, field);
+                if (exists == null || exists == 0) {
+                    jdbcTemplate.execute("ALTER TABLE dan_assessment_results ADD COLUMN " + field + " INT DEFAULT NULL COMMENT 'EMI维度 0-100'");
+                    log.info("已添加 {} 列到 dan_assessment_results 表", field);
+                }
+            } catch (Exception e) {
+                log.warn("检查/添加 {} 列失败: {}", field, e.getMessage());
+            }
+        }
+
+        // ==================== 1b-1: bazi_reading_template 五行命理解读表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS bazi_reading_template (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "element VARCHAR(20) NOT NULL COMMENT '五行: wood/fire/earth/metal/water', " +
+                    "`condition` VARCHAR(20) NOT NULL COMMENT '状态: strong/weak/balanced', " +
+                    "personal_trait TEXT COMMENT '性格特质描述', " +
+                    "mind_growth_advice TEXT COMMENT '心理成长建议', " +
+                    "daily_tip_template TEXT COMMENT '每日宜忌模板', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_element_condition (element, `condition`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五行命理解读模板'");
+            log.info("已创建 bazi_reading_template 表");
+        } catch (Exception e) {
+            log.warn("创建 bazi_reading_template 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 1b-1: numsoul_config 生命灵数配置表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS numsoul_config (" +
+                    "number INT PRIMARY KEY COMMENT '灵数1-9', " +
+                    "title VARCHAR(50) NOT NULL COMMENT '称号', " +
+                    "keywords VARCHAR(200) COMMENT '性格关键词', " +
+                    "mind_growth_advice TEXT COMMENT '心理成长建议', " +
+                    "recommended_article_tags VARCHAR(200) COMMENT '关联文章标签', " +
+                    "color_hex VARCHAR(7) COMMENT '代表色' " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生命灵数配置'");
+            log.info("已创建 numsoul_config 表");
+        } catch (Exception e) {
+            log.warn("创建 numsoul_config 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 1b-1: bazi_reading_template 种子数据 =============        
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM bazi_reading_template", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.batchUpdate(
+                        "INSERT INTO bazi_reading_template (element, `condition`, personal_trait, mind_growth_advice, daily_tip_template) VALUES (?, ?, ?, ?, ?)",
+                        java.util.Arrays.asList(new Object[][]{
+                                {"wood", "strong", "性格坚韧、有进取心,具有领导力和开拓精神", "学会适当放松,避免过刚易折,培养弹性思维", "宜进行伸展运动,忌高强度冲突"},
+                                {"wood", "weak", "温和有礼、善于合作,但决策时容易犹豫", "增强自信心训练,培养果断决策能力", "宜与自然相处,忌过度迁就他人"},
+                                {"wood", "balanced", "性格中庸有度,既有韧性又有灵活性", "保持现状,适当拓展社交圈", "宜户外活动,忌久坐不动"},
+                                {"fire", "strong", "热情开朗、充满活力,富有感染力和创造力", "注意情绪管理,避免急躁冲动", "宜冷水刺激冷静,忌暴怒争执"},
+                                {"fire", "weak", "内敛含蓄、心思细腻,对艺术有敏锐感知", "适当表达情感,培养社交自信", "宜温暖陪伴,忌独处过久"},
+                                {"fire", "balanced", "性格温润有度,既有热情又懂克制", "保持兴趣爱好,适度拓展表达", "宜参与社交活动,忌情绪压抑"},
+                                {"earth", "strong", "稳重可靠、有责任心,是团队中的中流砥柱", "避免过度承担,学会说'不'", "宜适量运动,忌思虑过重"},
+                                {"earth", "weak", "随和友善、适应性强,容易与他人相处", "增强主见性,培养独立思考能力", "宜接触新事物,忌固守不变"},
+                                {"earth", "balanced", "性情敦厚、处事稳妥,人际关系和谐", "保持学习,适当挑战自我", "宜亲近自然,忌暴饮暴食"},
+                                {"metal", "strong", "意志坚定、追求完美,具有卓越的执行力", "学会灵活变通,避免过于严苛", "宜艺术鉴赏,忌过度批评"},
+                                {"metal", "weak", "温和寡言、善于倾听,是值得信赖的朋友", "增强表达勇气,培养自信心", "宜团体活动,忌过度自我封闭"},
+                                {"metal", "balanced", "为人正直、做事有度,不乏柔韧与包容", "保持开放心态,适度多元发展", "宜阅读学习,忌钻牛角尖"},
+                                {"water", "strong", "智慧深沉、善于谋略,具有出色洞察力", "避免过度缜密,适当放松心情", "宜静坐冥想,忌思虑过深"},
+                                {"water", "weak", "敏感细腻、有同理心,富有创作天赋", "增强抵抗力,培养情绪韧性", "宜温暖环境,忌受寒受凉"},
+                                {"water", "balanced", "性格圆融、随机应变,处事灵活得宜", "保持好奇心,适度扩展影响力", "宜游泳泡澡,忌情绪波动"},
+                        })
+                );
+                log.info("bazi_reading_template 种子数据已加载 (15条)");
+            }
+        } catch (Exception e) {
+            log.warn("bazi_reading_template 种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // ==================== 1b-1: numsoul_config 种子数据 =============        
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM numsoul_config", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.batchUpdate(
+                        "INSERT INTO numsoul_config (number, title, keywords, mind_growth_advice, recommended_article_tags, color_hex) VALUES (?, ?, ?, ?, ?, ?)",
+                        java.util.Arrays.asList(new Object[][]{
+                                {1, "开创者", "独立、领导、创新、自信", "培养团队协作意识,避免独断专行", "领导力,自信培养", "#FF4444"},
+                                {2, "和平者", "温和、合作、敏感、耐心", "增强自我主张,避免过度迎合他人", "人际交往,情绪表达", "#FF8C00"},
+                                {3, "表达者", "创意、社交、乐观、表达", "保持专注,避免浅尝辄止", "创造力,社交能力", "#FFD700"},
+                                {4, "建设者", "务实、稳定、有序、勤奋", "学会变通,避免过于固执", "专注力,学习习惯", "#4CAF50"},
+                                {5, "自由者", "冒险、变化、好奇、灵活", "培养持久力,避免缺乏恒心", "探索精神,适应能力", "#2196F3"},
+                                {6, "关怀者", "责任、关爱、奉献、平衡", "学会自我关爱,避免过度付出", "家庭关系,责任心", "#9C27B0"},
+                                {7, "探索者", "智慧、分析、内省、完美", "与人分享见解,避免过度封闭", "思维能力,学习力", "#673AB7"},
+                                {8, "成就者", "雄心、管理、决策、权威", "关注内在成长,避免过于追求外在成就", "目标管理,理财思维", "#3F51B5"},
+                                {9, "博爱者", "包容、智慧、善良、豁达", "设定个人边界,避免过度牺牲", "人际关系,情绪管理", "#E91E63"},
+                        })
+                );
+                log.info("numsoul_config 种子数据已加载 (9条)");
+            }
+        } catch (Exception e) {
+            log.warn("numsoul_config 种子数据初始化失败: {}", e.getMessage());
+        }
+
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_address (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "receiver_name VARCHAR(50) NOT NULL COMMENT '收货人姓名', " +
+                    "phone VARCHAR(20) NOT NULL COMMENT '收货人电话', " +
+                    "province VARCHAR(50) COMMENT '省份', " +
+                    "city VARCHAR(50) COMMENT '城市', " +
+                    "district VARCHAR(50) COMMENT '区县', " +
+                    "street VARCHAR(200) COMMENT '详细地址', " +
+                    "is_default INT DEFAULT 0 COMMENT '是否默认 1=是 0=否', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表'");
+            log.info("已创建 user_address 表");
+        } catch (Exception e) {
+            log.warn("创建 user_address 表失败: {}", e.getMessage());
+        }
+
+        // ==================== 会员价格体系 ======        // 迁移: activities表添加member_price列
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'activities' AND COLUMN_NAME = 'member_price'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN member_price INT COMMENT '会员价(分)' AFTER price");
+                log.info("已添加member_price列到activities表");
+            } else {
+                log.info("member_price列已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加activities.member_price失败: {}", e.getMessage());
+        }
+
+        // 迁移: 创建 member_discount_configs 表(分类级别折扣配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_discount_configs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "level_code VARCHAR(20) NOT NULL COMMENT '会员等级: FAMILY/PROVIDER', " +
+                    "target_type VARCHAR(20) NOT NULL COMMENT '目标类型: product/activity', " +
+                    "target_id BIGINT DEFAULT NULL COMMENT '目标ID(NULL表示分类级别)', " +
+                    "category_id BIGINT DEFAULT NULL COMMENT '类目ID(分类级别折扣)', " +
+                    "discount_percent INT NOT NULL COMMENT '折扣百分比(如90表示90%)', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '是否启用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_level (level_code), " +
+                    "INDEX idx_target (target_type, target_id), " +
+                    "INDEX idx_category (target_type, category_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员折扣配置'");
+            log.info("已创建member_discount_configs表");
+        } catch (Exception e) {
+            log.warn("创建member_discount_configs表失败: {}", e.getMessage());
+        }
+
+        // ==================== 食材推荐指数 Phase: food_recommend_idx / food_recommend_idx_log =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `food_recommend_idx` (" +
+                    "`id` BIGINT NOT NULL AUTO_INCREMENT, " +
+                    "`user_id` BIGINT NOT NULL COMMENT '用户ID', " +
+                    "`food_id` BIGINT NOT NULL COMMENT '食材ID', " +
+                    "`food_name` VARCHAR(100) COMMENT '食材名称', " +
+                    "`index_score` INT NOT NULL DEFAULT 0 COMMENT '推荐指数(0-100, 来自菌群检测报告)', " +
+                    "`health_report_id` BIGINT COMMENT '关联健康报告ID', " +
+                    "`calculated_at` DATETIME COMMENT '计算时间', " +
+                    "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "PRIMARY KEY (`id`), " +
+                    "INDEX `idx_fri_user` (`user_id`), " +
+                    "INDEX `idx_fri_food` (`food_id`), " +
+                    "UNIQUE KEY `uk_fri_user_food` (`user_id`, `food_id`) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材推荐指数当前值'");
+            log.info("已创建/确认 food_recommend_idx 表");
+        } catch (Exception e) {
+            log.warn("创建 food_recommend_idx 表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `food_recommend_idx_log` (" +
+                    "`id` BIGINT NOT NULL AUTO_INCREMENT, " +
+                    "`user_id` BIGINT NOT NULL COMMENT '用户ID', " +
+                    "`food_id` BIGINT NOT NULL COMMENT '食材ID', " +
+                    "`food_name` VARCHAR(100) COMMENT '食材名称', " +
+                    "`previous_index` INT COMMENT '变更前推荐指数', " +
+                    "`new_index` INT COMMENT '变更后推荐指数', " +
+                    "`health_report_id` BIGINT COMMENT '触发的健康报告ID', " +
+                    "`change_reason` VARCHAR(200) COMMENT '变更原因', " +
+                    "`changed_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "PRIMARY KEY (`id`), " +
+                    "INDEX `idx_fril_user` (`user_id`), " +
+                    "INDEX `idx_fril_food` (`food_id`) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材推荐指数变更历史日志'");
+            log.info("已创建/确认 food_recommend_idx_log 表");
+        } catch (Exception e) {
+            log.warn("创建 food_recommend_idx_log 表失败: {}", e.getMessage());
+        }
+
+        // 迁移25: activities表添加审核相关字段(活动工作流:待审核/驳回/发布)
+        try {
+            jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN audit_status VARCHAR(20) DEFAULT NULL COMMENT '审核状态:approved/pending/rejected'");
+            log.info("已添加audit_status列到activities表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN audit_reason VARCHAR(500) DEFAULT NULL COMMENT '驳回原因'");
+            log.info("已添加audit_reason列到activities表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN auditor_id BIGINT DEFAULT NULL COMMENT '审核人ID'");
+            log.info("已添加auditor_id列到activities表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN audited_at DATETIME DEFAULT NULL COMMENT '审核时间'");
+            log.info("已添加audited_at列到activities表");
+        } catch (Exception e) {
+
+        }
+
+        // 家庭挑战:目标模式与目标值(aggregate=集体总合目标 / all_members=全员个人目标)
+        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=每人目标值)'");
+        // 迁移: family_challenge 表添加回复时效字段(待同意成员超时未回复自动拒绝)
+        ensureColumn("family_challenge", "respond_deadline", "DATETIME NULL COMMENT '回复时效截止时间(超过后未回复的 pending 成员自动置为 rejected)'");
+
+        // 迁移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) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移249: file_record 表添加 file_type 和 description 列(原迁移221/228误置于seedFullRegionData中,区域数据非空时永不执行)
+
+        // 迁移250: health_plans 添加 plan_json 和 status 列(支持结构化方案编辑)
+        try {
+            ensureColumn("health_plans", "plan_json", "JSON COMMENT '结构化方案内容(总览+营养+饮食+运动)'");
+        } catch (Exception e) {
+            log.warn("health_plans.plan_json 添加失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            ensureColumn("health_plans", "status", "VARCHAR(20) DEFAULT 'draft' COMMENT '方案状态: draft(草稿)/pending_review(待审核)/published(已发布)/rejected(已驳回)'");
+        } catch (Exception e) {
+            log.warn("health_plans.status 添加失败(可能已存在): " + e.getMessage());
+        }
+        // 迁移250: health_plans 新增审核字段(规划师/营养师方案维护流程)
+        try {
+            ensureColumn("health_plans", "reviewed_by", "BIGINT DEFAULT NULL COMMENT '审核人ID(规划师/营养师)'");
+            ensureColumn("health_plans", "reviewed_at", "DATETIME DEFAULT NULL COMMENT '审核时间'");
+            ensureColumn("health_plans", "review_comment", "TEXT DEFAULT NULL COMMENT '审核意见'");
+        } catch (Exception e) {
+            log.warn("health_plans 审核字段添加失败(可能已存在): " + e.getMessage());
+        }
+        // 迁移251: health_plans 新增 teacher_id 列(用户指定规划师)
+        try {
+            ensureColumn("health_plans", "teacher_id", "BIGINT DEFAULT NULL COMMENT '指定规划师ID(用户选择)'");
+        } catch (Exception ex) {
+            log.warn("health_plans.teacher_id 添加失败(可能已存在): " + ex.getMessage());
+        }
+        // 迁移251b: health_plans 新增 updated_at 列(实体HealthPlan含该字段,缺失导致selectById报Unknown column)
+        try {
+            ensureColumn("health_plans", "updated_at", "DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'");
+        } catch (Exception ex) {
+            log.warn("health_plans.updated_at 添加失败(可能已存在): " + ex.getMessage());
+        }
+        // 迁移251c: finance_checkins.child_id 允许NULL(家长本人打卡不选孩子,Service已支持childId为空;原NOT NULL无默认值导致INSERT报错)
+        try {
+            jdbcTemplate.execute("ALTER TABLE finance_checkins MODIFY COLUMN child_id BIGINT NULL COMMENT '孩子ID,家长本人打卡时为NULL'");
+            log.info("已修改finance_checkins.child_id字段允许NULL");
+        } catch (Exception e) {
+            log.warn("finance_checkins.child_id 修改失败: " + e.getMessage());
+        }
+        // 迁移251c: finance_checkins 补齐 type 列(initGuideModuleTables建表版本缺失,实体FinanceCheckin含type字段且收支统计依赖)
+        try {
+            ensureColumn("finance_checkins", "type", "VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)'");
+        } catch (Exception e) {
+            log.warn("finance_checkins.type 添加失败(可能已存在): " + e.getMessage());
+        }
+        // 迁移258: 创建energy_daily_limit表(每日能量上限配置持久化)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS energy_daily_limit (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "dimension_code VARCHAR(20) NOT NULL COMMENT '维度代码: body/mind/wisdom/action/wealth', " +
+                    "daily_limit INT NOT NULL DEFAULT 50 COMMENT '每日能量上限', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_dimension_code (dimension_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日能量上限配置'");
+            log.info("已创建energy_daily_limit表");
+            // 插入默认数据(幂等)
+            jdbcTemplate.execute("INSERT IGNORE INTO energy_daily_limit (dimension_code, daily_limit) VALUES " +
+                    "('body', 50), ('mind', 50), ('wisdom', 80), ('action', 60), ('wealth', 50)");
+            log.info("已插入energy_daily_limit默认数据");
+        } catch (Exception ex) {
+            log.warn("energy_daily_limit 表创建失败(可能已存在): " + ex.getMessage());
+        }
+        // 迁移251d: diet_preferences 补齐 must_eat 列(实体DietPreferences含mustEat字段,建表时两处均遗漏,查询报Unknown column)
+        try {
+            ensureColumn("diet_preferences", "must_eat", "JSON COMMENT '必吃食物列表'");
+        } catch (Exception e) {
+            log.warn("diet_preferences.must_eat 添加失败(可能已存在): " + e.getMessage());
+        }
+        ensureColumn("file_record", "file_type", "VARCHAR(50) DEFAULT NULL COMMENT '文件类型: image/pdf/video/audio/other'");
+        ensureColumn("file_record", "description", "TEXT COMMENT '文件描述'");
+
+        log.info("数据库迁移完成");
+
+        // 迁移252: 体素/心理/认知指标定义种子数据
+        try {
+            Object[][] rows = {
+                    {"body", "nutrition", "body_fat", "体脂率(%)", "18-28", "1"},
+                    {"body", "nutrition", "bmi", "BMI指数", "18.5-24", "2"},
+                    {"body", "nutrition", "protein", "蛋白质摄入(g/日)", "50-80", "3"},
+                    {"body", "nutrition", "calcium", "钙摄入(mg/日)", "800-1000", "4"},
+                    {"body", "nutrition", "vitamin_d", "维生素D(ng/ml)", "30-100", "5"},
+                    {"body", "chronic", "blood_pressure_systolic", "收缩压(mmHg)", "90-120", "1"},
+                    {"body", "chronic", "blood_pressure_diastolic", "舒张压(mmHg)", "60-80", "2"},
+                    {"body", "chronic", "blood_sugar_fasting", "空腹血糖(mmol/L)", "3.9-6.1", "3"},
+                    {"body", "chronic", "cholesterol_total", "总胆固醇(mmol/L)", "3.1-5.2", "4"},
+                    {"body", "chronic", "triglyceride", "甘油三酯(mmol/L)", "0.56-1.7", "5"},
+                    {"body", "exercise", "step_count", "日均步数", "6000-10000", "1"},
+                    {"body", "exercise", "sleep_duration", "睡眠时长(小时)", "7-9", "2"},
+                    {"mind", "emotion", "emotion_stability", "情绪稳定性", "0-100", "1"},
+                    {"mind", "emotion", "anxiety_level", "焦虑水平", "0-100", "2"},
+                    {"mind", "emotion", "sleep_quality", "睡眠质量", "0-100", "3"},
+                    {"mind", "personality", "openness", "开放性", "0-100", "1"},
+                    {"mind", "personality", "conscientiousness", "尽责性", "0-100", "2"},
+                    {"mind", "personality", "extraversion", "外向性", "0-100", "3"},
+                    {"mind", "personality", "agreeableness", "宜人性", "0-100", "4"},
+                    {"mind", "personality", "neuroticism", "神经质(反向)", "0-100", "5"},
+                    {"wisdom", "cognitive", "perception", "感知能力", "0-100", "1"},
+                    {"wisdom", "cognitive", "focus", "专注力", "0-100", "2"},
+                    {"wisdom", "cognitive", "memory", "记忆力", "0-100", "3"},
+                    {"wisdom", "cognitive", "logic", "逻辑思维", "0-100", "4"},
+                    {"wisdom", "cognitive", "spatial", "空间思维", "0-100", "5"},
+                    {"wisdom", "cognitive", "processing_speed", "加工速度", "0-100", "6"},
+                    {"wisdom", "cognitive", "growth_mindset", "成长型思维", "0-100", "7"}
+            };
+            for (Object[] r : rows) {
+                jdbcTemplate.update(
+                        "INSERT IGNORE INTO indicator_definitions (domain, category, code, name, data_type, ref_range, sort_order, status) VALUES (?,?,?,?,?,?,?,1)",
+                        r[0], r[1], r[2], r[3], "number", r[4], Integer.parseInt((String) r[5])
+                );
+            }
+            log.info("迁移252: indicator_definitions 种子数据已加载 ({}条)", rows.length);
+        } catch (Exception e) {
+            log.warn("迁移252失败: {}", e.getMessage());
+        }
+
+        log.info("数据库迁移完成");
+
+        // ==================== 健康维度 Phase 1: health_dimension_score / health_data_source_record / health_norm_reference =============        migrateHealthDimensionsTable();
+        seedHealthNorms();
+        seedMindActionWealthNorms();
+        runLateMigrations();
+        migration102();
+        // 迁移226-247: 原seedFullRegionData内的DDL迁移,确保不因省市区数据已存在而跳过
+        runFullRegionDataMigrations();
+        // seedFullRegionData(); 已废弃,区域数据由独立迁移处理
+
+        // 迁移269: sleep_record 修改 sleep_time/wake_time 为 TIME(前端按 HH:mm 打卡,java.sql.Time 三段格式)
+        try {
+            jdbcTemplate.execute("ALTER TABLE sleep_record MODIFY COLUMN sleep_time TIME, MODIFY COLUMN wake_time TIME");
+            log.info("已修改 sleep_record 时间为 TIME 类型");
+        } catch (Exception e) {
+            log.warn("sleep_record 时间列修改失败(可能已为 TIME): {}", e.getMessage());
+        }
+
+        // ==================== 迁移270: 先天画像来源权重配置表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS innate_portrait_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "source_type VARCHAR(20) NOT NULL," +
+                    "source_name VARCHAR(50) NOT NULL," +
+                    "weight INT DEFAULT 25," +
+                    "enabled TINYINT(1) DEFAULT 1," +
+                    "ai_enabled TINYINT(1) DEFAULT 1," +
+                    "sort_order INT DEFAULT 0," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "UNIQUE KEY uk_source (source_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='先天画像来源权重配置'");
+            Integer cnt = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM innate_portrait_config", Integer.class);
+            if (cnt == null || cnt == 0) {
+                jdbcTemplate.batchUpdate(
+                    "INSERT INTO innate_portrait_config (source_type, source_name, weight, sort_order) VALUES (?, ?, ?, ?)",
+                    java.util.Arrays.asList(new Object[][]{
+                        {"zodiac", "星座", 2500, 1},
+                        {"bazi", "八字", 2500, 2},
+                        {"blood", "血型", 2500, 3},
+                        {"numsoul", "数字能量", 2500, 4},
+                    })
+                );
+                log.info("迁移270: innate_portrait_config 种子数据已加载 (4条)");
+            }
+            log.info("迁移270: innate_portrait_config 表创建完成");
+        } catch (Exception e) {
+            log.warn("迁移270失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移271: 先天画像报告缓存表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS innate_portrait_report (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "member_id BIGINT NOT NULL," +
+                    "member_type VARCHAR(10) NOT NULL," +
+                    "family_id BIGINT NOT NULL," +
+                    "portrait_json TEXT," +
+                    "ai_reading TEXT," +
+                    "generated_at DATETIME," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "UNIQUE KEY uk_member (member_id, member_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='先天画像报告缓存'");
+            log.info("迁移271: innate_portrait_report 表创建完成");
+        } catch (Exception e) {
+            log.warn("迁移271失败: {}", e.getMessage());
+        }
+
+        // ==================== 迁移272: 数字能量详细配置表 =============        
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS numsoul_detail_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "number_type VARCHAR(20) NOT NULL," +
+                    "number_value INT NOT NULL," +
+                    "title VARCHAR(50)," +
+                    "keywords VARCHAR(200)," +
+                    "mind_base INT DEFAULT 0," +
+                    "mind_advice TEXT," +
+                    "color_hex VARCHAR(10)," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "UNIQUE KEY uk_type_num (number_type, number_value)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字能量详细配置'");
+            Integer cnt2 = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM numsoul_detail_config", Integer.class);
+            if (cnt2 == null || cnt2 == 0) {
+                jdbcTemplate.batchUpdate(
+                    "INSERT INTO numsoul_detail_config (number_type, number_value, title, keywords, mind_base, mind_advice, color_hex) VALUES (?, ?, ?, ?, ?, ?, ?)",
+                    java.util.Arrays.asList(new Object[][]{
+                        {"life_path", 1, "开创者", "独立、领导、创新", 68, "培养团队协作意识,避免独断专行", "#FF4444"},
+                        {"life_path", 2, "和平者", "温和、合作、敏感", 72, "增强自我主张,避免过度迎合", "#FF8C00"},
+                        {"life_path", 3, "表达者", "创意、社交、乐观", 78, "保持专注,避免浅尝辄止", "#FFD700"},
+                        {"life_path", 4, "建设者", "务实、稳定、有序", 70, "学会变通,避免过于固执", "#4CAF50"},
+                        {"life_path", 5, "自由者", "冒险、变化、好奇", 75, "培养持久力,避免缺乏恒心", "#2196F3"},
+                        {"life_path", 6, "关怀者", "责任、关爱、奉献", 80, "学会自我关爱,避免过度付出", "#9C27B0"},
+                        {"life_path", 7, "探索者", "智慧、分析、内省", 76, "与人分享见解,避免过度封闭", "#673AB7"},
+                        {"life_path", 8, "成就者", "雄心、管理、决策", 72, "关注内在成长,避免过度追求外在", "#3F51B5"},
+                        {"life_path", 9, "博爱者", "包容、智慧、善良", 82, "设定个人边界,避免过度牺牲", "#E91E63"},
+                        {"life_path", 11, "启明者", "直觉、灵感、理想", 85, "落地执行,避免空想", "#7C4DFF"},
+                        {"life_path", 22, "建造师", "远见、实践、宏图", 88, "分步落地大目标", "#536DFE"},
+                        {"life_path", 33, "引路人", "大爱、奉献、疗愈", 90, "先照顾好自己再照亮他人", "#FF4081"},
+                    })
+                );
+                log.info("迁移272: numsoul_detail_config 生命灵数种子已加载");
+            }
+            log.info("迁移272: numsoul_detail_config 表创建完成");
+        } catch (Exception e) {
+            log.warn("迁移272失败: {}", e.getMessage());
+        }
+
+        // 迁移273: dan_report_uploads.file_id 加宽(OSS 完整 URL 超过原 VARCHAR(100) 导致截断报错)
+        try {
+            jdbcTemplate.execute("ALTER TABLE dan_report_uploads MODIFY COLUMN file_id VARCHAR(500) COMMENT '媒资文件ID或相对路径'");
+            log.info("迁移273: 已加宽 dan_report_uploads.file_id 为 VARCHAR(500)");
+        } catch (Exception e) {
+            log.warn("迁移273失败: {}", e.getMessage());
+        }
+
+        // 迁移274: dan_assessment_results.report_file_id 加宽(确认入库写入 OSS URL 超长截断)
+        try {
+            jdbcTemplate.execute("ALTER TABLE dan_assessment_results MODIFY COLUMN report_file_id VARCHAR(500) COMMENT '上传文件媒资ID或相对路径'");
+            log.info("迁移274: 已加宽 dan_assessment_results.report_file_id 为 VARCHAR(500)");
+        } catch (Exception e) {
+            log.warn("迁移274失败: {}", e.getMessage());
+        }
+
+        // 迁移275: dan_assessment_results.dan_level 兼容旧库(NOT NULL 无默认导致家长上传确认失败)
+        ensureColumn("dan_assessment_results", "dan_level",
+                "VARCHAR(10) NULL DEFAULT NULL COMMENT 'DAN等级A/B/C/D'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE dan_assessment_results MODIFY COLUMN dan_level VARCHAR(10) NULL DEFAULT NULL COMMENT 'DAN等级A/B/C/D'");
+            log.info("迁移275: 已将 dan_assessment_results.dan_level 改为可空并带默认");
+        } catch (Exception e) {
+            log.warn("迁移275失败: {}", e.getMessage());
+        }
+
+        runSystemMigrations();
+        registerCollectExpectedIndicators();
+
+        // 减脂专题表创建(远程 fatloss 功能)
+        createFatWeightRecordTable();
+        createFatDietRecordTable();
+        createFatExerciseRecordTable();
+        createFatGoalTable();
+        addProductVendorVisibleColumn();
+
+        // 迁移367: products 表添加健康约束字段(基础疾病禁忌/过敏原/药物相互作用标记)
+        addProductHealthConstraintColumns();
+
+        // 迁移368: 创建 drug_interactions 表(药物-食物禁忌对照表)
+        createDrugInteractionsTable();
+
+        // 迁移369: 创建 disease_intake_limits 表(疾病-饮食摄入阈值)
+        createDiseaseIntakeLimitsTable();
+
+        // 迁移370: 初始化 drug_interactions 种子数据
+        seedDrugInteractionsData();
+
+        // 迁移371: 初始化 disease_intake_limits 种子数据
+        seedDiseaseIntakeLimitsData();
+
+        // 指标统一层迁移(356~362)
+        migrateIndicatorDefinitionsColumns();
+        migrateIndicatorValuesColumns();
+        addIndicatorValuesKeys();
+        createIndicatorMappingRulesTable();
+        createIndicatorCorrelationsTable();
+        ensureIndicatorCollectionDate();
+        seedDimensionDefinitions();
+
+        // 商品组合推荐与采购任务迁移(363)
+        createProcurementTaskTable();
+
+        // 迁移364: health_report_drafts 添加解析进度/重试字段(known-type 解析失败退避重试,不再静默回退 opencode)
+        addDraftParseProgressColumns();
+
+        // 迁移365: diet_preferences 添加无特殊偏好标记(问卷第3步「无特殊偏好」一键跳过落库)
+        ensureColumn("diet_preferences", "no_preference", "TINYINT(1) DEFAULT 0 COMMENT '无特殊偏好标记(1=用户明确选择无特殊偏好)'");
+
+        // 迁移366: 新增 LITE 轻享会员等级(9.9元/月,解锁健康自检问卷与个性化方案)
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_levels (level_code, level_name, level_desc, price_monthly, price_quarterly, price_yearly, max_children, max_tasks_per_day, ai_review_enabled, priority_support, teacher_consultation) VALUES " +
+                    "('LITE', '轻享会员', '9.9元/月,解锁健康自检问卷与个性化方案', 990, 2970, 11880, 2, 10, 1, 0, 0)");
+            log.info("迁移366: 已插入 LITE 轻享会员等级");
+        } catch (Exception e) {
+            log.warn("迁移366: 插入 LITE 会员等级失败或已存在: {}", e.getMessage());
+        }
+
+        // 迁移372: nutrition_product 添加 efficacy_tags 列(成分功效标签)
+        addNutritionProductEfficacyTags();
+
+        // 迁移373: products 添加 tags 列(文章-商品标签关联推荐)
+        addProductTagsColumn();
+    }
+
+    private void migrateHealthDimensionsTable() {
+        try {
+            if (tableExists("health_dimension_score")) {
+                jdbcTemplate.execute("RENAME TABLE `health_dimension_score` TO `health_dimension_score_old`");
+            }
+        } catch (Exception e) {
+            log.warn("health_dimension_score rename skipped: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `health_dimension_scores` (\n" +
+                    "  `id` bigint NOT NULL AUTO_INCREMENT,\n" +
+                    "  `member_id` bigint NOT NULL COMMENT '家庭成员ID',\n" +
+                    "  `dimension` varchar(20) NOT NULL COMMENT '维度: growth/sleep/vision/immunity/nutrition/gut/exercise',\n" +
+                    "  `score` int NOT NULL COMMENT '0-100分',\n" +
+                    "  `percentile` int DEFAULT NULL COMMENT '人群百分位 0-100',\n" +
+                    "  `data_source` varchar(20) NOT NULL COMMENT '数据源: REPORT/PHOTO/VOICE/MANUAL',\n" +
+                    "  `tier` tinyint NOT NULL DEFAULT 3 COMMENT '数据层级: 1自动/2语音/3手动',\n" +
+                    "  `raw_data` json DEFAULT NULL COMMENT '原始数据快照',\n" +
+                    "  `assess_date` date NOT NULL COMMENT '评估日期',\n" +
+                    "  `expire_date` date DEFAULT NULL COMMENT '数据过期日',\n" +
+                    "  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,\n" +
+                    "  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n" +
+                    "  PRIMARY KEY (`id`),\n" +
+                    "  UNIQUE KEY `uk_member_dim_date` (`member_id`, `dimension`, `assess_date`)\n" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康维度评分表'");
+        } catch (Exception e) {
+            log.warn("health_dimension_scores 表创建失败: {}", e.getMessage());
+        }
+
+        try {
+            if (tableExists("health_data_source_record")) {
+                jdbcTemplate.execute("RENAME TABLE `health_data_source_record` TO `health_data_source_record_old`");
+            }
+        } catch (Exception e) {
+            log.warn("health_data_source_record rename skipped: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `health_data_sources` (\n" +
+                    "  `id` bigint NOT NULL AUTO_INCREMENT,\n" +
+                    "  `member_id` bigint NOT NULL,\n" +
+                    "  `source_type` varchar(20) NOT NULL COMMENT 'PDF/PHOTO/VOICE/MANUAL/WEIXIN_STEP',\n" +
+                    "  `dimension_codes` json NOT NULL COMMENT '影响的维度列表',\n" +
+                    "  `parsed_result` json DEFAULT NULL COMMENT '解析结果全量',\n" +
+                    "  `file_url` varchar(500) DEFAULT NULL COMMENT '原始文件URL',\n" +
+                    "  `confidence` decimal(3,2) DEFAULT NULL COMMENT 'AI解析置信度',\n" +
+                    "  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,\n" +
+                    "  PRIMARY KEY (`id`),\n" +
+                    "  KEY `idx_member_source` (`member_id`, `source_type`)\n" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康数据源记录表'");
+        } catch (Exception e) {
+            log.warn("health_data_sources 表创建失败: {}", e.getMessage());
+        }
+
+        try {
+            if (tableExists("health_norm_reference")) {
+                jdbcTemplate.execute("RENAME TABLE `health_norm_reference` TO `health_norm_reference_old`");
+            }
+        } catch (Exception e) {
+            log.warn("health_norm_reference rename skipped: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `health_norm_reference` (\n" +
+                    "  `id` bigint NOT NULL AUTO_INCREMENT,\n" +
+                    "  `dimension` varchar(20) NOT NULL COMMENT '维度',\n" +
+                    "  `gender` varchar(10) NOT NULL COMMENT 'male/female/all',\n" +
+                    "  `age_min` int NOT NULL COMMENT '年龄下限(月)',\n" +
+                    "  `age_max` int NOT NULL COMMENT '年龄上限(月)',\n" +
+                    "  `percentile_5` int DEFAULT NULL,\n" +
+                    "  `percentile_15` int DEFAULT NULL,\n" +
+                    "  `percentile_25` int DEFAULT NULL,\n" +
+                    "  `percentile_50` int DEFAULT NULL COMMENT '中位值',\n" +
+                    "  `percentile_75` int DEFAULT NULL,\n" +
+                    "  `percentile_85` int DEFAULT NULL,\n" +
+                    "  `percentile_95` int DEFAULT NULL,\n" +
+                    "  `source` varchar(50) DEFAULT NULL COMMENT '常模来源',\n" +
+                    "  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,\n" +
+                    "  PRIMARY KEY (`id`),\n" +
+                    "  UNIQUE KEY `uk_dim_gender_age` (`dimension`, `gender`, `age_min`, `age_max`)\n" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康常模对照表'");
+        } catch (Exception e) {
+            log.warn("health_norm_reference 表重建失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO health_dimension_scores (member_id, dimension, score, data_source, tier, assess_date, created_at)\n" +
+                    " SELECT member_id,\n" +
+                    "  CASE dimension\n" +
+                    "    WHEN 'body' THEN 'growth'\n" +
+                    "    WHEN 'fitness' THEN 'exercise'\n" +
+                    "    WHEN 'cardio' THEN 'exercise'\n" +
+                    "    WHEN 'blood' THEN 'immunity'\n" +
+                    "    WHEN 'sleep' THEN 'sleep'\n" +
+                    "    WHEN 'psyche' THEN 'growth'\n" +
+                    "    WHEN 'diet' THEN 'nutrition'\n" +
+                    "    ELSE dimension\n" +
+                    "  END,\n" +
+                    "  score,\n" +
+                    "  UPPER(data_source),\n" +
+                    "  3,\n" +
+                    "  record_date,\n" +
+                    "  NOW()\n" +
+                    " FROM health_dimension_score_old WHERE is_latest = 1");
+        } catch (Exception e) {
+            log.warn("旧维度评分数据迁移跳过: {}", e.getMessage());
+        }
+    }
+
+    private void seedHealthNorms() {
+        try {
+            int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM health_norm_reference", Integer.class);
+            if (count == 0) {
+                jdbcTemplate.execute("INSERT INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES\n" +
+                        "('growth', 'male', 72, 84, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('growth', 'female', 72, 84, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('growth', 'male', 84, 96, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('growth', 'female', 84, 96, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('growth', 'male', 96, 120, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('growth', 'female', 96, 120, 25, 35, 45, 55, 70, 80, 90, 'BMI-for-age→百分制'),\n" +
+                        "('sleep', 'all', 0, 12, 40, 50, 60, 70, 80, 88, 95, 'NSF婴幼儿睡眠→百分制'),\n" +
+                        "('sleep', 'all', 12, 36, 40, 50, 60, 70, 80, 88, 95, 'NSF幼儿睡眠→百分制'),\n" +
+                        "('sleep', 'all', 36, 72, 40, 50, 60, 70, 80, 88, 95, 'NSF学龄前睡眠→百分制'),\n" +
+                        "('sleep', 'all', 72, 156, 40, 50, 60, 70, 80, 88, 95, 'NSF学龄儿童睡眠→百分制'),\n" +
+                        "('vision', 'male', 72, 144, 45, 55, 65, 75, 85, 90, 95, '视力发育标准→百分制'),\n" +
+                        "('vision', 'female', 72, 144, 45, 55, 65, 75, 85, 90, 95, '视力发育标准→百分制'),\n" +
+                        "('immunity', 'all', 0, 72, 15, 30, 45, 65, 80, 90, 95, '年患病次数→百分制'),\n" +
+                        "('immunity', 'all', 72, 192, 15, 35, 50, 70, 85, 92, 98, '年患病次数→百分制'),\n" +
+                        "('nutrition', 'all', 36, 192, 20, 30, 40, 50, 60, 70, 80, '膳食多样性→百分制'),\n" +
+                        "('gut', 'all', 0, 192, 40, 50, 60, 70, 80, 85, 95, '菌群人群分布(平台自建)'),\n" +
+                        "('exercise', 'male', 72, 144, 30, 40, 50, 60, 70, 80, 90, '国家学生体质健康标准'),\n" +
+                        "('exercise', 'female', 72, 144, 25, 35, 45, 55, 65, 75, 85, '国家学生体质健康标准')");
+                log.info("health_norm_reference 常模种子数据已加载 (18条, 统一0-100百分制)");
+            } else {
+                log.info("health_norm_reference 常模数据已存在 ({}条), 跳过", count);
+            }
+        } catch (Exception e) {
+            log.warn("health_norm_reference 常模种子数据初始化失败: {}", e.getMessage());
+        }
+
+        // 迁移: articles表添加审核和作者类型字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'audit_status'",
+                    Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE articles " +
+                        "ADD COLUMN author_type VARCHAR(20) DEFAULT 'admin' COMMENT '作者类型:admin/parent/child' AFTER created_by, " +
+                        "ADD COLUMN audit_status VARCHAR(20) DEFAULT 'approved' COMMENT '审核状态:approved/pending/rejected' AFTER status, " +
+                        "ADD COLUMN audit_reason TEXT COMMENT '驳回原因' AFTER audit_status, " +
+                        "ADD COLUMN auditor_id BIGINT DEFAULT 0 COMMENT '审核人 ID' AFTER audit_reason, " +
+                        "ADD COLUMN audited_at DATETIME COMMENT '审核时间' AFTER auditor_id");
+                log.info("已添加审核和作者类型字段到articles表");
+            } else {
+                log.info("articles表审核字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加articles表审核字段失败: {}", e.getMessage());
+        }
+
+        // 迁移20240709-01: content字段扩为TEXT - 用户输入内容可能超VARCHAR上限
+        // activities.description
+        modifyColumnToText("activities", "description", "活动描述");
+        // tasks.description
+        modifyColumnToText("tasks", "description", "任务描述");
+        // wishes.description, reject_reason, cancel_reason
+        modifyColumnToText("wishes", "description", "心愿描述");
+        modifyColumnToText("wishes", "reject_reason", "拒绝原因");
+        modifyColumnToText("wishes", "cancel_reason", "取消原因");
+        // reward_wishlists.description
+        modifyColumnToText("reward_wishlists", "description", "心愿描述");
+        // products.description, reject_reason
+        modifyColumnToText("products", "description", "商品描述");
+        modifyColumnToText("products", "reject_reason", "审核拒绝原因");
+        // product_orders.remark
+        modifyColumnToText("product_orders", "remark", "备注");
+        // articles.summary
+        modifyColumnToText("articles", "summary", "摘要");
+        // activities.audit_reason
+        modifyColumnToText("activities", "audit_reason", "驳回原因");
+        // guide_applications.reject_reason
+        modifyColumnToText("guide_applications", "reject_reason", "拒绝原因");
+        // guide_application_records.remark
+        modifyColumnToText("guide_application_records", "remark", "备注/原因");
+        // user_operation_log.description
+        modifyColumnToText("user_operation_log", "description", "操作描述");
+        // follow_up.description, teacher_review
+        modifyColumnToText("follow_up", "description", "描述");
+        modifyColumnToText("follow_up", "teacher_review", "规划师点评");
+        // energy_log.description
+        modifyColumnToText("energy_log", "description", "描述");
+        // sys_config.description
+        modifyColumnToText("sys_config", "description", "配置说明");
+        // points_log.description
+        modifyColumnToText("points_log", "description", "描述");
+        // insurance_policies.remark
+        modifyColumnToText("insurance_policies", "remark", "备注");
+        // health_reports.remark
+        modifyColumnToText("health_reports", "remark", "备注");
+        // guide_service_logs.notes
+        modifyColumnToText("guide_service_logs", "notes", "服务记录");
+        // guide_packages.description
+        modifyColumnToText("guide_packages", "description", "套餐说明");
+        // guide_package_templates.description
+        modifyColumnToText("guide_package_templates", "description", "说明");
+        // service_types.description
+        modifyColumnToText("service_types", "description", "描述");
+        // commission_records.remark
+        modifyColumnToText("commission_records", "remark", "备注");
+        // zodiac_pairing.description, advice
+        modifyColumnToText("zodiac_pairing", "description", "配对解读");
+        modifyColumnToText("zodiac_pairing", "advice", "相处建议");
+        // blood_type_pairing.description, advice
+        modifyColumnToText("blood_type_pairing", "description", "配对描述");
+        modifyColumnToText("blood_type_pairing", "advice", "相处建议");
+        // life_number_relationship.description, advice
+        modifyColumnToText("life_number_relationship", "description", "关系解读");
+        modifyColumnToText("life_number_relationship", "advice", "相处建议");
+        // daily_health_checkin.remark
+        modifyColumnToText("daily_health_checkin", "remark", "备注");
+        // health_sleep_record.remark
+        modifyColumnToText("health_sleep_record", "remark", "备注");
+        // health_exercise_record.remark
+        modifyColumnToText("health_exercise_record", "remark", "备注");
+        // finance_checkins.note
+        modifyColumnToText("finance_checkins", "note", "备注");
+        // guide_packages.description (old duplicate)
+        // assessment_appointments.remark
+        modifyColumnToText("assessment_appointments", "remark", "备注");
+        // withdrawal_requests.remark
+        modifyColumnToText("withdrawal_requests", "remark", "备注");
+        // supply_settlements.remark
+        modifyColumnToText("supply_settlements", "remark", "备注");
+        // content_sections.description
+        ensureColumn("content_sections", "description", "TEXT COMMENT '描述说明'");
+        // education_systems.description
+        modifyColumnToText("education_systems", "description", "描述");
+        // dimension_config.description
+        modifyColumnToText("dimension_config", "description", "描述");
+        // energy_rules.description
+        modifyColumnToText("energy_rules", "description", "规则描述");
+        // team_goals.description
+        modifyColumnToText("team_goals", "description", "团队描述");
+        // product_dimension_config.remark
+        modifyColumnToText("product_dimension_config", "remark", "备注");
+        // indicator_values.remark
+        modifyColumnToText("indicator_values", "remark", "备注");
+        // tongue_records.overall_assessment
+        modifyColumnToText("tongue_records", "overall_assessment", "总体评估");
+        // system_config.description, config_value
+        modifyColumnToText("system_config", "description", "说明");
+        modifyColumnToText("system_config", "config_value", "配置值");
+        // supply_system.description
+        modifyColumnToText("supply_system", "description", "描述");
+        // supply_settlement.remark
+        modifyColumnToText("supply_settlement", "remark", "备注");
+        // growth_task.description
+        modifyColumnToText("growth_task", "description", "描述");
+        // distribution_systems.description
+        modifyColumnToText("distribution_systems", "description", "团队描述");
+        // mini_games.description
+        modifyColumnToText("mini_games", "description", "游戏描述");
+        // blood_type_config.description
+        modifyColumnToText("blood_type_config", "description", "性格描述");
+
+        // 迁移20240709-02: 北京菌群报告采集增强(P0 疾病关联菌 / P1 菌群状态)
+        // health_disease_risk.related_bacteria: 疾病关联菌(异常菌)明细 JSON
+        ensureColumn("health_disease_risk", "related_bacteria", "TEXT NULL COMMENT '关联菌明细JSON:[{bacteria,correlation,value,status}]'");
+        // health_reports.gut_status_level: 肠道菌群状态(0度~4度)
+        ensureColumn("health_reports", "gut_status_level", "VARCHAR(20) NULL COMMENT '肠道菌群状态(0度~4度)'");
+
+        // 迁移248: 任务系统升级(行为调度中心)——tasks 新增字段
+        ensureColumn("tasks", "prerequisite_task_id", "BIGINT NULL COMMENT '前置任务ID:完成后才解锁本任务'");
+        ensureColumn("tasks", "action_type", "VARCHAR(32) NULL COMMENT '联动动作: minigame/focus/survey/article/activity/cart/order'");
+        ensureColumn("tasks", "action_config", "JSON NULL COMMENT '联动参数: {\"gameCode\":\"schulte\",\"productId\":123,\"minSeconds\":30,\"completeMilestone\":\"paid\"/\"received\",\"surveyId\":99}'");
+        ensureColumn("tasks", "require_input", "TINYINT DEFAULT 0 COMMENT '1=需填写内容才能完成'");
+        ensureColumn("tasks", "start_required", "TINYINT DEFAULT 0 COMMENT '1=需先开始再完成(两阶段)'");
+        ensureColumn("tasks", "start_count", "INT DEFAULT 0 COMMENT '已开始次数(累计)'");
+        ensureColumn("tasks", "min_duration_seconds", "INT DEFAULT 0 COMMENT '最短持续秒数(专注/游戏类,未达标不计完成)'");
+
+        // 迁移248: 任务执行进度表(两阶段/里程碑/填写内容记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS task_executions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "task_id BIGINT NOT NULL COMMENT '任务实例ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '执行成员ID(family_members.id)', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'started' COMMENT 'started/finished/failed', " +
+                    "started_at DATETIME NOT NULL COMMENT '开始时间', " +
+                    "finished_at DATETIME COMMENT '完成时间', " +
+                    "milestone VARCHAR(32) COMMENT '购物任务里程碑: cart_added/paid/received(当前进度)', " +
+                    "content TEXT COMMENT '填写内容(require_input 任务)', " +
+                    "content_type VARCHAR(20) COMMENT 'content 类型: text/image/audio/video', " +
+                    "extra JSON COMMENT '扩展数据: {gameScore, focusMinutes, orderId, articleId, surveyId}', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_task_member (task_id, member_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务执行进度表'");
+            log.info("已创建task_executions表");
+        } catch (Exception e) {
+            log.warn("创建task_executions表失败: {}", e.getMessage());
+        }
+
+        // 迁移248: focus_sessions 添加 task_id 字段(专注任务挂钩)
+        ensureColumn("focus_sessions", "task_id", "BIGINT NULL COMMENT '关联的任务ID(专注任务挂钩)'");
+    }
+
+
+    private void seedMindActionWealthNorms() {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM health_norm_reference WHERE dimension LIKE 'bigFive%' OR dimension LIKE 'emotion%' OR dimension LIKE 'drive%' OR dimension LIKE 'selfConcept%' OR dimension LIKE 'action%' OR dimension LIKE 'wealth%'",
+                Integer.class);
+            if (count != null && count > 0) {
+                log.info("mind/action/wealth 常模数据已存在 ({}条), 跳过", count);
+                return;
+            }
+            java.util.List<String> sqls = new java.util.ArrayList<>();
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('bigFiveOpenness','all',0,84,20,27,35,45,55,63,70,'心理常模'),\n" +
+    "('bigFiveOpenness','all',84,144,27,34,42,52,62,70,77,'心理常模'),\n" +
+    "('bigFiveOpenness','all',144,216,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('bigFiveOpenness','all',216,1200,25,32,40,50,60,68,75,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('bigFiveConscientiousness','all',0,84,15,22,30,40,50,58,65,'心理常模'),\n" +
+    "('bigFiveConscientiousness','all',84,144,23,30,38,48,58,66,73,'心理常模'),\n" +
+    "('bigFiveConscientiousness','all',144,216,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('bigFiveConscientiousness','all',216,1200,33,40,48,58,68,76,83,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('bigFiveExtraversion','all',0,84,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('bigFiveExtraversion','all',84,144,27,34,42,52,62,70,77,'心理常模'),\n" +
+    "('bigFiveExtraversion','all',144,216,25,32,40,50,60,68,75,'心理常模'),\n" +
+    "('bigFiveExtraversion','all',216,1200,25,32,40,50,60,68,75,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('bigFiveAgreeableness','all',0,84,33,40,48,58,68,76,83,'心理常模'),\n" +
+    "('bigFiveAgreeableness','all',84,144,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('bigFiveAgreeableness','all',144,216,28,35,43,53,63,71,78,'心理常模'),\n" +
+    "('bigFiveAgreeableness','all',216,1200,30,37,45,55,65,73,80,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('bigFiveNeuroticism','all',0,84,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('bigFiveNeuroticism','all',84,144,23,30,38,48,58,66,73,'心理常模'),\n" +
+    "('bigFiveNeuroticism','all',144,216,20,27,35,45,55,63,70,'心理常模'),\n" +
+    "('bigFiveNeuroticism','all',216,1200,30,37,45,55,65,73,80,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('emotionStability','all',0,84,17,24,32,42,52,60,67,'心理常模'),\n" +
+    "('emotionStability','all',84,144,27,34,42,52,62,70,77,'心理常模'),\n" +
+    "('emotionStability','all',144,216,33,40,48,58,68,76,83,'心理常模'),\n" +
+    "('emotionStability','all',216,1200,37,44,52,62,72,80,87,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('driveAutonomy','all',0,84,10,17,25,35,45,53,60,'心理常模'),\n" +
+    "('driveAutonomy','all',84,144,25,32,40,50,60,68,75,'心理常模'),\n" +
+    "('driveAutonomy','all',144,216,37,44,52,62,72,80,87,'心理常模'),\n" +
+    "('driveAutonomy','all',216,1200,43,50,58,68,78,86,93,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('driveCompetence','all',0,84,15,22,30,40,50,58,65,'心理常模'),\n" +
+    "('driveCompetence','all',84,144,27,34,42,52,62,70,77,'心理常模'),\n" +
+    "('driveCompetence','all',144,216,35,42,50,60,70,78,85,'心理常模'),\n" +
+    "('driveCompetence','all',216,1200,37,44,52,62,72,80,87,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('driveRelatedness','all',0,84,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('driveRelatedness','all',84,144,27,34,42,52,62,70,77,'心理常模'),\n" +
+    "('driveRelatedness','all',144,216,25,32,40,50,60,68,75,'心理常模'),\n" +
+    "('driveRelatedness','all',216,1200,30,37,45,55,65,73,80,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('selfConceptOverall','all',0,84,20,27,35,45,55,63,70,'心理常模'),\n" +
+    "('selfConceptOverall','all',84,144,30,37,45,55,65,73,80,'心理常模'),\n" +
+    "('selfConceptOverall','all',144,216,33,40,48,58,68,76,83,'心理常模'),\n" +
+    "('selfConceptOverall','all',216,1200,33,40,48,58,68,76,83,'心理常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionChildParentChild','all',0,84,35,42,50,60,70,78,85,'关系质量常模'),\n" +
+    "('actionChildParentChild','all',84,144,33,40,48,58,68,76,83,'关系质量常模'),\n" +
+    "('actionChildParentChild','all',144,216,30,37,45,55,65,73,80,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionSibling','all',0,84,20,27,35,45,55,63,70,'关系质量常模'),\n" +
+    "('actionSibling','all',84,144,25,32,40,50,60,68,75,'关系质量常模'),\n" +
+    "('actionSibling','all',144,216,30,37,45,55,65,73,80,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionRelative','all',0,84,25,32,40,50,60,68,75,'关系质量常模'),\n" +
+    "('actionRelative','all',84,144,30,37,45,55,65,73,80,'关系质量常模'),\n" +
+    "('actionRelative','all',144,216,33,40,48,58,68,76,83,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionTeacherStudent','all',0,84,25,32,40,50,60,68,75,'关系质量常模'),\n" +
+    "('actionTeacherStudent','all',84,144,35,42,50,60,70,78,85,'关系质量常模'),\n" +
+    "('actionTeacherStudent','all',144,216,37,44,52,62,72,80,87,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionChildSociality','all',0,84,23,30,38,48,58,66,73,'关系质量常模'),\n" +
+    "('actionChildSociality','all',84,144,30,37,45,55,65,73,80,'关系质量常模'),\n" +
+    "('actionChildSociality','all',144,216,35,42,50,60,70,78,85,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionChildInterpersonal','all',0,84,20,27,35,45,55,63,70,'关系质量常模'),\n" +
+    "('actionChildInterpersonal','all',84,144,27,34,42,52,62,70,77,'关系质量常模'),\n" +
+    "('actionChildInterpersonal','all',144,216,33,40,48,58,68,76,83,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionParentChild','all',216,1200,33,40,48,58,68,76,83,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionMarital','all',216,1200,30,37,45,55,65,73,80,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionParental','all',216,1200,27,34,42,52,62,70,77,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionLeadership','all',216,1200,30,37,45,55,65,73,80,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionSociality','all',216,1200,35,42,50,60,70,78,85,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('actionInterpersonal','all',216,1200,33,40,48,58,68,76,83,'关系质量常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('wealthSkill','all',0,84,10,17,25,35,45,53,60,'财富素养常模'),\n" +
+    "('wealthSkill','all',84,144,20,27,35,45,55,63,70,'财富素养常模'),\n" +
+    "('wealthSkill','all',144,216,30,37,45,55,65,73,80,'财富素养常模'),\n" +
+    "('wealthSkill','all',216,1200,35,42,50,60,70,78,85,'财富素养常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('wealthAltruism','all',0,84,5,12,20,30,40,48,55,'财富素养常模'),\n" +
+    "('wealthAltruism','all',84,144,15,22,30,40,50,58,65,'财富素养常模'),\n" +
+    "('wealthAltruism','all',144,216,25,32,40,50,60,68,75,'财富素养常模'),\n" +
+    "('wealthAltruism','all',216,1200,30,37,45,55,65,73,80,'财富素养常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('wealthIncome','all',0,84,0,7,15,25,35,43,50,'财富素养常模'),\n" +
+    "('wealthIncome','all',84,144,10,17,25,35,45,53,60,'财富素养常模'),\n" +
+    "('wealthIncome','all',144,216,20,27,35,45,55,63,70,'财富素养常模'),\n" +
+    "('wealthIncome','all',216,1200,30,37,45,55,65,73,80,'财富素养常模')");
+
+sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min, age_max, percentile_5, percentile_15, percentile_25, percentile_50, percentile_75, percentile_85, percentile_95, source) VALUES \n" +
+    "('wealthFx','all',0,84,0,2,10,20,30,38,45,'财富素养常模'),\n" +
+    "('wealthFx','all',84,144,5,12,20,30,40,48,55,'财富素养常模'),\n" +
+    "('wealthFx','all',144,216,15,22,30,40,50,58,65,'财富素养常模'),\n" +
+    "('wealthFx','all',216,1200,25,32,40,50,60,68,75,'财富素养常模')");
+            for (String sql : sqls) {
+                jdbcTemplate.execute(sql);
+            }
+            log.info("mind/action/wealth 常模种子数据已加载 ({}条)", sqls.size());
+        } catch (Exception e) {
+            log.warn("mind/action/wealth 常模种子数据初始化失败: {}", e.getMessage());
+        }
+    }
+
+    private void modifyColumnToText(String table, String column, String comment) {
+        if (!tableExists(table)) {
+            return;
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE " + table + " MODIFY COLUMN " + column + " TEXT COMMENT '" + comment + "'");
+            log.info("已修改{}.{}为TEXT类型", table, column);
+        } catch (Exception e) {
+            log.warn("修改{}.{}为TEXT失败: {}", table, column, e.getMessage());
+        }
+    }
+
+    private void initializeDefaultData() {
+        // 初始化预设奖励模板
+        try {
+            // 检查是否已有预设奖励
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM rewards WHERE is_template = 1", Integer.class);
+
+            if (count == null || count == 0) {
+// 插入预设奖励模板(child_id为NULL表示模板)
+                jdbcTemplate.execute("INSERT INTO rewards (family_id, child_id, title, points_required, category, is_template) VALUES " +
+                        "(0, NULL, \'看动画片30分钟\', 30, \'精神类\', 1), " +
+                        "(0, NULL, \'玩平板游戏30分钟\', 40, \'特权类\', 1), " +
+                        "(0, NULL, \'去游乐场\', 100, \'物质类\', 1), " +
+                        "(0, NULL, \'买玩具\', 150, \'物质类\', 1), " +
+                        "(0, NULL, \'吃冰淇淋\', 20, \'物质类\', 1), " +
+                        "(0, NULL, \'选择晚餐\', 25, \'特权类\', 1)");
+                log.info("预设奖励模板已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化预设奖励失败: {}", e.getMessage());
+        }
+
+        // 初始化奖励分类
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM reward_categories WHERE family_id = 0", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO reward_categories (family_id, name, icon, sort_order) VALUES " +
+                        "(0, '物质类', 'gift', 1), " +
+                        "(0, '精神类', 'star', 2), " +
+                        "(0, '特权类', 'crown', 3)");
+                log.info("奖励分类已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化奖励分类失败: {}", e.getMessage());
+        }
+
+        // 初始化打卡里程碑
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM streak_milestones", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO streak_milestones (days, reward_points) VALUES " +
+                        "(3, 5), (7, 10), (30, 30), (100, 100)");
+                log.info("打卡里程碑已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化打卡里程碑失败: {}", e.getMessage());
+        }
+
+        // 初始化勋章定义
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM badge_definitions", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO badge_definitions (badge_type, name, description, icon, condition_type, condition_value, reward_points) VALUES " +
+                        "('first_task', '初次完成任务', '首次完成任意任务', 'star', 'task_count', 1, 0), " +
+                        "('streak_3', '连续3天', '连续3天按时完成任务', 'fire', 'streak_days', 3, 5), " +
+                        "('streak_7', '连续7天', '连续7天按时完成任务', 'fire', 'streak_days', 7, 10), " +
+                        "('points_50', '积分达人', '一周获得50积分', 'trophy', 'weekly_points', 50, 0), " +
+                        "('task_100', '任务达人', '完成100个任务', 'medal', 'task_count', 100, 20)");
+                log.info("勋章定义已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化勋章定义失败: {}", e.getMessage());
+        }
+
+        // 初始化小游戏数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM mini_games", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO mini_games (game_code, game_name, description, icon, default_duration, default_points, game_url, sort_order, status) VALUES " +
+                        "('schulte', '舒尔特方格', '训练专注力和眼动能力,找到1-25按顺序点击', '🎯', 5, 5, '/pages/games/schulte', 1, 1), " +
+                        "('1a2b', '猜数字', '猜对数字和位置获得积分,锻炼逻辑思维', '🔢', 10, 5, '/pages/games/1a2b', 2, 1), " +
+                        "('sudoku', '数独', '填充数字使每行每列每个宫格都包含1-9', '🔳', 15, 8, '/pages/games/sudoku', 3, 1)");
+                log.info("小游戏数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化小游戏数据失败: {}", e.getMessage());
+        }
+
+// 初始化默认管理员
+        try {
+            // 先检查并添加role列(如果不存在)
+            try {
+                jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN role VARCHAR(20) DEFAULT \'admin\' AFTER email");
+                log.info("已添加role列到admins表");
+            } catch (Exception e) {
+                // 列已存在,忽略错误
+            }
+
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admins", Integer.class);
+
+            if (count == null || count == 0) {
+                // 默认管理员: 手机号 13800138000, 密码 admin123
+                String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
+                jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
+                        "(\'admin\', \'" + encryptedPassword + "\', \'系统管理员\', \'13800138000\', \'admin@xzyj.com\', \'admin\', 1)");
+                log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
+            }
+        } catch (Exception e) {
+            log.warn("初始化默认管理员失败: {}", e.getMessage());
+        }
+
+        // 初始化成长规划师WEB管理账号
+        try {
+            Integer teacherCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admins WHERE username = 'teacher'", Integer.class);
+            if (teacherCount == null || teacherCount == 0) {
+                String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_teacher123".getBytes());
+                jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, role, status) VALUES " +
+                        "('teacher', '" + teacherPassword + "', '成长规划师', '13800138003', 'teacher', 1)");
+                log.info("成长规划师管理账号已初始化: 用户名 teacher, 密码 teacher123");
+            }
+        } catch (Exception e) {
+            log.warn("初始化成长规划师管理账号失败: {}", e.getMessage());
+        }
+
+        // 初始化任务模板示例数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES " +
+                        "('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1), " +
+                        "('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1), " +
+                        "('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1), " +
+                        "('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1), " +
+                        "('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1), " +
+                        "('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1), " +
+                        "('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1), " +
+                        "('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1)");
+                log.info("任务模板示例数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化任务模板失败: {}", e.getMessage());
+        }
+
+        // 初始化街道数据示例(广东省广州市)
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM streets", Integer.class);
+
+            if (count == null || count == 0) {
+                // 省
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广东省', 1, NULL, 1)");
+
+                // 市
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '广东省广州市', 2, 1, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '广东省深圳市', 2, 1, 2), " +
+                        "('广东省', '44', '珠海市', '4404', '广东省珠海市', 2, 1, 3)");
+
+                // 区
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '广东省广州市天河区', 3, 2, 1), " +
+                        "('广东省', '44', '广州市', '4401', '越秀区', '440104', '广东省广州市越秀区', 3, 2, 2), " +
+                        "('广东省', '44', '广州市', '4401', '番禺区', '440113', '广东省广州市番禺区', 3, 2, 3), " +
+                        "('广东省', '44', '深圳市', '4403', '南山区', '440305', '广东省深圳市南山区', 3, 3, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '福田区', '440304', '广东省深圳市福田区', 3, 3, 2)");
+
+                // 街道
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, street, street_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '天河南街道', '440106001', '广东省广州市天河区天河南街道', 4, 4, 1), " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '沙河街道', '440106002', '广东省广州市天河区沙河街道', 4, 4, 2), " +
+                        "('广东省', '44', '广州市', '4401', '越秀区', '440104', '东山街道', '440104001', '广东省广州市越秀区东山街道', 4, 5, 1), " +
+                        "('广东省', '44', '广州市', '4401', '番禺区', '440113', '市桥街道', '440113001', '广东省广州市番禺区市桥街道', 4, 6, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '南山区', '440305', '南山街道', '440305001', '广东省深圳市南山区南山街道', 4, 7, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '福田区', '440304', '福田街道', '440304001', '广东省深圳市福田区福田街道', 4, 8, 1)");
+
+                log.info("街道示例数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化街道数据失败: {}", e.getMessage());
+        }
+
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admins", Integer.class);
+
+            if (count == null || count == 0) {
+                // 默认管理员: 手机号 13800138000, 密码 admin123
+                String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
+                jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
+                        "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 'admin', 1)");
+                log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
+            }
+        } catch (Exception e) {
+            log.warn("初始化任务模板失败: {}", e.getMessage());
+        }
+
+// 初始化 users 表测试用户
+        try {
+            // 创建默认家庭(如果不存在)
+            Integer familyCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM families WHERE id = 1", Integer.class);
+            if (familyCount == null || familyCount == 0) {
+                jdbcTemplate.execute("INSERT INTO families (id, name, invite_code) VALUES (1, '默认家庭', '888888')");
+                log.info("默认家庭已创建");
+            }
+
+            // 密码加密: MD5("xzyj_salt_" + password)
+            String adminPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_admin123".getBytes());
+            String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_teacher123".getBytes());
+
+            // 创建管理员用户(管理员角色,密码 admin123)
+            Integer adminUserCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM users WHERE phone = '13800138000'", Integer.class);
+            if (adminUserCount == null || adminUserCount == 0) {
+                jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
+                        "('seed_admin_13800138000', 1, 'admin', '13800138000', '系统管理员', '系统管理员', '" + adminPassword + "')");
+                log.info("测试管理员用户已创建: 13800138000 (admin角色, 密码 admin123)");
+            }
+
+            // 创建测试教师用户
+            Integer teacherUserCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM users WHERE phone = '13800138001'", Integer.class);
+            if (teacherUserCount == null || teacherUserCount == 0) {
+                jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
+                        "('seed_teacher_13800138001', 1, 'teacher', '13800138001', '测试教师', '测试教师', '" + teacherPassword + "')");
+                log.info("测试教师用户已创建: 13800138001 (教师角色, 密码 teacher123)");
+            }
+        } catch (Exception e) {
+            log.warn("初始化测试用户失败: {}", e.getMessage());
+        }
+
+        // 初始化星座配置种子数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM zodiac_config", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO zodiac_config (zodiac_code, zodiac_name, element, start_month, start_day, end_month, end_day, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
+                        "('aries',       '白羊座', '火', 3, 21, 4, 19, 65, 70, 1.10, 1.05, 0.95), " +
+                        "('taurus',      '金牛座', '土', 4, 20, 5, 20, 60, 65, 1.05, 1.10, 1.15), " +
+                        "('gemini',      '双子座', '金', 5, 21, 6, 21, 70, 80, 0.95, 1.15, 0.90), " +
+                        "('cancer',      '巨蟹座', '水', 6, 22, 7, 22, 75, 60, 1.00, 0.90, 1.05), " +
+                        "('leo',         '狮子座', '火', 7, 23, 8, 22, 65, 75, 1.15, 1.00, 1.10), " +
+                        "('virgo',       '处女座', '土', 8, 23, 9, 22, 55, 85, 0.95, 1.05, 1.00), " +
+                        "('libra',       '天秤座', '金', 9, 23, 10, 23, 70, 75, 0.90, 0.95, 1.10), " +
+                        "('scorpio',     '天蝎座', '水', 10, 24, 11, 22, 80, 70, 1.10, 1.00, 1.15), " +
+                        "('sagittarius', '射手座', '火', 11, 23, 12, 21, 60, 65, 1.15, 1.15, 0.95), " +
+                        "('capricorn',   '摩羯座', '土', 12, 22, 1, 19, 55, 75, 1.05, 1.10, 1.20), " +
+                        "('aquarius',    '水瓶座', '金', 1, 20, 2, 18, 75, 85, 0.95, 0.90, 1.00), " +
+                        "('pisces',      '双鱼座', '水', 2, 19, 3, 20, 80, 60, 0.90, 0.95, 0.95)");
+                log.info("星座配置种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化星座配置失败: {}", e.getMessage());
+        }
+
+        // 初始化八字配置种子数据(十天干 + 十二地支)
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM bazi_config", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO bazi_config (stem_or_branch, type, element, yin_yang, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
+                        "('甲', 'stem', '木', 'yang', 60, 75, 1.05, 1.10, 1.00), " +
+                        "('乙', 'stem', '木', 'yin',  65, 70, 0.95, 1.05, 1.00), " +
+                        "('丙', 'stem', '火', 'yang', 75, 65, 1.10, 1.00, 0.95), " +
+                        "('丁', 'stem', '火', 'yin',  70, 60, 1.05, 0.95, 1.00), " +
+                        "('戊', 'stem', '土', 'yang', 60, 65, 1.15, 1.05, 1.10), " +
+                        "('己', 'stem', '土', 'yin',  55, 60, 1.10, 1.00, 1.15), " +
+                        "('庚', 'stem', '金', 'yang', 65, 80, 1.05, 1.10, 1.05), " +
+                        "('辛', 'stem', '金', 'yin',  60, 75, 0.95, 1.00, 1.10), " +
+                        "('壬', 'stem', '水', 'yang', 70, 70, 1.00, 0.95, 1.15), " +
+                        "('癸', 'stem', '水', 'yin',  75, 65, 0.95, 0.90, 1.10), " +
+                        "('子', 'branch', '水', 'yang', 70, 65, 1.00, 0.95, 1.05), " +
+                        "('丑', 'branch', '土', 'yin',  55, 60, 1.10, 1.05, 1.10), " +
+                        "('寅', 'branch', '木', 'yang', 65, 75, 1.15, 1.10, 1.00), " +
+                        "('卯', 'branch', '木', 'yin',  60, 70, 1.05, 1.05, 0.95), " +
+                        "('辰', 'branch', '土', 'yang', 60, 65, 1.10, 1.00, 1.10), " +
+                        "('巳', 'branch', '火', 'yin',  75, 70, 1.05, 0.95, 1.00), " +
+                        "('午', 'branch', '火', 'yang', 80, 65, 1.15, 1.00, 0.95), " +
+                        "('未', 'branch', '土', 'yin',  55, 60, 1.05, 1.00, 1.05), " +
+                        "('申', 'branch', '金', 'yang', 65, 80, 1.00, 1.10, 1.05), " +
+                        "('酉', 'branch', '金', 'yin',  60, 75, 0.95, 1.05, 1.10), " +
+                        "('戌', 'branch', '土', 'yang', 60, 65, 1.10, 1.00, 1.15), " +
+                        "('亥', 'branch', '水', 'yin',  70, 60, 1.00, 0.95, 1.05)");
+                log.info("八字配置种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化八字配置失败: {}", e.getMessage());
+        }
+
+        // 初始化血型配置种子数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM blood_type_config", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO blood_type_config (blood_type, description, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
+                        "('A',  '认真负责、严谨细致,但易焦虑', 70, 75, 0.95, 1.00, 1.05), " +
+                        "('B',  '乐观开朗、好奇心强,但缺乏耐心', 65, 70, 1.05, 1.10, 0.95), " +
+                        "('AB', '理性冷静、适应力强,但优柔寡断', 75, 80, 1.00, 0.95, 1.10), " +
+                        "('O',  '自信果断、行动力强,但固执己见', 60, 65, 1.15, 1.15, 1.15)");
+                log.info("血型配置种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化血型配置失败: {}", e.getMessage());
+        }
+
+        // 初始化优惠券种子数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM coupon", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.update(
+                        "INSERT INTO coupon (name, type, value, min_spend, applicable_to, valid_from, valid_until, total_count, used_count, grant_type, grant_level_code) " +
+                                "VALUES (?, ?, ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 YEAR), -1, 0, NULL, NULL)",
+                        "新用户专享10元券", "FIXED", 1000, 0, "ALL");
+                jdbcTemplate.update(
+                        "INSERT INTO coupon (name, type, value, min_spend, applicable_to, valid_from, valid_until, total_count, used_count, grant_type, grant_level_code) " +
+                                "VALUES (?, ?, ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 YEAR), -1, 0, NULL, NULL)",
+                        "会员升级50元券", "FIXED", 5000, 9900, "MEMBERSHIP");
+                // JOIN 赠券模板:开通家庭会员赠 20 元券
+                jdbcTemplate.update(
+                        "INSERT INTO coupon (name, type, value, min_spend, applicable_to, valid_from, valid_until, total_count, used_count, grant_type, grant_level_code, grant_quantity) " +
+                                "VALUES (?, ?, ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 YEAR), -1, 0, 'JOIN', 'FAMILY', 1)");
+                // JOIN 赠券模板:开通 Premium 会员赠 50 元券
+                jdbcTemplate.update(
+                        "INSERT INTO coupon (name, type, value, min_spend, applicable_to, valid_from, valid_until, total_count, used_count, grant_type, grant_level_code, grant_quantity) " +
+                                "VALUES (?, ?, ?, ?, ?, NOW(), DATE_ADD(NOW(), INTERVAL 1 YEAR), -1, 0, 'JOIN', 'PREMIUM', 1)");
+                log.info("优惠券种子数据已初始化(含 JOIN 赠券模板)");
+            }
+        } catch (Exception e) {
+            log.warn("初始化优惠券数据失败: {}", e.getMessage());
+        }
+
+        // 初始化成长规划师模块相关表
+        initGuideModuleTables(jdbcTemplate);
+    }
+
+    private void initGuideModuleTables(JdbcTemplate jdbcTemplate) {
+        // 服务类型表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_types (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '类型名称', " +
+                    "icon VARCHAR(50) COMMENT '图标', " +
+                    "description VARCHAR(500) COMMENT '描述', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建service_types表失败: {}", e.getMessage());
+        }
+
+        // 服务内容表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_contents (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "type_id BIGINT NOT NULL COMMENT '关联服务类型', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '内容标题', " +
+                    "content TEXT COMMENT '富文本内容', " +
+                    "duration INT COMMENT '建议时长(分钟)', " +
+                    "age_range VARCHAR(50) COMMENT '适用年龄', " +
+                    "price INT COMMENT '参考价格(分)', " +
+                    "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/ACTIVE', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建service_contents表失败: {}", e.getMessage());
+        }
+
+        // 套餐模板表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_templates (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '模板名称', " +
+                    "validity_months INT COMMENT '有效期(月)', " +
+                    "reference_price INT COMMENT '参考价格(分)', " +
+                    "min_price INT COMMENT '最低售价(分)', " +
+                    "description VARCHAR(500) COMMENT '说明', " +
+                    "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_package_templates表失败: {}", e.getMessage());
+        }
+
+        // 套餐模板服务项表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS package_template_services (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "template_id BIGINT NOT NULL COMMENT '关联模板', " +
+                    "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
+                    "quantity INT DEFAULT 1 COMMENT '数量', " +
+                    "unit VARCHAR(20) COMMENT '单位' " +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建package_template_services表失败: {}", e.getMessage());
+        }
+
+        // 成长规划师套餐表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_packages (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
+                    "template_id BIGINT COMMENT '关联模板', " +
+                    "name VARCHAR(100) NOT NULL COMMENT '套餐名称', " +
+                    "price INT NOT NULL COMMENT '销售价格(分)', " +
+                    "validity_months INT COMMENT '有效期(月)', " +
+                    "description VARCHAR(500) COMMENT '套餐说明', " +
+                    "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/PAUSED', " +
+                    "sales_count INT DEFAULT 0 COMMENT '销量', " +
+                    "share_code VARCHAR(50) COMMENT '分享码', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_packages表失败: {}", e.getMessage());
+        }
+
+        // 迁移:guide_packages 表添加缺失列
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID' AFTER id");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN package_id BIGINT COMMENT '关联模板' AFTER guide_id");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN commission_rate INT COMMENT '佣金比例(bps,100=1%)' AFTER package_id");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN teacher_id BIGINT NULL");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN template_id BIGINT NULL");
+        } catch (Exception e) {
+        }
+
+        // 迁移:guide_packages 表添加 level 列(GuidePackage 实体需要)
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN level VARCHAR(20) COMMENT '套餐等级' AFTER name");
+        } catch (Exception e) {
+        }
+
+        // 迁移:guide_packages 表添加 validity_days 列
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN validity_days INT DEFAULT 0 COMMENT '有效期天数' AFTER validity_months");
+        } catch (Exception e) {
+        }
+
+        // 迁移:guide_packages 表添加 sort_order 列
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序' AFTER status");
+        } catch (Exception e) {
+        }
+
+        // 成长规划师套餐服务项表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_services (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "package_id BIGINT NOT NULL COMMENT '关联套餐', " +
+                    "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
+                    "quantity INT DEFAULT 1 COMMENT '数量', " +
+                    "unit VARCHAR(20) COMMENT '单位' " +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_package_services表失败: {}", e.getMessage());
+        }
+
+        // 订单表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_orders (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_no VARCHAR(50) NOT NULL COMMENT '订单号', " +
+                    "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "package_id BIGINT NOT NULL COMMENT '套餐ID', " +
+                    "amount INT NOT NULL COMMENT '金额(分)', " +
+                    "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/PAID/COMPLETED/REFUNDED', " +
+                    "pay_time DATETIME COMMENT '支付时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_orders表失败: {}", e.getMessage());
+        }
+
+        // 佣金明细表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_commissions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
+                    "order_id BIGINT NOT NULL COMMENT '订单ID', " +
+                    "amount INT NOT NULL COMMENT '佣金金额(分)', " +
+                    "rate INT COMMENT '佣金比例(bps,100=1%)', " +
+                    "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/COMPLETED', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_commissions表失败: {}", e.getMessage());
+        }
+
+        // 服务记录表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_service_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "order_id BIGINT COMMENT '订单ID', " +
+                    "service_content_id BIGINT NOT NULL COMMENT '服务内容ID', " +
+                    "notes VARCHAR(500) COMMENT '服务记录', " +
+                    "duration INT COMMENT '服务时长(分钟)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_service_logs表失败: {}", e.getMessage());
+        }
+
+        // 成长规划师邀请码表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_invite_codes (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
+                    "code VARCHAR(50) NOT NULL UNIQUE COMMENT '邀请码', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态', " +
+                    "used_by_user_id BIGINT COMMENT '使用该邀请码的用户ID', " +
+                    "used_at DATETIME COMMENT '使用时间', " +
+                    "expire_at DATETIME COMMENT '过期时间', " +
+                    "use_count INT DEFAULT 0 COMMENT '使用次数', " +
+                    "max_use_count INT COMMENT '最大使用次数', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                    ")");
+        } catch (Exception e) {
+            log.warn("创建guide_invite_codes表失败: {}", e.getMessage());
+        }
+
+        // 迁移29: assessment_orders表添加appointment_id字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_orders ADD COLUMN appointment_id BIGINT COMMENT '关联的预约ID'");
+            log.info("已添加appointment_id列到assessment_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 财富维度: 财商打卡记录表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS finance_checkins (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT NULL COMMENT '孩子ID,家长本人打卡时为NULL', " +
+                    "checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review', " +
+                    "type VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)', " +
+                    "checkin_date DATE NOT NULL COMMENT '打卡日期', " +
+                    "amount INT COMMENT '金额(分,收支记录)', " +
+                    "category VARCHAR(50) COMMENT '收支分类', " +
+                    "note VARCHAR(200) COMMENT '备注', " +
+                    "mood VARCHAR(20) COMMENT '心情', " +
+                    "earned_energy INT DEFAULT 0 COMMENT '获得的能量值', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_date (user_id, checkin_date), " +
+                    "INDEX idx_child (child_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财商打卡记录表'");
+            log.info("已创建finance_checkins表");
+        } catch (Exception e) {
+            log.warn("创建finance_checkins表失败: {}", e.getMessage());
+        }
+
+        // 财富维度: 保单管理表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS insurance_policies (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT COMMENT '关联孩子ID', " +
+                    "policy_name VARCHAR(100) NOT NULL COMMENT '保单名称', " +
+                    "insurance_company VARCHAR(100) COMMENT '保险公司', " +
+                    "policy_type VARCHAR(50) COMMENT '保单类型: medical/life/accident/education/endowment', " +
+                    "insured_person VARCHAR(50) NOT NULL COMMENT '被保人姓名', " +
+                    "premium INT COMMENT '保费(分)', " +
+                    "sum_insured INT COMMENT '保额(分)', " +
+                    "start_date DATE COMMENT '生效日期', " +
+                    "end_date DATE COMMENT '到期日期', " +
+                    "renewal_type VARCHAR(20) DEFAULT 'annual' COMMENT '续费类型: annual/monthly/single', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired/cancelled', " +
+                    "policy_no VARCHAR(100) COMMENT '保单号', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_child (child_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单管理表'");
+            log.info("已创建insurance_policies表");
+        } catch (Exception e) {
+            log.warn("创建insurance_policies表失败: {}", e.getMessage());
+        }
+
+        // ==================== 健康检测报告模块 =============        // 健康报告表(体检报告/菌群检测报告)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_reports (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)', " +
+                    "report_date DATE COMMENT '报告日期', " +
+                    "overall_score INT COMMENT '健康总分(0-100)', " +
+                    "gut_health_score INT COMMENT '菌群健康分', " +
+                    "chronic_disease_score INT COMMENT '慢病控制分', " +
+                    "nutrition_score INT COMMENT '营养均衡分', " +
+                    "gut_age VARCHAR(32) COMMENT '肠道预测年龄', " +
+                    "gut_type VARCHAR(50) COMMENT '肠型', " +
+                    "file_url VARCHAR(500) COMMENT '报告文件URL', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_report_type (report_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康检测报告表'");
+            log.info("已创建health_reports表");
+        } catch (Exception e) {
+            log.warn("创建health_reports表可能已存在: {}", e.getMessage());
+        }
+
+        // 健康指标明细表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_indicators (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
+                    "category VARCHAR(50) COMMENT '指标分类: 营养状况/疾病风险/肠道屏障/神经递质/...', " +
+                    "indicator_name VARCHAR(100) NOT NULL COMMENT '指标名称', " +
+                    "indicator_value VARCHAR(100) COMMENT '检测值', " +
+                    "unit VARCHAR(50) COMMENT '单位', " +
+                    "ref_range VARCHAR(200) COMMENT '参考范围', " +
+                    "status VARCHAR(50) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出', " +
+                    "sort_order INT DEFAULT 0 COMMENT '显示排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_report_id (report_id), " +
+                    "INDEX idx_category (category)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标明细表'");
+            log.info("已创建health_indicators表");
+        } catch (Exception e) {
+            log.warn("创建health_indicators表可能已存在: {}", e.getMessage());
+        }
+
+        // ---------- 健康报告表 新增菌群摘要字段 ----------
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports " +
+                    "ADD COLUMN gut_balance_score INT COMMENT '菌群平衡分(0-100)', " +
+                    "ADD COLUMN gut_diversity_score INT COMMENT '菌群多样性分(0-100)', " +
+                    "ADD COLUMN beneficial_bacteria_score INT COMMENT '有益菌分(0-100)', " +
+                    "ADD COLUMN harmful_bacteria_score INT COMMENT '有害菌分(0-100)', " +
+                    "ADD COLUMN core_species_score INT COMMENT '核心菌属分(0-100)', " +
+                    "ADD COLUMN health_rating VARCHAR(100) COMMENT '总分评级,如亚健康/优秀等'");
+            log.info("已为health_reports表添加6个菌群摘要字段");
+        } catch (Exception e) {
+            log.warn("为health_reports表添加字段可能已存在: {}", e.getMessage());
+        }
+
+
+        ensureColumn("health_reports", "subject_id", "BIGINT COMMENT '报告主体用户ID(关联users表,为null表示未绑定)'");
+
+        log.info("成长规划师模块、财富维度及健康检测报告数据库表初始化完成");
+
+        // 健康打卡表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_checkins (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID(家长)', " +
+                    "child_id BIGINT COMMENT '孩子ID', " +
+                    "checkin_date DATE COMMENT '打卡日期', " +
+                    "behaviors TEXT COMMENT '打卡行为JSON数组', " +
+                    "note VARCHAR(500) COMMENT '备注', " +
+                    "mood VARCHAR(20) COMMENT '心情: happy/normal/sad/angry/sick', " +
+                    "earned_energy INT DEFAULT 5 COMMENT '获得能量值', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_id (child_id), " +
+                    "INDEX idx_checkin_date (checkin_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康打卡表'");
+            log.info("已创建health_checkins表");
+        } catch (Exception e) {
+            log.warn("创建health_checkins表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移31: 创建联系人表
+
+        // 创建文章阅读记录表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_reading_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                    "content VARCHAR(500) COMMENT '阅读内容摘要', " +
+                    "duration_seconds INT DEFAULT 0 COMMENT '阅读时长(秒)', " +
+                    "read_at DATETIME COMMENT '阅读时间', " +
+                    "created_at DATETIME, " +
+                    "INDEX idx_child_id (child_id), " +
+                    "INDEX idx_read_at (read_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章阅读记录表'");
+            log.info("已创建article_reading_records表");
+        } catch (Exception e) {
+            log.warn("创建article_reading_records表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移32: products表添加配送/类目/销售字段(商品编辑表单对齐)
+        ensureColumn("products", "delivery_method", "TINYINT DEFAULT 1 COMMENT '配送方式: 1=快递 2=自提 3=两者皆可'");
+        ensureColumn("products", "category_id", "BIGINT COMMENT '商品类目ID'");
+        ensureColumn("products", "distribution_system_id", "BIGINT COMMENT '销售体系ID'");
+
+        // 迁移33: 创建供应商销售体系表 + product_orders扩展字段
+        // 迁移222: health_reports 添加 shannon_index, payload_json 列并创建 health_report_edit_logs 表
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN shannon_index VARCHAR(32) DEFAULT NULL COMMENT '香农指数'");
+            log.info("已添加shannon_index列到health_reports表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN payload_json LONGTEXT COMMENT '报告完整载荷快照'");
+            log.info("已添加payload_json列到health_reports表");
+        } 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 '销售佣金(分)'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD KEY idx_supply_system_id (supply_system_id)");
+            log.info("已添加idx_supply_system_id索引到product_orders表");
+        } catch (Exception e) {
+            log.warn("添加idx_supply_system_id索引可能已存在: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_system (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '体系名称', " +
+                    "admin_id BIGINT NOT NULL COMMENT '管理员用户ID', " +
+                    "settlement_period_days INT NOT NULL DEFAULT 30 COMMENT '账期(天)', " +
+                    "platform_profit_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '平台留利比例(%)', " +
+                    "description VARCHAR(500) COMMENT '描述', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "KEY idx_admin_id (admin_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系'");
+            log.info("已创建supply_system表");
+        } catch (Exception e) {
+            log.warn("创建supply_system表可能已存在: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_system_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL COMMENT '体系ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "role VARCHAR(20) NOT NULL DEFAULT 'member' COMMENT '角色: admin/member', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/inactive', " +
+                    "joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_system_user (system_id, user_id), " +
+                    "KEY idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体系成员'");
+            log.info("已创建supply_system_member表");
+        } catch (Exception e) {
+            log.warn("创建supply_system_member表可能已存在: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_settlement (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL COMMENT '体系ID', " +
+                    "period_start DATE NOT NULL COMMENT '结算周期开始', " +
+                    "period_end DATE NOT NULL COMMENT '结算周期结束', " +
+                    "total_sales INT NOT NULL DEFAULT 0 COMMENT '销售总额(分)', " +
+                    "platform_profit INT NOT NULL DEFAULT 0 COMMENT '平台留利金额(分)', " +
+                    "supplier_payout INT NOT NULL DEFAULT 0 COMMENT '供应商实付(分)', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/settled', " +
+                    "settled_at DATETIME COMMENT '结算时间', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "KEY idx_system_id (system_id), " +
+                    "KEY idx_period (period_start, period_end)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算记录'");
+            log.info("已创建supply_settlement表");
+        } catch (Exception e) {
+            log.warn("创建supply_settlement表可能已存在: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_settlement_detail (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "settlement_id BIGINT NOT NULL COMMENT '结算ID', " +
+                    "order_id BIGINT NOT NULL COMMENT '订单ID', " +
+                    "order_no VARCHAR(64) NOT NULL COMMENT '订单号', " +
+                    "product_name VARCHAR(200) NOT NULL COMMENT '商品名称', " +
+                    "amount INT NOT NULL COMMENT '订单金额(分)', " +
+                    "platform_fee INT NOT NULL COMMENT '平台留利(分)', " +
+                    "supplier_id BIGINT COMMENT '收款供应商用户ID', " +
+                    "supplier_name VARCHAR(100) COMMENT '收款供应商名称', " +
+                    "payout INT NOT NULL COMMENT '供应商实付(分)', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "KEY idx_settlement_id (settlement_id), " +
+                    "KEY idx_order_id (order_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算明细'");
+            log.info("已创建supply_settlement_detail表");
+        } catch (Exception e) {
+            log.warn("创建supply_settlement_detail表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移: growth_records表添加来源类型/去重/补充字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_type VARCHAR(20) DEFAULT 'manual' COMMENT '来源类型: manual/platform_purchase/self_upload/supplement'");
+            log.info("已添加source_type列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_file_url VARCHAR(500) COMMENT '原始报告文件URL'");
+            log.info("已添加source_file_url列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN order_id BIGINT COMMENT '关联订单ID'");
+            log.info("已添加order_id列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN report_date DATETIME COMMENT '测评/报告日期'");
+            log.info("已添加report_date列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parent_record_id BIGINT COMMENT '关联的原始档案ID(补充档案专用)'");
+            log.info("已添加parent_record_id列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN is_latest TINYINT(1) DEFAULT 1 COMMENT '是否同组最新记录'");
+            log.info("已添加is_latest列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN dedup_hash VARCHAR(64) COMMENT '去重哈希值(SHA256)'");
+            log.info("已添加dedup_hash列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parsed_data TEXT COMMENT '上传报告的解析结果(JSON)'");
+            log.info("已添加parsed_data列到growth_records表");
+        } catch (Exception e) {
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE UNIQUE INDEX IF NOT EXISTS uk_growth_dedup_hash ON growth_records(dedup_hash)");
+            log.info("已添加uk_growth_dedup_hash唯一索引到growth_records表");
+        } catch (Exception e) {
+            try {
+                jdbcTemplate.execute("ALTER TABLE growth_records ADD UNIQUE INDEX uk_growth_dedup_hash (dedup_hash)");
+                log.info("已通过ALTER TABLE添加uk_growth_dedup_hash唯一索引");
+            } catch (Exception e2) {
+                log.warn("添加uk_growth_dedup_hash索引可能已存在: {}", e2.getMessage());
+            }
+        }
+
+        // 初始化成长任务种子数据
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM growth_task", Integer.class);
+
+            if (count == null || count == 0) {
+                jdbcTemplate.execute("INSERT INTO growth_task (type, title, description, reward_points, reward_energy, target_value, task_key, enabled, sort_order, created_at) VALUES " +
+                        "('DAILY', '每日签到', '完成健康打卡签到', 20, 3, 1, 'DAILY_CHECKIN', 1, 1, NOW()), " +
+                        "('DAILY', '完成任务', '完成一个任务', 30, 5, 1, 'DAILY_TASK', 1, 2, NOW()), " +
+                        "('DAILY', '每日分享', '分享小程序给好友', 10, 2, 1, 'DAILY_SHARE', 1, 3, NOW()), " +
+                        "('DAILY', 'AI对话', '与AI助手对话一次', 15, 3, 1, 'DAILY_AI', 1, 4, NOW())");
+                log.info("成长任务种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化成长任务种子数据失败: {}", e.getMessage());
+        }
+
+        // 迁移34: dan_assessment_results表添加家庭成员/EMI心理/大五人格/孩子快照字段
+        ensureColumn("dan_assessment_results", "family_member_id", "BIGINT COMMENT '家庭成员ID'");
+        ensureColumn("dan_assessment_results", "family_member_name", "VARCHAR(50) COMMENT '成员姓名快照'");
+        ensureColumn("dan_assessment_results", "family_member_birthday", "DATE COMMENT '成员出生日期快照'");
+        ensureColumn("dan_assessment_results", "family_member_gender", "VARCHAR(10) COMMENT '成员性别快照'");
+        ensureColumn("dan_assessment_results", "family_member_age", "INT COMMENT '成员年龄快照'");
+        ensureColumn("dan_assessment_results", "emotion_score", "INT COMMENT '情绪商数(EMI)'");
+        ensureColumn("dan_assessment_results", "resilience_score", "INT COMMENT '心理韧性(EMI)'");
+        ensureColumn("dan_assessment_results", "stress_coping_score", "INT COMMENT '压力应对(EMI)'");
+        ensureColumn("dan_assessment_results", "self_awareness_score", "INT COMMENT '自我认知(EMI)'");
+        ensureColumn("dan_assessment_results", "openness_score", "INT COMMENT '开放性(大五人格)'");
+        ensureColumn("dan_assessment_results", "conscientiousness_score", "INT COMMENT '尽责性(大五人格)'");
+        ensureColumn("dan_assessment_results", "extraversion_score", "INT COMMENT '外向性(大五人格)'");
+        ensureColumn("dan_assessment_results", "agreeableness_score", "INT COMMENT '宜人性(大五人格)'");
+        ensureColumn("dan_assessment_results", "neuroticism_score", "INT COMMENT '神经质(大五人格)'");
+        ensureColumn("dan_assessment_results", "big_five_overall_score", "INT COMMENT '大五人格综合分'");
+        ensureColumn("dan_assessment_results", "child_name", "VARCHAR(50) COMMENT '孩子姓名快照'");
+        ensureColumn("dan_assessment_results", "child_birthday", "DATE COMMENT '出生日期快照'");
+        ensureColumn("dan_assessment_results", "child_gender", "VARCHAR(10) COMMENT '性别快照'");
+        ensureColumn("dan_assessment_results", "child_age", "INT COMMENT '年龄快照'");
+        ensureColumn("dan_assessment_results", "school", "VARCHAR(100) COMMENT '学校快照'");
+        ensureColumn("dan_assessment_results", "grade", "VARCHAR(20) COMMENT '年级快照'");
+        ensureColumn("dan_assessment_results", "grade_class", "VARCHAR(20) COMMENT '班级快照'");
+        ensureColumn("dan_assessment_results", "height", "INT COMMENT '身高(CM)快照'");
+        ensureColumn("dan_assessment_results", "weight", "INT COMMENT '体重(KG)快照'");
+        ensureColumn("dan_assessment_results", "family_status", "VARCHAR(50) COMMENT '家庭状况快照'");
+        ensureColumn("dan_assessment_results", "parent_degree", "VARCHAR(50) COMMENT '父母最高学历快照'");
+        ensureColumn("dan_assessment_results", "nation", "VARCHAR(20) COMMENT '民族快照'");
+        ensureColumn("dan_assessment_results", "province", "VARCHAR(50) COMMENT '省快照'");
+        ensureColumn("dan_assessment_results", "city", "VARCHAR(50) COMMENT '市快照'");
+        ensureColumn("dan_assessment_results", "district", "VARCHAR(50) COMMENT '区快照'");
+        ensureColumn("dan_assessment_results", "address", "VARCHAR(200) COMMENT '详细地址快照'");
+        ensureColumn("dan_assessment_results", "single_child", "VARCHAR(10) COMMENT '是否独生子女快照'");
+        ensureColumn("dan_assessment_results", "school_score", "VARCHAR(20) COMMENT '在校成绩快照'");
+        ensureColumn("dan_assessment_results", "urgent_name", "VARCHAR(50) COMMENT '紧急联系人姓名'");
+        ensureColumn("dan_assessment_results", "urgent_phone", "VARCHAR(20) COMMENT '紧急联系人电话'");
+        ensureColumn("dan_assessment_results", "pics", "TEXT COMMENT '图片IDs'");
+        ensureColumn("dan_assessment_results", "videos", "TEXT COMMENT '视频IDs'");
+        ensureColumn("dan_assessment_results", "audio", "VARCHAR(100) COMMENT '音频ID'");
+        ensureColumn("dan_assessment_results", "audio_url", "VARCHAR(255) COMMENT '音频URL'");
+        ensureColumn("dan_assessment_results", "process_desc", "TEXT COMMENT '过程描述'");
+        ensureColumn("dan_assessment_results", "result_desc", "TEXT COMMENT '结果简评'");
+
+        // 迁移35: points_log表添加family_member_id列(EnergyService.calcChildWealth家庭能量计算需要)
+        ensureColumn("points_log", "family_member_id", "BIGINT COMMENT '家庭成员ID'");
+
+        // 迁移36: family_members表添加generation + is_spouse列(辈分重构)
+        ensureColumn("family_members", "generation", "INT COMMENT '辈分值(0=家庭创建者,+n=向上n代,-n=向下n代)'");
+        ensureColumn("family_members", "is_spouse", "TINYINT(1) DEFAULT 0 COMMENT '是否配偶(同辈)'");
+        // 迁移37: relationship_type列已删除,旧数据已迁移完成
+
+        // 迁移38: product_orders表添加points_used/points_cost/money_amount列(混合支付支持)
+        ensureColumn("product_orders", "points_used", "INT DEFAULT 0 COMMENT '使用的积分数量'");
+        ensureColumn("product_orders", "points_cost", "INT DEFAULT 0 COMMENT '积分抵扣金额(分)'");
+        ensureColumn("product_orders", "money_amount", "INT DEFAULT 0 COMMENT '现金支付金额(分)'");
+
+        // 迁移39: 创建供应商体系相关表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_system (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '体系名称', " +
+                    "admin_id BIGINT COMMENT '管理员用户ID', " +
+                    "settlement_period_days INT DEFAULT 30 COMMENT '账期天数', " +
+                    "platform_profit_rate DECIMAL(10,2) DEFAULT 0.00 COMMENT '平台留利比例(%)', " +
+                    "description VARCHAR(500) COMMENT '描述', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/disabled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_admin (admin_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系'");
+            log.info("已创建supply_system表");
+        } catch (Exception e) {
+            log.warn("创建supply_system表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_system_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL COMMENT '体系ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "role VARCHAR(20) DEFAULT 'member' COMMENT '角色 admin/member', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/disabled', " +
+                    "joined_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_system (system_id), " +
+                    "INDEX idx_user (user_id), " +
+                    "UNIQUE INDEX idx_system_user (system_id, user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系成员'");
+            log.info("已创建supply_system_member表");
+        } catch (Exception e) {
+            log.warn("创建supply_system_member表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_settlement (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL COMMENT '体系ID', " +
+                    "period_start DATE COMMENT '结算周期开始', " +
+                    "period_end DATE COMMENT '结算周期结束', " +
+                    "total_sales INT DEFAULT 0 COMMENT '销售总额(分)', " +
+                    "platform_profit INT DEFAULT 0 COMMENT '平台留利(分)', " +
+                    "supplier_payout INT DEFAULT 0 COMMENT '供应商实付(分)', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态 pending/settled', " +
+                    "settled_at DATETIME COMMENT '结算时间', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_system (system_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_period (period_start, period_end)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商结算单'");
+            log.info("已创建supply_settlement表");
+        } catch (Exception e) {
+            log.warn("创建supply_settlement表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_settlement_detail (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "settlement_id BIGINT NOT NULL COMMENT '结算单ID', " +
+                    "order_id BIGINT COMMENT '订单ID', " +
+                    "order_no VARCHAR(64) COMMENT '订单号', " +
+                    "product_name VARCHAR(200) COMMENT '商品名称', " +
+                    "amount INT DEFAULT 0 COMMENT '订单金额(分)', " +
+                    "platform_fee INT DEFAULT 0 COMMENT '平台留利(分)', " +
+                    "supplier_id BIGINT COMMENT '收款供应商ID', " +
+                    "supplier_name VARCHAR(100) COMMENT '收款供应商名称', " +
+                    "payout INT DEFAULT 0 COMMENT '实付(分)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_settlement (settlement_id), " +
+                    "INDEX idx_order (order_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商结算明细'");
+            log.info("已创建supply_settlement_detail表");
+        } catch (Exception e) {
+            log.warn("创建supply_settlement_detail表失败: {}", e.getMessage());
+        }
+
+        // 迁移40: 添加supplier_admin角色到users.role(列已为VARCHAR,直接可用)
+        // 当前role列为VARCHAR(20),无需ALTER ENUM
+
+        // 迁移40: users表添加mascot字段(AI助手形象: xibao浠宝/fubao福宝)
+        ensureColumn("users", "mascot", "VARCHAR(20) DEFAULT NULL COMMENT 'AI助手形象: xibao(浠宝)/fubao(福宝)'");
+
+        // 迁移41: 创建family_relationships表(家庭成员双向关系,替代relationship_type/role_override)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_relationships (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '所属家庭ID', " +
+                    "from_member_id BIGINT NOT NULL COMMENT '关系起点成员ID', " +
+                    "to_member_id BIGINT NOT NULL COMMENT '关系终点成员ID', " +
+                    "generation_value INT NOT NULL COMMENT '辈分值: -1=子女, 0=同辈, 1=父母, 2=祖父母, 3=曾祖父母', " +
+                    "relationship_key VARCHAR(50) COMMENT '关系标签key(冗余用于显示)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_from_to (from_member_id, to_member_id), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_from (from_member_id), " +
+                    "INDEX idx_to (to_member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员双向关系表'");
+            log.info("已创建family_relationships表");
+        } catch (Exception e) {
+            log.warn("创建family_relationships表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移42: 创建butler_sessions表(AI管家会话管理)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS butler_sessions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "title VARCHAR(200) DEFAULT '新会话' COMMENT '会话标题', " +
+                    "conversation_id VARCHAR(100) COMMENT 'Dify对话ID', " +
+                    "context TEXT COMMENT '上下文信息(JSON)', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active(进行中)/archived(已归档)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_user_status (user_id, status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI管家会话表'");
+            log.info("已创建butler_sessions表");
+        } catch (Exception e) {
+            log.warn("创建butler_sessions表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移 79: 创建 tianpan_daily_fortune 表(家庭每日运势)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS tianpan_daily_fortune (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "date VARCHAR(20) NOT NULL COMMENT '日期 YYYY-MM-DD', " +
+                    "lunar_date VARCHAR(50) COMMENT '农历日期', " +
+                    "day_ganzhi VARCHAR(10) COMMENT '日柱', " +
+                    "five_elements_main VARCHAR(20) COMMENT '当日五行主气', " +
+                    "five_elements_detail VARCHAR(100) COMMENT '五行详情', " +
+                    "auspicious TEXT COMMENT '宜做的事(JSON数组)', " +
+                    "inauspicious TEXT COMMENT '忌做的事(JSON数组)', " +
+                    "family_advice TEXT COMMENT '家庭建议', " +
+                    "focus_member_name VARCHAR(50) COMMENT '关注成员', " +
+                    "focus_member_tip TEXT COMMENT '成员提示', " +
+                    "wuxing_tip TEXT COMMENT '五行提示', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_family_date (family_id, date), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_date (date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭每日运势表'");
+            log.info("已创建 tianpan_daily_fortune 表");
+        } catch (Exception e) {
+            log.warn("创建 tianpan_daily_fortune 表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移 80: 创建 tianpan_member_annual_energy 表(成员年度能量)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS tianpan_member_annual_energy (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '成员ID', " +
+                    "year INT NOT NULL COMMENT '年份', " +
+                    "dimensions_body INT DEFAULT 0 COMMENT '身能量', " +
+                    "dimensions_mind INT DEFAULT 0 COMMENT '心能量', " +
+                    "dimensions_wisdom INT DEFAULT 0 COMMENT '智能量', " +
+                    "dimensions_action INT DEFAULT 0 COMMENT '行能量', " +
+                    "dimensions_wealth INT DEFAULT 0 COMMENT '富能量', " +
+                    "jupiter_phase VARCHAR(50) COMMENT '木星相位', " +
+                    "saturn_phase VARCHAR(50) COMMENT '土星相位', " +
+                    "highlights TEXT COMMENT '年度亮点(JSON数组)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_member_year (member_id, year), " +
+                    "INDEX idx_member_id (member_id), " +
+                    "INDEX idx_year (year)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成员年度能量表'");
+            log.info("已创建 tianpan_member_annual_energy 表");
+        } catch (Exception e) {
+            log.warn("创建 tianpan_member_annual_energy 表可能已存在: {}", e.getMessage());
+        }
+    }
+
+    private void insertSysConfigSeed(String key, String value, String desc) {
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM sys_config WHERE config_key = ?", Integer.class, key);
+        if (count == null || count == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO sys_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, NOW())",
+                    key, value, desc);
+        }
+    }
+
+    private void initFoodTables() {
+        log.info("开始初始化食品相关表...");
+
+        // 4张新表 (CREATE TABLE IF NOT EXISTS)
+        String[] ddl = {
+"CREATE TABLE IF NOT EXISTS foods ("
+            + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+            + "name VARCHAR(100) NOT NULL COMMENT '食材名称', "
+            + "category VARCHAR(50) COMMENT '分类: 蔬菜/水果/肉禽/水产/蛋奶/豆类/谷物/调味/其他', "
+            + "unit VARCHAR(20) DEFAULT 'g' COMMENT '计量单位', "
+            + "calories DECIMAL(8,2) COMMENT '热量(kcal/100g)', "
+            + "protein DECIMAL(8,2) COMMENT '蛋白质(g/100g)', "
+            + "fat DECIMAL(8,2) COMMENT '脂肪(g/100g)', "
+            + "carbs DECIMAL(8,2) COMMENT '碳水(g/100g)', "
+            + "fiber DECIMAL(8,2) COMMENT '膳食纤维(g/100g)', "
+            + "energy_kj DECIMAL(8,2) COMMENT '能量(kJ/100g)', "
+            + "starch DECIMAL(8,2) COMMENT '淀粉(g/100g)', "
+            + "cholesterol DECIMAL(8,2) COMMENT '胆固醇(mg/100g)', "
+            + "price_level TINYINT COMMENT '价格等级 1-5', "
+            + "nutrition_tags VARCHAR(500) COMMENT '营养标签JSON', "
+            + "suitable_for VARCHAR(200) COMMENT '适宜人群JSON', "
+            + "allergens VARCHAR(200) COMMENT '过敏原JSON', "
+            + "external_source VARCHAR(100) COMMENT '数据来源', "
+            + "external_id VARCHAR(100) COMMENT '外部ID', "
+            + "cover_image VARCHAR(500) COMMENT '食材图片', "
+            + "report_id BIGINT COMMENT '关联报告ID(从PDF解析的食物推荐)', "
+            + "score INT COMMENT '食物评分', "
+            + "diet_type VARCHAR(50) COMMENT '饮食类型', "
+            + "status VARCHAR(20) DEFAULT 'active', "
+            + "sort_order INT DEFAULT 0, "
+            + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+            + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+            + "INDEX idx_category (category), "
+            + "INDEX idx_price (price_level)"
+            + ");",
+
+                "CREATE TABLE IF NOT EXISTS recipes ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "name VARCHAR(200) NOT NULL COMMENT '食谱名称', "
+                        + "meal_type VARCHAR(20) COMMENT '早餐/午餐/晚餐/加餐', "
+                        + "description TEXT COMMENT '简介', "
+                        + "ingredients TEXT NOT NULL COMMENT '食材清单JSON', "
+                        + "steps TEXT COMMENT '烹饪步骤JSON', "
+                        + "cook_time INT COMMENT '烹饪时间(分钟)', "
+                        + "difficulty VARCHAR(20) COMMENT '难度: 简单/中等/困难', "
+                        + "total_calories DECIMAL(8,2) COMMENT '总热量', "
+                        + "total_protein DECIMAL(8,2) COMMENT '总蛋白质', "
+                        + "total_fat DECIMAL(8,2) COMMENT '总脂肪', "
+                        + "total_carbs DECIMAL(8,2) COMMENT '总碳水', "
+                        + "total_fiber DECIMAL(8,2) COMMENT '总膳食纤维', "
+                        + "suitable_for VARCHAR(200) COMMENT '适宜场景', "
+                        + "nutrition_tags VARCHAR(500) COMMENT '营养标签', "
+                        + "external_source VARCHAR(100), "
+                        + "external_id VARCHAR(100), "
+                        + "cover_image VARCHAR(500), "
+                        + "status VARCHAR(20) DEFAULT 'active', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+                        + "INDEX idx_meal_type (meal_type), "
+                        + "INDEX idx_cook_time (cook_time)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS seasonal_foods ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "food_id BIGINT NOT NULL COMMENT '关联foods.id', "
+                        + "month TINYINT NOT NULL COMMENT '月份 1-12', "
+                        + "region VARCHAR(50) DEFAULT '全国' COMMENT '产地', "
+                        + "peak_flag TINYINT DEFAULT 0 COMMENT '盛产标志', "
+                        + "sort_order INT DEFAULT 0, "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "INDEX idx_food_month (food_id, month), "
+                        + "INDEX idx_month (month)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS meal_logs ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "user_id BIGINT NOT NULL COMMENT '用户ID', "
+                        + "child_id BIGINT COMMENT '孩子ID', "
+                        + "meal_type VARCHAR(20) NOT NULL COMMENT '早餐/午餐/晚餐/加餐', "
+                        + "meal_date DATE NOT NULL COMMENT '用餐日期', "
+                        + "meal_time DATETIME COMMENT '具体时间', "
+                        + "foods TEXT NOT NULL COMMENT '食物清单JSON', "
+                        + "total_calories DECIMAL(8,2) COMMENT '总热量', "
+                        + "total_protein DECIMAL(8,2) COMMENT '总蛋白质', "
+                        + "total_fat DECIMAL(8,2) COMMENT '总脂肪', "
+                        + "total_carbs DECIMAL(8,2) COMMENT '总碳水', "
+                        + "total_fiber DECIMAL(8,2) COMMENT '总膳食纤维', "
+                        + "note TEXT COMMENT '备注', "
+                        + "images VARCHAR(1000) COMMENT '图片JSON', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "INDEX idx_user_date (user_id, meal_date), "
+                        + "INDEX idx_child_date (child_id, meal_date)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS report_food_suitability ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "report_id BIGINT NOT NULL COMMENT '健康报告ID', "
+                        + "food_id BIGINT COMMENT '食材ID', "
+                        + "food_name VARCHAR(100) NOT NULL COMMENT '食材名称', "
+                        + "category VARCHAR(50) COMMENT '分类', "
+                        + "score INT NOT NULL COMMENT '适宜度评分', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "INDEX idx_report (report_id), "
+                        + "INDEX idx_food (food_id)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS user_food_preference ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "user_id BIGINT NOT NULL COMMENT '用户ID', "
+                        + "food_id BIGINT COMMENT '食材ID', "
+                        + "food_name VARCHAR(100) NOT NULL COMMENT '食材名称', "
+                        + "score INT NOT NULL DEFAULT 0 COMMENT '评分', "
+                        + "reason VARCHAR(500) COMMENT '原因', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+                        + "INDEX idx_user (user_id)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS food_nutrients ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "food_id BIGINT NOT NULL COMMENT '食材ID', "
+                        + "nutrient_code VARCHAR(20) NOT NULL COMMENT '营养素编码', "
+                        + "amount_per_100g DECIMAL(10,4) NOT NULL COMMENT '每100g含量', "
+                        + "unit VARCHAR(20) NOT NULL COMMENT '单位', "
+                        + "source VARCHAR(50) DEFAULT 'importer' COMMENT '数据来源', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+                        + "UNIQUE KEY uk_food_nutrient (food_id, nutrient_code), "
+                        + "INDEX idx_nutrient (nutrient_code)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS rdi_standards ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "nutrient_code VARCHAR(20) NOT NULL COMMENT '营养素编码', "
+                        + "age_group VARCHAR(10) NOT NULL COMMENT '年龄段', "
+                        + "gender VARCHAR(6) DEFAULT 'all' COMMENT '性别', "
+                        + "rdi_type VARCHAR(10) NOT NULL COMMENT '参考类型', "
+                        + "amount DECIMAL(10,4) NOT NULL COMMENT '推荐量', "
+                        + "unit VARCHAR(20) NOT NULL COMMENT '单位', "
+                        + "source VARCHAR(100) COMMENT '数据来源', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "UNIQUE KEY uk_rdi (nutrient_code, age_group, gender, rdi_type), "
+                        + "INDEX idx_age (age_group)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS shopping_lists ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "user_id BIGINT NOT NULL COMMENT '用户ID', "
+                        + "child_id BIGINT COMMENT '孩子ID', "
+                        + "report_id BIGINT COMMENT '健康报告ID', "
+                        + "title VARCHAR(200) COMMENT '标题', "
+                        + "source VARCHAR(20) NOT NULL COMMENT '来源', "
+                        + "status VARCHAR(20) DEFAULT 'active' COMMENT '状态', "
+                        + "total_items INT DEFAULT 0 COMMENT '总项数', "
+                        + "purchased_items INT DEFAULT 0 COMMENT '已购项数', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+                        + "INDEX idx_user (user_id), "
+                        + "INDEX idx_report (report_id), "
+                        + "INDEX idx_status (status)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS shopping_list_items ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "list_id BIGINT NOT NULL COMMENT '购物清单ID', "
+                        + "food_id BIGINT COMMENT '食材ID', "
+                        + "food_name VARCHAR(100) NOT NULL COMMENT '食材名称', "
+                        + "category VARCHAR(50) COMMENT '分类', "
+                        + "quantity DECIMAL(8,2) DEFAULT 1 COMMENT '数量', "
+                        + "unit VARCHAR(20) DEFAULT '份' COMMENT '单位', "
+                        + "priority VARCHAR(10) DEFAULT 'medium' COMMENT '优先级', "
+                        + "reason VARCHAR(200) COMMENT '推荐原因', "
+                        + "score INT COMMENT '评分', "
+                        + "source VARCHAR(20) COMMENT '来源', "
+                        + "purchased TINYINT DEFAULT 0 COMMENT '是否已购', "
+                        + "purchased_at DATETIME COMMENT '购买时间', "
+                        + "sort_order INT DEFAULT 0 COMMENT '排序', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "INDEX idx_list (list_id), "
+                        + "INDEX idx_purchased (purchased)"
+                        + ");",
+
+                "CREATE TABLE IF NOT EXISTS follow_up ("
+                        + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
+                        + "family_id BIGINT NOT NULL COMMENT '家庭ID', "
+                        + "child_id BIGINT COMMENT '孩子ID', "
+                        + "teacher_id BIGINT COMMENT '规划师ID', "
+                        + "deficiency_id BIGINT COMMENT '缺乏项ID', "
+                        + "message_id BIGINT COMMENT '消息ID', "
+                        + "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态', "
+                        + "priority VARCHAR(10) DEFAULT 'medium' COMMENT '优先级', "
+                        + "description VARCHAR(500) COMMENT '描述', "
+                        + "parent_feedback TEXT COMMENT '家长反馈', "
+                        + "teacher_review VARCHAR(500) COMMENT '规划师点评', "
+                        + "due_date DATETIME COMMENT '截止日期', "
+                        + "completed_at DATETIME COMMENT '完成时间', "
+                        + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
+                        + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
+                        + "INDEX idx_family (family_id), "
+                        + "INDEX idx_teacher (teacher_id), "
+                        + "INDEX idx_status (status), "
+                        + "INDEX idx_due (due_date)"
+                        + ");"
+        };
+
+        for (String sql : ddl) {
+            try {
+                jdbcTemplate.execute(sql);
+            } catch (Exception e) {
+                log.warn("食品表DDL执行失败(可能已存在): {}", e.getMessage());
+            }
+        }
+
+        // 扩展 user_nutrition_profile(如果表不存在则跳过)
+        ensureColumn("user_nutrition_profile", "budget_monthly", "INT COMMENT '月食品预算(元)'");
+        ensureColumn("user_nutrition_profile", "family_taste_preferences", "TEXT COMMENT '家庭成员口味偏好JSON'");
+        ensureColumn("user_nutrition_profile", "cuisine_style", "VARCHAR(100) COMMENT '偏好菜系'");
+        ensureColumn("user_nutrition_profile", "meal_count", "INT DEFAULT 3 COMMENT '每日餐数'");
+        ensureColumn("user_nutrition_profile", "cooking_ability", "VARCHAR(20) DEFAULT 'simple' COMMENT '烹饪能力: simple/medium/advanced'");
+
+        log.info("食品相关表初始化完成");
+    }
+
+    private boolean tableExists(String tableName) {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?",
+                    Integer.class, tableName);
+            return count != null && count > 0;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private boolean columnExists(String table, String column) {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?",
+                    Integer.class, table, column);
+            return count != null && count > 0;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private void ensureColumn(String table, String column, String definition) {
+        try {
+            jdbcTemplate.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
+            log.info("已添加列 {}.{}", table, column);
+        } catch (Exception e) {
+            String msg = e.getMessage();
+            if (msg != null && (msg.contains("Duplicate") || msg.contains("already exists"))) {
+                log.info("列 {}.{} 已存在,跳过", table, column);
+            } else {
+                log.error("添加列 {}.{} 失败:{}", table, column, msg);
+                throw e;
+            }
+        }
+    }
+
+    /** 幂等创建任务模板包,返回 package id */
+    private Long ensureTemplatePackage(String name, String desc, int cycleDays, String category) {
+        Integer cnt = jdbcTemplate.queryForObject(
+            "SELECT COUNT(*) FROM task_template_packages WHERE name = ?", Integer.class, name);
+        if (cnt != null && cnt > 0) {
+            return jdbcTemplate.queryForObject(
+                "SELECT id FROM task_template_packages WHERE name = ? LIMIT 1", Long.class, name);
+        }
+        jdbcTemplate.update(
+            "INSERT INTO task_template_packages (creator_id, name, description, cycle_days, category, is_public, status, created_at, updated_at) " +
+            "VALUES (1, ?, ?, ?, ?, 1, 'published', NOW(), NOW())",
+            name, desc, cycleDays, category);
+        return jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+    }
+
+    /** 幂等写入模板包任务项 */
+    private void ensureTemplateItems(Long packageId, String[][] items) {
+        Integer cnt = jdbcTemplate.queryForObject(
+            "SELECT COUNT(*) FROM task_template_items WHERE package_id = ?", Integer.class, packageId);
+        if (cnt != null && cnt > 0) return;
+        for (String[] it : items) {
+            jdbcTemplate.update(
+                "INSERT INTO task_template_items (package_id, task_name, description, points, frequency, sort_order, created_at, updated_at) " +
+                "VALUES (?, ?, ?, ?, ?, 0, NOW(), NOW())",
+                packageId, it[0], it[1], Integer.valueOf(it[2]), it[3]);
+        }
+    }
+
+    /** 幂等写入方案生成规则 */
+    private void ensurePlanRule(String dimension, int scoreMin, int scoreMax, Long templateId, int priority) {
+        Integer cnt = jdbcTemplate.queryForObject(
+            "SELECT COUNT(*) FROM assessment_plan_rules WHERE dimension = ? AND score_min = ? AND score_max = ?",
+            Integer.class, dimension, scoreMin, scoreMax);
+        if (cnt != null && cnt > 0) return;
+        jdbcTemplate.update(
+            "INSERT INTO assessment_plan_rules (dimension, score_min, score_max, template_id, priority, is_active, created_at, updated_at) " +
+            "VALUES (?, ?, ?, ?, ?, 1, NOW(), NOW())",
+            dimension, scoreMin, scoreMax, templateId, priority);
+    }
+
+    private void insertProductSeed(String name, String desc, String type,
+                                   Integer price,
+                                   String domain, String memberEligible) {
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM products WHERE name = ? AND product_type = ?", Integer.class, name, type);
+        if (count == null || count == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO products (name, description, price, product_type, vendor_id, vendor_name, status, domain, member_eligible, created_at, updated_at) " +
+                            "VALUES (?, ?, ?, ?, NULL, '平台官方', 'on_shelf', ?, ?, NOW(), NOW())",
+                    name, desc, price, type, domain, memberEligible);
+        }
+    }
+
+    private void insertActivitySeed(String title, String desc, String dimensionCode,
+                                    String activityType, String status,
+                                    String startTime, String endTime, String location,
+                                    int maxParticipants, int price, int memberPrice,
+                                    String dimensionWeights) {
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM activities WHERE title = ?", Integer.class, title);
+        if (count == null || count == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO activities (title, description, dimension_code, dimension_weights, activity_type, status, " +
+                            "start_time, end_time, location, max_participants, current_participants, price, member_price, " +
+                            "require_registration, checkin_points, created_at, updated_at) " +
+                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, 1, 10, NOW(), NOW())",
+                    title, desc, dimensionCode, dimensionWeights, activityType, status,
+                    startTime, endTime, location, maxParticipants, price, memberPrice);
+        }
+    }
+
+    private void insertArticleSeed(String title, String summary, String contentType,
+                                   Long categoryId, String dimensionCode, String status,
+                                   String author, String articleType, int readTime, int wordCount,
+                                   String dimensionWeights) {
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM articles WHERE title = ?", Integer.class, title);
+        if (count == null || count == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO articles (title, summary, content, content_type, category_id, " +
+                            "related_dimensions, dimension_weights, status, author, article_type, " +
+                            "read_time, word_count, is_featured, view_count, author_type, audit_status, " +
+                            "visibility, created_at, updated_at) " +
+                            "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, 'admin', 'approved', 'public', NOW(), NOW())",
+                    title, summary, "(此处为示例文章正文内容,实际数据应在运营后台编辑发布。)",
+                    contentType, categoryId, "[\"" + dimensionCode + "\"]", dimensionWeights,
+                    status, author, articleType, readTime, wordCount);
+        }
+    }
+
+    private void insertContentSectionSeed(String pageKey, String sectionKey, String title,
+                                          String allowedRoles, int sortOrder) {
+        Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM content_sections WHERE page_key = ? AND section_key = ?",
+                Integer.class, pageKey, sectionKey);
+        if (count == null || count == 0) {
+            jdbcTemplate.update(
+                    "INSERT INTO content_sections (page_key, section_key, title, allowed_roles, sort_order, status, created_at, updated_at) " +
+                            "VALUES (?, ?, ?, ?, ?, 'active', NOW(), NOW())",
+                    pageKey, sectionKey, title, allowedRoles, sortOrder);
+        }
+    }
+
+    private void runLateMigrations() {
+        // 迁移51: 创建 member_subscription 表(家庭订阅记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_subscription (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "`level` VARCHAR(10) NOT NULL DEFAULT 'L1' COMMENT 'L1=一生一世, L2=久久一生', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active/expired/cancelled', " +
+                    "start_time DATETIME DEFAULT NULL COMMENT '开始时间', " +
+                    "expire_time DATETIME DEFAULT NULL COMMENT '到期时间', " +
+                    "payment_id BIGINT DEFAULT NULL COMMENT '关联订单ID', " +
+                    "auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费(L2)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_expire_time (expire_time)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭订阅记录'");
+            log.info("已创建member_subscription表");
+        } catch (Exception e) {
+            log.warn("创建member_subscription表失败: {}", e.getMessage());
+        }
+
+        // 迁移52: 创建 member_subscription_order 表(订阅订单)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_subscription_order (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "`level` VARCHAR(10) NOT NULL COMMENT 'L1/L2', " +
+                    "amount INT NOT NULL COMMENT '金额(分)', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/cancelled/refunded', " +
+                    "payment_type VARCHAR(20) DEFAULT NULL COMMENT '支付方式 wechat/alipay', " +
+                    "transaction_id VARCHAR(64) DEFAULT NULL COMMENT '微信支付订单号', " +
+                    "order_no VARCHAR(64) NOT NULL COMMENT '订单号', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "paid_at DATETIME DEFAULT NULL, " +
+                    "UNIQUE KEY uk_order_no (order_no), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅订单'");
+            log.info("已创建member_subscription_order表");
+        } catch (Exception e) {
+            log.warn("创建member_subscription_order表失败: {}", e.getMessage());
+        }
+
+        // 迁移53: 创建 subscription_benefit_log 表(权益使用日志)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS subscription_benefit_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "subscription_id BIGINT NOT NULL COMMENT '订阅ID', " +
+                    "benefit_code VARCHAR(50) NOT NULL COMMENT '权益代码', " +
+                    "used_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "detail TEXT COMMENT '使用详情(JSON)', " +
+                    "INDEX idx_subscription_id (subscription_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权益使用日志'");
+            log.info("已创建subscription_benefit_log表");
+        } catch (Exception e) {
+            log.warn("创建subscription_benefit_log表失败: {}", e.getMessage());
+        }
+
+        // 迁移54: 创建 promotion_tier 表(推广等级 R0-R4)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS promotion_tier (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "tier VARCHAR(20) NOT NULL DEFAULT 'R0' COMMENT 'R0利他者/R1传福人/R2聚能师/R3启慧者/R4传承者', " +
+                    "team_size_1st INT DEFAULT 0 COMMENT '一层团队人数', " +
+                    "team_size_2nd INT DEFAULT 0 COMMENT '二层团队人数', " +
+                    "team_size_3rd INT DEFAULT 0 COMMENT '三层团队人数', " +
+                    "total_team_size INT DEFAULT 0 COMMENT '三层累计总人数', " +
+                    "total_referral_earnings INT DEFAULT 0 COMMENT '累计推荐佣金(分)', " +
+                    "total_share_earnings INT DEFAULT 0 COMMENT '累计消费返利(分)', " +
+                    "promoted_at DATETIME DEFAULT NULL COMMENT '最近晋级时间', " +
+                    "last_change_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_id (user_id), " +
+                    "INDEX idx_tier (tier)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广等级'");
+            log.info("已创建promotion_tier表");
+        } catch (Exception e) {
+            log.warn("创建promotion_tier表失败: {}", e.getMessage());
+        }
+
+        // 迁移55: 创建 promotion_tier_change_log 表(推广等级变更记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS promotion_tier_change_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "old_tier VARCHAR(20) DEFAULT NULL COMMENT '变更前等级', " +
+                    "new_tier VARCHAR(20) NOT NULL COMMENT '变更后等级', " +
+                    "reason VARCHAR(50) DEFAULT NULL COMMENT 'qualify升级/demote降级/admin管理员调整', " +
+                    "changed_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广等级变更记录'");
+            log.info("已创建promotion_tier_change_log表");
+        } catch (Exception e) {
+            log.warn("创建promotion_tier_change_log表失败: {}", e.getMessage());
+        }
+
+        // 迁移56: 创建 referral_tree 表(推荐关系树三层)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS referral_tree (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "parent_id BIGINT NOT NULL COMMENT '推荐人ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '被推荐人ID', " +
+                    "level INT NOT NULL COMMENT '层级深度(1/2/3)', " +
+                    "path VARCHAR(255) DEFAULT NULL COMMENT 'materialized path如/1/2/3/', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_child_id (child_id), " +
+                    "INDEX idx_parent_id (parent_id), " +
+                    "INDEX idx_level (level)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐关系树'");
+            log.info("已创建referral_tree表");
+        } catch (Exception e) {
+            log.warn("创建referral_tree表失败: {}", e.getMessage());
+        }
+
+        // 迁移57: 创建 product_ppoint 表(产品P点配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_ppoint (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '产品ID', " +
+                    "ppoint INT NOT NULL DEFAULT 0 COMMENT 'P点值(平台利润分)', " +
+                    "start_date DATE DEFAULT NULL COMMENT '生效日期', " +
+                    "end_date DATE DEFAULT NULL COMMENT '失效日期', " +
+                    "created_by BIGINT DEFAULT NULL COMMENT '创建人', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product_id (product_id), " +
+                    "INDEX idx_dates (start_date, end_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品P点配置'");
+            log.info("已创建product_ppoint表");
+        } catch (Exception e) {
+            log.warn("创建product_ppoint表失败: {}", e.getMessage());
+        }
+
+        // 迁移58: 创建 assessment_products 表(测评商品扩展信息)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_products (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '关联products表', " +
+                    "assessment_type VARCHAR(20) NOT NULL COMMENT 'single=单包, bundle=套餐', " +
+                    "total_sessions INT NOT NULL DEFAULT 1 COMMENT '包含测评次数', " +
+                    "validity_days INT NOT NULL DEFAULT 365 COMMENT '有效期(天)', " +
+                    "guide_scope VARCHAR(50) DEFAULT 'all' COMMENT '规划师范围: all/assigned/auto', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product_id (product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评商品扩展信息'");
+            log.info("已创建assessment_products表");
+        } catch (Exception e) {
+            log.warn("创建assessment_products表失败: {}", e.getMessage());
+        }
+
+        // 迁移59: 创建 assessment_quotas 表(测评额度)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_quotas (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "quota_no VARCHAR(32) NOT NULL COMMENT '额度编号', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "child_id BIGINT COMMENT '指定孩子ID(NULL=不限)', " +
+                    "product_order_id BIGINT NOT NULL COMMENT '来源订单ID', " +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID', " +
+                    "product_name VARCHAR(100) NOT NULL COMMENT '商品名快照', " +
+                    "total_sessions INT NOT NULL DEFAULT 1 COMMENT '总次数', " +
+                    "remaining_sessions INT NOT NULL DEFAULT 1 COMMENT '剩余次数', " +
+                    "validity_start DATETIME NOT NULL COMMENT '有效期开始', " +
+                    "validity_end DATETIME NOT NULL COMMENT '有效期截止', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active/expired/used_all/cancelled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family (family_id), " +
+                    "INDEX idx_order (product_order_id), " +
+                    "UNIQUE KEY uk_quota_no (quota_no)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评额度表'");
+            log.info("已创建assessment_quotas表");
+        } catch (Exception e) {
+            log.warn("创建assessment_quotas表失败: {}", e.getMessage());
+        }
+
+        // 迁移60: 创建 assessment_executions 表(测评执行记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_executions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "quota_id BIGINT NOT NULL COMMENT '关联额度ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '被测评孩子', " +
+                    "guide_id BIGINT COMMENT '执行规划师ID', " +
+                    "assessment_type VARCHAR(30) COMMENT '本次具体测评类型', " +
+                    "appointment_id BIGINT COMMENT '关联预约ID', " +
+                    "result_id BIGINT COMMENT '关联测评结果ID', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/in_progress/completed/cancelled', " +
+                    "scheduled_date DATETIME COMMENT '预约执行时间', " +
+                    "completed_at DATETIME COMMENT '实际完成时间', " +
+                    "notes VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_quota (quota_id), " +
+                    "INDEX idx_child (child_id), " +
+                    "INDEX idx_appointment (appointment_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评执行记录表'");
+            log.info("已创建assessment_executions表");
+        } catch (Exception e) {
+            log.warn("创建assessment_executions表失败: {}", e.getMessage());
+        }
+
+        // 迁移61: dan_assessment_results表添加source字段(报告来源)
+        ensureColumn("dan_assessment_results", "source",
+                "VARCHAR(20) DEFAULT 'planner_entry' COMMENT '报告来源: planner_entry/parent_upload/auto_fetch'");
+
+        // 迁移62: 创建 growth_guidances 表(结构化指导意见)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS growth_guidances (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "result_id BIGINT NOT NULL COMMENT '关联测评结果ID', " +
+                    "dimension VARCHAR(30) NOT NULL COMMENT '维度: attention/focus/memory/logic/emotion/general', " +
+                    "score INT COMMENT '该维度得分(0-100)', " +
+                    "suggestion TEXT NOT NULL COMMENT '建议内容', " +
+                    "priority INT DEFAULT 0 COMMENT '优先级(0=普通, 1=重要, 2=紧急)', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_result (result_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评指导意见(结构化)'");
+            log.info("已创建growth_guidances表");
+        } catch (Exception e) {
+            log.warn("创建growth_guidances表失败: {}", e.getMessage());
+        }
+
+        // 迁移63: 创建 assessment_plan_rules 表(方案生成规则)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_plan_rules (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "dimension VARCHAR(30) NOT NULL COMMENT '测评维度: attention/focus/memory/logic/emotion', " +
+                    "score_min INT NOT NULL COMMENT '分数下限(含)', " +
+                    "score_max INT NOT NULL COMMENT '分数上限(含)', " +
+                    "template_id BIGINT NOT NULL COMMENT '关联TaskTemplatePackage ID', " +
+                    "priority INT DEFAULT 0 COMMENT '优先级', " +
+                    "is_active TINYINT(1) DEFAULT 1 COMMENT '是否启用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_dimension (dimension), " +
+                    "INDEX idx_score (score_min, score_max)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='方案生成规则'");
+            log.info("已创建assessment_plan_rules表");
+        } catch (Exception e) {
+            log.warn("创建assessment_plan_rules表失败: {}", e.getMessage());
+        }
+
+        // 迁移64: task_plan_instances表添加source字段(方案来源)
+        ensureColumn("task_plan_instances", "source",
+                "VARCHAR(20) DEFAULT 'manual' COMMENT '方案来源: manual/assessment/template'");
+
+        // 迁移65: task_plan_instances表添加source_result_id和remark字段
+        ensureColumn("task_plan_instances", "source_result_id",
+                "BIGINT COMMENT '关联测评结果ID(来源为assessment时)'");
+        ensureColumn("task_plan_instances", "remark",
+                "TEXT COMMENT '方案备注(含指导意见摘要)'");
+
+        // 迁移66: task_plan_instances表添加审核确认字段(阶段五)
+        ensureColumn("task_plan_instances", "reviewed_by",
+                "BIGINT COMMENT '审核人(规划师ID)'");
+        ensureColumn("task_plan_instances", "reviewed_at",
+                "DATETIME COMMENT '审核时间'");
+        ensureColumn("task_plan_instances", "review_comment",
+                "VARCHAR(500) COMMENT '审核意见'");
+        ensureColumn("task_plan_instances", "confirmed_by",
+                "BIGINT COMMENT '确认人(家长ID)'");
+        ensureColumn("task_plan_instances", "confirmed_at",
+                "DATETIME COMMENT '确认时间'");
+        ensureColumn("task_plan_instances", "activated_at",
+                "DATETIME COMMENT '激活时间'");
+
+        // 迁移67: family_access_logs 表(G7 familyId 越权访问日志,可选,用于安全审计)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_access_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '访问方用户ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '被访问的家庭ID', " +
+                    "request_uri VARCHAR(500) NOT NULL COMMENT '请求路径', " +
+                    "request_method VARCHAR(10) NOT NULL, " +
+                    "access_type VARCHAR(20) NOT NULL COMMENT 'check_type: query/path/body', " +
+                    "result VARCHAR(10) NOT NULL COMMENT 'allowed/denied/log_only', " +
+                    "client_ip VARCHAR(50), " +
+                    "user_agent VARCHAR(500), " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_time (user_id, created_at), " +
+                    "INDEX idx_family_time (family_id, created_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭访问安全日志'");
+            log.info("已创建family_access_logs表");
+        } catch (Exception e) {
+            log.warn("创建family_access_logs表失败: {}", e.getMessage());
+        }
+
+        // 迁移68: 创建 five_dimension_scores 表(G1 五维能量分数持久化,记录每次计算的维度分数快照)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS five_dimension_scores (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '成员ID(user或family_member)', " +
+                    "member_type VARCHAR(20) NOT NULL COMMENT '成员类型: user/member', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "dimension_code VARCHAR(20) NOT NULL COMMENT '维度编码: body/mind/wisdom/action/wealth', " +
+                    "score INT NOT NULL COMMENT '分数(0-100)', " +
+                    "assessed_at DATETIME NOT NULL COMMENT '评估时间', " +
+                    "source_type VARCHAR(50) COMMENT '来源类型: periodic/task/assessment/manual', " +
+                    "source_id BIGINT COMMENT '来源记录ID', " +
+                    "detail_json TEXT COMMENT '详细数据JSON', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_member_dim (member_id, member_type, dimension_code), " +
+                    "INDEX idx_family_time (family_id, assessed_at), " +
+                    "INDEX idx_source (source_type, source_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维能量分数历史记录'");
+            log.info("已创建five_dimension_scores表");
+            // 迁移69: 创建 promotion_tier_config 表(推广等级R0-R4阈值配置)
+            try {
+                jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS promotion_tier_config (" +
+                        "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                        "tier_code VARCHAR(10) NOT NULL UNIQUE COMMENT '等级编码: R0/R1/R2/R3/R4', " +
+                        "tier_name VARCHAR(50) NOT NULL COMMENT '等级名称', " +
+                        "min_team_size_1st INT NOT NULL DEFAULT 0 COMMENT '一级团队人数下限', " +
+                        "min_team_size_2nd INT NOT NULL DEFAULT 0 COMMENT '二级团队人数下限', " +
+                        "min_total_referral_earnings INT NOT NULL DEFAULT 0 COMMENT '累计推荐佣金下限(分)', " +
+                        "min_total_share_earnings INT NOT NULL DEFAULT 0 COMMENT '累计消费返利下限(分)', " +
+                        "commission_rate_l1 INT NOT NULL DEFAULT 0 COMMENT 'L1佣金比例(%)', " +
+                        "commission_rate_l2 INT NOT NULL DEFAULT 0 COMMENT 'L2佣金比例(%)', " +
+                        "sort_order INT NOT NULL DEFAULT 0 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("已创建promotion_tier_config表");
+            } catch (Exception e) {
+                log.warn("创建promotion_tier_config表失败: {}", e.getMessage());
+            }
+
+            try {
+                Integer configCount = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM promotion_tier_config", Integer.class);
+                if (configCount == null || configCount == 0) {
+                    jdbcTemplate.execute("INSERT INTO promotion_tier_config (tier_code, tier_name, min_team_size_1st, min_team_size_2nd, min_total_referral_earnings, min_total_share_earnings, commission_rate_l1, commission_rate_l2, sort_order) VALUES " +
+                            "('R0', '初始用户', 0, 0, 0, 0, 0, 0, 0), " +
+                            "('R1', '推广达人', 3, 0, 0, 0, 20, 5, 1), " +
+                            "('R2', '高级推广', 10, 5, 50000, 20000, 25, 8, 2), " +
+                            "('R3', '资深推广', 30, 20, 200000, 80000, 30, 10, 3), " +
+                            "('R4', '首席推广', 100, 50, 500000, 200000, 35, 15, 4)");
+                    log.info("R0-R4推广等级阈值种子数据已初始化");
+                }
+            } catch (Exception e) {
+                log.warn("初始化推广等级阈值种子数据失败: {}", e.getMessage());
+            }
+
+        } catch (Exception e) {
+            log.warn("创建five_dimension_scores表失败: {}", e.getMessage());
+        }
+
+        // 迁移70: package_orders表添加user_coupon_id列
+        try {
+            jdbcTemplate.execute("ALTER TABLE package_orders ADD COLUMN user_coupon_id BIGINT COMMENT '使用的用户优惠券ID'");
+            log.info("已添加user_coupon_id列到package_orders表");
+        } catch (Exception e) {
+            log.warn("添加user_coupon_id列失败(可能已存在): {}", e.getMessage());
+        }
+
+        // 迁移71: family_invitations表添加max_use_count和used_count列(G9 邀请次数控制)
+        ensureColumn("family_invitations", "max_use_count", "INT DEFAULT 1 COMMENT '最大使用次数'");
+        ensureColumn("family_invitations", "used_count", "INT DEFAULT 0 COMMENT '已使用次数'");
+
+        // 迁移72: 创建teacher_family_binding_requests表(G10 规划师绑定家庭审批)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS teacher_family_binding_requests (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "teacher_id BIGINT NOT NULL COMMENT '规划师用户ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "invitation_token VARCHAR(128) COMMENT '关联邀请码/令牌', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/approved/rejected/cancelled', " +
+                    "requested_at DATETIME COMMENT '申请时间', " +
+                    "decided_by BIGINT COMMENT '审批人用户ID', " +
+                    "decided_at DATETIME COMMENT '审批时间', " +
+                    "reject_reason VARCHAR(200) COMMENT '拒绝原因', " +
+                    "message VARCHAR(500) COMMENT '申请留言', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_teacher_family (teacher_id, family_id), " +
+                    "INDEX idx_family_status (family_id, status) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规划师绑定家庭申请表'");
+            log.info("已创建teacher_family_binding_requests表");
+        } catch (Exception e) {
+            log.warn("创建teacher_family_binding_requests表失败: {}", e.getMessage());
+        }
+
+        // 迁移73: 创建chat_conversations表(G14 Dify会话本地镜像)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS chat_conversations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "dify_conversation_id VARCHAR(64), " +
+                    "assistant_type VARCHAR(32) COMMENT 'family/nutrition/tongue', " +
+                    "title VARCHAR(200), " +
+                    "last_message_at DATETIME, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_dify_conv (dify_conversation_id), " +
+                    "INDEX idx_user (user_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建chat_conversations表");
+        } catch (Exception e) {
+            log.warn("创建chat_conversations表失败: {}", e.getMessage());
+        }
+
+        // 迁移74: 创建chat_messages表(G14 Dify会话本地镜像)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS chat_messages (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "conversation_id BIGINT NOT NULL, " +
+                    "dify_message_id VARCHAR(64), " +
+                    "role VARCHAR(20) NOT NULL COMMENT 'user/assistant', " +
+                    "content TEXT NOT NULL, " +
+                    "inputs_json TEXT, " +
+                    "metadata TEXT, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_conv (conversation_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建chat_messages表");
+        } catch (Exception e) {
+            log.warn("创建chat_messages表失败: {}", e.getMessage());
+        }
+
+        // 迁移75: 创建schema_versions表(G23 迁移版本追踪)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS schema_versions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "version_code VARCHAR(64) NOT NULL UNIQUE, " +
+                    "description VARCHAR(500), " +
+                    "checksum VARCHAR(64), " +
+                    "applied_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "applied_by VARCHAR(100) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='迁移版本追踪表'");
+            log.info("已创建schema_versions表");
+        } catch (Exception e) {
+            log.warn("创建schema_versions表失败: {}", e.getMessage());
+        }
+
+        // 迁移76: DanAssessmentResult添加structured_analysis字段(G3)
+        ensureColumn("dan_assessment_results", "structured_analysis", "TEXT COMMENT '结构化分析JSON'");
+
+
+        // 迁移77: 创建 pending_refund 表(待退款记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS pending_refund (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "withdrawal_request_id BIGINT COMMENT '提现申请ID', " +
+                    "amount INT NOT NULL COMMENT '退款金额(分)', " +
+                    "attempt_count INT DEFAULT 0 COMMENT '当前尝试次数', " +
+                    "max_attempts INT DEFAULT 10 COMMENT '最大重试次数', " +
+                    "next_retry_time DATETIME COMMENT '下次重试时间', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/processing/success/failed', " +
+                    "failure_reason VARCHAR(500) COMMENT '失败原因', " +
+                    "refunded_amount INT COMMENT '实际退款金额(分)', " +
+                    "refunded_at DATETIME COMMENT '退款时间', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_next_retry (next_retry_time), " +
+                    "INDEX idx_withdrawal_request (withdrawal_request_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='待退款记录表'");
+            log.info("已创建pending_refund表");
+        } catch (Exception e) {
+            log.warn("创建pending_refund表失败: {}", e.getMessage());
+        }
+
+        // 迁移78: product_orders添加consignee_id字段(OrderTimeoutCancelTask查询需要)
+        ensureColumn("product_orders", "consignee_id", "BIGINT COMMENT '收货人ID'");
+
+        // 迁移79: pending_refund添加order_type/order_no字段(商品退款走待退款队列)
+        ensureColumn("pending_refund", "order_type", "VARCHAR(20) DEFAULT 'withdrawal' COMMENT '退款类型: withdrawal/product'");
+        ensureColumn("pending_refund", "order_no", "VARCHAR(64) COMMENT '商品订单号'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_type (order_type)");
+        } catch (Exception e) {
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_no (order_no)");
+        } catch (Exception e) {
+        }
+
+        // 迁移80: dan_assessment_results添加dimension字段(区分mind/wisdom报告上传)
+        ensureColumn("dan_assessment_results", "dimension",
+                "VARCHAR(10) DEFAULT 'mind' COMMENT '报告维度: mind(心-A2)/wisdom(智-B4)'");
+        ensureColumn("dan_assessment_results", "report_file_id", "VARCHAR(100) COMMENT '上传文件媒资ID'");
+        ensureColumn("dan_assessment_results", "draft_status",
+                "VARCHAR(10) DEFAULT 'pending' COMMENT '草稿状态: pending(待确认)/confirmed(已确认)'");
+
+        // 迁移81: activities添加dimension_weights JSON列(五维权重百分比)
+        ensureColumn("activities", "dimension_weights",
+                "JSON DEFAULT NULL COMMENT '{\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+        ensureColumn("products", "dimension_weights",
+                "JSON DEFAULT NULL COMMENT '{\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+        ensureColumn("articles", "dimension_weights",
+                "JSON DEFAULT NULL COMMENT '{\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+
+        // 迁移82: 存量数据回填 — 根据dimension_code/domain/relatedDimensions初始化dimension_weights
+        try {
+            // 回填activities: 已有dimension_code的记录,设对应维度为100
+            int actCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM activities WHERE dimension_code IS NOT NULL AND dimension_code != '' AND dimension_weights IS NULL",
+                    Integer.class);
+            if (actCount > 0) {
+                jdbcTemplate.update(
+                        "UPDATE activities SET dimension_weights = " +
+                                "JSON_OBJECT(" +
+                                "  'body', IF(dimension_code='body',100,0)," +
+                                "  'mind', IF(dimension_code='mind',100,0)," +
+                                "  'wisdom', IF(dimension_code='wisdom',100,0)," +
+                                "  'action', IF(dimension_code='action',100,0)," +
+                                "  'wealth', IF(dimension_code='wealth',100,0)" +
+                                ") WHERE dimension_code IS NOT NULL AND dimension_code != '' AND dimension_weights IS NULL");
+                log.info("已回填{}条activities的dimension_weights", actCount);
+            }
+        } catch (Exception e) {
+            log.warn("回填activities.dimension_weights失败: {}", e.getMessage());
+        }
+
+        try {
+            // 回填products: 已有domain的记录,设对应维度为100
+            int prodCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM products WHERE domain IS NOT NULL AND domain != '' AND dimension_weights IS NULL",
+                    Integer.class);
+            if (prodCount > 0) {
+                jdbcTemplate.update(
+                        "UPDATE products SET dimension_weights = " +
+                                "JSON_OBJECT(" +
+                                "  'body', IF(domain='body',100,0)," +
+                                "  'mind', IF(domain='mind',100,0)," +
+                                "  'wisdom', IF(domain='wisdom',100,0)," +
+                                "  'action', IF(domain='action',100,0)," +
+                                "  'wealth', IF(domain='wealth',100,0)" +
+                                ") WHERE domain IS NOT NULL AND domain != '' AND dimension_weights IS NULL");
+                log.info("已回填{}条products的dimension_weights", prodCount);
+            }
+        } catch (Exception e) {
+            log.warn("回填products.dimension_weights失败: {}", e.getMessage());
+        }
+
+        try {
+            // 回填articles: 已有relatedDimensions的记录,尝试解析维度名并设权重
+            int artCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM articles WHERE related_dimensions IS NOT NULL AND related_dimensions != '' AND dimension_weights IS NULL",
+                    Integer.class);
+            if (artCount > 0) {
+                // 用MySQL函数解析relatedDimensions中的维度关键词
+                jdbcTemplate.update("UPDATE articles SET dimension_weights = JSON_OBJECT(" +
+                        "'body', IF(INSTR(COALESCE(related_dimensions,''),'body')>0 OR INSTR(COALESCE(related_dimensions,''),'身')>0, 100, 0)," +
+                        "'mind', IF(INSTR(COALESCE(related_dimensions,''),'mind')>0 OR INSTR(COALESCE(related_dimensions,''),'心')>0, 100, 0)," +
+                        "'wisdom', IF(INSTR(COALESCE(related_dimensions,''),'wisdom')>0 OR INSTR(COALESCE(related_dimensions,''),'智')>0, 100, 0)," +
+                        "'action', IF(INSTR(COALESCE(related_dimensions,''),'action')>0 OR INSTR(COALESCE(related_dimensions,''),'行')>0, 100, 0)," +
+                        "'wealth', IF(INSTR(COALESCE(related_dimensions,''),'wealth')>0 OR INSTR(COALESCE(related_dimensions,''),'富')>0, 100, 0)" +
+                        ") WHERE related_dimensions IS NOT NULL AND related_dimensions != '' AND dimension_weights IS NULL");
+                log.info("已回填{}条articles的dimension_weights", artCount);
+            }
+        } catch (Exception e) {
+            log.warn("回填articles.dimension_weights失败: {}", e.getMessage());
+        }
+
+        // 迁移83: 创建dimension_weights表(管理端保存维度权重使用)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dimension_weights (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "target_type VARCHAR(20) NOT NULL COMMENT '目标类型: activity/product/article'," +
+                    "target_id BIGINT NOT NULL COMMENT '目标ID'," +
+                    "dimension VARCHAR(20) NOT NULL COMMENT '维度: body/mind/wisdom/action/wealth'," +
+                    "weight INT DEFAULT 0 COMMENT '权重百分比'," +
+                    "enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "INDEX idx_target (target_type, target_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度权重表'");
+            log.info("已创建dimension_weights表");
+        } catch (Exception e) {
+            log.warn("创建dimension_weights表失败: {}", e.getMessage());
+        }
+
+// 迁移84: cart表添加skuId和specOptionIds字段(支持SKU选择)
+        ensureColumn("cart", "sku_id", "BIGINT COMMENT '选中的SKU ID'");
+        ensureColumn("cart", "spec_option_ids", "VARCHAR(500) COMMENT '选中的规格选项IDs JSON'");
+
+        // 迁移86: product_orders表添加purchase_info字段(购买信息快照JSON,OrderTimeoutCancelTask查询需要)
+        ensureColumn("product_orders", "purchase_info", "TEXT COMMENT '购买信息JSON: {id_card, hand_signature, ...}'");
+
+        // 迁移87: articles表添加content_type/difficulty_level/dimension_ids/dimension_weights字段
+        ensureColumn("articles", "content_type", "VARCHAR(20) DEFAULT 'text' COMMENT '内容格式:text/html/markdown'");
+        ensureColumn("articles", "difficulty_level", "INT DEFAULT 0 COMMENT '难度等级:1-5'");
+        ensureColumn("articles", "dimension_ids", "VARCHAR(100) DEFAULT '' COMMENT '关联维度IDs JSON'");
+        ensureColumn("articles", "dimension_weights", "TEXT COMMENT '五维能量权重JSON'");
+
+        // 迁移88: activities表添加dimension_weights列(权重感知活动查询)
+        ensureColumn("activities", "dimension_weights", "TEXT COMMENT '五维能量权重JSON'");
+
+        // 迁移89: products表添加dimension_weights列(权重感知商品查询)
+        ensureColumn("products", "dimension_weights", "TEXT COMMENT '五维能量权重JSON'");
+
+        // 迁移85: 创建社交圈子表(珍珠图/圈子系统)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '圈子名称', " +
+                    "type VARCHAR(50) NOT NULL COMMENT '类型: topic/activity/hobby/ability/product/health/provider', " +
+                    "match_source VARCHAR(50) NOT NULL COMMENT '匹配源', " +
+                    "source_id BIGINT COMMENT '匹配源ID', " +
+                    "member_count INT DEFAULT 0 COMMENT '成员数', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='社交圈/珍珠'");
+            log.info("已创建social_circle表");
+        } catch (Exception e) { /* 表已存在忽略 */ }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL, " +
+                    "member_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "member_type VARCHAR(20) NOT NULL COMMENT 'parent/child', " +
+                    "joined_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_circle_member (circle_id, member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子成员'");
+            log.info("已创建social_circle_member表");
+        } catch (Exception e) { /* 表已存在忽略 */ }
+
+        // 迁移96: 创建活动订单评价表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS review_orders (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_id BIGINT NOT NULL COMMENT '订单ID', " +
+                    "reviewer_id BIGINT NOT NULL COMMENT '评价人ID', " +
+                    "rating INT COMMENT '总体评分', " +
+                    "review_text TEXT COMMENT '评价内容', " +
+                    "review_at DATETIME COMMENT '评价时间', " +
+                    "organization_rating INT COMMENT '活动组织评分(0-5)', " +
+                    "content_rating INT COMMENT '活动内容评分(0-5)', " +
+                    "experience_rating INT COMMENT '活动体验评分(0-5)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_order_id (order_id), " +
+                    "INDEX idx_reviewer_id (reviewer_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动订单评价表'");
+            log.info("已创建review_orders表");
+        } catch (Exception e) {
+            log.error("迁移96 失败: " + e.getMessage());
+        }
+
+        // 迁移97: growth_task表扩展 — 支持AI_GENERATED类型 + 五维维度/来源会话字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE growth_task MODIFY COLUMN type "
+                    + "VARCHAR(16) NOT NULL COMMENT 'DAILY/NEWBIE/AI_GENERATED'");
+            log.info("已扩展growth_task.type为支持AI_GENERATED");
+        } catch (Exception e) {
+            log.warn("扩展growth_task.type失败(可能已经扩展): " + e.getMessage());
+        }
+        ensureColumn("growth_task", "dimension", "VARCHAR(20) NULL COMMENT '五维维度(body/mind/wisdom/action/wealth)'");
+        ensureColumn("growth_task", "source_conversation_id", "VARCHAR(100) NULL COMMENT '来源AI会话ID'");
+        log.info("迁移97: growth_task 表扩展完成");
+
+        // 迁移98: health_gut_flora 表扩展 level 字段长度(支持 CLASS/ORDER/FAMILY)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora MODIFY COLUMN level VARCHAR(20) COMMENT '级别: CLASS/ORDER/FAMILY/GENUS/SPECIES'");
+            log.info("已扩展health_gut_flora.level字段长度");
+        } catch (Exception e) {
+            log.warn("扩展health_gut_flora.level失败: {}", e.getMessage());
+        }
+        log.info("迁移 98: health_gut_flora 表扩展完成");
+        
+        // 迁移 100: health_gut_flora 表扩展 population_level 和 detection_rate 字段长度(支持更长百分比)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora MODIFY COLUMN population_level VARCHAR(100) DEFAULT NULL COMMENT '处于人群%水平'");
+            log.info("已扩展 health_gut_flora.population_level 字段长度至 VARCHAR(100)");
+        } catch (Exception e) {
+            log.warn("扩展 health_gut_flora.population_level 失败:{}", e.getMessage());
+        }
+        
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora MODIFY COLUMN detection_rate VARCHAR(100) DEFAULT NULL COMMENT '%正常人有检出'");
+            log.info("已扩展 health_gut_flora.detection_rate 字段长度至 VARCHAR(100)");
+        } catch (Exception e) {
+            log.warn("扩展 health_gut_flora.detection_rate 失败:{}", e.getMessage());
+        }
+        log.info("迁移 100: health_gut_flora 表扩展完成");
+
+        // 迁移99: users.role 从 ENUM 改为 VARCHAR(20)(支持动态角色值)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role VARCHAR(20) NOT NULL COMMENT '角色: parent/child/teacher'");
+            log.info("已修改users.role为VARCHAR(20)");
+        } catch (Exception e) {
+            log.warn("修改users.role失败(可能已修改): " + e.getMessage());
+        }
+
+        // 迁移100: assessment_appointments表添加assessor_id(测评师角色分离)
+        ensureColumn("assessment_appointments", "assessor_id",
+                "BIGINT COMMENT '测评师ID(供应商体系), 替代guideId语义'");
+        // 迁移100: dan_assessment_executions表添加appointment_id
+        ensureColumn("dan_assessment_executions", "appointment_id",
+                "BIGINT COMMENT '关联assessment_appointments.id'");
+        log.info("迁移100: 已添加assessor_id和appointment_id字段");
+
+        // 迁移101: 创建 micro_actions 表(12个预定义1秒微行动)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS micro_actions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "code VARCHAR(32) NOT NULL COMMENT '行动编码: DRINK_WATER, DEEP_BREATH, ...', " +
+                    "name VARCHAR(50) NOT NULL COMMENT '行动名称', " +
+                    "icon VARCHAR(10) COMMENT '图标emoji', " +
+                    "description VARCHAR(200) COMMENT '行动描述', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序号', " +
+                    "status TINYINT DEFAULT 1 COMMENT '状态: 1启用 0禁用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_code (code), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微行动定义(12个1秒健康行动)'");
+            log.info("已创建micro_actions表");
+
+            // 插入12个预定义微行动
+            jdbcTemplate.execute("INSERT IGNORE INTO micro_actions (code, name, icon, description, sort_order) VALUES " +
+                    "('DRINK_WATER', '喝一杯水', '☕️', '喝一杯温开水,补充水分', 1), " +
+                    "('DEEP_BREATH', '深呼吸3次', '🫁', '深吸慢呼3次,放松身心', 2), " +
+                    "('STRETCH', '站起来伸懒腰', '🧘', '站起来伸个懒腰,活动筋骨', 3), " +
+                    "('LOOK_FAR', '看远处10秒', '🌿', '看远处绿色植物10秒,缓解眼疲劳', 4), " +
+                    "('SMILE', '微笑一下', '😊', '对自己微笑,心情会变好', 5), " +
+                    "('NECK_ROLL', '转转头颈5圈', '🔄', '缓慢转动头颈5圈,放松颈椎', 6), " +
+                    "('FIST_RELAX', '握拳放松3次', '✊', '用力握拳3秒后放松,重复3次', 7), " +
+                    "('SIP_WATER', '喝口水润喉', '💧', '小口喝水润润喉咙', 8), " +
+                    "('BLINK', '眨眨眼5次', '👁️', '快速眨眼5次,湿润眼球', 9), " +
+                    "('ACUPRESSURE', '按揉合谷穴3下', '🖐️', '按揉虎口合谷穴3下,提神醒脑', 10), " +
+                    "('TIPTOE', '踮脚尖5次', '⬆️', '踮起脚尖再放下,重复5次', 11), " +
+                    "('SHRUG', '耸肩放松3次', '↕️', '耸肩至耳朵再放松,重复3次', 12)");
+            log.info("已初始化12个微行动");
+        } catch (Exception e) {
+            log.warn("创建micro_actions表或初始化数据失败: {}", e.getMessage());
+        }
+
+        // 迁移102: 创建 micro_action_records 表(微行动完成记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS micro_action_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "action_id BIGINT NOT NULL COMMENT '行动ID', " +
+                    "completed_date DATETIME NOT NULL COMMENT '完成日期', " +
+                    "earned_energy INT DEFAULT 1 COMMENT '获得能量值', " +
+                    "note VARCHAR(200) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_date (child_id, completed_date), " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微行动完成记录'");
+            log.info("已创建micro_action_records表");
+        } catch (Exception e) {
+            log.warn("创建micro_action_records表失败: {}", e.getMessage());
+        }
+
+        // 迁移103: 创建 daily_feedback 表(AI/预设每日健康反馈)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS daily_feedback (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "feedback_date DATETIME NOT NULL COMMENT '反馈日期', " +
+                    "feedback_type VARCHAR(20) DEFAULT 'daily' COMMENT '反馈类型: daily/weekly/milestone', " +
+                    "content VARCHAR(500) NOT NULL COMMENT 'AI生成的反馈文本', " +
+                    "milestone_event VARCHAR(100) COMMENT '关联的里程碑事件', " +
+                    "is_read TINYINT DEFAULT 0 COMMENT '是否已读: 0未读 1已读', " +
+                    "source VARCHAR(20) DEFAULT 'preset' COMMENT '来源: ai_generated / preset', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_date (child_id, feedback_date), " +
+                    "INDEX idx_feedback_type (feedback_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日健康反馈'");
+            log.info("已创建daily_feedback表");
+        } catch (Exception e) {
+            log.warn("创建daily_feedback表失败: {}", e.getMessage());
+        }
+
+        // 迁移104: 创建 health_alerts 表(健康行为智能预警)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_alerts (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "rule_code VARCHAR(10) NOT NULL COMMENT '规则编码: R001-R006', " +
+                    "alert_text VARCHAR(200) NOT NULL COMMENT '预警文案', " +
+                    "action_suggestion VARCHAR(200) COMMENT '行动建议', " +
+                    "severity VARCHAR(10) DEFAULT 'medium' COMMENT '严重程度: high/medium/low', " +
+                    "dismissed TINYINT DEFAULT 0 COMMENT '是否已关闭: 0未关闭 1已关闭', " +
+                    "dismissed_at DATETIME COMMENT '关闭时间', " +
+                    "alert_date DATETIME NOT NULL COMMENT '预警日期', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_date (child_id, alert_date), " +
+                    "INDEX idx_rule (rule_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康预警记录'");
+            log.info("已创建health_alerts表");
+        } catch (Exception e) {
+            log.warn("创建health_alerts表失败: {}", e.getMessage());
+        }
+
+        // 迁移105: 创建 health_level 表(身份重塑等级定义)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_level (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "level_num INT NOT NULL COMMENT '等级编号 1-5', " +
+                    "title VARCHAR(50) NOT NULL COMMENT '称号', " +
+                    "badge_icon VARCHAR(20) COMMENT '徽章图标', " +
+                    "min_score INT DEFAULT 0 COMMENT '所需最低健康积分', " +
+                    "max_score INT DEFAULT 99999 COMMENT '最高积分上限'" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康等级定义'");
+            log.info("已创建health_level表");
+        } catch (Exception e) {
+            log.warn("创建health_level表失败: {}", e.getMessage());
+        }
+
+        // 迁移106: 创建 health_timeline_event 表(健康故事时间线)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_timeline_event (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "event_type VARCHAR(30) NOT NULL COMMENT '事件类型: streak_7/level_2/first_report', " +
+                    "event_text VARCHAR(200) NOT NULL COMMENT '事件描述', " +
+                    "ref_id VARCHAR(50) COMMENT '关联ID', " +
+                    "event_date DATETIME NOT NULL COMMENT '事件日期', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_date (child_id, event_date), " +
+                    "INDEX idx_event_type (event_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康时间线事件'");
+            log.info("已创建health_timeline_event表");
+        } catch (Exception e) {
+            log.warn("创建health_timeline_event表失败: {}", e.getMessage());
+        }
+
+        // 迁移107: 创建 family_challenge 表(P2-2家庭挑战)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "challenge_type VARCHAR(50) NOT NULL COMMENT 'full_checkin/sports/health_week', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '挑战标题', " +
+                    "description TEXT COMMENT '挑战描述', " +
+                    "duration_days INT DEFAULT 7 COMMENT '持续天数', " +
+                    "reward_points INT DEFAULT 0 COMMENT '奖励积分', " +
+                    "reward_badge VARCHAR(100) COMMENT '奖励徽章', " +
+                    "start_date DATE COMMENT '开始日期', " +
+                    "end_date DATE COMMENT '结束日期', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT 'active/completed/cancelled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_family_id (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战'");
+            log.info("已创建family_challenge表");
+        } catch (Exception e) {
+            log.warn("创建family_challenge表失败: {}", e.getMessage());
+        }
+
+        // 迁移108: 创建 family_challenge_progress 表(P2-2挑战进度)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge_progress (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "challenge_id BIGINT NOT NULL COMMENT '挑战ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "progress_value INT DEFAULT 0 COMMENT '进度值', " +
+                    "completed TINYINT(1) DEFAULT 0 COMMENT '是否完成', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
+                    "INDEX idx_challenge_id (challenge_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战进度'");
+            log.info("已创建family_challenge_progress表");
+        } catch (Exception e) {
+            log.warn("创建family_challenge_progress表失败: {}", e.getMessage());
+        }
+
+        // 迁移109: 创建 health_interaction 表(P2-2点赞记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_interaction (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "from_user_id BIGINT NOT NULL COMMENT '点赞用户ID', " +
+                    "to_user_id BIGINT NOT NULL COMMENT '被赞用户ID', " +
+                    "action_type VARCHAR(50) NOT NULL COMMENT '行为类型', " +
+                    "action_id BIGINT COMMENT '关联行为记录ID', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_family_action (family_id, action_type, action_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康互动记录(点赞)'");
+            log.info("已创建health_interaction表");
+        } catch (Exception e) {
+            log.warn("创建health_interaction表失败: {}", e.getMessage());
+        }
+
+        // 迁移110: 创建 health_circle 表(P3 健康圈)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_circle (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '圈名称', " +
+                    "description VARCHAR(500) COMMENT '圈描述', " +
+                    "creator_family_id BIGINT COMMENT '创建家庭ID', " +
+                    "invite_code VARCHAR(32) COMMENT '邀请码', " +
+                    "member_limit INT DEFAULT 50 COMMENT '成员上限', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/closed', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_invite_code (invite_code), " +
+                    "INDEX idx_creator (creator_family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康圈'");
+            log.info("已创建health_circle表");
+        } catch (Exception e) {
+            log.warn("创建health_circle表失败: {}", e.getMessage());
+        }
+
+        // 迁移111: 创建 health_circle_member 表(P3 健康圈成员)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_circle_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL COMMENT '圈ID', " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "role VARCHAR(20) DEFAULT 'member' COMMENT '角色 member/admin', " +
+                    "joined_at DATETIME COMMENT '加入时间', " +
+                    "left_at DATETIME COMMENT '退出时间', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/inactive', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_circle_id (circle_id), " +
+                    "INDEX idx_family_id (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康圈成员'");
+            log.info("已创建health_circle_member表");
+        } catch (Exception e) {
+            log.warn("创建health_circle_member表失败: {}", e.getMessage());
+        }
+
+        // 迁移112: 创建 circle_challenge 表(P3 圈挑战)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS circle_challenge (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL COMMENT '圈ID', " +
+                    "challenge_type VARCHAR(50) NOT NULL COMMENT '挑战类型 checkin/step/meditation', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '挑战标题', " +
+                    "description VARCHAR(500) COMMENT '挑战描述', " +
+                    "duration_days INT DEFAULT 7 COMMENT '持续天数', " +
+                    "period VARCHAR(20) DEFAULT 'weekly' COMMENT '周期 weekly/monthly', " +
+                    "start_date DATETIME COMMENT '开始日期', " +
+                    "end_date DATETIME COMMENT '结束日期', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/completed/cancelled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_circle_id (circle_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈挑战'");
+            log.info("已创建circle_challenge表");
+        } catch (Exception e) {
+            log.warn("创建circle_challenge表失败: {}", e.getMessage());
+        }
+
+        // 迁移113: 创建 family_commission 表(P2 推广佣金)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_commission (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "promoter_family_id BIGINT NOT NULL COMMENT '推广方家庭ID', " +
+                    "buyer_family_id BIGINT NOT NULL COMMENT '购买方家庭ID', " +
+                    "order_id BIGINT COMMENT '关联订单ID', " +
+                    "amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '订单金额', " +
+                    "commission_rate DECIMAL(5,2) DEFAULT 10.00 COMMENT '佣金比例%', " +
+                    "commission_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '佣金金额', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/paid/cancelled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "paid_at DATETIME COMMENT '支付时间', " +
+                    "INDEX idx_promoter (promoter_family_id), " +
+                    "INDEX idx_buyer (buyer_family_id), " +
+                    "INDEX idx_order (order_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭推广佣金记录'");
+            log.info("已创建family_commission表");
+        } catch (Exception e) {
+            log.warn("创建family_commission表失败: {}", e.getMessage());
+        }
+
+        // 迁移114: 创建 promotion_binding 表(P2 推广绑定)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS promotion_binding (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "promoter_family_id BIGINT NOT NULL COMMENT '推广方家庭ID', " +
+                    "invitee_family_id BIGINT NOT NULL COMMENT '被邀请方家庭ID', " +
+                    "invite_code VARCHAR(32) COMMENT '使用的邀请码', " +
+                    "binding_type VARCHAR(20) DEFAULT 'direct' COMMENT 'direct/indirect', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT 'active/inactive', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "UNIQUE KEY uk_promoter_invitee (promoter_family_id, invitee_family_id), " +
+                    "INDEX idx_promoter (promoter_family_id), " +
+                    "INDEX idx_invitee (invitee_family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广绑定关系'");
+            log.info("已创建promotion_binding表");
+        } catch (Exception e) {
+            log.warn("创建promotion_binding表失败: {}", e.getMessage());
+        }
+
+        // 迁移115: 创建 activity_feedback 表(活动评价功能)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS activity_feedback (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
+                    "activity_id BIGINT NOT NULL COMMENT '活动ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "child_id BIGINT COMMENT '孩子ID', " +
+                    "ratings TEXT COMMENT '评分JSON: {\"组织形式\":5,\"会场气氛\":4}', " +
+                    "comment TEXT COMMENT '文字评价', " +
+                    "status TINYINT DEFAULT 0 COMMENT '状态: 0=待审核 1=已发布', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_activity_id (activity_id), " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_child_id (child_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动评价表'");
+            log.info("已创建activity_feedback表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移115b: activity表添加feedback_dimensions字段
+        ensureColumn("activities", "feedback_dimensions", "TEXT COMMENT '评价维度列表JSON: [\"组织形式\",\"会场气氛\",\"主持人\",\"专家\"]'");
+
+        // 迁移116: growth_records表添加family_member_id和family_member_age列(修复/record/list 500错误)
+        ensureColumn("growth_records", "family_member_id", "BIGINT COMMENT '关联家庭成员ID'");
+        ensureColumn("growth_records", "family_member_age", "INT COMMENT '成员年龄快照'");
+
+        // 迁移117: 创建 emotion_checkin 表情绪打卡表(Fix 心理与情绪模块 500 错误)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS emotion_checkin (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "mood_weather VARCHAR(50) COMMENT '心情晴雨表: sunny/cloudy/rainy/stormy', " +
+                    "emotion_tags VARCHAR(200) COMMENT '情绪标签(逗号分隔)', " +
+                    "photo_url VARCHAR(500) COMMENT '配图URL', " +
+                    "note TEXT COMMENT '文字备注', " +
+                    "energy_awarded INT DEFAULT 0 COMMENT '奖励能量值', " +
+                    "checkin_date DATE COMMENT '打卡日期', " +
+                    "mood_score INT COMMENT '心情分数(1-10)', " +
+                    "emotion_type VARCHAR(50) COMMENT '情绪类型', " +
+                    "stress_level INT COMMENT '压力等级(1-10)', " +
+                    "arousal_level INT COMMENT '激活水平(1-10)', " +
+                    "energy_level INT COMMENT '精力水平(1-10)', " +
+                    "sleep_hours DECIMAL(4,1) COMMENT '睡眠时长', " +
+                    "sleep_quality INT COMMENT '睡眠质量(1-10)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_date (child_id, checkin_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='情绪打卡记录'");
+            log.info("已创建emotion_checkin表");
+        } catch (Exception e) {
+            log.warn("创建emotion_checkin表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移118: 创建 emotion_alert 表情绪告警表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS emotion_alert (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "child_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "alert_type VARCHAR(50) NOT NULL COMMENT '告警类型: consecutive_low_mood/crisis_keyword/sharp_decline', " +
+                    "severity VARCHAR(20) NOT NULL DEFAULT 'low' COMMENT '严重级别: low/medium/high', " +
+                    "title VARCHAR(200) COMMENT '告警标题', " +
+                    "message TEXT COMMENT '告警具体描述', " +
+                    "related_checkin_id BIGINT COMMENT '关联情绪打卡ID', " +
+                    "resolved TINYINT(1) DEFAULT 0 COMMENT '是否已处理', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "resolved_at DATETIME COMMENT '处理时间', " +
+                    "INDEX idx_child (child_id), " +
+                    "INDEX idx_alert_type (alert_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='情绪告警'");
+            log.info("已创建emotion_alert表");
+        } catch (Exception e) {
+            log.warn("创建emotion_alert表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移119: 创建 membership_levels 表(会员等级配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS membership_levels (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "level_code VARCHAR(50) NOT NULL COMMENT '等级编码: FREE/PROFESSIONAL/ENTERPRISE', " +
+                    "level_name VARCHAR(100) NOT NULL COMMENT '等级名称', " +
+                    "level_desc TEXT COMMENT '等级描述', " +
+                    "price_monthly INT COMMENT '月费(分)', " +
+                    "price_quarterly INT COMMENT '季费(分)', " +
+                    "price_yearly INT COMMENT '年费(分)', " +
+                    "features TEXT COMMENT '功能权限列表(JSON)', " +
+                    "max_children INT DEFAULT 0 COMMENT '可添加孩子数量', " +
+                    "max_tasks_per_day INT DEFAULT 0 COMMENT '每日任务上限', " +
+                    "ai_review_enabled TINYINT(1) DEFAULT 0 COMMENT 'AI审核功能', " +
+                    "priority_support TINYINT(1) DEFAULT 0 COMMENT '优先客服', " +
+                    "teacher_consultation TINYINT(1) DEFAULT 0 COMMENT '老师咨询', " +
+                    "level_order INT DEFAULT 1 COMMENT '级别数字(越小级别越低)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_level_code (level_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级配置'");
+            log.info("已创建membership_levels表");
+        } catch (Exception e) {
+            log.warn("创建membership_levels表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移120: 插入 membership_levels 种子数据
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_levels (level_code, level_name, level_desc, price_monthly, price_quarterly, price_yearly, max_children, max_tasks_per_day, ai_review_enabled, priority_support, teacher_consultation) VALUES " +
+                    "('FREE', '免费会员', '基础免费功能', 0, 0, 0, 2, 5, 0, 0, 0), " +
+                    "('FAMILY', '家庭会员', '家庭会员全部功能', 13140, 32850, 131400, 6, 20, 1, 1, 1), " +
+                    "('PROVIDER', '标准会员', '标准会员服务', 13140, 32850, 131400, 10, 50, 1, 1, 1)");
+            log.info("已插入membership_levels种子数据");
+        } catch (Exception e) {
+            log.warn("插入membership_levels种子数据可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移121: 创建 family_memberships 表(家庭会员订阅记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_memberships (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "level_code VARCHAR(50) NOT NULL DEFAULT 'FREE' COMMENT '会员等级编码', " +
+                    "start_date DATETIME COMMENT '开始时间', " +
+                    "end_date DATETIME COMMENT '结束时间', " +
+                    "payment_method VARCHAR(50) COMMENT '支付方式: wechat/alipay/card', " +
+                    "payment_status VARCHAR(20) DEFAULT 'pending' COMMENT '支付状态: pending/paid/expired/refunded', " +
+                    "order_no VARCHAR(100) COMMENT '订单号', " +
+                    "transaction_id VARCHAR(200) COMMENT '交易流水号', " +
+                    "amount INT COMMENT '支付金额(分)', " +
+                    "auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_end_date (end_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭会员订阅'");
+            log.info("已创建family_memberships表");
+        } catch (Exception e) {
+            log.warn("创建family_memberships表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移121: 创建 share_events 表(分享事件记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS share_events (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '分享用户ID', " +
+                    "target_type VARCHAR(50) NOT NULL COMMENT '分享目标类型: article/growth/product', " +
+                    "target_id BIGINT NOT NULL COMMENT '分享目标ID', " +
+                    "share_channel VARCHAR(50) COMMENT '分享渠道: wechat/timeline/qq/url', " +
+                    "share_title VARCHAR(200) COMMENT '分享标题', " +
+                    "share_url VARCHAR(500) COMMENT '分享URL', " +
+                    "click_count INT DEFAULT 0 COMMENT '点击次数', " +
+                    "read_count INT DEFAULT 0 COMMENT '阅读次数', " +
+                    "reward_points INT DEFAULT 0 COMMENT '奖励积分', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired', " +
+                    "expires_at DATETIME COMMENT '过期时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_target (target_type, target_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分享事件'");
+            log.info("已创建share_events表");
+        } catch (Exception e) {
+            log.warn("创建share_events表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移122: 创建 share_reads 表(分享阅读记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS share_reads (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "share_event_id BIGINT NOT NULL COMMENT '分享事件ID', " +
+                    "reader_id BIGINT COMMENT '阅读者用户ID', " +
+                    "reader_ip VARCHAR(50) COMMENT '阅读者IP', " +
+                    "read_duration INT COMMENT '阅读时长(秒)', " +
+                    "read_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '阅读时间', " +
+                    "INDEX idx_share_event (share_event_id), " +
+                    "INDEX idx_reader (reader_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分享阅读记录'");
+            log.info("已创建share_reads表");
+        } catch (Exception e) {
+            log.warn("创建share_reads表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移123: 创建 food_recommend_idx 表(饮食推荐指数)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS food_recommend_idx (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "food_id BIGINT NOT NULL COMMENT '食材ID', " +
+                    "food_name VARCHAR(100) COMMENT '食材名称', " +
+                    "index_score INT COMMENT '推荐指数(0-100)', " +
+                    "health_report_id BIGINT COMMENT '关联健康报告ID', " +
+                    "calculated_at DATETIME COMMENT '计算时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_food (food_id), " +
+                    "INDEX idx_report (health_report_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食推荐指数'");
+            log.info("已创建food_recommend_idx表");
+        } catch (Exception e) {
+            log.warn("创建food_recommend_idx表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移124: 创建 points_exchange_products 表(积分兑换商品)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS points_exchange_products (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(200) NOT NULL COMMENT '商品名称', " +
+                    "description TEXT COMMENT '商品描述', " +
+                    "cover_image VARCHAR(500) COMMENT '封面图片', " +
+                    "points_price INT NOT NULL COMMENT '积分价格', " +
+                    "stock INT DEFAULT 0 COMMENT '库存', " +
+                    "product_type VARCHAR(50) COMMENT '商品类型', " +
+                    "category VARCHAR(50) COMMENT '分类', " +
+                    "expiry_days INT COMMENT '有效期(天)', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/disabled', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_category (category)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分兑换商品'");
+            log.info("已创建points_exchange_products表");
+        } catch (Exception e) {
+            log.warn("创建points_exchange_products表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移125: 创建 points_exchange_records 表(积分兑换记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS points_exchange_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID', " +
+                    "child_id BIGINT COMMENT '孩子ID', " +
+                    "family_member_id BIGINT COMMENT '家庭成员ID', " +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID', " +
+                    "product_name VARCHAR(200) COMMENT '商品名称快照', " +
+                    "points_cost INT NOT NULL COMMENT '消耗积分', " +
+                    "quantity INT DEFAULT 1 COMMENT '数量', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed/cancelled', " +
+                    "redeem_code VARCHAR(100) COMMENT '兑换码', " +
+                    "expired_at DATETIME COMMENT '过期时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_product (product_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分兑换记录'");
+            log.info("已创建points_exchange_records表");
+        } catch (Exception e) {
+            log.warn("创建points_exchange_records表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移126: emotion_checkin表修复列类型与添加新列
+        ensureColumn("emotion_checkin", "mood_weather", "VARCHAR(20) DEFAULT NULL COMMENT '心情天气: sunny/cloudy/rainy/stormy'");
+        ensureColumn("emotion_checkin", "emotion_tags", "VARCHAR(200) DEFAULT NULL COMMENT '情绪标签,逗号分隔'");
+        ensureColumn("emotion_checkin", "energy_awarded", "INT DEFAULT 0 COMMENT '发放能量值'");
+        ensureColumn("emotion_checkin", "mood_score", "INT DEFAULT NULL COMMENT '心情分数(0-100)'");
+        ensureColumn("emotion_checkin", "stress_level", "INT DEFAULT NULL COMMENT '压力等级(1-5)'");
+        ensureColumn("emotion_checkin", "arousal_level", "INT DEFAULT NULL COMMENT '唤醒等级(1-5)'");
+        ensureColumn("emotion_checkin", "sleep_hours", "DECIMAL(4,1) DEFAULT NULL COMMENT '睡眠时长(小时)'");
+        ensureColumn("emotion_checkin", "sleep_quality", "VARCHAR(20) DEFAULT NULL COMMENT '睡眠质量: good/fair/poor'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE emotion_checkin MODIFY COLUMN user_id BIGINT NULL COMMENT '用户ID(旧字段,可为空)'");
+            log.info("已修改emotion_checkin.user_id为可空");
+        } catch (Exception e) {
+            log.warn("修改emotion_checkin.user_id可能已处理: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE emotion_checkin MODIFY COLUMN emotion_type VARCHAR(50) DEFAULT 'custom' COMMENT '情绪类型'");
+            log.info("已修改emotion_checkin.emotion_type为VARCHAR");
+} catch (Exception e) {
+            log.warn("修改emotion_checkin.emotion_type可能已处理: {}", e.getMessage());
+        }
+
+        ensureColumn("family_fortune", "fortune_level_text", "VARCHAR(50) DEFAULT NULL COMMENT '运势等级文字'");
+        ensureColumn("family_fortune", "fortune_description", "VARCHAR(500) DEFAULT NULL COMMENT '运势描述'");
+        ensureColumn("family_fortune", "mood_emoji", "VARCHAR(20) DEFAULT NULL COMMENT '心情表情'");
+        ensureColumn("family_fortune", "mood_text", "VARCHAR(100) DEFAULT NULL COMMENT '心情描述文字'");
+        ensureColumn("family_fortune", "weekly_tip", "VARCHAR(500) DEFAULT NULL COMMENT '本周建议'");
+
+        // 迁移129: membership_levels表添加price_quarterly列
+        ensureColumn("membership_levels", "price_quarterly", "DECIMAL(10,2) DEFAULT NULL COMMENT '季度价格'");
+
+        // 迁移130: share_events表添加缺失列
+        ensureColumn("share_events", "target_type", "VARCHAR(20) DEFAULT NULL COMMENT '分享目标类型'");
+        ensureColumn("share_events", "share_channel", "VARCHAR(20) DEFAULT NULL COMMENT '分享渠道'");
+        ensureColumn("share_events", "share_title", "VARCHAR(200) DEFAULT NULL COMMENT '分享标题'");
+        ensureColumn("share_events", "share_url", "VARCHAR(500) DEFAULT NULL COMMENT '分享URL'");
+        ensureColumn("share_events", "read_count", "INT DEFAULT 0 COMMENT '阅读次数'");
+        ensureColumn("share_events", "reward_points", "INT DEFAULT 0 COMMENT '奖励积分'");
+        ensureColumn("share_events", "status", "VARCHAR(20) DEFAULT 'active' COMMENT '状态'");
+        ensureColumn("share_events", "expires_at", "DATETIME DEFAULT NULL COMMENT '过期时间'");
+        ensureColumn("share_events", "updated_at", "DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'");
+
+        // 迁移131: share_reads表添加缺失列
+        ensureColumn("share_reads", "read_duration", "INT DEFAULT NULL COMMENT '阅读时长(秒)'");
+        ensureColumn("share_reads", "read_at", "DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '阅读时间'");
+
+        // 迁移132: points_exchange_records表确保family_member_id列
+        ensureColumn("points_exchange_records", "family_member_id", "BIGINT COMMENT '家庭成员ID'");
+
+        // 迁移133: products表列对齐 — rename min_quantity→purchase_count_threshold, add missing columns
+        if (columnExists("products", "min_quantity")) {
+            try {
+                jdbcTemplate.execute("ALTER TABLE products CHANGE COLUMN min_quantity purchase_count_threshold INT DEFAULT 0 COMMENT '最小购买数量限制'");
+                log.info("已重命名products.min_quantity→purchase_count_threshold");
+            } catch (Exception e) {
+                log.warn("重命名products列失败(可能已处理): {}", e.getMessage());
+            }
+        }
+
+        // 迁移134: products表添加sort_order列(商品按排序字段排序展示)
+        ensureColumn("products", "sort_order", "INT DEFAULT 0 COMMENT '排序(值越小越靠前)'");
+        ensureColumn("products", "recommendation_tags", "VARCHAR(500) DEFAULT NULL COMMENT '推荐标签'");
+        ensureColumn("products", "is_recommended", "TINYINT(1) DEFAULT 0 COMMENT '是否推荐商品 0否 1是'");
+        ensureColumn("products", "repurchase_interval_days", "INT DEFAULT 30 COMMENT '复购间隔天数'");
+
+        // 迁移134: 创建 category_ppoint 表(品类P点比例配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS category_ppoint (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "category_id BIGINT NOT NULL COMMENT '商品类目ID', " +
+                    "ratio INT DEFAULT 0 COMMENT 'P点比例(千分比)', " +
+                    "start_date DATETIME COMMENT '生效日期', " +
+                    "end_date DATETIME COMMENT '失效日期(null=永不过期)', " +
+                    "created_by BIGINT COMMENT '创建人', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_category_id (category_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='品类P点比例配置'");
+            log.info("已创建category_ppoint表");
+        } catch (Exception e) {
+            log.warn("创建category_ppoint表失败: {}", e.getMessage());
+        }
+
+        // 迁移135: 创建 commission_dist_log 表(P点分润记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS commission_dist_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "buyer_id BIGINT NOT NULL COMMENT '买家用户ID', " +
+                    "order_id BIGINT COMMENT '订单ID', " +
+                    "product_id BIGINT COMMENT '商品ID', " +
+                    "source_ppoint INT DEFAULT 0 COMMENT '本次产生的原始P点数量', " +
+                    "level1_user_id BIGINT COMMENT '一级推荐人用户ID', " +
+                    "level1_ppoint INT DEFAULT 0 COMMENT '一级推荐人获得P点', " +
+                    "level1_wealth_percent INT DEFAULT 0 COMMENT '一级推荐人当时财富等级(profitSharePercent)', " +
+                    "level2_user_id BIGINT COMMENT '二级推荐人用户ID', " +
+                    "level2_ppoint INT DEFAULT 0 COMMENT '二级推荐人获得P点', " +
+                    "level2_wealth_percent INT DEFAULT 0 COMMENT '二级推荐人当时财富等级(profitSharePercent)', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed/failed', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
+                    "INDEX idx_buyer (buyer_id), " +
+                    "INDEX idx_order (order_id), " +
+                    "INDEX idx_level1 (level1_user_id), " +
+                    "INDEX idx_level2 (level2_user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='P点分润记录'");
+            log.info("已创建commission_dist_log表");
+        } catch (Exception e) {
+            log.warn("创建commission_dist_log表失败: {}", e.getMessage());
+        }
+
+        // 迁移136: payment_orders表添加period列(会员订阅周期)
+        ensureColumn("payment_orders", "period", "VARCHAR(20) DEFAULT 'yearly' COMMENT '订阅周期: monthly/quarterly/yearly'");
+
+        // 迁移137: 扩大payment_orders.payment_type列长度(原列可能为VARCHAR(5),存不下"yearly")
+        try {
+            jdbcTemplate.execute("ALTER TABLE payment_orders MODIFY COLUMN payment_type VARCHAR(20) DEFAULT 'yearly' COMMENT '支付类型: yearly/monthly/trial'");
+            log.info("已修改payment_orders.payment_type列长度为VARCHAR(20)");
+        } catch (Exception e) {
+            log.warn("修改payment_orders.payment_type列长度失败: {}", e.getMessage());
+        }
+
+        // 迁移138: tasks表添加dimension_code和dimension_weights列(原migration102内从未被调用)
+        ensureColumn("tasks", "dimension_code", "VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth'");
+        ensureColumn("tasks", "dimension_weights", "JSON COMMENT '五维权重: {\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+
+        // 迁移139: products表添加family_scope列(推荐系统-家庭消费去重,原migration102内从未被调用)
+        ensureColumn("products", "family_scope", "TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员消费过即排除, 0=仅按个人消费排除'");
+
+        // 迁移140: activities表添加family_scope列(推荐系统-家庭参与去重,原migration102内从未被调用)
+        ensureColumn("activities", "family_scope", "TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员参与过即排除, 0=仅按个人参与排除'");
+
+        // 迁移141: 修正五维权重维度code体系(后台DimensionWeightPicker曾用mind=智/heart=心,标准体系为wisdom=智/mind=心)
+        // 旧体系写入的数据导致小程序端按wisdom/mind查询JSON_EXTRACT匹配不到,新活动显示不出来
+        try {
+            // 1. dimension_weights 表:仅处理含 heart 的目标(旧体系写入的),mind(智)→wisdom,heart(心)→mind
+            int dwHeartCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM dimension_weights WHERE dimension = 'heart'", Integer.class);
+            if (dwHeartCount > 0) {
+                jdbcTemplate.update("UPDATE dimension_weights dw " +
+                        "JOIN (SELECT DISTINCT target_type, target_id FROM dimension_weights WHERE dimension = 'heart') h " +
+                        "ON dw.target_type = h.target_type AND dw.target_id = h.target_id " +
+                        "SET dw.dimension = 'wisdom' WHERE dw.dimension = 'mind'");
+                jdbcTemplate.update("UPDATE dimension_weights SET dimension = 'mind' WHERE dimension = 'heart'");
+                log.info("已修正dimension_weights表维度code: {}条heart记录", dwHeartCount);
+            }
+        } catch (Exception e) {
+            log.warn("修正dimension_weights表维度code失败: {}", e.getMessage());
+        }
+
+        try {
+            // 2. 主表 dimension_weights JSON 列:含 heart 键的记录,mind键→wisdom键,heart键→mind键
+            String[] jsonTables = {"activities", "products", "articles"};
+            for (String table : jsonTables) {
+                int jsonCount = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM " + table + " WHERE dimension_weights IS NOT NULL " +
+                                "AND JSON_CONTAINS_PATH(dimension_weights, 'one', '$.heart')", Integer.class);
+                if (jsonCount > 0) {
+                    jdbcTemplate.update("UPDATE " + table + " SET dimension_weights = JSON_SET(" +
+                            "JSON_REMOVE(JSON_REMOVE(dimension_weights, '$.mind'), '$.heart'), " +
+                            "'$.wisdom', JSON_EXTRACT(dimension_weights, '$.mind'), " +
+                            "'$.mind', JSON_EXTRACT(dimension_weights, '$.heart')) " +
+                            "WHERE dimension_weights IS NOT NULL " +
+                            "AND JSON_CONTAINS_PATH(dimension_weights, 'one', '$.heart')");
+                    log.info("已修正{}表dimension_weights JSON: {}条", table, jsonCount);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("修正主表dimension_weights JSON失败: {}", e.getMessage());
+        }
+
+        // 迁移142: activities表添加报名截止时间与会员/非会员双收费字段(活动升级:报名时间窗口+孩子/家长双收费+会员区分,需求#3#6)
+        ensureColumn("activities", "registration_deadline", "DATETIME COMMENT '报名截止时间(NULL=不限制, 默认=活动开始时间)'");
+        ensureColumn("activities", "child_fee", "INT DEFAULT 0 COMMENT '孩子·非会员价(分)'");
+        ensureColumn("activities", "child_member_fee", "INT DEFAULT 0 COMMENT '孩子·会员价(分), 0=免费'");
+        ensureColumn("activities", "parent_fee", "INT DEFAULT 0 COMMENT '家长·非会员价(分)'");
+        ensureColumn("activities", "parent_member_fee", "INT DEFAULT 0 COMMENT '家长·会员价(分), 0=免费'");
+        ensureColumn("activities", "creator_id", "BIGINT COMMENT '创建人userId(普通用户发布者, admin创建可为空)'");
+
+        // 迁移143: 创建活动报名订单表(活动付费报名:先支付后报名,需求#4)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS activity_orders (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单号(ACT+时间戳+随机)', " +
+                    "activity_id BIGINT NOT NULL COMMENT '活动ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '下单人userId', " +
+                    "total_amount INT NOT NULL COMMENT '实付金额(分)', " +
+                    "registrants_json TEXT COMMENT '报名人快照JSON: [{name, phone, childId, type: child/parent, fee}]', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/cancelled', " +
+                    "transaction_id VARCHAR(64) COMMENT '微信支付单号', " +
+                    "paid_at DATETIME COMMENT '支付时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_activity (activity_id), " +
+                    "INDEX idx_user (user_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动报名订单表'");
+            log.info("已创建activity_orders表");
+        } catch (Exception e) {
+            log.warn("创建activity_orders表失败: {}", e.getMessage());
+        }
+
+        // 迁移144: 创建活动总结资料表(总结中阶段发布人上传资料,需求#9)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS activity_materials (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "activity_id BIGINT NOT NULL COMMENT '活动ID', " +
+                    "title VARCHAR(200) COMMENT '标题', " +
+                    "file_url VARCHAR(500) NOT NULL COMMENT '文件地址(图片/PDF/外链URL)', " +
+                    "file_type VARCHAR(20) DEFAULT 'image' COMMENT '类型: image/pdf/link', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_activity (activity_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动总结资料表'");
+            log.info("已创建activity_materials表");
+        } catch (Exception e) {
+            log.warn("创建activity_materials表失败: {}", e.getMessage());
+        }
+    }
+
+    private void runMigration82() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS ai_conversation_summary (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "conversation_id VARCHAR(100) NOT NULL COMMENT 'Dify会话ID', " +
+                    "summary_text TEXT COMMENT '对话摘要文本', " +
+                    "summary_tokens INT DEFAULT 0 COMMENT '摘要token数', " +
+                    "message_count INT DEFAULT 0 COMMENT '本次对话消息数', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "INDEX idx_conversation (conversation_id), " +
+                    "INDEX idx_user (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI对话摘要'");
+            log.info("已创建ai_conversation_summary表");
+        } catch (Exception e) {
+            log.warn("创建ai_conversation_summary表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS ai_user_facts (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "fact_key VARCHAR(100) NOT NULL COMMENT '事实键', " +
+                    "fact_value VARCHAR(500) COMMENT '事实值', " +
+                    "source_conversation VARCHAR(100) COMMENT '来源会话ID', " +
+                    "confidence DECIMAL(3,2) DEFAULT 1.00 COMMENT '置信度0-1', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
+                    "INDEX idx_user_key (user_id, fact_key), " +
+                    "INDEX idx_conversation (source_conversation)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI用户关键事实'");
+            log.info("已创建ai_user_facts表");
+        } catch (Exception e) {
+            log.warn("创建ai_user_facts表失败: {}", e.getMessage());
+        }
+    }
+
+    private void runMigration86() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_hierarchy (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL COMMENT '供应商体系ID', " +
+                    "parent_id BIGINT NOT NULL COMMENT '上级供应商ID', " +
+                    "child_id BIGINT NOT NULL COMMENT '下级供应商ID', " +
+                    "level INT DEFAULT 1 COMMENT '层级深度', " +
+                    "status VARCHAR(20) DEFAULT 'active', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_system_parent_child (system_id, parent_id, child_id), " +
+                    "KEY idx_system_child (system_id, child_id), " +
+                    "KEY idx_parent (parent_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商上下级关系表'");
+            log.info("已创建supply_hierarchy表");
+        } catch (Exception e) {
+            log.warn("创建supply_hierarchy表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_hierarchy_change_request (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "system_id BIGINT NOT NULL, " +
+                    "applicant_id BIGINT NOT NULL COMMENT '申请人ID', " +
+                    "target_parent_id BIGINT NOT NULL COMMENT '目标上级ID', " +
+                    "status VARCHAR(20) DEFAULT 'pending', " +
+                    "reason VARCHAR(500), " +
+                    "reject_reason VARCHAR(500), " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "reviewed_at DATETIME, " +
+                    "reviewed_by BIGINT, " +
+                    "KEY idx_system_applicant (system_id, applicant_id), " +
+                    "KEY idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商更换上级申请表'");
+            log.info("已创建supply_hierarchy_change_request表");
+        } catch (Exception e) {
+            log.warn("创建supply_hierarchy_change_request表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS butler_profiles (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "tier VARCHAR(10), " +
+                    "status VARCHAR(20) DEFAULT 'pending', " +
+                    "member_count INT DEFAULT 0, " +
+                    "max_members INT DEFAULT 50, " +
+                    "annual_fee_l1 BIGINT, " +
+                    "annual_fee_l2 BIGINT, " +
+                    "description TEXT, " +
+                    "certificates TEXT, " +
+                    "reject_reason VARCHAR(500), " +
+                    "commission_rate_l1 INT DEFAULT 1000, " +
+                    "commission_rate_l2 INT DEFAULT 5000, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "approved_at DATETIME, " +
+                    "UNIQUE KEY uk_user_id (user_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_tier (tier)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家服务提供商档案'");
+            log.info("已创建butler_profiles表");
+        } catch (Exception e) {
+            log.warn("创建butler_profiles表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS butler_service_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "butler_id BIGINT NOT NULL, " +
+                    "member_user_id BIGINT NOT NULL, " +
+                    "subscription_id BIGINT, " +
+                    "service_type VARCHAR(20) NOT NULL, " +
+                    "amount_p BIGINT NOT NULL, " +
+                    "platform_fee_p BIGINT DEFAULT 0, " +
+                    "butler_amount_p BIGINT DEFAULT 0, " +
+                    "member_tier VARCHAR(10), " +
+                    "`year_month` VARCHAR(7), " +
+                    "remark VARCHAR(500), " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_butler_id (butler_id), " +
+                    "INDEX idx_member_user_id (member_user_id), " +
+                    "INDEX idx_year_month (`year_month`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家服务记录'");
+            log.info("已创建butler_service_records表");
+        } catch (Exception e) {
+            log.warn("创建butler_service_records表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS butler_commission_settlements (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "butler_id BIGINT NOT NULL, " +
+                    "`year_month` VARCHAR(7) NOT NULL, " +
+                    "member_count INT DEFAULT 0, " +
+                    "annual_base_amount_p BIGINT DEFAULT 0, " +
+                    "annual_butler_amount_p BIGINT DEFAULT 0, " +
+                    "annual_platform_amount_p BIGINT DEFAULT 0, " +
+                    "per_session_total_p BIGINT DEFAULT 0, " +
+                    "per_session_platform_total_p BIGINT DEFAULT 0, " +
+                    "per_session_butler_total_p BIGINT DEFAULT 0, " +
+                    "total_butler_amount_p BIGINT DEFAULT 0, " +
+                    "status VARCHAR(20) DEFAULT 'pending', " +
+                    "settled_at DATETIME, " +
+                    "settled_by BIGINT, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_butler_month (butler_id, `year_month`), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_year_month (`year_month`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家月度佣金结算单'");
+            log.info("已创建butler_commission_settlements表");
+        } catch (Exception e) {
+            log.warn("创建butler_commission_settlements表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN butler_status VARCHAR(20)");
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN butler_info TEXT");
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN butler_approved_at DATETIME");
+            log.info("已添加 butler 相关列到 users 表");
+        } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
+        }
+
+        // 迁移 86: 创建 butler_assignments 表(会员 - 管家分配记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS butler_assignments (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭 ID', " +
+                    "butler_user_id BIGINT NOT NULL COMMENT '管家用户 ID', " +
+                    "subscription_id BIGINT COMMENT '关联订阅 ID', " +
+                    "level VARCHAR(10) NOT NULL COMMENT '订阅级别 L1/L2', " +
+                    "assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_family_assignment (family_id), " +
+                    "INDEX idx_butler_user_id (butler_user_id), " +
+                    "INDEX idx_subscription_id (subscription_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员 - 管家分配记录'");
+            log.info("已创建 butler_assignments 表");
+        } catch (Exception e) {
+            log.warn("创建 butler_assignments 表失败:{}", e.getMessage());
+        }
+    }
+
+    private void runMigration88() {
+        // 迁移 88: 创建家庭公共账户体系(family_earnings / family_earnings_member / family_earnings_record)
+        // 表 1: family_earnings(家庭总账)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL UNIQUE COMMENT '家庭 ID', " +
+                    "total_earned BIGINT NOT NULL DEFAULT 0 COMMENT '累计推荐收入(分)', " +
+                    "available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '当前可支配余额(分)', " +
+                    "withdrawn_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已提现金额(分)', " +
+                    "distributed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已颁发给成员的金额(分)', " +
+                    "updated_at DATETIME, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭公共账户'");
+            log.info("已创建 family_earnings 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings 表失败:{}", e.getMessage());
+        }
+
+        // 表 2: family_earnings_member(成员已分配余额)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "allocated_amount BIGINT NOT NULL DEFAULT 0 COMMENT '管理员累计分配(分)', " +
+                    "consumed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已消费金额(分)', " +
+                    "available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '可消费余额=allocated-consumed(分)', " +
+                    "can_use_family_balance TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许使用家庭公共池', " +
+                    "updated_at DATETIME, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY idx_family_user (family_id, user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员已分配余额'");
+            log.info("已创建 family_earnings_member 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings_member 表失败:{}", e.getMessage());
+        }
+
+        // 表 3: family_earnings_record(收支流水)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL, " +
+                    "type VARCHAR(20) NOT NULL COMMENT 'earn 收入/withdraw 提现/distribute 颁发/consume 消费', " +
+                    "amount BIGINT NOT NULL COMMENT '金额(分)', " +
+                    "from_user_id BIGINT COMMENT '操作人', " +
+                    "to_user_id BIGINT COMMENT '受益人(颁发/消费时填写)', " +
+                    "note VARCHAR(255) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_type (type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭收益收支流水'");
+            log.info("已创建 family_earnings_record 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings_record 表失败:{}", e.getMessage());
+        }
+
+        // commission_records 加 family_id 列
+        ensureColumn("commission_records", "family_id", "BIGINT COMMENT '归属家庭 ID'");
+
+        // 迁移90: contacts表添加家庭成员关联字段(联系人→家庭成员升级路径)
+        ensureColumn("contacts", "family_member_id", "BIGINT DEFAULT NULL COMMENT '关联的家庭成员ID'");
+        ensureColumn("contacts", "invited_status", "VARCHAR(16) DEFAULT 'none' COMMENT '邀请加入家庭状态: none/invited/accepted/declined'");
+
+        // 迁移91: 创建family_member_logs表(家庭成员变更日志)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_member_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '成员ID', " +
+                    "action VARCHAR(32) NOT NULL COMMENT '操作: join/leave/update/kick', " +
+                    "old_value VARCHAR(500) COMMENT '旧值', " +
+                    "new_value VARCHAR(500) COMMENT '新值', " +
+                    "operator_id BIGINT COMMENT '操作人用户ID', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员变更日志'");
+            log.info("已创建family_member_logs表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移92: 创建health_meal_record表(饮食打卡记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_meal_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "checkin_id BIGINT COMMENT '关联打卡ID', " +
+                    "meal_type VARCHAR(32) COMMENT '餐次: breakfast/lunch/dinner/snack', " +
+                    "food_items TEXT COMMENT '食物清单JSON', " +
+                    "total_calories INT COMMENT '总卡路里', " +
+                    "protein_g INT COMMENT '蛋白质克数', " +
+                    "fat_g INT COMMENT '脂肪克数', " +
+                    "carbs_g INT COMMENT '碳水克数', " +
+                    "fiber_g INT COMMENT '膳食纤维克数', " +
+                    "meal_time DATETIME COMMENT '用餐时间', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食打卡记录'");
+            log.info("已创建health_meal_record表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移93: 创建health_meditation_record表(冥想打卡记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_meditation_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "checkin_id BIGINT COMMENT '关联打卡ID', " +
+                    "duration_minutes INT COMMENT '时长(分钟)', " +
+                    "type VARCHAR(32) COMMENT '类型: mindfulness/guided/breathing/body_scan', " +
+                    "mood VARCHAR(32) COMMENT '冥想后心情', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='冥想打卡记录'");
+            log.info("已创建health_meditation_record表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移94: 创建ecom_supplier表(电商供应商管理)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS ecom_supplier (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '供应商名称', " +
+                    "contact_name VARCHAR(50) COMMENT '联系人', " +
+                    "contact_phone VARCHAR(20) COMMENT '联系电话', " +
+                    "status TINYINT DEFAULT 1 COMMENT '启用状态 1启用 0禁用', " +
+                    "remark VARCHAR(500) 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("已创建ecom_supplier表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移95: 创建rebate_commission_config表(佣金返利配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS rebate_commission_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '配置名称', " +
+                    "rate INT COMMENT '佣金比例(万分比)', " +
+                    "rate_type TINYINT DEFAULT 1 COMMENT '比例类型 1百分比 2固定金额', " +
+                    "min_amount INT COMMENT '最低金额(分)', " +
+                    "max_amount INT COMMENT '最高金额(分)', " +
+                    "remark VARCHAR(500) 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("已创建rebate_commission_config表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+    }
+
+    private void runMigration89() {
+        // 迁移 89: supply_system表添加models字段(AI模型供应商配置)
+        ensureColumn("supply_system", "models", "TEXT COMMENT 'AI模型列表JSON,如[\"agnes-image-2.0-flash\",\"agnes-2.5-flash\"]'");
+        log.info("已添加models列到supply_system表");
+    }
+
+    private void runMigration90() {
+        // 迁移 90: supply_system表添加api_endpoint和api_key字段
+        ensureColumn("supply_system", "api_endpoint", "VARCHAR(512) COMMENT 'API端点'");
+        ensureColumn("supply_system", "api_key", "VARCHAR(256) COMMENT 'API密钥'");
+        log.info("已添加api_endpoint和api_key列到supply_system表");
+    }
+
+    private void runMigration91() {
+        // 迁移 91: users表添加营养师审核字段
+        ensureColumn("users", "nutritionist_status", "VARCHAR(20) DEFAULT NULL COMMENT '营养师审核状态: pending/approved/rejected'");
+        ensureColumn("users", "nutritionist_reject_reason", "VARCHAR(500) DEFAULT NULL COMMENT '营养师审核拒绝原因'");
+        log.info("已添加nutritionist_status和nutritionist_reject_reason列到users表");
+    }
+
+    private void runMigration92() {
+        // 迁移 92: 创建dan_assessment_executions表(DAN测评服务执行记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `dan_assessment_executions` (" +
+                    "`id` BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "`assessment_execution_id` BIGINT NOT NULL COMMENT '执行记录ID', " +
+                    "`assessment_order_id` BIGINT NOT NULL COMMENT '订单ID', " +
+                    "`child_id` BIGINT NOT NULL COMMENT '孩子ID', " +
+                    "`family_id` BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "`quota_id` BIGINT COMMENT '额度ID', " +
+                    "`assessor_id` BIGINT NOT NULL COMMENT '测评师userId', " +
+                    "`assessor_status` ENUM('pending','completed','rejected') DEFAULT 'pending' COMMENT '测评师状态', " +
+                    "`assessor_completed_at` DATETIME COMMENT '测评师完成时间', " +
+                    "`assessor_notes` VARCHAR(200) COMMENT '测评摘要/服务备注', " +
+                    "`planner_id` BIGINT COMMENT '规划师userId', " +
+                    "`planner_status` ENUM('pending','completed','rejected') DEFAULT 'pending' COMMENT '规划师状态', " +
+                    "`planner_completed_at` DATETIME COMMENT '规划师完成时间', " +
+                    "`planner_notes` VARCHAR(200) COMMENT '规划服务备注', " +
+                    "`user_confirmed` TINYINT(1) DEFAULT 0 COMMENT '用户确认:0未确认,1已确认', " +
+                    "`user_confirmed_at` DATETIME COMMENT '用户确认时间', " +
+                    "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "KEY `idx_assessment_execution_id` (`assessment_execution_id`), " +
+                    "KEY `idx_assessment_order_id` (`assessment_order_id`), " +
+                    "KEY `idx_assessor_id` (`assessor_id`), " +
+                    "KEY `idx_planner_id` (`planner_id`), " +
+                    "KEY `idx_child_id` (`child_id`), " +
+                    "KEY `idx_family_id` (`family_id`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DAN测评服务执行记录'");
+            log.info("已创建dan_assessment_executions表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移 92: 创建service_ratings表(服务评分记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `service_ratings` (" +
+                    "`id` BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "`execution_id` BIGINT NOT NULL COMMENT '执行记录ID', " +
+                    "`assessment_order_id` BIGINT NOT NULL COMMENT '订单ID', " +
+                    "`user_id` BIGINT NOT NULL COMMENT '评分用户ID', " +
+                    "`assessor_id` BIGINT NOT NULL COMMENT '测评师userId', " +
+                    "`assessor_rating` TINYINT DEFAULT 0 COMMENT '测评师评分 1-5', " +
+                    "`assessor_comment` VARCHAR(500) COMMENT '评价内容', " +
+                    "`planner_id` BIGINT COMMENT '规划师userId', " +
+                    "`planner_rating` TINYINT COMMENT '规划师评分 1-5', " +
+                    "`planner_comment` VARCHAR(500) COMMENT '评价内容', " +
+                    "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY `uniq_execution_user` (`execution_id`, `user_id`)," +
+                    "KEY `idx_assessor_id` (`assessor_id`)," +
+                    "KEY `idx_planner_id` (`planner_id`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务评分记录'");
+            log.info("已创建service_ratings表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移 92: 创建service_settlements表(服务结算记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `service_settlements` (" +
+                    "`id` BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "`execution_id` BIGINT NOT NULL COMMENT '执行记录ID', " +
+                    "`assessment_order_id` BIGINT NOT NULL COMMENT '订单ID', " +
+                    "`provider_id` BIGINT NOT NULL COMMENT '服务提供方userId', " +
+                    "`provider_type` ENUM('assessor','planner') NOT NULL COMMENT '服务方类型', " +
+                    "`service_fee` BIGINT DEFAULT 0 COMMENT '服务费用(分)', " +
+                    "`commission_rate` INT COMMENT '佣金比例(bps)', " +
+                    "`commission_amount` BIGINT DEFAULT 0 COMMENT '结算金额(分)', " +
+                    "`status` ENUM('pending','settled','failed') DEFAULT 'pending' COMMENT '结算状态', " +
+                    "`settled_at` DATETIME COMMENT '结算时间', " +
+                    "`transaction_id` VARCHAR(100) COMMENT '转账交易ID', " +
+                    "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "KEY `idx_execution_id` (`execution_id`), " +
+                    "KEY `idx_provider_id` (`provider_id`), " +
+                    "KEY `idx_status` (`status`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务结算记录'");
+            log.info("已创建service_settlements表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移 96: product_orders 添加活动provision字段 + activity_provisions 表(活动商品统一管理)
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN activity_id BIGINT DEFAULT NULL COMMENT '关联活动ID(product_type=activity时有效)'");
+            log.info("已添加activity_id列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN provision_status VARCHAR(32) DEFAULT NULL COMMENT '活动provision状态: unprovisioned(待审核)/provisioned(已发货)/signed_in(已签到)/completed(已完成)/cancelled(审核拒绝)'");
+            log.info("已添加provision_status列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN auditor_id BIGINT DEFAULT NULL COMMENT '审核人ID'");
+            log.info("已添加auditor_id列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN audited_at DATETIME DEFAULT NULL COMMENT '审核时间'");
+            log.info("已添加audited_at列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN audit_reason TEXT DEFAULT NULL COMMENT '审核拒绝原因'");
+            log.info("已添加audit_reason列到product_orders表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS activity_provisions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_id BIGINT NOT NULL COMMENT '商品订单ID', " +
+                    "order_no VARCHAR(64) NOT NULL COMMENT '订单编号', " +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID', " +
+                    "activity_id BIGINT NOT NULL COMMENT '活动ID', " +
+                    "buyer_id BIGINT NOT NULL COMMENT '购买者用户ID', " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "child_id BIGINT COMMENT '报名孩子ID(签到时填写)', " +
+                    "status VARCHAR(32) NOT NULL DEFAULT 'unprovisioned' COMMENT 'unprovisioned/provisioned/signed_in/completed/cancelled', " +
+                    "qr_code VARCHAR(512) COMMENT '签到二维码', " +
+                    "checked_in_at DATETIME COMMENT '签到时间', " +
+                    "completed_at DATETIME COMMENT '完成时间', " +
+                    "refund_amount INT COMMENT '退款金额(分)', " +
+                    "refund_at DATETIME COMMENT '退款时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_order (order_id), " +
+                    "INDEX idx_activity (activity_id), " +
+                    "INDEX idx_buyer (buyer_id), " +
+                    "INDEX idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动provision记录表'");
+            log.info("已创建activity_provisions表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移 97: DAN测评服务佣金配置
+        seedDanCommissionConfig();
+
+        // 迁移 98: 创建 health_knowledge_base 表(菌群/营养知识库)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `health_knowledge_base` (" +
+                    "`id` BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "`item_type` VARCHAR(50) NOT NULL COMMENT '类型: bacteria/nutrient/indicator/vitamin/amino_acid/disease', " +
+                    "`item_name` VARCHAR(200) NOT NULL COMMENT '项目名称(精确匹配)', " +
+                    "`category` VARCHAR(100) COMMENT '分类(如 有益菌/有害菌)', " +
+                    "`normal_range` VARCHAR(200) COMMENT '正常范围参考', " +
+                    "`description` TEXT COMMENT '详细说明', " +
+                    "`suggestion` TEXT COMMENT '相关建议', " +
+                    "`source` VARCHAR(100) COMMENT '数据来源', " +
+                    "`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY `uk_item_type_name` (`item_type`, `item_name`), " +
+                    "KEY `idx_item_type` (`item_type`)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康知识库 - 菌属/营养素/指标说明'");
+            log.info("已创建health_knowledge_base表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+    }
+
+    private void seedDanCommissionConfig() {
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO sys_config (config_key, config_value, description, created_at) VALUES " +
+                    "('dan_commission_rate', '10', 'DAN测评服务通用佣金比例(%)', NOW()), " +
+                    "('dan_commission_rate_assessor', '10', 'DAN测评评估师佣金比例(%)', NOW()), " +
+                    "('dan_commission_rate_planner', '10', 'DAN测评规划师佣金比例(%)', NOW())");
+            log.info("已初始化DAN测评佣金配置");
+        } catch (Exception e) {
+            log.warn("DAN佣金配置种子数据可能已存在: " + e.getMessage());
+        }
+
+        // 迁移 98: users 表添加软删除标记(@TableLogic)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN deleted TINYINT DEFAULT 0 COMMENT '软删除标记: 0=未删除, 1=已删除'");
+            log.info("已添加deleted列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移 103: products表添加min_quantity列(商品最小购买数限制)
+        ensureColumn("products", "min_quantity", "INT DEFAULT 1 COMMENT '最小购买数量限制'");
+
+        // 迁移 104: tianpan_daily_fortune表添加缺失字段(FamilyFortune实体对齐)
+        ensureColumn("tianpan_daily_fortune", "lucky_direction", "VARCHAR(20) COMMENT '幸运方向'");
+        ensureColumn("tianpan_daily_fortune", "fortune_level", "VARCHAR(20) COMMENT '运势等级'");
+        ensureColumn("tianpan_daily_fortune", "fortune_level_text", "VARCHAR(100) COMMENT '运势等级描述'");
+        ensureColumn("tianpan_daily_fortune", "fortune_description", "TEXT COMMENT '运势描述'");
+        ensureColumn("tianpan_daily_fortune", "mood_score", "INT DEFAULT 50 COMMENT '心情分数'");
+        ensureColumn("tianpan_daily_fortune", "mood_emoji", "VARCHAR(10) COMMENT '心情emoji'");
+        ensureColumn("tianpan_daily_fortune", "mood_text", "VARCHAR(50) COMMENT '心情文本'");
+        ensureColumn("tianpan_daily_fortune", "weekly_tip", "TEXT COMMENT '每周提示'");
+
+        // 验证迁移104: 确认tianpan_daily_fortune关键列存在
+        try {
+            jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='tianpan_daily_fortune' AND COLUMN_NAME='fortune_level_text'",
+                    Integer.class);
+            log.info("验证通过: tianpan_daily_fortune.fortune_level_text 列存在");
+        } catch (Exception e) {
+            log.error("验证失败: tianpan_daily_fortune.fortune_level_text 列仍然不存在,迁移可能失败: {}", e.getMessage());
+        }
+
+        // 验证迁移103: 确认products.min_quantity列存在
+        try {
+            jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='products' AND COLUMN_NAME='min_quantity'",
+                    Integer.class);
+            log.info("验证通过: products.min_quantity 列存在");
+        } catch (Exception e) {
+            log.error("验证失败: products.min_quantity 列仍然不存在,迁移可能失败: {}", e.getMessage());
+        }
+        // 迁移 105: users表添加article_admin_status和activity_provider_status字段
+        ensureColumn("users", "article_admin_status", "VARCHAR(20) DEFAULT 'pending' COMMENT '文章管理员审核状态'");
+        ensureColumn("users", "activity_provider_status", "VARCHAR(20) DEFAULT 'pending' COMMENT '活动供应商审核状态'");
+    }
+
+    private void runMigration93() {
+        // 迁移 93: 创建 dan_report_uploads 表(DAN PDF报告独立化)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dan_report_uploads (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "family_member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "uploader_user_id BIGINT NOT NULL COMMENT '上传者用户ID', " +
+                    "dimension VARCHAR(16) NOT NULL COMMENT '维度: mind/wisdom', " +
+                    "report_type VARCHAR(16) COMMENT 'A2/B4', " +
+                    "file_url VARCHAR(500) NOT NULL COMMENT 'PDF文件URL', " +
+                    "file_id VARCHAR(500) COMMENT '媒资文件ID或相对路径', " +
+                    "file_name VARCHAR(200) COMMENT '原始文件名', " +
+                    "file_size BIGINT COMMENT '文件大小(字节)', " +
+                    "uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "parsed_items JSON COMMENT '解析的指标项', " +
+                    "parsed_extra JSON COMMENT '解析的额外信息', " +
+                    "parsed_summary TEXT COMMENT '解析的总结文本', " +
+                    "parsed_suggestions TEXT COMMENT '解析的建议文本', " +
+                    "parser_version VARCHAR(32) COMMENT '解析器版本', " +
+                    "parse_status VARCHAR(16) DEFAULT 'pending' COMMENT 'pending/success/failed', " +
+                    "parse_error VARCHAR(500) COMMENT '解析失败原因', " +
+                    "parsed_at DATETIME COMMENT '解析完成时间', " +
+                    "draft_status VARCHAR(16) DEFAULT 'pending' COMMENT 'pending/confirmed/discarded', " +
+                    "confirmed_at DATETIME COMMENT '确认时间', " +
+                    "confirmed_by BIGINT COMMENT '确认操作人用户ID', " +
+                    "edited_items JSON COMMENT '编辑后的指标项', " +
+                    "edited_extra JSON COMMENT '编辑后的额外信息', " +
+                    "edited_summary TEXT COMMENT '编辑后的总结', " +
+                    "edited_suggestions TEXT COMMENT '编辑后的建议', " +
+                    "edit_count INT DEFAULT 0 COMMENT '编辑次数', " +
+                    "linked_result_id BIGINT COMMENT '关联 dan_assessment_results.id', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "deleted_at DATETIME COMMENT '软删除时间', " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_member_id (family_member_id), " +
+                    "INDEX idx_draft_status (draft_status), " +
+                    "INDEX idx_dimension (dimension), " +
+                    "INDEX idx_parse_status (parse_status), " +
+                    "INDEX idx_linked_result (linked_result_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DAN测评PDF报告独立记录'");
+            log.info("已创建dan_report_uploads表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+    }
+
+    private void runMigration94() {
+        // 迁移 94: dan_assessment_results表添加独立化扩展字段(关联新表)
+        ensureColumn("dan_assessment_results", "source_report_id", "BIGINT DEFAULT NULL COMMENT '关联 dan_report_uploads.id'");
+        ensureColumn("dan_assessment_results", "analysis_status", "VARCHAR(16) DEFAULT 'pending' COMMENT '分析状态: pending/confirmed'");
+        log.info("已添加独立化扩展列到dan_assessment_results表");
+    }
+
+    private void runMigration95() {
+        // 迁移 95: 历史 dan_assessment_results 数据 ETL 回填到 dan_report_uploads
+        // 条件: source='parent_upload' AND source_report_id IS NULL(真正的历史遗留数据)
+        // 注意: 新流程的记录通过 confirmUpload 已正确设置 source_report_id,无需处理
+        try {
+            // Step 1: 插入 dan_report_uploads 记录(来自 dan_assessment_results 的历史数据)
+            jdbcTemplate.execute("INSERT IGNORE INTO dan_report_uploads " +
+                    "(family_id, family_member_id, uploader_user_id, dimension, report_type, " +
+                    "file_url, file_id, draft_status, confirmed_at, confirmed_by, " +
+                    "edited_items, edited_summary, edited_suggestions, linked_result_id, " +
+                    "parsed_status, parse_status, created_at, updated_at) " +
+                    "SELECT " +
+                    "  fm.family_id, " +
+                    "  r.family_member_id, " +
+                    "  r.teacher_id, " +
+                    "  r.dimension, " +
+                    "  r.source, " +
+                    "  COALESCE(r.report_file_id, ''), " +
+                    "  r.report_file_id, " +
+                    "  'confirmed', " +
+                    "  r.created_at, " +
+                    "  r.teacher_id, " +
+                    "  r.structured_analysis, " +
+                    "  r.analysis_report, " +
+                    "  r.growth_suggestions, " +
+                    "  r.id, " +
+                    "  'success', " +
+                    "  'confirmed', " +
+                    "  r.created_at, " +
+                    "  r.updated_at " +
+                    "FROM dan_assessment_results r " +
+                    "LEFT JOIN family_members fm ON fm.id = r.family_member_id " +
+                    "WHERE r.source = 'parent_upload' " +
+                    "  AND r.source_report_id IS NULL " +
+                    "  AND r.draft_status = 'confirmed' " +
+                    "  AND NOT EXISTS (SELECT 1 FROM dan_report_uploads u WHERE u.linked_result_id = r.id)"
+            );
+            log.info("迁移95 Step1: 已将历史 dan_assessment_results 回填到 dan_report_uploads");
+        } catch (Exception e) {
+            log.error("迁移95 Step1 失败: " + e.getMessage());
+        }
+
+        // Step 2: 回填 dan_assessment_results.source_report_id(关联新建的 dan_report_uploads 记录)
+        try {
+            jdbcTemplate.execute("UPDATE dan_assessment_results r " +
+                    "INNER JOIN dan_report_uploads u ON u.linked_result_id = r.id " +
+                    "SET r.source_report_id = u.id " +
+                    "WHERE r.source = 'parent_upload' " +
+                    "  AND r.source_report_id IS NULL " +
+                    "  AND r.draft_status = 'confirmed'"
+            );
+            log.info("迁移95 Step2: 已回填 dan_assessment_results.source_report_id");
+        } catch (Exception e) {
+            log.error("迁移95 Step2 失败: " + e.getMessage());
+        }
+    }
+
+    private void runMigration97() {
+        // 迁移93: users表添加roles字段
+        ensureColumn("users", "roles", "VARCHAR(100) COMMENT '角色列表'");
+    }
+
+    private void runMigration96() {
+        // 迁移96: 修复assessment_appointments.status列类型为TINYINT(原库可能为VARCHAR/ENUM导致Data truncated)
+        try {
+            jdbcTemplate.execute("ALTER TABLE assessment_appointments MODIFY COLUMN status TINYINT DEFAULT 0 COMMENT '0=待确认,1=已确认,2=已完成,3=已取消,4=未到'");
+            log.info("已修复assessment_appointments.status列类型为TINYINT");
+        } catch (Exception e) {
+            log.warn("Migration 96 skipped: {}", e.getMessage());
+        }
+    }
+
+    private void runMigration98() {
+        // 迁移98: family_members表新增辈分字段(TASK-P0-001 辈分重构)
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN generation INT COMMENT '辈分值(0=家庭创建者,+n=向上,-n=向下)'");
+            log.info("已添加generation列到family_members表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN is_spouse TINYINT(1) DEFAULT 0 COMMENT '是否配偶(同辈中)'");
+            log.info("已添加is_spouse列到family_members表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移: products表添加min_quantity列(商品最小购买数限制)
+        ensureColumn("products", "min_quantity", "INT DEFAULT 1 COMMENT '最小购买数量限制'");
+    }
+
+    private void runMigration99() {
+        // 迁移99: 旧关系类型数据回填到辈分字段(TASK-P0-001 辈分重构)
+        try {
+            jdbcTemplate.execute("UPDATE family_members SET generation = 0, is_spouse = 1 WHERE relationship_type = 'spouse'");
+            jdbcTemplate.execute("UPDATE family_members SET generation = 1 WHERE relationship_type = 'parent'");
+            jdbcTemplate.execute("UPDATE family_members SET generation = -1 WHERE relationship_type = 'child'");
+            jdbcTemplate.execute("UPDATE family_members SET generation = 0 WHERE relationship_type = 'sibling'");
+            jdbcTemplate.execute("UPDATE family_members SET generation = 0 WHERE relationship_type IS NULL");
+            log.info("迁移99: 已回填generation/is_spouse旧数据");
+        } catch (Exception e) {
+            log.error("迁移99 失败: " + e.getMessage());
+        }
+
+        ensureColumn("products", "min_quantity", "INT DEFAULT 1 COMMENT '最小购买数量限制'");
+
+        ensureColumn("growth_records", "family_member_id", "BIGINT COMMENT '家庭成员ID'");
+        ensureColumn("growth_records", "family_member_age", "INT COMMENT '家庭成员年龄(月)'");
+        ensureColumn("growth_records", "school", "VARCHAR(100) COMMENT '学校名称'");
+        ensureColumn("growth_records", "grade", "INT COMMENT '年级(数字)'");
+        ensureColumn("growth_records", "academic_performance", "VARCHAR(200) COMMENT '成绩/学业表现'");
+        ensureColumn("growth_records", "school_province", "VARCHAR(50) COMMENT '学校所在省'");
+        ensureColumn("growth_records", "school_city", "VARCHAR(50) COMMENT '学校所在市'");
+        ensureColumn("growth_records", "school_district", "VARCHAR(50) COMMENT '学校所在区'");
+        ensureColumn("growth_records", "school_street", "VARCHAR(100) COMMENT '学校所在街道'");
+    }
+
+    private void runMigration100() {
+        // 迁移100: 删除废弃列和关系类型配置表(TASK-P0-001 辈分重构)
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members DROP COLUMN relationship_type");
+            log.info("已删除family_members.relationship_type列");
+        } catch (Exception e) {
+            // 列已不存在,忽略
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members DROP COLUMN role_override");
+            log.info("已删除family_members.role_override列");
+        } catch (Exception e) {
+            // 列已不存在,忽略
+        }
+    }
+
+    // 迁移 101: product_skus 表添加 min_quantity 列(商品规格最小购买数限制)
+    private void runMigration101() {
+        ensureColumn("product_skus", "min_quantity", "INT DEFAULT 1 COMMENT '最小购买数量限制'");
+    }
+    
+    // 迁移 102: health_gut_flora 表扩大 population_level 和 detection_rate 字段长度
+    private void runMigration102() {
+        // 将 VARCHAR(50) 扩大到 TEXT(处理 AI 解析出的长文本)
+        ensureColumn("health_gut_flora", "population_level",
+                "TEXT DEFAULT NULL COMMENT '处于人群%水平'");
+        ensureColumn("health_gut_flora", "detection_rate",
+                "TEXT DEFAULT NULL COMMENT '%正常人有检出'");
+    }
+
+    private void migration102() {
+        // 迁移 102: health_gut_flora 表扩大 population_level 和 detection_rate 字段长度(TEXT)
+        ensureColumn("health_gut_flora", "population_level",
+                "TEXT DEFAULT NULL COMMENT '处于人群%水平'");
+        ensureColumn("health_gut_flora", "detection_rate",
+                "TEXT DEFAULT NULL COMMENT '%正常人有检出'");
+    
+        // 迁移 102: 创建 health_dimension_scores 表(7 维健康模型)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_dimension_scores (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "dimension VARCHAR(20) NOT NULL COMMENT '维度编码', " +
+                    "score INT NOT NULL COMMENT '评分(0-100)', " +
+                    "percentile INT COMMENT '百分位', " +
+                    "data_source VARCHAR(20) COMMENT '数据来源', " +
+                    "tier INT DEFAULT 3 COMMENT '数据可信度层级', " +
+                    "raw_data TEXT COMMENT '原始数据JSON', " +
+                    "assess_date DATE COMMENT '评估日期', " +
+                    "expire_date DATE COMMENT '过期日期', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_dim (member_id, dimension), " +
+                    "INDEX idx_assess_date (assess_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康维度评分'");
+            log.info("已创建health_dimension_scores表");
+        } catch (Exception e) {
+            log.warn("创建health_dimension_scores表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移102b: 创建 health_data_sources 表(多媒体数据源记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_data_sources (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "source_type VARCHAR(20) NOT NULL COMMENT '数据源类型: VOICE/PHOTO/REPORT/MANUAL', " +
+                    "dimension_codes VARCHAR(100) COMMENT '关联维度编码(逗号分隔)', " +
+                    "parsed_result TEXT COMMENT '解析结果JSON', " +
+                    "file_url VARCHAR(500) COMMENT '原始文件URL', " +
+                    "confidence DECIMAL(5,4) COMMENT 'AI解析置信度(0-1)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_source (member_id, source_type), " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康数据源记录'");
+            log.info("已创建health_data_sources表");
+        } catch (Exception e) {
+            log.warn("创建health_data_sources表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移103: health_indicators表添加collection_date列(指标采集日期)
+        ensureColumn("health_indicators", "collection_date", "DATETIME COMMENT '指标采集日期(报告检测日期)'");
+
+        // 迁移104: promotion_tier表确保team_size_1st/2nd/3rd列存在(MyBatis-Plus camelCase映射修正)
+        ensureColumn("promotion_tier", "team_size_1st", "INT DEFAULT 0 COMMENT '一层团队人数'");
+        ensureColumn("promotion_tier", "team_size_2nd", "INT DEFAULT 0 COMMENT '二层团队人数'");
+        ensureColumn("promotion_tier", "team_size_3rd", "INT DEFAULT 0 COMMENT '三层团队人数'");
+
+        // 迁移105: article_reading_records表添加article_id和updated_at列
+        try {
+            jdbcTemplate.execute("ALTER TABLE article_reading_records ADD COLUMN article_id BIGINT DEFAULT NULL COMMENT '文章ID' AFTER id");
+            log.info("已添加article_id列到article_reading_records表");
+        } catch (Exception e) {
+            // 列已存在,忽略
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE article_reading_records ADD COLUMN updated_at DATETIME DEFAULT NULL COMMENT '更新时间' AFTER read_at");
+            log.info("已添加updated_at列到article_reading_records表");
+        } catch (Exception e) {
+            // 列已存在,忽略
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE article_reading_records ADD INDEX idx_article_child_date (article_id, child_id, read_at)");
+            log.info("已添加idx_article_child_date索引");
+        } catch (Exception e) {
+            // 索引已存在,忽略
+        }
+
+        // 迁移106: 创建 report_summary 统一报告汇总表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_summary (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
+                    "report_type VARCHAR(32) NOT NULL COMMENT '报告类型', " +
+                    "source_table VARCHAR(32) NOT NULL COMMENT '来源表', " +
+                    "source_id BIGINT NOT NULL COMMENT '来源表记录ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '创建者用户ID', " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "subject_id BIGINT COMMENT '报告主体ID', " +
+                    "subject_name VARCHAR(50) COMMENT '报告主体名称', " +
+                    "title VARCHAR(200) COMMENT '报告标题', " +
+                    "summary TEXT COMMENT '摘要', " +
+                    "file_url VARCHAR(500) COMMENT '文件URL', " +
+                    "file_size BIGINT COMMENT '文件大小', " +
+                    "file_name VARCHAR(200) COMMENT '文件名', " +
+                    "has_file TINYINT DEFAULT 0 COMMENT '是否有文件', " +
+                    "overall_score INT COMMENT '综合评分', " +
+                    "report_date DATE COMMENT '报告日期', " +
+                    "status VARCHAR(20) COMMENT '状态', " +
+                    "detail_route VARCHAR(100) COMMENT '详情页路由', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_subject_id (subject_id), " +
+                    "INDEX idx_report_type (report_type), " +
+                    "INDEX idx_report_date (report_date), " +
+                    "INDEX idx_status (status), " +
+                    "UNIQUE KEY uk_source (source_table, source_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一报告汇总表'");
+            log.info("已创建 report_summary 表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移107: tasks表添加dimension_code和dimension_weights列(五维维度支持)
+        try {
+            ensureColumn("tasks", "dimension_code", "VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth'");
+            ensureColumn("tasks", "dimension_weights", "JSON COMMENT '五维权重: {\"body\":0,\"mind\":0,\"wisdom\":0,\"action\":0,\"wealth\":0}'");
+            log.info("已完成tasks表维度字段添加");
+        } catch (Exception e) {
+            log.warn("添加tasks表维度字段失败: {}", e.getMessage());
+        }
+
+        // 迁移108: 创建 energy_behavior_config 表(能量行为配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS energy_behavior_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "behavior_code VARCHAR(32) NOT NULL COMMENT '行为编码', " +
+                    "behavior_name VARCHAR(50) NOT NULL COMMENT '行为名称', " +
+                    "description VARCHAR(200) COMMENT '行为描述', " +
+                    "default_amount INT DEFAULT 5 COMMENT '默认能量值', " +
+                    "amount_source VARCHAR(32) DEFAULT 'fixed' COMMENT '能量值来源', " +
+                    "dimension_assign JSON COMMENT '维度分配JSON', " +
+                    "daily_limit INT DEFAULT 0 COMMENT '每日上限', " +
+                    "cooldown_seconds INT DEFAULT 0 COMMENT '冷却时间(秒)', " +
+                    "expire_days INT DEFAULT 0 COMMENT '过期天数', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '是否启用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_behavior_code (behavior_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量行为配置表'");
+            log.info("已创建 energy_behavior_config 表");
+
+            // 插入预置数据(仅当表为空时插入)
+            Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM energy_behavior_config", Integer.class);
+            if (count == null || count == 0) {
+                String[][] seedData = {
+                        {"task_complete", "完成任务", "完成任务获得的能量", "5", "points", "[{\"dim\":\"action\",\"ratio\":100}]", "200", "0", "0"},
+                        {"game_finish", "完成游戏", "完成小游戏获得的能量", "5", "points", "[{\"dim\":\"mind\",\"ratio\":30},{\"dim\":\"wisdom\",\"ratio\":50},{\"dim\":\"action\",\"ratio\":20}]", "200", "0", "0"},
+                        {"activity_checkin", "活动签到", "活动签到获得的能量", "5", "fixed", "[{\"dim\":\"body\",\"ratio\":30},{\"dim\":\"action\",\"ratio\":70}]", "100", "0", "0"},
+                        {"article_read", "阅读文章", "阅读文章获得的能量", "5", "points", "[{\"dim\":\"mind\",\"ratio\":30},{\"dim\":\"wisdom\",\"ratio\":50},{\"dim\":\"action\",\"ratio\":20}]", "100", "0", "0"},
+                        {"product_purchase", "商品购买", "购买商品获得的能量", "10", "order_amount", "[{\"dim\":\"wealth\",\"ratio\":100}]", "500", "0", "30"},
+                        {"health_checkin", "健康打卡", "健康打卡获得的能量", "5", "fixed", "[{\"dim\":\"body\",\"ratio\":100}]", "50", "0", "30"},
+                        {"finance_checkin", "理财打卡", "理财打卡获得的能量", "5", "fixed", "[{\"dim\":\"wealth\",\"ratio\":100}]", "50", "0", "0"},
+                        {"emotion_checkin", "情绪打卡", "情绪打卡获得的能量", "5", "fixed", "[{\"dim\":\"mind\",\"ratio\":100}]", "40", "0", "0"},
+                        {"appointment_submit", "提交测评预约", "提交测评预约获得的能量", "10", "fixed", "[{\"dim\":\"action\",\"ratio\":100}]", "50", "0", "0"},
+                        {"appointment_complete", "完成测评", "完成测评获得的能量", "30", "fixed", "[{\"dim\":\"mind\",\"ratio\":50},{\"dim\":\"wisdom\",\"ratio\":50}]", "100", "0", "0"},
+                        {"streak_daily", "连续打卡每日", "每日连续打卡获得的能量", "5", "fixed", "[{\"dim\":\"body\",\"ratio\":30},{\"dim\":\"mind\",\"ratio\":20},{\"dim\":\"action\",\"ratio\":50}]", "50", "86400", "0"},
+                        {"streak_milestone", "打卡里程碑", "达到打卡里程碑获得的能量", "20", "config", "[{\"dim\":\"body\",\"ratio\":30},{\"dim\":\"mind\",\"ratio\":20},{\"dim\":\"action\",\"ratio\":50}]", "100", "0", "0"},
+                        {"growth_task", "成长任务", "完成每日成长任务获得的能量", "10", "config", "[{\"dim\":\"body\",\"ratio\":20},{\"dim\":\"mind\",\"ratio\":20},{\"dim\":\"wisdom\",\"ratio\":20},{\"dim\":\"action\",\"ratio\":20},{\"dim\":\"wealth\",\"ratio\":20}]", "100", "0", "1"},
+                        {"micro_action", "微行动", "完成微行动获得的能量", "1", "fixed", "[{\"dim\":\"action\",\"ratio\":100}]", "20", "60", "30"},
+                        {"onboarding", "新手引导", "完成新手引导任务获得的能量", "10", "config", "[{\"dim\":\"body\",\"ratio\":20},{\"dim\":\"mind\",\"ratio\":20},{\"dim\":\"wisdom\",\"ratio\":20},{\"dim\":\"action\",\"ratio\":20},{\"dim\":\"wealth\",\"ratio\":20}]", "50", "0", "30"},
+                        {"invite_milestone", "邀请里程碑", "达到邀请人数里程碑获得的能量", "30", "config", "[{\"dim\":\"action\",\"ratio\":40},{\"dim\":\"wealth\",\"ratio\":60}]", "200", "0", "30"},
+                        {"dimension_sync", "维度同步", "健康维度数据上传同步获得的能量", "10", "config", "[{\"dim\":\"body\",\"ratio\":100}]", "100", "0", "30"},
+                        {"invite_friend", "邀请好友加入", "邀请好友注册加入获得的能量", "20", "fixed", "[{\"dim\":\"action\",\"ratio\":50},{\"dim\":\"wealth\",\"ratio\":50}]", "200", "0", "0"},
+                        {"invite_family", "邀请家庭成员", "邀请家庭成员加入家庭获得的能量", "10", "fixed", "[{\"dim\":\"mind\",\"ratio\":30},{\"dim\":\"action\",\"ratio\":70}]", "100", "0", "0"},
+                        {"report_upload", "上传体检报告", "上传体检报告获得的能量", "30", "fixed", "[{\"dim\":\"body\",\"ratio\":100}]", "100", "0", "30"},
+                        {"plan_complete", "完成成长计划", "完成成长计划复盘获得的能量", "20", "fixed", "[{\"dim\":\"body\",\"ratio\":20},{\"dim\":\"mind\",\"ratio\":20},{\"dim\":\"wisdom\",\"ratio\":20},{\"dim\":\"action\",\"ratio\":20},{\"dim\":\"wealth\",\"ratio\":20}]", "100", "0", "0"},
+                        {"wisdom_report", "上传智测评报告", "上传智慧测评报告获得的能量", "30", "fixed", "[{\"dim\":\"wisdom\",\"ratio\":100}]", "100", "0", "30"},
+                };
+                String sql = "INSERT INTO energy_behavior_config (behavior_code, behavior_name, description, default_amount, amount_source, dimension_assign, daily_limit, cooldown_seconds, expire_days, enabled) VALUES (?,?,?,?,?,?,?,?,?,1)";
+                for (String[] row : seedData) {
+                    try {
+                        jdbcTemplate.update(sql, row[0], row[1], row[2], Integer.parseInt(row[3]), row[4], row[5], Integer.parseInt(row[6]), Integer.parseInt(row[7]), Integer.parseInt(row[8]));
+                    } catch (Exception e) {
+                        log.warn("插入能量行为配置失败: behavior_code={}, error={}", row[0], e.getMessage());
+                    }
+                }
+                log.info("已插入{}条能量行为配置", seedData.length);
+            }
+        } catch (Exception e) {
+            log.warn("创建 energy_behavior_config 表失败: {}", e.getMessage());
+        }
+
+        // 迁移109: finance_checkins表添加type列(记账类型: income/expense)
+        try {
+            ensureColumn("finance_checkins", "type", "VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)'");
+            log.info("已完成finance_checkins.type字段添加");
+        } catch (Exception e) {
+            log.warn("添加finance_checkins.type字段失败: {}", e.getMessage());
+        }
+
+        // 迁移110: 创建 article_comments 表(文章评论)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_comments (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "article_id BIGINT NOT NULL COMMENT '文章ID', " +
+                    "parent_id BIGINT COMMENT '父评论ID', " +
+                    "reply_to_id BIGINT COMMENT '引用的评论ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '评论用户ID', " +
+                    "content TEXT NOT NULL COMMENT '评论内容', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected', " +
+                    "audit_by BIGINT COMMENT '审核人ID', " +
+                    "audit_at DATETIME COMMENT '审核时间', " +
+                    "earned_energy INT DEFAULT 0 COMMENT '获得的能量', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_article (article_id), " +
+                    "INDEX idx_article_status (article_id, status), " +
+                    "INDEX idx_parent (parent_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章评论'");
+            log.info("已创建 article_comments 表");
+        } catch (Exception e) {
+            log.warn("创建 article_comments 表失败: {}", e.getMessage());
+        }
+
+        // 迁移111: 创建 service_role_applications 表(服务角色统一申请)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_role_applications (" +
+                    "id int(11) NOT NULL AUTO_INCREMENT, " +
+                    "user_id int(11) NOT NULL COMMENT '申请人ID', " +
+                    "roles varchar(255) NOT NULL COMMENT '申请角色,JSON数组', " +
+                    "id_card varchar(18) DEFAULT '' COMMENT '身份证号', " +
+                    "id_card_images text COMMENT '身份证照片,JSON数组', " +
+                    "certificate_images text COMMENT '资质证书照片,JSON数组', " +
+                    "province varchar(50) DEFAULT '' COMMENT '省', " +
+                    "city varchar(50) DEFAULT '' COMMENT '市', " +
+                    "district varchar(50) DEFAULT '' COMMENT '区', " +
+                    "street varchar(50) DEFAULT '' COMMENT '街道', " +
+                    "address_detail varchar(200) DEFAULT '' COMMENT '详细地址', " +
+                    "status tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-pending 1-approved 2-rejected', " +
+                    "reject_reason varchar(500) DEFAULT '' COMMENT '驳回原因', " +
+                    "reviewed_by int(11) DEFAULT NULL COMMENT '审核人ID', " +
+                    "reviewed_at datetime DEFAULT NULL COMMENT '审核时间', " +
+                    "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_user_id (user_id), " +
+                    "KEY idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务角色申请表'");
+            log.info("已创建service_role_applications表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移112: users表添加article_admin_status字段(文章管理员状态)
+        log.info("迁移112: 开始添加article_admin_status列到users表");
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN article_admin_status VARCHAR(20) DEFAULT NULL COMMENT '文章管理员状态: pending/approved/rejected'");
+            log.info("迁移112: 已添加article_admin_status列到users表");
+        } catch (Exception e) {
+            // 列已存在,幂等忽略(用warn而非error避免日志噪声)
+            log.warn("迁移112: 添加article_admin_status列失败(可能已存在): {}", e.getMessage());
+        }
+
+        // 迁移113: users表添加activity_provider_status字段(活动提供商状态)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN activity_provider_status VARCHAR(20) DEFAULT NULL COMMENT '活动提供商状态: pending/approved/rejected'");
+            log.info("已添加activity_provider_status列到users表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移114: 创建 periodic_services 表(周期性服务续费配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS periodic_services (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '关联商品ID', " +
+                    "service_name VARCHAR(100) COMMENT '服务名称', " +
+                    "cycle_days INT NOT NULL COMMENT '周期天数(如365=年度服务)', " +
+                    "remind_before_days INT DEFAULT 30 COMMENT '提前提醒天数', " +
+                    "auto_renew TINYINT DEFAULT 0 COMMENT '是否支持自动续费: 0=否 1=是', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '是否启用: 0=禁用 1=启用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product (product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='周期性服务续费配置'");
+            log.info("已创建 periodic_services 表");
+        } catch (Exception e) {
+            log.warn("创建 periodic_services 表失败: {}", e.getMessage());
+        }
+
+        // 迁移115: 创建 recommendation_logs 表(推荐行为记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS recommendation_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT COMMENT '用户ID', " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "child_id BIGINT COMMENT '孩子ID', " +
+                    "content_type VARCHAR(20) NOT NULL COMMENT '内容类型: article/task/product/activity', " +
+                    "content_id BIGINT NOT NULL COMMENT '内容ID', " +
+                    "recommend_score INT COMMENT '推荐分值', " +
+                    "action VARCHAR(20) COMMENT '行为: show/click/consume', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_content (user_id, content_type, content_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐行为记录'");
+            log.info("已创建 recommendation_logs 表");
+        } catch (Exception e) {
+            log.warn("创建 recommendation_logs 表失败: {}", e.getMessage());
+        }
+
+        // 迁移116: products表添加family_scope列(推荐系统-家庭消费去重)
+        try {
+            ensureColumn("products", "family_scope", "TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员消费过即排除, 0=仅按个人消费排除'");
+            log.info("已完成products.family_scope字段添加");
+        } catch (Exception e) {
+            log.warn("添加products.family_scope字段失败: {}", e.getMessage());
+        }
+
+        // 迁移117: activities表添加family_scope列(推荐系统-家庭参与去重)
+        try {
+            ensureColumn("activities", "family_scope", "TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员参与过即排除, 0=仅按个人参与排除'");
+            log.info("已完成activities.family_scope字段添加");
+        } catch (Exception e) {
+            log.warn("添加activities.family_scope字段失败: {}", e.getMessage());
+        }
+
+        // 迁移118: activity_registrations表child_id改为可空 + 添加registrant_name/phone(报名表单-多人报名)
+        try {
+            ensureColumn("activity_registrations", "registrant_name", "VARCHAR(100) COMMENT '报名人姓名'");
+            log.info("已完成activity_registrations.registrant_name字段添加");
+        } catch (Exception e) {
+            log.warn("添加activity_registrations.registrant_name字段失败: {}", e.getMessage());
+        }
+        try {
+            ensureColumn("activity_registrations", "registrant_phone", "VARCHAR(20) COMMENT '报名人联系方式'");
+            log.info("已完成activity_registrations.registrant_phone字段添加");
+        } catch (Exception e) {
+            log.warn("添加activity_registrations.registrant_phone字段失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE activity_registrations MODIFY COLUMN child_id BIGINT COMMENT '孩子ID(手动输入报名人为空)'");
+            log.info("已完成activity_registrations.child_id改为可空");
+        } catch (Exception e) {
+            log.warn("修改activity_registrations.child_id失败: {}", e.getMessage());
+        }
+        // 迁移119: package_orders表添加联系人字段(产品页下单)
+        try {
+            ensureColumn("package_orders", "contact_name", "VARCHAR(50) COMMENT '联系人姓名'");
+            ensureColumn("package_orders", "contact_phone", "VARCHAR(20) COMMENT '联系人手机号'");
+            ensureColumn("package_orders", "contact_address", "VARCHAR(500) COMMENT '收件地址'");
+            log.info("已完成package_orders表联系人字段添加");
+        } catch (Exception e) {
+            log.warn("添加package_orders联系人字段失败: {}", e.getMessage());
+        }
+
+        // 迁移120: activities表添加registration_deadline列(活动中心-报名截止时间展示)
+        try {
+            ensureColumn("activities", "registration_deadline", "DATETIME COMMENT '报名截止时间'");
+            log.info("已完成activities.registration_deadline字段添加");
+        } catch (Exception e) {
+            log.warn("添加activities.registration_deadline字段失败: {}", e.getMessage());
+        }
+
+        // 迁移143: 创建小程序虚拟支付道具映射表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS virtual_goods_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "goods_type VARCHAR(32) NOT NULL COMMENT 'ASSESSMENT_PACKAGE / MEMBERSHIP / SUBSCRIPTION', " +
+                    "biz_key VARCHAR(64) NOT NULL COMMENT '业务SKU标识: 测评套餐ID / 会员等级code / 订阅档位code', " +
+                    "product_id VARCHAR(64) NOT NULL COMMENT '微信商户后台道具ID', " +
+                    "goods_name VARCHAR(128) NOT NULL COMMENT '道具名称', " +
+                    "status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=停用', " +
+                    "create_time DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_goods_type_biz (goods_type, biz_key)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小程序虚拟支付道具映射'");
+            log.info("已创建virtual_goods_config表");
+        } catch (Exception e) {
+            log.warn("创建virtual_goods_config表失败: {}", e.getMessage());
+        }
+
+        // 迁移144: users表添加session_key列(小程序虚拟支付signature签名会话密钥)
+        try {
+            ensureColumn("users", "session_key", "VARCHAR(64) COMMENT '微信登录会话密钥(虚拟支付签名用)'");
+            log.info("已完成users.session_key字段添加");
+        } catch (Exception e) {
+            log.warn("添加users.session_key字段失败: {}", e.getMessage());
+        }
+
+        // 迁移145: 创建家庭天盘周报导出记录表(ReportSummaryService 全量同步依赖,此前从未建表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_fortune_report (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id INT NOT NULL COMMENT '家庭ID', " +
+                    "element VARCHAR(32) COMMENT '主导五行元素', " +
+                    "lucky_direction VARCHAR(64) COMMENT '本周吉位', " +
+                    "pdf_path VARCHAR(255) COMMENT 'PDF周报文件路径', " +
+                    "file_size BIGINT COMMENT '文件大小(字节)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭天盘周报导出记录'");
+            log.info("已创建family_fortune_report表");
+        } catch (Exception e) {
+            log.warn("创建family_fortune_report表失败: {}", e.getMessage());
+        }
+
+        // 迁移146: 创建五维家庭自检记录表(P0-1 五维自检问卷,书稿终章15题)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS five_dimension_self_checks (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '答卷人用户ID', " +
+                    "family_member_id BIGINT COMMENT '关联家庭成员ID(可选)', " +
+                    "answers_json TEXT COMMENT '用户答案JSON', " +
+                    "scores_json VARCHAR(512) COMMENT '各维度得分JSON', " +
+                    "total_score INT COMMENT '五维总分(满分45)', " +
+                    "advice_json TEXT COMMENT '五行相生寻源建议JSON', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维家庭自检记录'");
+            log.info("已创建five_dimension_self_checks表");
+        } catch (Exception e) {
+            log.warn("创建five_dimension_self_checks表失败: {}", e.getMessage());
+        }
+
+        // 迁移147: virtual_goods_config 插入会员年卡道具映射(虚拟支付上线准备)
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO virtual_goods_config (goods_type, biz_key, product_id, goods_name, status) " +
+                    "VALUES ('MEMBERSHIP', 'member1314', 'TBD', '会员年卡', 1)");
+            log.info("已插入会员年卡道具映射: member1314");
+        } catch (Exception e) {
+            log.warn("插入会员年卡道具映射失败: {}", e.getMessage());
+        }
+
+        // 迁移148: 修复 membership_levels 表重复行 + 添加 UNIQUE KEY uk_level_code
+        // 原因:早期建表缺少 UNIQUE KEY uk_level_code,加上 INSERT IGNORE 在无唯一键时反复插入,
+        //       导致生产环境 FREE/FAMILY/PROVIDER 三个等级各 69 行重复,
+        //       MembershipService.createOrder() 调用 selectOne(level_code) 触发 TooManyResultsException。
+        try {
+            // 1) 删除重复行:保留每个 level_code 最早插入的(id 最小),其余删除
+            int deleted = jdbcTemplate.update(
+                    "DELETE m1 FROM membership_levels m1 " +
+                            "INNER JOIN membership_levels m2 " +
+                            "ON m1.level_code = m2.level_code AND m1.id > m2.id");
+            log.info("已清理 membership_levels 重复行: 删除{}条", deleted);
+        } catch (Exception e) {
+            log.warn("清理 membership_levels 重复行失败: {}", e.getMessage());
+        }
+        try {
+            // 2) 添加 UNIQUE KEY(幂等:先查再 ADD)
+            Integer idxCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.statistics " +
+                            "WHERE table_schema = DATABASE() AND table_name = 'membership_levels' AND index_name = 'uk_level_code'",
+                    Integer.class);
+            if (idxCount == null || idxCount == 0) {
+                jdbcTemplate.execute("ALTER TABLE membership_levels ADD UNIQUE KEY uk_level_code (level_code)");
+                log.info("已添加 uk_level_code 唯一索引到 membership_levels 表");
+            } else {
+                log.info("uk_level_code 唯一索引已存在,跳过添加");
+            }
+        } catch (Exception e) {
+            log.warn("添加 uk_level_code 唯一索引失败: {}", e.getMessage());
+        }
+
+        // 迁移149: payment_orders表添加user_coupon_id列(VirtualPayTimeoutTask selectList 触发 Unknown column)
+        // 原因:旧库建表时缺少此列,entity PaymentOrder 已声明 userCouponId,导致 MyBatis-Plus 全列查询失败。
+        //      本地与远端新库已有,本迁移对它们幂等。
+        try {
+            ensureColumn("payment_orders", "user_coupon_id", "BIGINT COMMENT '使用的用户优惠券ID'");
+            log.info("已确保payment_orders.user_coupon_id列存在");
+        } catch (Exception e) {
+            log.warn("添加payment_orders.user_coupon_id列失败: {}", e.getMessage());
+        }
+
+        // 迁移150: activities表添加member_price列(会员价支持)
+        try {
+            ensureColumn("activities", "member_price", "INT COMMENT '会员价(分)' AFTER price");
+            log.info("已确保activities.member_price列存在");
+        } catch (Exception e) {
+            log.warn("添加activities.member_price列失败: {}", e.getMessage());
+        }
+
+        // 迁移151: growth_records表补齐学校/年级字段(GrowthRecord 实体全列查询 Unknown column 'school')
+        // 原因:实体已声明 school/grade/academicPerformance/schoolProvince/schoolCity/schoolDistrict/schoolStreet,
+        //      且 @TableField 为 camelCase 列名,但旧库建表时缺失这些列;
+        //      runMigration99() 为死代码从未执行,且其 snake_case 列名与实体 camelCase 不匹配。
+        ensureColumn("growth_records", "school", "VARCHAR(100) COMMENT '学校名称'");
+        ensureColumn("growth_records", "grade", "INT COMMENT '年级(数字)'");
+        ensureColumn("growth_records", "academicPerformance", "VARCHAR(200) COMMENT '成绩/学业表现'");
+        ensureColumn("growth_records", "schoolProvince", "VARCHAR(50) COMMENT '学校所在省'");
+        ensureColumn("growth_records", "schoolCity", "VARCHAR(50) COMMENT '学校所在市'");
+        ensureColumn("growth_records", "schoolDistrict", "VARCHAR(50) COMMENT '学校所在区'");
+        ensureColumn("growth_records", "schoolStreet", "VARCHAR(100) COMMENT '学校所在街道'");
+        log.info("已确保growth_records学校/年级相关列存在");
+
+        // 迁移152: 创建 product_gift_rules 表(商品赠送会员规则)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_gift_rules (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '关联products表', " +
+                    "level_code VARCHAR(50) NOT NULL COMMENT '赠送会员等级编码(FAMILY/PREMIUM等)', " +
+                    "status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_product_level (product_id, level_code), " +
+                    "INDEX idx_product_id (product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠送会员规则'");
+            log.info("创建product_gift_rules表成功");
+        } catch (Exception e) {
+            log.warn("创建product_gift_rules表失败: {}", e.getMessage());
+        }
+
+        // 迁移153: 创建 growth_archive_products 表(成长档案关联商品配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS growth_archive_products (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '关联products表', " +
+                    "status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用', " +
+                    "sort_order INT DEFAULT 0 COMMENT '展示排序(升序)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
+                    "UNIQUE KEY uk_product_id (product_id), " +
+                    "KEY idx_status (status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长档案关联商品配置'");
+            log.info("已创建growth_archive_products表");
+        } catch (Exception e) {
+            log.warn("创建growth_archive_products表失败: {}", e.getMessage());
+        }
+
+        // 迁移154: membership_levels 表插入 PREMIUM 等级种子数据(前端升级页可选"高级家庭会员",createOrder 查表为 null 抛"会员等级不存在")
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_levels (level_code, level_name, level_desc, price_monthly, price_quarterly, price_yearly, max_children, max_tasks_per_day, ai_review_enabled, priority_support, teacher_consultation) VALUES " +
+                    "('PREMIUM', '高级家庭会员', '高级家庭会员专属功能', 131400, NULL, 1316800, 10, 50, 1, 1, 1)");
+            log.info("已插入PREMIUM等级种子数据");
+        } catch (Exception e) {
+            log.warn("插入PREMIUM等级种子数据失败: {}", e.getMessage());
+        }
+
+        // 迁移155: 创建 interaction_logs 表(关系互动记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS interaction_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '所属家庭ID', " +
+                    "from_member_id BIGINT NOT NULL COMMENT '互动发起方成员ID', " +
+                    "to_member_id BIGINT NOT NULL COMMENT '互动接收方成员ID', " +
+                    "interaction_type VARCHAR(20) NOT NULL COMMENT '互动类型: dining/outgoing/call/video/chat/play/gift', " +
+                    "description VARCHAR(500) COMMENT '互动描述', " +
+                    "happened_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '互动发生时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_ilog_family (family_id), " +
+                    "INDEX idx_ilog_to_member (to_member_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员互动记录'");
+            log.info("已创建interaction_logs表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移156: 创建 relationship_questionnaire_snapshots 表(关系问卷快照)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS relationship_questionnaire_snapshots (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "respondent_id BIGINT NOT NULL COMMENT '答卷人用户ID', " +
+                    "member_name VARCHAR(50) COMMENT '成员名称', " +
+                    "relationship_type VARCHAR(20) COMMENT '关系类型: parent/child', " +
+                    "ai_generated_json TEXT COMMENT 'AI生成的题目JSON', " +
+                    "version VARCHAR(20) COMMENT '版本号', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_q_snap_member (family_member_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系问卷快照'");
+            log.info("已创建relationship_questionnaire_snapshots表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移158: users.family_id 允许 NULL(新用户微信登录时无家庭)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN family_id BIGINT COMMENT '家庭ID'");
+            log.info("已修改users.family_id允许NULL");
+        } catch (Exception e) {
+            // 忽略错误
+        }
+
+        // 迁移157: 创建 relationship_questionnaire_responses 表(关系问卷回答)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS relationship_questionnaire_responses (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "snapshot_id BIGINT NOT NULL COMMENT '问卷快照ID', " +
+                    "respondent_id BIGINT NOT NULL COMMENT '答卷人用户ID', " +
+                    "answers_json TEXT COMMENT '用户回答JSON', " +
+                    "trust_score DECIMAL(5,2) DEFAULT 0 COMMENT '信任度得分', " +
+                    "intimacy_score DECIMAL(5,2) DEFAULT 0 COMMENT '亲密度得分', " +
+                    "communication_score DECIMAL(5,2) DEFAULT 0 COMMENT '沟通质量得分', " +
+                    "interaction_bonus DECIMAL(5,2) DEFAULT 0 COMMENT '互动记录加分', " +
+                    "total_score DECIMAL(5,2) DEFAULT 0 COMMENT '综合得分', " +
+                    "calculated_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_q_resp_snap (snapshot_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系问卷回答记录'");
+            log.info("已创建relationship_questionnaire_responses表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移158: family_challenge 添加 creator_id 列(用户自定义挑战)
+        ensureColumn("family_challenge", "creator_id", "BIGINT COMMENT '创建者ID(NULL=系统创建)'");
+
+        // 迁移159: guide_orders表order_no加唯一约束(防重复下单,整改R1)
+        try {
+            // 先清理历史重复数据:每组order_no保留id最小的一行
+            jdbcTemplate.execute("DELETE g1 FROM guide_orders g1 INNER JOIN guide_orders g2 " +
+                    "ON g1.order_no = g2.order_no AND g1.id > g2.id");
+            log.info("已清理guide_orders重复order_no");
+        } catch (Exception e) {
+            // 忽略:无重复数据时删除0行,不报错
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE guide_orders ADD UNIQUE KEY uk_order_no (order_no)");
+            log.info("已为guide_orders.order_no添加唯一约束");
+        } catch (Exception e) {
+            // 约束已存在,忽略
+        }
+
+        // 迁移160: payment_orders表order_no升级为唯一约束(防支付回调重复,整改R2)
+        try {
+            jdbcTemplate.execute("DELETE p1 FROM payment_orders p1 INNER JOIN payment_orders p2 " +
+                    "ON p1.order_no = p2.order_no AND p1.id > p2.id");
+            log.info("已清理payment_orders重复order_no");
+        } catch (Exception e) {
+            // 忽略
+        }
+        try {
+            // 先删除原普通索引,再加唯一索引(同名列不可并存)
+            jdbcTemplate.execute("ALTER TABLE payment_orders DROP INDEX idx_order_no");
+            jdbcTemplate.execute("ALTER TABLE payment_orders ADD UNIQUE KEY uk_order_no (order_no)");
+            log.info("已为payment_orders.order_no添加唯一约束");
+        } catch (Exception e) {
+            // 索引已存在或已处理,忽略
+        }
+
+        // 迁移161: 创建 daily_checkin 表(每日健康综合打卡,含饮食/运动/睡眠/饮水/心情评分)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS daily_checkin (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "checkin_date DATE COMMENT '打卡日期', " +
+                    "diet_score INT COMMENT '饮食评分', " +
+                    "exercise_score INT COMMENT '运动评分', " +
+                    "sleep_score INT COMMENT '睡眠评分', " +
+                    "water_intake INT COMMENT '饮水量(ml)', " +
+                    "mood_score INT COMMENT '心情评分', " +
+                    "weight_kg DECIMAL(5,2) COMMENT '体重(kg)', " +
+                    "height_cm DECIMAL(5,2) COMMENT '身高(cm)', " +
+                    "remark TEXT COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日健康打卡表'");
+            log.info("已创建daily_checkin表");
+        } catch (Exception ex) {
+            log.warn("创建daily_checkin表失败: {}", ex.getMessage());
+        }
+
+        // 迁移162: 创建 exercise_record 表(运动打卡记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS exercise_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "checkin_id BIGINT COMMENT '关联打卡ID', " +
+                    "exercise_type VARCHAR(50) COMMENT '运动类型', " +
+                    "duration_minutes INT COMMENT '时长(分钟)', " +
+                    "intensity VARCHAR(20) COMMENT '强度: low/medium/high', " +
+                    "calories_burned INT COMMENT '消耗卡路里', " +
+                    "heart_rate_avg INT COMMENT '平均心率', " +
+                    "heart_rate_max INT COMMENT '最大心率', " +
+                    "step_count INT COMMENT '微信运动步数', " +
+                    "start_time DATETIME COMMENT '开始时间', " +
+                    "end_time DATETIME COMMENT '结束时间', " +
+                    "remark TEXT COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动打卡记录'");
+            log.info("已创建exercise_record表");
+        } catch (Exception ex) {
+            log.warn("创建exercise_record表失败: {}", ex.getMessage());
+        }
+
+        // 迁移163: family_challenge 添加 creator_id 列(用户自定义挑战,原迁移158在runMigration100死代码中从未执行)
+        ensureColumn("family_challenge", "creator_id", "BIGINT COMMENT '创建者ID(NULL=系统创建)'");
+
+        // 迁移164: coupon表扩展会员券字段 + 创建coupon_grant_log表(会员体系重构: 统一价+优惠券发放/积分兑换)
+        try {
+            ensureColumn("coupon", "discount_rate", "INT COMMENT '折扣券折扣率(千分比,9000=9折)'");
+            ensureColumn("coupon", "product_id", "BIGINT COMMENT '绑定商品ID,NULL=全场通用'");
+            ensureColumn("coupon", "points_price", "INT DEFAULT 0 COMMENT '积分兑换价,0=不可积分兑换'");
+            ensureColumn("coupon", "grant_type", "VARCHAR(16) COMMENT '发放规则:JOIN/PERIODIC/POPULATION,NULL=不自动发放'");
+            ensureColumn("coupon", "grant_level_code", "VARCHAR(32) COMMENT '发放规则对应会员等级'");
+            ensureColumn("coupon", "grant_period", "VARCHAR(16) COMMENT 'PERIODIC周期:MONTHLY/QUARTERLY'");
+            ensureColumn("coupon", "grant_quantity", "INT DEFAULT 1 COMMENT '每次发放数量'");
+            log.info("已为coupon表添加会员券字段");
+        } catch (Exception e) {
+            log.warn("添加coupon表会员券字段失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS coupon_grant_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '收券用户ID', " +
+                    "coupon_id BIGINT NOT NULL COMMENT '券模板ID', " +
+                    "grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE', " +
+                    "period VARCHAR(16) COMMENT '周期标识(YYYY-MM或YYYY-Qn),PERIODIC防重用', " +
+                    "quantity INT DEFAULT 1 COMMENT '发放数量', " +
+                    "source VARCHAR(64) COMMENT '触发来源(订单号/成员ID等)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_grant (user_id, coupon_id, grant_type, period), " +
+                    "INDEX idx_coupon (coupon_id), " +
+                    "INDEX idx_user (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券发放流水表'");
+            log.info("已创建coupon_grant_log表");
+        } catch (Exception e) {
+            log.warn("创建coupon_grant_log表失败: {}", e.getMessage());
+        }
+
+        // 迁移165: virtual_goods_config 添加 goods_price 列(会员道具价格,用于拆单判断)
+        ensureColumn("virtual_goods_config", "goods_price", "INT COMMENT '道具价格(分), 用于拆单判断'");
+
+        // 迁移166: payment_orders 添加 group_no/period_no 列(会员拆单支付,配置驱动拆单)
+        ensureColumn("payment_orders", "group_no", "VARCHAR(64) COMMENT '拆单组号(同一拆单组共享)'");
+        ensureColumn("payment_orders", "period_no", "INT DEFAULT 1 COMMENT '期数序号(1开始)'");
+        // 迁移167: 回填 families.creator_id(早期版本从未写入,导致会员开通/虚拟支付兜底失败)
+        // 优先 is_family_admin=true 的用户
+        try {
+            jdbcTemplate.execute("UPDATE families f JOIN users u ON f.id = u.family_id AND u.is_family_admin = 1 " +
+                    "SET f.creator_id = u.id WHERE f.creator_id IS NULL");
+            log.info("已回填families.creator_id(优先家庭管理员)");
+        } catch (Exception e) {
+            log.warn("回填families.creator_id(管理员)失败: {}", e.getMessage());
+        }
+        // 剩余家庭:任一 openid 非空用户(子查询确定性取一条)
+        try {
+            jdbcTemplate.execute("UPDATE families f SET f.creator_id = (" +
+                    "SELECT u.id FROM users u WHERE u.family_id = f.id " +
+                    "AND u.openid IS NOT NULL AND u.openid <> '' " +
+                    "ORDER BY (u.is_family_admin = 1) DESC, u.id ASC LIMIT 1" +
+                    ") WHERE f.creator_id IS NULL");
+            log.info("已回填families.creator_id(任一成员)");
+        } catch (Exception e) {
+            log.warn("回填families.creator_id(任一成员)失败: {}", e.getMessage());
+        }
+
+        // 迁移168: 修复文章表 null status 为 'draft'(旧版新建文章未设默认值导致无法编辑)
+        try {
+            jdbcTemplate.execute("UPDATE articles SET status = 'draft' WHERE status IS NULL");
+            log.info("已修复articles表中status为null的记录为draft");
+        } catch (Exception e) {
+            log.warn("修复articles表null status失败: {}", e.getMessage());
+        }
+
+        // 迁移169: 清理已废弃的 relationship_types 表(schema.sql 已移除,迁移代码中 ALTER 语句已清理)
+        try {
+            jdbcTemplate.execute("DROP TABLE IF EXISTS relationship_types");
+            log.info("已清理废弃的relationship_types表");
+        } catch (Exception e) {
+            log.warn("清理relationship_types表失败: {}", e.getMessage());
+        }
+
+        // 迁移170: 创建 family_join_requests 表(家庭加入请求待审批)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_join_requests (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "requester_user_id BIGINT NOT NULL COMMENT '申请加入的用户ID', " +
+                    "target_family_id BIGINT NOT NULL COMMENT '目标家庭ID', " +
+                    "join_method VARCHAR(20) NOT NULL COMMENT '加入方式: by_code/by_token', " +
+                    "invite_ref VARCHAR(50) COMMENT '邀请码或令牌', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected/cancelled', " +
+                    "approver_user_id BIGINT COMMENT '审批人ID', " +
+                    "comment TEXT COMMENT '审批备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_requester (requester_user_id), " +
+                    "INDEX idx_target_family (target_family_id), " +
+                    "INDEX idx_status (status) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭加入请求(待审批)'");
+            log.info("已创建family_join_requests表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移171: 创建 knowledge_base_section 表(统一知识库按模块存储JSON,DB为查询源,classpath种子兜底)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS knowledge_base_section (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "module_key VARCHAR(100) NOT NULL COMMENT '模块键(如 食物营养库/菌属功能库/报告指标分级标准)', " +
+                    "module_name VARCHAR(200) COMMENT '模块中文名', " +
+                    "content LONGTEXT COMMENT '模块内容(JSON序列化)', " +
+                    "version VARCHAR(50) COMMENT '数据版本(来自元数据.版本)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_module_key (module_key) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一知识库 - 按模块存储JSON'");
+            log.info("已创建knowledge_base_section表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移172: products 添加 min_stock_alert 列(库存预警阈值,配合库存管理模块)
+        // 迁移173: 商品配送与履约扩展(自取/线上服务),需求: 配送方式1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人
+        // 173a: product_orders 新增履约字段
+        ensureColumn("product_orders", "pickup_location_id", "BIGINT COMMENT '自取地点ID'");
+        ensureColumn("product_orders", "pickup_location_snapshot", "TEXT COMMENT '自取地点快照(JSON)'");
+        ensureColumn("product_orders", "time_slot_id", "BIGINT COMMENT '所选时间段ID'");
+        ensureColumn("product_orders", "time_slot_snapshot", "TEXT COMMENT '时间段快照(JSON)'");
+        ensureColumn("product_orders", "service_person_id", "BIGINT COMMENT '所选服务者ID(deliveryMethod=4)'");
+        ensureColumn("product_orders", "service_person_snapshot", "TEXT COMMENT '服务者快照(JSON)'");
+        ensureColumn("product_orders", "fulfill_status", "VARCHAR(32) DEFAULT 'pending' COMMENT '履约状态: pending/confirmed_by_vendor/received_by_user/completed'");
+        ensureColumn("product_orders", "fulfill_confirmed_at", "DATETIME COMMENT '供应商确认时间'");
+        ensureColumn("product_orders", "received_at", "DATETIME COMMENT '用户确认收货时间'");
+        // 173b: 自取地点表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_pickup_locations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', " +
+                    "product_id BIGINT NOT NULL COMMENT '所属商品ID', " +
+                    "name VARCHAR(100) NOT NULL COMMENT '地点名称', " +
+                    "address VARCHAR(500) NOT NULL COMMENT '详细地址', " +
+                    "longitude DECIMAL(10,7) COMMENT '经度', " +
+                    "latitude DECIMAL(10,7) COMMENT '纬度', " +
+                    "contact_name VARCHAR(50) COMMENT '联系人', " +
+                    "contact_phone VARCHAR(20) COMMENT '联系电话', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '启用: 0/1', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product_id (product_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品自取地点'");
+            log.info("已创建product_pickup_locations表");
+        } catch (Exception ex) {
+            log.warn("创建product_pickup_locations表失败: {}", ex.getMessage());
+        }
+        // 173c: 自取时间段模板表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_pickup_time_slots (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', " +
+                    "location_id BIGINT NOT NULL COMMENT '所属地点ID', " +
+                    "start_time TIME NOT NULL COMMENT '开始时间 HH:MM', " +
+                    "end_time TIME NOT NULL COMMENT '结束时间 HH:MM', " +
+                    "weekday TINYINT COMMENT '周几(1-7), null=按具体日期', " +
+                    "specific_date DATE COMMENT '具体日期, null=按周循环', " +
+                    "max_capacity INT DEFAULT 1 COMMENT '该时段最大接待人数', " +
+                    "booked_count INT DEFAULT 0 COMMENT '已预订数', " +
+                    "enabled TINYINT DEFAULT 1, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_location_id (location_id), " +
+                    "INDEX idx_specific_date (specific_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自取时间段模板'");
+            log.info("已创建product_pickup_time_slots表");
+        } catch (Exception ex) {
+            log.warn("创建product_pickup_time_slots表失败: {}", ex.getMessage());
+        }
+        // 173d: 线上服务者表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_service_persons (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', " +
+                    "product_id BIGINT NOT NULL COMMENT '所属商品ID', " +
+                    "user_id BIGINT NOT NULL COMMENT '服务者用户ID(关联users表)', " +
+                    "name VARCHAR(50) NOT NULL COMMENT '展示名称', " +
+                    "title VARCHAR(100) COMMENT '头衔(如高级营养师)', " +
+                    "intro TEXT COMMENT '个人介绍', " +
+                    "avatar VARCHAR(500) COMMENT '头像URL', " +
+                    "price INT NOT NULL COMMENT '该人专属价格(分)', " +
+                    "original_price INT COMMENT '原价(分)', " +
+                    "sort_order INT DEFAULT 0, " +
+                    "enabled TINYINT DEFAULT 1, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product_id (product_id), " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品线上服务者'");
+            log.info("已创建product_service_persons表");
+        } catch (Exception ex) {
+            log.warn("创建product_service_persons表失败: {}", ex.getMessage());
+        }
+        // 173e: 服务者可预约时间段表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_service_person_slots (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', " +
+                    "person_id BIGINT NOT NULL COMMENT '所属服务者ID', " +
+                    "start_time DATETIME NOT NULL COMMENT '开始时间', " +
+                    "end_time DATETIME NOT NULL COMMENT '结束时间', " +
+                    "max_capacity INT DEFAULT 1 COMMENT '容量', " +
+                    "booked_count INT DEFAULT 0 COMMENT '已预订数', " +
+                    "enabled TINYINT DEFAULT 1, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_person_id (person_id), " +
+                    "INDEX idx_start_time (start_time)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务者可预约时间段'");
+            log.info("已创建product_service_person_slots表");
+        } catch (Exception ex) {
+            log.warn("创建product_service_person_slots表失败: {}", ex.getMessage());
+        }
+        // 173f: 单人线上服务时间段表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_online_slots (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', " +
+                    "product_id BIGINT NOT NULL COMMENT '所属商品ID', " +
+                    "start_time DATETIME NOT NULL COMMENT '开始时间', " +
+                    "end_time DATETIME NOT NULL COMMENT '结束时间', " +
+                    "max_capacity INT DEFAULT 1 COMMENT '容量', " +
+                    "booked_count INT DEFAULT 0 COMMENT '已预订数', " +
+                    "enabled TINYINT DEFAULT 1, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_product_id (product_id), " +
+                    "INDEX idx_start_time (start_time)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单人线上服务时间段'");
+            log.info("已创建product_online_slots表");
+        } catch (Exception ex) {
+            log.warn("创建product_online_slots表失败: {}", ex.getMessage());
+        }
+
+        // 迁移166: 创建 membership_benefits 表(会员权益配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS membership_benefits (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "level_code VARCHAR(50) NOT NULL COMMENT '会员等级: FREE/PROFESSIONAL/ENTERPRISE', " +
+                    "benefit_code VARCHAR(50) NOT NULL COMMENT '权益编码: AI_CHAT,FAMILY_MEMBERS等', " +
+                    "benefit_name VARCHAR(100) NOT NULL COMMENT '权益名称', " +
+                    "benefit_desc VARCHAR(500) COMMENT '权益描述', " +
+                    "icon VARCHAR(50) COMMENT '图标emoji', " +
+                    "bg_color VARCHAR(20) COMMENT '背景色', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序顺序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_level_benefit (level_code, benefit_code), " +
+                    "INDEX idx_level (level_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员权益配置'");
+            log.info("已创建membership_benefits表");
+        } catch (Exception ex) {
+            log.warn("创建membership_benefits表失败: {}", ex.getMessage());
+        }
+
+        // 迁移167: membership_benefits 种子数据
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES " +
+                    "('FREE', 'NO_BENEFIT', '暂无会员权益', '开通会员解锁更多权益', '🔒', '#F3F4F6', 0), " +
+                    "('FREE', 'UPGRADE_HINT', '升级会员', '开通会员享专属权益', '👑', '#FEF3C7', 99), " +
+                    "('FAMILY', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1), " +
+                    "('FAMILY', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 10 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2), " +
+                    "('FAMILY', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案', '📋', '#FEF3C7', 3), " +
+                    "('FAMILY', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 解读', '📊', '#FCE7F3', 4), " +
+                    "('FAMILY', 'MANAGER_SERVICE', '专属健康教练服务', '认证健康教练月度随访', '👨‍🏫', '#FFEDD5', 5), " +
+                    "('FAMILY', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 8-9 折优惠', '🛒', '#E0E7FF', 6), " +
+                    "('FAMILY', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券享更多优惠', '🎫', '#FEE2E2', 7), " +
+                    "('PREMIUM', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1), " +
+                    "('PREMIUM', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 20 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2), " +
+                    "('PREMIUM', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案 + 定制营养建议', '📋', '#FEF3C7', 3), " +
+                    "('PREMIUM', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 深度解读', '📊', '#FCE7F3', 4), " +
+                    "('PREMIUM', 'MANAGER_SERVICE', '专属健康教练服务', '每周 1 次电话随访 + 在线答疑', '👨‍🏫', '#FFEDD5', 5), " +
+                    "('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 6-8 折优惠', '🛒', '#E0E7FF', 6), " +
+                    "('PREMIUM', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券 + 会员专属券', '🎫', '#FEE2E2', 7), " +
+                    "('PREMIUM', 'PRIORITY_SUPPORT', '优先客服', '专属客服通道,快速响应', '🎧', '#EDE9FE', 8), " +
+                    "('PREMIUM', 'TEACHER_CONSULT', '规划师一对一咨询', '每月 1 次成长规划师专业咨询', '👩‍🏫', '#FBCFE8', 9)");
+            log.info("已插入membership_benefits种子数据");
+        } catch (Exception ex) {
+            log.warn("插入membership_benefits种子数据失败: {}", ex.getMessage());
+        }
+
+        // 迁移168: 创建用户同意记录表(法律合规)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_consent_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "consent_type VARCHAR(50) NOT NULL COMMENT '同意类型: privacy/agreement/children_data/ai_processing', " +
+                    "version VARCHAR(20) NOT NULL COMMENT '协议版本号', " +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'agreed' COMMENT 'agreed/withdrawn/archived', " +
+                    "ip_address VARCHAR(45) COMMENT '用户IP地址', " +
+                    "created_at DATETIME NOT NULL, " +
+                    "updated_at DATETIME, " +
+                    "INDEX idx_user_consent (user_id, consent_type), " +
+                    "INDEX idx_consent_type (consent_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户同意记录'");
+            log.info("已创建user_consent_records表");
+        } catch (Exception e) {
+            log.warn("创建user_consent_records表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移169: 创建用户数据授权表(法律合规)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_data_permissions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "allow_ai_analysis TINYINT(1) DEFAULT 0 COMMENT '允许AI分析对话数据', " +
+                    "allow_personalized_recommend TINYINT(1) DEFAULT 1 COMMENT '允许个性化推荐', " +
+                    "allow_share_teacher TINYINT(1) DEFAULT 1 COMMENT '允许向成长规划师共享数据', " +
+                    "allow_third_party_share TINYINT(1) DEFAULT 0 COMMENT '允许与第三方共享数据', " +
+                    "created_at DATETIME NOT NULL, " +
+                    "updated_at DATETIME, " +
+                    "INDEX idx_user_perm (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户数据使用授权'");
+            log.info("已创建user_data_permissions表");
+        } catch (Exception e) {
+            log.warn("创建user_data_permissions表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移174: family_members 添加饮食相关字段(饮食模块需求)
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN is_living_together TINYINT(1) DEFAULT 1 COMMENT '是否同住(0=不同住,不出现在食谱推荐中)'");
+            log.info("已添加is_living_together列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 is_living_together 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_breakfast_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日早餐同餐'");
+            log.info("已添加meal_with_breakfast_weekday列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_breakfast_weekday 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_lunch_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日午餐同餐'");
+            log.info("已添加meal_with_lunch_weekday列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_lunch_weekday 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_dinner_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日晚餐同餐'");
+            log.info("已添加meal_with_dinner_weekday列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_dinner_weekday 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_breakfast_weekend TINYINT(1) DEFAULT 0 COMMENT '周末早餐同餐'");
+            log.info("已添加meal_with_breakfast_weekend列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_breakfast_weekend 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_lunch_weekend TINYINT(1) DEFAULT 0 COMMENT '周末午餐同餐'");
+            log.info("已添加meal_with_lunch_weekend列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_lunch_weekend 列失败(可能已存在): " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_members ADD COLUMN meal_with_dinner_weekend TINYINT(1) DEFAULT 0 COMMENT '周末晚餐同餐'");
+            log.info("已添加meal_with_dinner_weekend列到family_members表");
+        } catch (Exception e) {
+            log.warn("family_members 添加 meal_with_dinner_weekend 列失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移175: 创建 diet_preferences 表(饮食偏好调研表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_preferences (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_member_id BIGINT NOT NULL COMMENT '绑定家庭成员', " +
+                    "allergies JSON COMMENT '过敏原列表', " +
+                    "absolute_avoid JSON COMMENT '绝对忌口列表', " +
+                    "must_eat JSON COMMENT '必吃食物列表', " +
+                    "religious_diet VARCHAR(50) DEFAULT 'none' COMMENT '宗教饮食', " +
+                    "spice_level TINYINT DEFAULT NULL COMMENT '辣度 0-5', " +
+                    "flavor_pref JSON COMMENT '口味偏好', " +
+                    "cuisine_pref JSON COMMENT '菜系偏好', " +
+                    "cooking_methods JSON COMMENT '烹饪方式偏好', " +
+                    "health_goals JSON COMMENT '健康目标', " +
+                    "goal_source VARCHAR(20) DEFAULT NULL COMMENT 'manual/system_recommend', " +
+                    "goal_confirmed TINYINT(1) DEFAULT 0 COMMENT '用户已确认', " +
+                    "filled_at TIMESTAMP NULL COMMENT '首次填写时间', " +
+                    "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_member (family_member_id), " +
+                    "INDEX idx_updated (updated_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食偏好调研表'");
+            log.info("已创建diet_preferences表");
+        } catch (Exception e) {
+            log.warn("创建 diet_preferences 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移176: 创建 meal_configs 表(共餐配置表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS meal_configs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "config_date_type VARCHAR(20) NOT NULL COMMENT '工作日/周末/节假日', " +
+                    "meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner', " +
+                    "participant_member_ids JSON NOT NULL COMMENT '参与成员ID列表', " +
+                    "notes VARCHAR(255) DEFAULT NULL COMMENT '备注', " +
+                    "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_config (family_id, config_date_type, meal_type), " +
+                    "INDEX idx_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='共餐配置表'");
+            log.info("已创建meal_configs表");
+        } catch (Exception e) {
+            log.warn("创建 meal_configs 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移177: 创建 diet_recommendations 表(食谱推荐方案表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_recommendations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "recommendation_date DATE NOT NULL COMMENT '推荐日期', " +
+                    "meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner', " +
+                    "participant_member_ids JSON COMMENT '参与成员ID列表', " +
+                    "menu_json MEDIUMTEXT NOT NULL COMMENT '菜品列表JSON', " +
+                    "nutrition_summary JSON COMMENT '营养汇总', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/accepted/adjusted/completed/skipped', " +
+                    "version INT DEFAULT 1 COMMENT '版本号', " +
+                    "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_date (family_id, recommendation_date), " +
+                    "INDEX idx_meal (meal_type, recommendation_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱推荐方案表'");
+            log.info("已创建diet_recommendations表");
+        } catch (Exception e) {
+            log.warn("创建 diet_recommendations 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移178: 创建 diet_records 表(饮食记录表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '吃的成员ID', " +
+                    "meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner/snack', " +
+                    "record_date DATE NOT NULL COMMENT '记录日期', " +
+                    "record_method VARCHAR(20) DEFAULT NULL COMMENT 'photo/manual/from_recommendation', " +
+                    "image_url VARCHAR(500) DEFAULT NULL COMMENT '原图OSS路径', " +
+                    "ai_recognized_foods JSON DEFAULT NULL COMMENT 'AI识别结果', " +
+                    "user_confirmed_foods JSON NOT NULL COMMENT '用户最终确认食材', " +
+                    "notes VARCHAR(500) DEFAULT NULL COMMENT '备注', " +
+                    "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_date (member_id, record_date), " +
+                    "INDEX idx_family_date (family_id, record_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录表'");
+            log.info("已创建diet_records表");
+        } catch (Exception e) {
+            log.warn("创建 diet_records 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移179: 创建 diet_record_items 表(饮食记录食材明细表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_record_items (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "record_id BIGINT NOT NULL COMMENT 'diet_records.id', " +
+                    "food_name VARCHAR(100) NOT NULL COMMENT '食材名称', " +
+                    "food_id BIGINT DEFAULT NULL COMMENT '关联foods表ID', " +
+                    "confidence DECIMAL(3,2) DEFAULT NULL COMMENT 'AI置信度0-1', " +
+                    "source VARCHAR(20) DEFAULT NULL COMMENT 'ai_recognized/manual/from_recommendation', " +
+                    "INDEX idx_record (record_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录食材明细表'");
+            log.info("已创建diet_record_items表");
+        } catch (Exception e) {
+            log.warn("创建 diet_record_items 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移180: health_reports 添加代上传字段(规划师/营养师代用户提交报告,修复 Unknown column 'proxy_uploader_id' 错误)
+        ensureColumn("health_reports", "proxy_uploader_id", "BIGINT COMMENT '代上传人ID(规划师/营养师)'");
+        ensureColumn("health_reports", "proxy_upload_note", "TEXT COMMENT '代上传备注'");
+        ensureColumn("health_reports", "ai_analysis", "TEXT COMMENT 'AI解读结果JSON'");
+        // 迁移180b: health_reports 添加报告来源格式(beijing/guhe,两种菌群报告数据结构不兼容,需区分)
+        ensureColumn("health_reports", "report_format", "VARCHAR(32) COMMENT '报告来源格式: beijing(北京菌群)/guhe(谷禾)'");
+
+        // 迁移181: health_report_drafts 添加备注字段(代上传说明文字)
+        ensureColumn("health_report_drafts", "remark", "VARCHAR(500) DEFAULT NULL COMMENT '备注(如代上传时的说明文字)'");
+
+        // 迁移182: 创建 family_challenge_participant 表(挑战参与成员,任意成员发起/可选参与)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge_participant (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "challenge_id BIGINT NOT NULL COMMENT '挑战ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " +
+                    "status VARCHAR(20) NOT NULL COMMENT 'pending/agreed/rejected', " +
+                    "agreed_at DATETIME NULL COMMENT '同意/拒绝时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_challenge_member (challenge_id, member_id), " +
+                    "INDEX idx_member (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战参与成员'");
+            log.info("已创建family_challenge_participant表");
+        } catch (Exception e) {
+            log.warn("创建 family_challenge_participant 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移183: 创建 family_relationship_scores 表(方向性关系分数,A对B ≠ B对A)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_relationship_scores (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "from_member_id BIGINT NOT NULL COMMENT '分数持有方(A对B的行)', " +
+                    "to_member_id BIGINT NOT NULL COMMENT '分数对象方', " +
+                    "trust_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的信任', " +
+                    "intimacy_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的亲近', " +
+                    "communication_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的沟通', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_from_to (from_member_id, to_member_id), " +
+                    "INDEX idx_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='方向性关系分数'");
+            log.info("已创建family_relationship_scores表");
+        } catch (Exception e) {
+            log.warn("创建 family_relationship_scores 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移184: 创建 family_challenge_score_config 表(挑战评分配置,后台可配置)+ 默认数据
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_challenge_score_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "action VARCHAR(20) NOT NULL COMMENT 'initiate/agree/reject', " +
+                    "score_type VARCHAR(20) NOT NULL COMMENT 'energy/trust/intimacy/communication', " +
+                    "value INT NOT NULL DEFAULT 0 COMMENT '增减值(可正可负,默认±1)', " +
+                    "enabled TINYINT DEFAULT 1, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_action_type (action, score_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战评分配置'");
+            jdbcTemplate.execute("INSERT IGNORE INTO family_challenge_score_config (action, score_type, value, enabled) VALUES " +
+                    "('initiate','energy',1,1),('initiate','trust',0,1),('initiate','intimacy',1,1),('initiate','communication',1,1)," +
+                    "('agree','energy',1,1),('agree','trust',1,1),('agree','intimacy',1,1),('agree','communication',1,1)," +
+                    "('reject','energy',-1,1),('reject','trust',-1,1),('reject','intimacy',-1,1),('reject','communication',1,1)");
+            log.info("已创建family_challenge_score_config表并写入默认配置");
+        } catch (Exception e) {
+            log.warn("创建 family_challenge_score_config 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移185: 存量挑战补全 participant 行(全员 agreed,兼容旧数据,不破坏)
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO family_challenge_participant (challenge_id, member_id, status, agreed_at) " +
+                    "SELECT fc.id, fm.id, 'agreed', NOW() FROM family_challenge fc " +
+                    "JOIN family_members fm ON fm.family_id = fc.family_id " +
+                    "WHERE NOT EXISTS (SELECT 1 FROM family_challenge_participant fcp WHERE fcp.challenge_id = fc.id)");
+            log.info("已为存量挑战补全participant行");
+        } catch (Exception e) {
+            log.warn("存量挑战补全participant失败(可能已处理): " + e.getMessage());
+        }
+
+        // ===== 报告指纹检测与解析器调度系统 =====
+
+        // 迁移186: 创建 report_type_registry 表(报告类型注册表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_type_registry (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "type_id VARCHAR(64) NOT NULL COMMENT '类型标识,如 dan_a1/gut_beijing', " +
+                    "family VARCHAR(32) DEFAULT NULL COMMENT '报告家族: dan/gut_flora/其他', " +
+                    "display_name VARCHAR(100) NOT NULL COMMENT '显示名称', " +
+                    "description VARCHAR(500) DEFAULT NULL COMMENT '描述', " +
+                    "parser_path VARCHAR(255) DEFAULT NULL COMMENT 'Python解析器脚本路径', " +
+                    "min_confidence INT DEFAULT 10 COMMENT '识别阈值', " +
+                    "is_active TINYINT(1) DEFAULT 1 COMMENT '启用状态', " +
+                    "source VARCHAR(20) DEFAULT 'prebuilt' COMMENT '来源: prebuilt/imported/auto_generated', " +
+                    "review_status VARCHAR(20) DEFAULT 'approved' COMMENT '审核状态: approved/pending/rejected', " +
+                    "version VARCHAR(20) DEFAULT '1.0' COMMENT '版本', " +
+                    "created_by BIGINT DEFAULT NULL COMMENT '创建人ID', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_type_id (type_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告类型注册表'");
+            log.info("已创建report_type_registry表");
+        } catch (Exception e) {
+            log.warn("创建 report_type_registry 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移187: 创建 report_fingerprint 表(指纹规则)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_fingerprint (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "type_id VARCHAR(64) NOT NULL COMMENT '关联report_type_registry.type_id', " +
+                    "pattern VARCHAR(200) NOT NULL COMMENT '文本特征', " +
+                    "weight INT DEFAULT 10 COMMENT '权重(正=加分,负=排除扣分)', " +
+                    "is_exclusion TINYINT(1) DEFAULT 0 COMMENT '是否为排除规则', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fp_type (type_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告指纹规则表'");
+            log.info("已创建report_fingerprint表");
+        } catch (Exception e) {
+            log.warn("创建 report_fingerprint 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移188: 创建 report_unknown_cluster 表(未知报告聚类,自学习用)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_unknown_cluster (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "cluster_key VARCHAR(64) NOT NULL COMMENT '聚类哈希键(同类报告分组)', " +
+                    "common_signatures TEXT COMMENT '公共文本特征JSON(用于生成指纹)', " +
+                    "llm_template TEXT COMMENT 'LLM解析结果模板JSON', " +
+                    "report_count INT DEFAULT 0 COMMENT '累计报告数', " +
+                    "status VARCHAR(20) DEFAULT 'collecting' COMMENT '状态: collecting/ready/generated/imported', " +
+                    "generated_type_id VARCHAR(64) DEFAULT NULL COMMENT '生成的类型ID', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_cluster_key (cluster_key)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='未知报告聚类表'");
+            log.info("已创建report_unknown_cluster表");
+        } catch (Exception e) {
+            log.warn("创建 report_unknown_cluster 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移189: 创建 report_unknown_upload 表(未知报告明细)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_unknown_upload (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "cluster_id BIGINT DEFAULT NULL COMMENT '关联report_unknown_cluster.id', " +
+                    "file_url VARCHAR(500) DEFAULT NULL COMMENT '文件路径', " +
+                    "file_hash VARCHAR(64) DEFAULT NULL COMMENT '文件哈希', " +
+                    "llm_result TEXT COMMENT 'LLM解析结果JSON', " +
+                    "uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_unknown_cluster (cluster_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='未知报告上传明细表'");
+            log.info("已创建report_unknown_upload表");
+        } catch (Exception e) {
+            log.warn("创建 report_unknown_upload 表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移190: 创建 report_parser_import 表(外部导入记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_parser_import (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "package_name VARCHAR(100) NOT NULL COMMENT '导入包名称', " +
+                    "package_file_url VARCHAR(500) DEFAULT NULL COMMENT '导入包文件路径', " +
+                    "fingerprint_json TEXT COMMENT '指纹规则JSON', " +
+                    "parser_script TEXT COMMENT '解析器脚本内容', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected', " +
+                    "imported_by BIGINT DEFAULT NULL COMMENT '导入人ID', " +
+                    "reviewed_by BIGINT DEFAULT NULL COMMENT '审核人ID', " +
+                    "review_note VARCHAR(500) DEFAULT NULL 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("已创建report_parser_import表");
+        } catch (Exception e) {
+            log.warn("创建 report_parser_import 表失败(可能已存在): " + e.getMessage());
+        }
+
+// 迁移191: foods 添加 report_id/score 列(Food 实体已含字段,生产库缺列导致 Unknown column 'report_id' 错误)
+        ensureColumn("foods", "report_id", "BIGINT DEFAULT NULL COMMENT '关联报告ID(从PDF解析的食物推荐)'");
+        ensureColumn("foods", "score", "INT DEFAULT NULL COMMENT '食物评分'");
+
+		// 迁移263: foods 补齐 energy_kj/starch/cholesterol/diet_type 列(前三列原迁移在文件重组时丢失、生产靠历史执行残留;
+		//   dietType 为实体孤立字段从未建列,MyBatis-Plus 全字段 SELECT 报 Unknown column)
+		ensureColumn("foods", "energy_kj", "DECIMAL(8,2) DEFAULT NULL COMMENT '能量(kJ/100g)'");
+		ensureColumn("foods", "starch", "DECIMAL(8,2) DEFAULT NULL COMMENT '淀粉(g/100g)'");
+		ensureColumn("foods", "cholesterol", "DECIMAL(8,2) DEFAULT NULL COMMENT '胆固醇(mg/100g)'");
+		ensureColumn("foods", "diet_type", "VARCHAR(50) DEFAULT NULL COMMENT '饮食类型'");
+
+		// 迁移192: verification_codes.type 添加默认值(旧版插入未携带 type 字段,DB 列 NOT NULL 无默认值导致报错)
+		try {
+			jdbcTemplate.execute("ALTER TABLE verification_codes MODIFY COLUMN `type` VARCHAR(20) NOT NULL DEFAULT 'login' COMMENT '验证码类型: login/register/reset'");
+			log.info("已为verification_codes.type添加默认值");
+		} catch (Exception e) {
+			log.warn("verification_codes.type 默认值修改失败(可能已修改): " + e.getMessage());
+		}
+
+        // 迁移193: family_relationships表添加关系质量评分三列(关系条展示增强)
+        ensureColumn("family_relationships", "trust_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '信任度(答卷人视角)'");
+        ensureColumn("family_relationships", "intimacy_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '亲密度(答卷人视角)'");
+        ensureColumn("family_relationships", "communication_score", "DECIMAL(5,2) DEFAULT 0 COMMENT '沟通质量(答卷人视角)'");
+
+        // 迁移194: family_join_requests 添加 family_role 列(保存申请人角色)
+        ensureColumn("family_join_requests", "family_role", "VARCHAR(20) DEFAULT NULL COMMENT '申请人在家庭中的角色:爸爸/妈妈/爷爷/奶奶等'");
+
+        // 迁移195: tasks 表新增 accept_deadline 列 + 扩展 status 枚举(支持 assigned/in_progress/rejected)
+        ensureColumn("tasks", "accept_deadline", "DATETIME DEFAULT NULL COMMENT '接受截止时间(分配给其他家庭成员的任务)'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN `status` ENUM('pending','assigned','in_progress','completed','rejected','overdue','cancelled') DEFAULT 'pending' COMMENT '状态: pending=待审核 assigned=待接受 in_progress=进行中 completed=已完成 rejected=已拒绝 overdue=已过期 cancelled=已取消'");
+            log.info("已更新tasks.status枚举值");
+        } catch (Exception e) {
+            log.warn("tasks.status枚举修改失败(可能已修改): " + e.getMessage());
+        }
+
+        // 迁移196: 创建 user_platform_balance 表(CF值余额)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_platform_balance (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "family_id BIGINT COMMENT '归属家庭ID', " +
+                    "total_earned INT DEFAULT 0 COMMENT '累计获得', " +
+                    "available INT DEFAULT 0 COMMENT '可用余额', " +
+                    "frozen INT DEFAULT 0 COMMENT '冻结(提现审核中)', " +
+                    "withdrawn INT DEFAULT 0 COMMENT '已提现', " +
+                    "exchanged INT DEFAULT 0 COMMENT '已兑换', " +
+                    "updated_at DATETIME, " +
+                    "created_at DATETIME, " +
+                    "UNIQUE KEY uk_user (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户CF值余额'");
+            log.info("已创建user_platform_balance表");
+        } catch (Exception e) {
+            log.warn("创建user_platform_balance表失败: " + e.getMessage());
+        }
+
+        // 迁移197: 创建 platform_balance_log 表(CF值流水)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS platform_balance_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "family_id BIGINT COMMENT '归属家庭ID', " +
+                    "type VARCHAR(20) NOT NULL COMMENT 'earn/spend/freeze/unfreeze/withdraw', " +
+                    "amount INT NOT NULL COMMENT '变动数量', " +
+                    "balance_after INT NOT NULL COMMENT '变动后可用余额', " +
+                    "ref_type VARCHAR(30) COMMENT 'product_order/activity/withdrawal/migration', " +
+                    "ref_id BIGINT COMMENT '关联业务ID', " +
+                    "remark VARCHAR(255) COMMENT '备注', " +
+                    "created_at DATETIME, " +
+                    "INDEX idx_user_time (user_id, created_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值流水'");
+            log.info("已创建platform_balance_log表");
+        } catch (Exception e) {
+            log.warn("创建platform_balance_log表失败: " + e.getMessage());
+        }
+
+        // 迁移198: 存量佣金迁移为CF值(settled→available, pending→frozen)
+        try {
+            jdbcTemplate.execute("INSERT INTO user_platform_balance (user_id, total_earned, available, created_at, updated_at) " +
+                    "SELECT referrer_id, SUM(commission_amount), SUM(commission_amount), NOW(), NOW() " +
+                    "FROM commission_records WHERE status='settled' GROUP BY referrer_id " +
+                    "ON DUPLICATE KEY UPDATE available=available+VALUES(available), " +
+                    "total_earned=total_earned+VALUES(total_earned), updated_at=NOW()");
+            log.info("已迁移settled佣金为可用CF值");
+        } catch (Exception e) {
+            log.warn("迁移settled佣金失败: " + e.getMessage());
+        }
+
+        // 迁移199: pending 佣金 → 冻结CF
+        try {
+            jdbcTemplate.execute("INSERT INTO user_platform_balance (user_id, total_earned, frozen, created_at, updated_at) " +
+                    "SELECT referrer_id, SUM(commission_amount), SUM(commission_amount), NOW(), NOW() " +
+                    "FROM commission_records WHERE status='pending' GROUP BY referrer_id " +
+                    "ON DUPLICATE KEY UPDATE frozen=frozen+VALUES(frozen), " +
+                    "total_earned=total_earned+VALUES(total_earned), updated_at=NOW()");
+            log.info("已迁移pending佣金为冻结CF值");
+        } catch (Exception e) {
+            log.warn("迁移pending佣金失败: " + e.getMessage());
+        }
+
+        // 迁移200: activities表添加platform_points字段
+        ensureColumn("activities", "platform_points", "INT DEFAULT 0 COMMENT '活动奖励CF值(0=不发)'");
+
+        // 迁移201: 新增CF值配置项
+        insertSysConfigSeed("product_platform_points_share", "1000", "商品CF值利润分成比例(bps)");
+        insertSysConfigSeed("platform_withdraw_rate", "100", "CF值提现汇率(CF数/元)");
+
+        // 迁移202: withdrawal_requests表添加 type 字段(提现来源)
+        ensureColumn("withdrawal_requests", "type", "VARCHAR(20) DEFAULT 'platform_points' COMMENT '提现来源: platform_points(CF值) / commission(历史佣金)'");
+
+// 迁移203: membership_levels 添加原价字段(LIFETIME 需求:价格来源统一迁移至表内)
+        ensureColumn("membership_levels", "original_price_monthly", "INT NULL COMMENT '原价-月费(分)'");
+        ensureColumn("membership_levels", "original_price_quarterly", "INT NULL COMMENT '原价-季费(分)'");
+        ensureColumn("membership_levels", "original_price_yearly", "INT NULL COMMENT '原价-年费(分)'");
+
+        // 迁移203.1: 幂等数据同步(sys_config 原价/现值 → membership_levels,仅当表内为 0/NULL 时)
+        try {
+            // FAMILY 原价年费(sys_config member_fee_family_original_yearly / member_fee_family_original,默认 199900)
+            jdbcTemplate.update("UPDATE membership_levels SET original_price_yearly = 199900 " +
+                    "WHERE level_code = 'FAMILY' AND (original_price_yearly IS NULL OR original_price_yearly = 0)");
+            // PREMIUM 原价年费/月费(sys_config member_fee_premium_original / member_fee_premium_original_monthly)
+            jdbcTemplate.update("UPDATE membership_levels SET original_price_yearly = 1599900, original_price_monthly = 159900 " +
+                    "WHERE level_code = 'PREMIUM' AND (original_price_yearly IS NULL OR original_price_yearly = 0)");
+            // FAMILY 现价补齐(现值 13140/32850/131400)
+            jdbcTemplate.update("UPDATE membership_levels SET price_monthly = 13140, price_quarterly = 32850, price_yearly = 131400 " +
+                    "WHERE level_code = 'FAMILY' AND (price_yearly IS NULL OR price_yearly = 0)");
+            // PREMIUM 现价补齐(现值 131400/1316800;季费保持 NULL=不支持)
+            jdbcTemplate.update("UPDATE membership_levels SET price_monthly = 131400, price_yearly = 1316800 " +
+                    "WHERE level_code = 'PREMIUM' AND (price_yearly IS NULL OR price_yearly = 0)");
+            log.info("已同步会员价格/原价至membership_levels");
+        } catch (Exception e) {
+            log.warn("同步membership_levels价格失败: {}", e.getMessage());
+        }
+
+        // 迁移204: 插入 LIFETIME 终身会员等级行(不可购买,仅管理员授予,价格全 0)
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_levels " +
+                    "(level_code, level_name, level_desc, price_monthly, price_quarterly, price_yearly, " +
+                    "original_price_monthly, original_price_quarterly, original_price_yearly, " +
+                    "features, max_children, max_tasks_per_day, ai_review_enabled, priority_support, teacher_consultation) VALUES " +
+                    "('LIFETIME', '终身会员', '终身家庭会员权益,永久有效无需续费', 0, 0, 0, 0, 0, 0, " +
+                    "'[\"free_activities\",\"free_courses\",\"full_price_purchase\",\"referral_commission\"," +
+                    "\"discount_purchase\",\"member_activities\",\"create_family\",\"invite_family\",\"purchase_commission\"]', " +
+                    "6, 20, 1, 1, 1)");
+            log.info("已插入LIFETIME等级种子数据");
+        } catch (Exception e) {
+            log.warn("插入LIFETIME等级种子数据失败: {}", e.getMessage());
+        }
+
+        // 迁移205: 创建平台公告表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS notices (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "title VARCHAR(200) NOT NULL COMMENT '公告标题', " +
+                            "content TEXT NOT NULL COMMENT '公告内容', " +
+                            "type VARCHAR(20) DEFAULT 'platform' COMMENT '类型: platform=平台公告 activity=活动 promotion=优惠', " +
+                            "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: draft=草稿 active=已发布 deleted=已删除', " +
+                            "admin_id BIGINT DEFAULT NULL COMMENT '发布管理员ID', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台公告'"
+            );
+            log.info("已创建notices表");
+        } catch (Exception e) {
+            log.warn("创建notices表失败: " + e.getMessage());
+        }
+
+        // 迁移206: member_upgrade_records 添加审批字段(LIFETIME 授予审批需求,修复升级记录查询 Unknown column 500)
+        ensureColumn("member_upgrade_records", "approve_remark", "VARCHAR(500) DEFAULT NULL COMMENT '审批备注'");
+        ensureColumn("member_upgrade_records", "approve_status", "VARCHAR(20) DEFAULT 'approved' COMMENT '审批状态: approved/rejected'");
+        ensureColumn("member_upgrade_records", "approve_user_id", "BIGINT DEFAULT NULL COMMENT '审批人ID'");
+        ensureColumn("member_upgrade_records", "approve_time", "DATETIME DEFAULT NULL COMMENT '审批时间'");
+
+        // 迁移207: 创建 health_plans 表(健康方案汇总记录)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS health_plans (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                            "member_ids VARCHAR(200) NOT NULL COMMENT '目标成员ID列表(逗号分隔)', " +
+                            "dimensions VARCHAR(100) NOT NULL COMMENT '维度列表(逗号分隔)', " +
+                            "goal TEXT NOT NULL COMMENT '用户输入的目标', " +
+                            "plan_content TEXT COMMENT 'LangGraph生成的方案内容', " +
+                            "member_name VARCHAR(50) COMMENT '成员名称摘要', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "INDEX idx_family (family_id), " +
+                            "INDEX idx_created (created_at)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康方案汇总记录'"
+            );
+            log.info("已创建health_plans表");
+        } catch (Exception e) {
+            log.warn("创建health_plans表失败(可能已存在): " + e.getMessage());
+        }
+
+        // 迁移208: health_gut_flora.detection_rate 列扩容(VARCHAR(50)→VARCHAR(255),源数据超长导致 MysqlDataTruncation)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora MODIFY COLUMN detection_rate VARCHAR(255) DEFAULT NULL COMMENT '检出率'");
+            log.info("health_gut_flora.detection_rate 已扩容为 VARCHAR(255)");
+        } catch (Exception e) {
+            log.warn("health_gut_flora.detection_rate 扩容失败(可能已修改): " + e.getMessage());
+        }
+
+        // 迁移225: articles 添加 growth_category 列(成长分类筛选,Article 实体已含字段,生产库缺列导致 Unknown column 'growth_category')
+        ensureColumn("articles", "growth_category", "VARCHAR(50) DEFAULT NULL COMMENT '成长分类'");
+
+        // 迁移209: 将 ecom_supplier 合并至 supply_system(商品供应商管理 → 供应商体系管理统一)
+        // 1. 给 supply_system 加联系人字段
+        ensureColumn("supply_system", "contact_name", "VARCHAR(50) DEFAULT NULL COMMENT '联系人'");
+        ensureColumn("supply_system", "contact_phone", "VARCHAR(20) DEFAULT NULL COMMENT '联系电话'");
+        ensureColumn("supply_system", "remark", "VARCHAR(500) DEFAULT NULL COMMENT '备注'");
+        log.info("迁移209: supply_system 已添加 contact_name/contact_phone/remark 列");
+
+        // 2. 将 ecom_supplier 存量数据迁移到 supply_system(以平台管理员 id=1 为默认 admin)
+        try {
+            jdbcTemplate.update(
+                    "INSERT INTO supply_system (name, admin_id, settlement_period_days, platform_profit_rate, status, " +
+                            "contact_name, contact_phone, remark, created_at, updated_at) " +
+                            "SELECT name, 1, 30, 0, " +
+                            "CASE WHEN status = 1 THEN 'active' ELSE 'disabled' END, " +
+                            "contact_name, contact_phone, remark, created_at, COALESCE(updated_at, created_at) " +
+                            "FROM ecom_supplier WHERE NOT EXISTS (" +
+                            "  SELECT 1 FROM supply_system ss WHERE ss.name = ecom_supplier.name LIMIT 1)"
+            );
+            log.info("迁移209: ecom_supplier 存量数据已迁移至 supply_system");
+        } catch (Exception e) {
+            log.warn("迁移209: ecom_supplier 迁移失败(可能已存在): {}", e.getMessage());
+        }
+    }
+
+    private void runFullRegionDataMigrations() {
+        // 迁移226: 创建 family_platform_balance 表 + family_platform_balance_log 表 + products.points_multiplier 列
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS family_platform_balance (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "  family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                            "  total_earned INT DEFAULT 0 COMMENT '累计获得', " +
+                            "  available INT DEFAULT 0 COMMENT '可用余额', " +
+                            "  frozen INT DEFAULT 0 COMMENT '冻结(提现审核中)', " +
+                            "  withdrawn INT DEFAULT 0 COMMENT '已提现', " +
+                            "  exchanged INT DEFAULT 0 COMMENT '已兑换', " +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "  UNIQUE KEY uk_family (family_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭平台积分池余额'"
+            );
+            log.info("已创建family_platform_balance表");
+        } catch (Exception e) {
+            log.warn("创建family_platform_balance表失败: " + e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS family_platform_balance_log (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "  family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                            "  type VARCHAR(20) NOT NULL COMMENT 'earn/spend/freeze/unfreeze/withdraw/adjust/migration', " +
+                            "  amount INT NOT NULL COMMENT '变动数量', " +
+                            "  balance_after INT NOT NULL COMMENT '变动后可用余额', " +
+                            "  ref_type VARCHAR(50) COMMENT 'product_order/referral_dist/activity/withdrawal/adjust/migration/refund', " +
+                            "  ref_id BIGINT COMMENT '关联业务ID', " +
+                            "  remark VARCHAR(255) COMMENT '备注', " +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "  INDEX idx_family (family_id), " +
+                            "  INDEX idx_ref (ref_type, ref_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭平台积分流水'"
+            );
+            log.info("已创建family_platform_balance_log表");
+        } catch (Exception e) {
+            log.warn("创建family_platform_balance_log表失败: " + e.getMessage());
+        }
+        ensureColumn("products", "points_multiplier", "DECIMAL(10,2) NOT NULL DEFAULT 1.00 COMMENT '平台积分倍数,1元=1积分×倍数'");
+        log.info("已添加products.points_multiplier列");
+        // 迁移227: 创建 family_platform_exchange_record 表(家庭CF值兑换记录)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS family_platform_exchange_record (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "  user_id BIGINT NOT NULL COMMENT '操作用户ID', " +
+                            "  family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                            "  coupon_id BIGINT NOT NULL COMMENT '关联优惠券ID', " +
+                            "  coupon_name VARCHAR(200) COMMENT '优惠券名称', " +
+                            "  cf_cost INT NOT NULL COMMENT '消耗CF值数量', " +
+                            "  status VARCHAR(20) NOT NULL DEFAULT 'completed' COMMENT 'completed/failed', " +
+                            "  redeem_code VARCHAR(32) COMMENT '兑换码', " +
+                            "  remark VARCHAR(255) COMMENT '备注', " +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "  INDEX idx_family_time (family_id, created_at), " +
+                            "  INDEX idx_user (user_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭CF值兑换记录'"
+            );
+            log.info("已创建family_platform_exchange_record表");
+        } catch (Exception e) {
+            log.warn("创建family_platform_exchange_record表失败: " + e.getMessage());
+        }
+
+        // 迁移228: nutrition_product 添加 ingredients 列 + suitable_for 升级为 TEXT(存放详情页图像提取的成份/配料表与适用说明)
+        ensureColumn("nutrition_product", "ingredients", "TEXT DEFAULT NULL COMMENT '成份/配料表(从详情页图片提取)'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE nutrition_product MODIFY COLUMN suitable_for TEXT DEFAULT NULL COMMENT '适用人群/使用方法'");
+            log.info("已将nutrition_product.suitable_for升级为TEXT类型");
+        } catch (Exception e) {
+            // 修改失败,忽略错误(幂等)
+        }
+
+        // 迁移222: 创建 file_tag 表(文件标签字典)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS file_tag (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "name VARCHAR(50) NOT NULL COMMENT '标签名称', " +
+                            "color VARCHAR(7) DEFAULT '#409EFF' COMMENT '标签颜色', " +
+                            "sort_order INT DEFAULT 0 COMMENT '排序号', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "UNIQUE KEY uk_name (name)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件标签字典'"
+            );
+            log.info("已创建file_tag表");
+        } catch (Exception e) {
+            log.warn("创建file_tag表失败: " + e.getMessage());
+        }
+
+        String[][] presetTags = {
+                {"测评报告", "#409EFF"}, {"健康报告", "#67C23A"}, {"文章配图", "#E6A23C"},
+                {"成长档案", "#909399"}, {"身", "#FF8C42"}, {"智", "#6366F1"},
+                {"富", "#F59E0B"}, {"行", "#10B981"}, {"心", "#FF6B9D"}, {"重要", "#F56C6C"}
+        };
+        for (int i = 0; i < presetTags.length; i++) {
+            try {
+                jdbcTemplate.update("INSERT IGNORE INTO file_tag (name, color, sort_order) VALUES (?, ?, ?)",
+                        presetTags[i][0], presetTags[i][1], i);
+            } catch (Exception e) { /* 忽略重复 */ }
+        }
+        log.info("已预置文件标签");
+
+        // 迁移223: 创建 file_tag_rel 表
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS file_tag_rel (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "file_id BIGINT NOT NULL COMMENT 'file_record.id', " +
+                            "tag_id BIGINT NOT NULL COMMENT 'file_tag.id', " +
+                            "UNIQUE KEY uk_file_tag (file_id, tag_id), " +
+                            "INDEX idx_tag (tag_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件-标签关联'"
+            );
+            log.info("已创建file_tag_rel表");
+        } catch (Exception e) {
+            log.warn("创建file_tag_rel表失败: " + e.getMessage());
+        }
+
+        // 迁移229: 新增GROWTH_COACH权益(7X24小时成长教练)到FAMILY/PREMIUM,及mascot_ai功能开关
+        try {
+            jdbcTemplate.execute("INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES " +
+                    "('FAMILY', 'GROWTH_COACH', '7X24小时成长教练', 'AI智能教练随时解答家庭教育问题', '🎯', '#FEF3C7', 10), " +
+                    "('PREMIUM', 'GROWTH_COACH', '7X24小时成长教练', 'AI智能教练随时解答家庭教育问题 + 优先响应', '🎯', '#FEF3C7', 10)");
+            log.info("已插入GROWTH_COACH权益种子数据");
+        } catch (Exception ex) {
+            log.warn("插入GROWTH_COACH权益种子数据失败: {}", ex.getMessage());
+        }
+
+
+        // 迁移232: recommendation_logs 追加 recommend_type 字段
+        ensureColumn("recommendation_logs", "recommend_type", "VARCHAR(20) DEFAULT 'tag' COMMENT '推荐方式: profile/dimension/tag'");
+        log.info("已添加recommendation_logs.recommend_type列");
+
+        // 迁移233: 将 Article/Activity 现有 dimension_weights JSON 同步到 dimension_weights 表
+        // (修正:源表名 article→articles、activity→activities;activity与article同为五维JSON对象按维度键展开;
+        //   dimension_weights表无唯一键,INSERT IGNORE不去重,用sys_config一次性标记+自连接去重兜底)
+        try {
+            Integer synced = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM sys_config WHERE config_key = 'dimension_weights_legacy_sync_done'",
+                    Integer.class);
+            if (synced == null || synced == 0) {
+                jdbcTemplate.execute(
+                        "INSERT IGNORE INTO dimension_weights (target_type, target_id, dimension, weight, enabled) " +
+                                "SELECT 'article', a.id, 'body', COALESCE(CAST(JSON_EXTRACT(a.dimension_weights, '$.body') AS SIGNED), 0), 1 " +
+                                "FROM articles a WHERE a.dimension_weights IS NOT NULL AND a.dimension_weights != '' AND JSON_EXTRACT(a.dimension_weights, '$.body') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'article', a.id, 'mind', COALESCE(CAST(JSON_EXTRACT(a.dimension_weights, '$.mind') AS SIGNED), 0), 1 " +
+                                "FROM articles a WHERE a.dimension_weights IS NOT NULL AND a.dimension_weights != '' AND JSON_EXTRACT(a.dimension_weights, '$.mind') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'article', a.id, 'wisdom', COALESCE(CAST(JSON_EXTRACT(a.dimension_weights, '$.wisdom') AS SIGNED), 0), 1 " +
+                                "FROM articles a WHERE a.dimension_weights IS NOT NULL AND a.dimension_weights != '' AND JSON_EXTRACT(a.dimension_weights, '$.wisdom') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'article', a.id, 'action', COALESCE(CAST(JSON_EXTRACT(a.dimension_weights, '$.action') AS SIGNED), 0), 1 " +
+                                "FROM articles a WHERE a.dimension_weights IS NOT NULL AND a.dimension_weights != '' AND JSON_EXTRACT(a.dimension_weights, '$.action') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'article', a.id, 'wealth', COALESCE(CAST(JSON_EXTRACT(a.dimension_weights, '$.wealth') AS SIGNED), 0), 1 " +
+                                "FROM articles a WHERE a.dimension_weights IS NOT NULL AND a.dimension_weights != '' AND JSON_EXTRACT(a.dimension_weights, '$.wealth') IS NOT NULL"
+                );
+                jdbcTemplate.execute(
+                        "INSERT IGNORE INTO dimension_weights (target_type, target_id, dimension, weight, enabled) " +
+                                "SELECT 'activity', act.id, 'body', COALESCE(CAST(JSON_EXTRACT(act.dimension_weights, '$.body') AS SIGNED), 0), 1 " +
+                                "FROM activities act WHERE act.dimension_weights IS NOT NULL AND act.dimension_weights != '' AND JSON_EXTRACT(act.dimension_weights, '$.body') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'activity', act.id, 'mind', COALESCE(CAST(JSON_EXTRACT(act.dimension_weights, '$.mind') AS SIGNED), 0), 1 " +
+                                "FROM activities act WHERE act.dimension_weights IS NOT NULL AND act.dimension_weights != '' AND JSON_EXTRACT(act.dimension_weights, '$.mind') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'activity', act.id, 'wisdom', COALESCE(CAST(JSON_EXTRACT(act.dimension_weights, '$.wisdom') AS SIGNED), 0), 1 " +
+                                "FROM activities act WHERE act.dimension_weights IS NOT NULL AND act.dimension_weights != '' AND JSON_EXTRACT(act.dimension_weights, '$.wisdom') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'activity', act.id, 'action', COALESCE(CAST(JSON_EXTRACT(act.dimension_weights, '$.action') AS SIGNED), 0), 1 " +
+                                "FROM activities act WHERE act.dimension_weights IS NOT NULL AND act.dimension_weights != '' AND JSON_EXTRACT(act.dimension_weights, '$.action') IS NOT NULL " +
+                                "UNION ALL " +
+                                "SELECT 'activity', act.id, 'wealth', COALESCE(CAST(JSON_EXTRACT(act.dimension_weights, '$.wealth') AS SIGNED), 0), 1 " +
+                                "FROM activities act WHERE act.dimension_weights IS NOT NULL AND act.dimension_weights != '' AND JSON_EXTRACT(act.dimension_weights, '$.wealth') IS NOT NULL"
+                );
+                jdbcTemplate.execute(
+                        "DELETE dw1 FROM dimension_weights dw1 INNER JOIN dimension_weights dw2 ON " +
+                                "dw1.target_type = dw2.target_type AND dw1.target_id = dw2.target_id AND dw1.dimension = dw2.dimension " +
+                                "AND dw1.id > dw2.id"
+                );
+                jdbcTemplate.update("INSERT INTO sys_config (config_key, config_value, description) VALUES ('dimension_weights_legacy_sync_done', '1', 'Article/Activity五维权重已同步到dimension_weights表')");
+                log.info("已完成Article/Activity维度权重到dimension_weights表的一次性迁移");
+            } else {
+                log.info("迁移233: 维度权重已同步过,跳过");
+            }
+        } catch (Exception ex) {
+            log.warn("Article/Activity维度权重同步失败: {}", ex.getMessage());
+        }
+
+        // 迁移234: 创建 problem_domains 表(问题域定义,改版v1:需求驱动·逐步解锁)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS problem_domains (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "code VARCHAR(50) NOT NULL COMMENT '唯一编码: weight_loss/sleep/constipation', " +
+                            "name VARCHAR(50) NOT NULL COMMENT '名称: 减肥/睡眠/便秘', " +
+                            "icon VARCHAR(255) DEFAULT '' COMMENT '图标', " +
+                            "description VARCHAR(500) DEFAULT '' COMMENT '一句话描述', " +
+                            "flow_intro_json TEXT COMMENT '服务流程介绍(7步) JSON', " +
+                            "survey_template_id BIGINT DEFAULT NULL COMMENT '专项调研问卷模板ID(SurveyTemplate.id)', " +
+                            "content_tags VARCHAR(500) DEFAULT '' COMMENT '内容标签,逗号分隔(过滤文章/知识库)', " +
+                            "recommended_product_ids VARCHAR(500) DEFAULT '' COMMENT '推荐产品ID,逗号分隔(Product.id)', " +
+                            "task_template_package_ids VARCHAR(500) DEFAULT '' COMMENT '套餐绑定的任务模板包ID(TaskTemplatePackage.id)', " +
+                            "reward_cf_value INT DEFAULT 0 COMMENT '全部完成奖励CF值', " +
+                            "status TINYINT DEFAULT 1 COMMENT '1启用 0停用', " +
+                            "sort_order INT DEFAULT 0, " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "UNIQUE KEY uk_code (code)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问题域定义(改版v1)'"
+            );
+            log.info("已创建problem_domains表");
+        } catch (Exception e) {
+            log.warn("创建problem_domains表失败: " + e.getMessage());
+        }
+
+        // 迁移235: 创建 user_intents 表(用户意图与服务旅程)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS user_intents (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                            "intent_type VARCHAR(20) DEFAULT '' COMMENT 'A/B/C 用户类型', " +
+                            "problem_domain_code VARCHAR(50) DEFAULT '' COMMENT '问题域编码(仅C类)', " +
+                            "journey_stage INT DEFAULT 0 COMMENT '旅程阶段 0-7', " +
+                            "stage_updated_at DATETIME DEFAULT NULL COMMENT '最近阶段变化时间', " +
+                            "survey_template_id BIGINT DEFAULT NULL COMMENT '最近作答问卷模板ID', " +
+                            "survey_submitted_at DATETIME DEFAULT NULL COMMENT '最近问卷提交时间', " +
+                            "survey_result_json TEXT COMMENT '问卷答案→推荐结果快照', " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "UNIQUE KEY uk_user (user_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户意图与服务旅程(改版v1)'"
+            );
+            log.info("已创建user_intents表");
+        } catch (Exception e) {
+            log.warn("创建user_intents表失败: " + e.getMessage());
+        }
+
+        // 迁移236: 创建 user_notifications 表(用户级站内主动通知,与notices平台公告并存)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS user_notifications (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "user_id BIGINT NOT NULL COMMENT '接收用户ID', " +
+                            "type VARCHAR(30) DEFAULT 'report_ready' COMMENT 'report_ready/reward_granted/logistics/system', " +
+                            "title VARCHAR(200) NOT NULL, " +
+                            "content VARCHAR(1000) DEFAULT '', " +
+                            "ref_type VARCHAR(30) DEFAULT '' COMMENT '关联对象类型: report/package/task', " +
+                            "ref_id BIGINT DEFAULT NULL COMMENT '关联对象ID', " +
+                            "is_read TINYINT DEFAULT 0, " +
+                            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "INDEX idx_user_read (user_id, is_read)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内通知(改版v1)'"
+            );
+            log.info("已创建user_notifications表");
+        } catch (Exception e) {
+            log.warn("创建user_notifications表失败: " + e.getMessage());
+        }
+
+        // 迁移237: tasks 表添加 member_only 字段(1=会员任务,仅方案/套餐生成)
+        ensureColumn("tasks", "member_only", "TINYINT DEFAULT 0 COMMENT '1=会员任务(方案/套餐生成),执行需会员; 0=普通任务可免费执行'");
+
+        // 迁移238: package_orders 表添加物流状态字段(试剂盒寄送/样本寄回)
+        ensureColumn("package_orders", "logistics_status", "VARCHAR(20) DEFAULT 'none' COMMENT 'none/kit_shipped/kit_received/sample_shipped/sample_received'");
+        ensureColumn("package_orders", "tracking_no", "VARCHAR(100) DEFAULT '' COMMENT '快递单号'");
+        log.info("已添加package_orders物流字段");
+
+        // 迁移239: 创建 health_status 表(当前状态调研-健康现状档案,SS-2026-08-13)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS health_status (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "user_id BIGINT NOT NULL COMMENT '所属账号(一个账号一份)', " +
+                            "height_cm DECIMAL(5,1) DEFAULT NULL COMMENT '身高(cm)', " +
+                            "weight_kg DECIMAL(5,1) DEFAULT NULL COMMENT '体重(kg)', " +
+                            "blood_pressure VARCHAR(20) DEFAULT NULL COMMENT '血压,如 120/80', " +
+                            "blood_glucose DECIMAL(4,1) DEFAULT NULL COMMENT '空腹血糖(mmol/L)', " +
+                            "blood_lipids JSON DEFAULT NULL COMMENT '血脂 [{name,value,unit,note}]', " +
+                            "disease_history JSON DEFAULT NULL COMMENT '疾病史 [{name,note}]', " +
+                            "medications JSON DEFAULT NULL COMMENT '在服药物/治疗 [{name,note}]', " +
+                            "notes VARCHAR(500) DEFAULT NULL COMMENT '其他补充说明', " +
+                            "filled_at DATETIME DEFAULT NULL COMMENT '首次填写时间', " +
+                            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
+                            "UNIQUE KEY uk_user (user_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='当前状态调研-健康现状档案'"
+            );
+            log.info("已创建health_status表");
+        } catch (Exception e) {
+            log.warn("创建health_status表失败: " + e.getMessage());
+        }
+
+        // 迁移240: 创建 system_prompts 表(系统提示词统一管理,cfc-web后台可维护)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS system_prompts (" +
+                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "prompt_key VARCHAR(64) NOT NULL COMMENT '提示词键(如 health_coach/analysis/chat)', " +
+                            "title VARCHAR(100) NOT NULL COMMENT '提示词标题', " +
+                            "content TEXT NOT NULL COMMENT '提示词内容', " +
+                            "default_content TEXT COMMENT '出厂默认内容(重置时恢复)', " +
+                            "version INT NOT NULL DEFAULT 1 COMMENT '版本号', " +
+                            "status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=禁用', " +
+                            "remark VARCHAR(255) COMMENT '备注', " +
+                            "updated_by VARCHAR(64) COMMENT '更新人', " +
+                            "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                            "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "UNIQUE KEY uk_prompt_key (prompt_key)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统提示词管理'"
+            );
+            log.info("已创建system_prompts表");
+        } catch (Exception e) {
+            log.warn("创建system_prompts表失败: " + e.getMessage());
+        }
+
+        // 迁移240b: 插入默认系统提示词(仅当表为空时)
+        try {
+            jdbcTemplate.execute(
+                    "INSERT IGNORE INTO system_prompts (prompt_key, title, content, default_content, version, remark) VALUES " +
+                            "('health_butler', '家庭健康管家', " +
+                            "'你是一个家庭AI健康管家, 为用户提供专业、温暖的个性化健康建议。\\n你的能力:\\n1. 基于健康知识库(菌属功能、指标解读、营养素、食物)提供科学建议\\n2. 解读健康报告数据, 结合知识库给出改善方案\\n3. 根据菌群检测结果推荐精准营养方案\\n4. 提供可执行的健康微行动\\n回答原则:\\n1. 用中文, 像家庭医生一样亲切但专业\\n2. 引用知识库中的菌属/指标/食物信息作为证据\\n3. 优先使用知识库数据, 其次使用通用健康知识\\n4. 需要产品推荐时使用搜索工具\\n5. 生成 [TASK: {\"title\": \"任务名\", \"dimension\": \"身\", \"points\": 10, \"frequency\": \"每天\"}] 标记创建健康行动任务\\n6. 超出知识库范围的慢性病/急症问题, 建议咨询专业医生\\n7. 不要编造医学建议, 不夸大效果', " +
+                            "'你是一个家庭AI健康管家, 为用户提供专业、温暖的个性化健康建议。\\n你的能力:\\n1. 基于健康知识库(菌属功能、指标解读、营养素、食物)提供科学建议\\n2. 解读健康报告数据, 结合知识库给出改善方案\\n3. 根据菌群检测结果推荐精准营养方案\\n4. 提供可执行的健康微行动\\n回答原则:\\n1. 用中文, 像家庭医生一样亲切但专业\\n2. 引用知识库中的菌属/指标/食物信息作为证据\\n3. 优先使用知识库数据, 其次使用通用健康知识\\n4. 需要产品推荐时使用搜索工具\\n5. 生成 [TASK: {\"title\": \"任务名\", \"dimension\": \"身\", \"points\": 10, \"frequency\": \"每天\"}] 标记创建健康行动任务\\n6. 超出知识库范围的慢性病/急症问题, 建议咨询专业医生\\n7. 不要编造医学建议, 不夸大效果', " +
+                            "1, 'AI健康管家对话'), " +
+                            "('health_coach', '儿童健康教练', " +
+                            "'你是一个儿童健康教练,喜欢用提问的方式引导家长发现问题和解决方案。\\n核心原则:\\n1. 先问后答:不要直接给答案,用问题引导家长思考\\n2. 用开放式问题了解情况,再给针对性建议\\n3. 语气温暖,像一个耐心的教练在陪伴家长成长\\n你的职责:\\n1. 儿童体质调理(如挑食、瘦弱、易疲劳、反复生病)—— 用提问了解情况\\n2. 科学喂养和营养建议 —— 先问饮食现状再给建议\\n3. 日常保健(睡眠、运动、季节防护)—— 引导家长发现生活中的问题\\n4. 体质辨识基础咨询 —— 通过提问帮助家长理解孩子体质特点\\n5. 健康问题判断 —— 先收集症状信息再建议是否就医\\n回答结构: 提问-分析-建议-跟进', " +
+                            "'你是一个儿童健康教练,喜欢用提问的方式引导家长发现问题和解决方案。\\n核心原则:\\n1. 先问后答:不要直接给答案,用问题引导家长思考\\n2. 用开放式问题了解情况,再给针对性建议\\n3. 语气温暖,像一个耐心的教练在陪伴家长成长\\n你的职责:\\n1. 儿童体质调理(如挑食、瘦弱、易疲劳、反复生病)—— 用提问了解情况\\n2. 科学喂养和营养建议 —— 先问饮食现状再给建议\\n3. 日常保健(睡眠、运动、季节防护)—— 引导家长发现生活中的问题\\n4. 体质辨识基础咨询 —— 通过提问帮助家长理解孩子体质特点\\n5. 健康问题判断 —— 先收集症状信息再建议是否就医\\n回答结构: 提问-分析-建议-跟进', " +
+                            "1, '儿童健康咨询对话'), " +
+                            "('chat_assistant', '儿童成长助手', " +
+                            "'你是一个儿童成长家庭助手, 回答关于孩子成长、健康、教育的各种问题。', " +
+                            "'你是一个儿童成长家庭助手, 回答关于孩子成长、健康、教育的各种问题。', " +
+                            "1, '通用对话助手')"
+            );
+            log.info("已插入默认系统提示词");
+        } catch (Exception e) {
+            log.warn("插入默认系统提示词失败: " + e.getMessage());
+        }
+        // 迁移242: 创建 sys_menu 表(管理后台侧边栏菜单,数据库驱动 + 角色授权)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS sys_menu (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "  parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父菜单ID,0=顶级', " +
+                            "  title VARCHAR(50) COMMENT '目录标题(有子菜单时用,对应 el-submenu)', " +
+                            "  label VARCHAR(50) COMMENT '叶子菜单显示名(对应 el-menu-item)', " +
+                            "  path VARCHAR(200) COMMENT '路由路径(叶子菜单必填,如 /families)', " +
+                            "  icon VARCHAR(100) COMMENT 'Element UI 图标类名,如 el-icon-s-custom', " +
+                            "  perm VARCHAR(100) COMMENT '权限标识,如 family:list / task:* / system:config', " +
+                            "  sort INT DEFAULT 0 COMMENT '排序号,小的在前', " +
+                            "  visible TINYINT DEFAULT 1 COMMENT '是否显示 1=显示 0=隐藏', " +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                            "  INDEX idx_parent (parent_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台侧边栏菜单表'"
+            );
+            log.info("已创建sys_menu表");
+        } catch (Exception e) {
+            log.warn("创建sys_menu表失败: " + e.getMessage());
+        }
+
+        // 迁移243: 创建 sys_role_menu 表(角色-菜单授权)
+        try {
+            jdbcTemplate.execute(
+                    "CREATE TABLE IF NOT EXISTS sys_role_menu (" +
+                            "  id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                            "  role VARCHAR(50) NOT NULL COMMENT '角色标识: admin/teacher/nutritionist/article_manager/activity_manager/supplier_admin', " +
+                            "  menu_id BIGINT NOT NULL COMMENT 'sys_menu.id', " +
+                            "  created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                            "  UNIQUE KEY uk_role_menu (role, menu_id)" +
+                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色-菜单授权表'"
+            );
+            log.info("已创建sys_role_menu表");
+        } catch (Exception e) {
+            log.warn("创建sys_role_menu表失败: " + e.getMessage());
+        }
+
+        // 迁移244: sys_menu 菜单种子数据(对应 Layout.vue 硬编码 menuItems,表为空才插入,幂等)
+        try {
+            Integer menuCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sys_menu", Integer.class);
+            if (menuCount == null || menuCount == 0) {
+                seedSysMenus();
+                log.info("sys_menu 种子数据已插入");
+            } else {
+                log.info("sys_menu 已有 {} 条数据,跳过种子插入", menuCount);
+            }
+        } catch (Exception e) {
+            log.warn("初始化sys_menu种子数据失败: " + e.getMessage());
+        }
+
+// 迁移245: 创建 notification_templates 表(通知模板管理 W3)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS notification_templates (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "type VARCHAR(30) NOT NULL COMMENT '通知类型: report_ready/reward_granted/system', " +
+                    "title VARCHAR(200) NOT NULL COMMENT '默认标题(支持占位符)', " +
+                    "content VARCHAR(1000) NOT NULL COMMENT '默认内容模板(支持占位符)', " +
+                    "channels VARCHAR(100) DEFAULT 'in_app' COMMENT '推送通道: in_app,sms,wechat,phone 逗号分隔', " +
+                    "status TINYINT DEFAULT 1 COMMENT '1启用 0停用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_type (type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知模板(改版v1 W3)'");
+            log.info("已创建notification_templates表");
+        } catch (Exception e) {
+            log.warn("创建notification_templates表可能已存在: {}", e.getMessage());
+        }
+        // 迁移245b: notification_templates 种子数据(表为空才插入,幂等)
+        try {
+            Integer tplCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM notification_templates", Integer.class);
+            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'), " +
+                        "('system', '系统通知', '您有一条新的系统通知,请前往查看。', 'in_app'), " +
+                        "('energy_change', '能量变动', '{description}获得{amount}能量', 'in_app'), " +
+                        "('points_change', '积分变动', '{description}变动{amount}积分', 'in_app'), " +
+                        "('cf_change', 'CF值变动', '{remark}获得{amount} CF值', 'in_app'), " +
+                        "('procurement_completed', '采购任务已完成', '您的【{comboTier}】套餐采购任务已完成,共{productCount}件商品,下单时间:{createTime},产品即将送达,请及时查收。', 'in_app,wechat')");
+                log.info("notification_templates 种子数据已插入");
+            } else {
+                log.info("notification_templates 已有 {} 条数据,跳过种子插入", tplCount);
+            }
+        } catch (Exception e) {
+            log.warn("初始化notification_templates种子数据失败: " + e.getMessage());
+        }
+
+        // 迁移246: product_skus 存量价格 ×100(元→分),sys_config 幂等标记
+        try {
+            Integer migrated = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM sys_config WHERE config_key = 'sku_price_to_cents_done'",
+                    Integer.class);
+            if (migrated == null || migrated == 0) {
+                jdbcTemplate.update("UPDATE product_skus SET price = IFNULL(price, 0) * 100, original_price = IFNULL(original_price, 0) * 100");
+                jdbcTemplate.update("INSERT INTO sys_config (config_key, config_value, description) VALUES ('sku_price_to_cents_done', '1', 'SKU价格已迁移为分单位')");
+                log.info("迁移246: product_skus 价格已×100(元→分)");
+            } else {
+                log.info("迁移246: product_skus 价格已迁移过,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("迁移246失败: {}", 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'");
+
+        // 迁移248: families 添加 butler_id(B-5 管家行级隔离)
+        ensureColumn("families", "butler_id", "BIGINT COMMENT '绑定的管家userId'");
+        try {
+            jdbcTemplate.execute("CREATE INDEX idx_families_butler_id ON families(butler_id)");
+            log.info("已创建families.butler_id索引");
+        } catch (Exception e) {
+            String msg = e.getMessage();
+            if (msg == null || !(msg.contains("Duplicate") || msg.contains("exists"))) {
+                log.warn("创建families.butler_id索引失败: {}", msg);
+            }
+        }
+
+        // 迁移249: sys_menu 菜单归类调整 + 权限点拆分(《角色权限与菜单归类整合方案》第二部分/B-3,幂等)
+        migrateSysMenuReorg();
+
+        // 迁移250: 清理测试脏数据(B-7:TEST-Energy-Grant 测试任务、乱码昵称)
+        try {
+            int deletedTasks = jdbcTemplate.update("DELETE FROM tasks WHERE title LIKE 'TEST-Energy-Grant%'");
+            int fixedNicknames = jdbcTemplate.update("UPDATE users SET nickname = '测试家庭' WHERE nickname LIKE '娴嬭瘯%' AND nickname <> '测试家庭'");
+            if (deletedTasks > 0 || fixedNicknames > 0) {
+                log.info("迁移250: 已清理测试任务 {} 条,修复乱码昵称 {} 个", deletedTasks, fixedNicknames);
+            }
+        } catch (Exception e) {
+            log.warn("迁移250失败: {}", e.getMessage());
+        }
+
+        // 迁移251/372: 健康饮食组报告管理模块重组(挂载报告管理/代传报告/应季食材,删除健康报告/报告审核菜单)
+        migrateHealthMenuMount();
+
+        // 迁移252: 创建用户画像表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS profile_snapshot (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL UNIQUE, " +
+                    "dimension_scores JSON DEFAULT NULL, " +
+                    "body_metrics JSON DEFAULT NULL, " +
+                    "mind_metrics JSON DEFAULT NULL, " +
+                    "wisdom_metrics JSON DEFAULT NULL, " +
+                    "action_metrics JSON DEFAULT NULL, " +
+                    "wealth_metrics JSON DEFAULT NULL, " +
+                    "problem_domains VARCHAR(500) DEFAULT NULL, " +
+                    "computed_at DATETIME DEFAULT NULL, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_id (member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建profile_snapshot表");
+        } catch (Exception e) {
+            log.warn("创建profile_snapshot表可能已存在: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS profile_history (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "snapshot_date DATE NOT NULL, " +
+                    "all_metrics JSON NOT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_member_date (member_id, snapshot_date)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建profile_history表");
+        } catch (Exception e) {
+            log.warn("创建profile_history表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移254: 创建 report_template 表(报告展示模板配置)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_template (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "name VARCHAR(100) NOT NULL COMMENT '模板名称', " +
+                    "report_type VARCHAR(50) NOT NULL COMMENT '报告类型: gut_flora/dan/physical_exam/tongue', " +
+                    "config JSON NOT NULL COMMENT '模板配置JSON: fixedBlocks+freeDisplay', " +
+                    "is_active TINYINT DEFAULT 1 COMMENT '0=停用 1=启用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_report_type (report_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告展示模板'");
+            log.info("已创建report_template表");
+        } catch (Exception e) {
+            log.warn("report_template表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移255: report_unknown_upload 添加 annotation 列(管理员标注训练数据)
+        ensureColumn("report_unknown_upload", "annotation", "TEXT COMMENT '管理员标注: 手动指定类型+确认提取结果JSON'");
+
+        // 迁移256: social_circle 添加圈子管理字段
+        ensureColumn("social_circle", "description", "VARCHAR(500) NULL COMMENT '圈子描述'");
+        ensureColumn("social_circle", "cover_image", "VARCHAR(500) NULL COMMENT '封面图URL'");
+        ensureColumn("social_circle", "join_condition", "TINYINT(1) DEFAULT 0 COMMENT '加入条件: 0=公开/1=审核/2=邀请'");
+        ensureColumn("social_circle", "review_required", "TINYINT(1) DEFAULT 0 COMMENT '是否需要审核: 0=否/1=是'");
+        ensureColumn("social_circle", "is_public", "TINYINT(1) DEFAULT 1 COMMENT '是否公开可见: 0=否/1=是'");
+
+        // 迁移257: social_circle_member 添加审核状态和角色字段
+        ensureColumn("social_circle_member", "status", "VARCHAR(20) DEFAULT 'approved' COMMENT '申请状态: pending/approved/rejected'");
+        ensureColumn("social_circle_member", "role", "VARCHAR(20) DEFAULT 'member' COMMENT '角色: member/admin'");
+
+        // 迁移258: 创建圈子动态帖子表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle_post (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL COMMENT '圈子ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '发布者成员ID', " +
+                    "member_type VARCHAR(20) NOT NULL COMMENT 'parent/child', " +
+                    "type VARCHAR(50) NOT NULL COMMENT '帖子类型: checkin/diet/exercise/sleep/water/mood/text', " +
+                    "content TEXT COMMENT '文字内容', " +
+                    "checkin_id BIGINT COMMENT '关联打卡记录ID', " +
+                    "image_urls TEXT COMMENT '图片URL列表JSON', " +
+                    "like_count INT DEFAULT 0 COMMENT '点赞数', " +
+                    "comment_count INT DEFAULT 0 COMMENT '评论数', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "deleted TINYINT(1) DEFAULT 0, " +
+                    "INDEX idx_circle_time (circle_id, created_at), " +
+                    "INDEX idx_member_time (member_id, created_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子动态帖子'");
+            log.info("已创建social_circle_post表");
+        } catch (Exception e) {
+            log.warn("social_circle_post表可能已存在");
+        }
+
+        // 迁移259: 创建圈子点赞表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle_like (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL, " +
+                    "post_id BIGINT NOT NULL COMMENT '被点赞的帖子ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '点赞成员ID', " +
+                    "member_type VARCHAR(20) NOT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_like (circle_id, post_id, member_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子点赞记录'");
+            log.info("已创建social_circle_like表");
+        } catch (Exception e) {
+            log.warn("social_circle_like表可能已存在");
+        }
+
+        // 迁移260: 创建圈子评论表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle_comment (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "circle_id BIGINT NOT NULL, " +
+                    "post_id BIGINT NOT NULL COMMENT '所属帖子ID', " +
+                    "parent_id BIGINT DEFAULT NULL COMMENT '回复的评论ID(NULL=一级评论)', " +
+                    "member_id BIGINT NOT NULL COMMENT '评论者成员ID', " +
+                    "member_type VARCHAR(20) NOT NULL, " +
+                    "content TEXT NOT NULL COMMENT '评论内容', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_post_time (post_id, created_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子评论记录'");
+            log.info("已创建social_circle_comment表");
+        } catch (Exception e) {
+            log.warn("social_circle_comment表可能已存在");
+        }
+
+        // 迁移261: butler_profiles 添加停接新单开关(管家自助选择功能)
+        ensureColumn("butler_profiles", "accepting",
+                "TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否接受新家庭绑定: 0=停接/1=接单'");
+        // 迁移262: family_join_requests 添加 inviter_user_id 列(审批人=邀请人,谁分享的卡谁确认)
+        try {
+            jdbcTemplate.execute("ALTER TABLE family_join_requests ADD COLUMN inviter_user_id BIGINT NULL COMMENT '邀请人用户ID(分享邀请令牌的人)' AFTER invite_ref");
+            jdbcTemplate.execute("ALTER TABLE family_join_requests ADD INDEX idx_inviter (inviter_user_id)");
+            log.info("已添加inviter_user_id列到family_join_requests表");
+        } catch (Exception e) {
+            // 列/索引已存在,忽略错误
+            log.warn("family_join_requests.inviter_user_id可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移263: users 表添加 portrait_prompt 列(用户画像 prompt 拼装需求)
+        try {
+            jdbcTemplate.execute("ALTER TABLE users ADD COLUMN portrait_prompt TEXT COMMENT '用户自定义画像 prompt' AFTER mascot");
+            log.info("已添加 portrait_prompt 列到 users 表");
+        } catch (Exception e) {
+            log.warn("users.portrait_prompt可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移264: 方案分解任务补全 family_member_id,修正 deadline 落在次日 00:00 的边界问题
+        try {
+            jdbcTemplate.update(
+                    "UPDATE tasks SET family_member_id = child_id " +
+                    "WHERE source_type = 'plan' AND family_member_id IS NULL AND child_id IS NOT NULL");
+            jdbcTemplate.update(
+                    "UPDATE tasks SET deadline = TIMESTAMP(DATE(deadline)) - INTERVAL 1 SECOND " +
+                    "WHERE source_type = 'plan' AND TIME(deadline) = '00:00:00'");
+            log.info("已修复方案分解任务的 family_member_id 与 deadline 边界");
+        } catch (Exception e) {
+            log.warn("修复方案分解任务字段失败: {}", e.getMessage());
+        }
+
+        // 迁移265: 方案分解任务开启审核,打卡后需规划师/管理员审核通过才发放积分
+        try {
+            jdbcTemplate.update(
+                    "UPDATE tasks SET need_review = 1 " +
+                    "WHERE source_type = 'plan' AND (need_review IS NULL OR need_review = 0) AND status IN ('pending', 'pending_review')");
+            log.info("已开启方案分解任务的审核模式");
+        } catch (Exception e) {
+            log.warn("开启方案分解任务审核失败: {}", e.getMessage());
+        }
+
+        // 迁移266: tasks.status 扩展 pending_review/locked(打卡提交待审核、前置任务锁定)
+        try {
+            jdbcTemplate.execute(
+                    "ALTER TABLE tasks MODIFY COLUMN `status` " +
+                    "ENUM('pending','assigned','in_progress','completed','rejected','overdue','cancelled','locked','pending_review') " +
+                    "DEFAULT 'pending' COMMENT '状态: pending=待完成 assigned=待接受 in_progress=进行中 completed=已完成 rejected=已拒绝 overdue=已过期 cancelled=已取消 locked=前置锁定 pending_review=待审核'");
+            log.info("已扩展 tasks.status 枚举,支持 pending_review/locked");
+        } catch (Exception e) {
+            log.warn("扩展 tasks.status 枚举失败(可能已修改): {}", e.getMessage());
+        }
+
+        // 迁移267: 方案中心菜单(插在家庭运营与任务中心之间)
+        migratePlanCenterMenu();
+        // 迁移268: coupon 表添加 status 列(优惠券停用/启用)
+        migrateCouponStatus();
+// 迁移276: five_dimension_self_checks 表添加 question_ids_json 列(自检轮换用)
+        ensureColumn("five_dimension_self_checks", "question_ids_json", "VARCHAR(255) COMMENT '本次自检使用的题号JSON: [1,5,9,...]'");
+
+        // 迁移277: 创建 self_check_ignores 表(记录用户忽略自检提醒的动作,作为 15 天周期重置依据)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS self_check_ignores (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "check_id BIGINT COMMENT '忽略的自检记录ID(可空)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '忽略时间', " +
+                    "INDEX idx_user_id (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维自检忽略提醒记录'");
+            log.info("已创建 self_check_ignores 表");
+        } catch (Exception e) {
+            log.warn("创建 self_check_ignores 表失败: {}", e.getMessage());
+        }
+
+        // 迁移275: users 表添加 family_inviter_id(家庭邀请追踪)
+        ensureColumn("users", "family_inviter_id", "BIGINT DEFAULT NULL COMMENT '家庭邀请人ID(首次注册时绑定,用于追踪邀请来源)'");
+    }
+
+    /**
+     * 迁移267: 存量库插入「方案中心」一级菜单及子菜单(幂等)
+     * 位置:家庭运营(sort=1) 与 任务中心 之间;任务中心及之后顶级菜单 sort+1
+     */
+    private void migratePlanCenterMenu() {
+        try {
+            Long existed = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=0 AND title='方案中心'");
+            if (existed == null) {
+                // 为任务中心及之后的顶级菜单腾出 sort=2
+                jdbcTemplate.update(
+                        "UPDATE sys_menu SET sort = sort + 1 WHERE parent_id=0 AND sort >= 2 " +
+                        "AND title <> '方案中心'");
+                jdbcTemplate.update(
+                        "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) " +
+                        "VALUES (0, '方案中心', NULL, NULL, 'el-icon-notebook-2', 'plan:*', 2, 1)");
+                existed = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+                log.info("迁移267: 已插入方案中心一级菜单");
+            }
+            if (existed == null) {
+                return;
+            }
+            Long healthPlanId = insertPlanCenterChild(existed, "健康方案管理", "/health-plan-manage",
+                    "el-icon-first-aid-kit", "plan:health", 0);
+            Long assessmentPlanId = insertPlanCenterChild(existed, "测评方案管理", "/assessment-plan-manage",
+                    "el-icon-edit", "plan:assessment", 1);
+            Integer grantCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sys_role_menu", Integer.class);
+            if (grantCount != null && grantCount > 0) {
+                String[] roles = {"admin", "butler", "nutritionist", "operator"};
+                Long[] menuIds = {existed, healthPlanId, assessmentPlanId};
+                for (String role : roles) {
+                    for (Long menuId : menuIds) {
+                        grantMenuToRole(role, menuId);
+                    }
+                }
+            }
+            log.info("迁移267: 方案中心菜单挂载完成");
+        } catch (Exception e) {
+            log.warn("迁移267失败: {}", e.getMessage());
+        }
+    }
+
+    private Long insertPlanCenterChild(Long parentId, String label, String path, String icon, String perm, int sort) {
+        try {
+            Long existed = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=" + parentId + " AND path='" + path + "'");
+            if (existed != null) {
+                return existed;
+            }
+            jdbcTemplate.update(
+                    "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) VALUES (?, NULL, ?, ?, ?, ?, ?, 1)",
+                    parentId, label, path, icon, perm, sort
+            );
+            return jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+        } catch (Exception e) {
+            log.warn("迁移267: 挂载子菜单「{}」失败: {}", label, e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 迁移268: coupon 表添加 status 列(优惠券停用/启用,幂等)
+     * 存量数据 status 为 NULL,查询层按 ACTIVE 处理,不影响现有券。
+     */
+    private void migrateCouponStatus() {
+        try {
+            jdbcTemplate.execute("ALTER TABLE coupon ADD COLUMN status VARCHAR(16) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE=启用/DISABLED=停用'");
+            log.info("迁移268: 已为 coupon 表添加 status 列");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+    }
+
+    /**
+     * 迁移249: 存量库 sys_menu 菜单归类调整 + 权限点拆分(幂等,按 path/title 定位)
+     * 1. 审核中心改为分组,归集:服务角色申请审核/活动审核/待退款管理/收益提现审核
+     * 2. 能量规则/能量行为/周期性服务 → 系统配置·健康配置
+     * 3. LangGraph 管理 → 报告解析系统
+     * 4. 测评管理/测评订单/待分配规划师 → 测评咨询(删除系统配置下原"测评管理"子组)
+     * 5. 菜单组权限统一 模块:* 格式;system:config 拆分为 system:base/config:member/config:dimension/config:health/config:report-parser
+     * 6. 术语统一:七维能量配置 → 五维能量配置
+     */
+    private void migrateSysMenuReorg() {
+        try {
+            // A. 审核中心:顶级叶子 → 分组,并插入"审核中心总览"子项
+            jdbcTemplate.update("UPDATE sys_menu SET title='审核中心', label=NULL, path=NULL, perm='audit', sort=3 WHERE path='/review-center' AND parent_id=0 AND title IS NULL");
+            Long reviewGroup = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=0 AND title='审核中心' AND (path IS NULL OR path='')");
+            if (reviewGroup != null) {
+                Integer childCount = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM sys_menu WHERE parent_id=" + reviewGroup + " AND path='/review-center'", Integer.class);
+                if (childCount == null || childCount == 0) {
+                    jdbcTemplate.update(
+                            "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) VALUES (?, NULL, '审核中心总览', '/review-center', 'el-icon-s-check', 'audit', 0, 1)",
+                            reviewGroup);
+                }
+                // 迁入审核中心
+                moveMenu("/service-role-applications", reviewGroup, "audit:service-role", 1);
+                moveMenu("/activity-review", reviewGroup, "audit:activity", 2);
+                moveMenu("/pending-refund", reviewGroup, "audit:refund", 3);
+                moveMenu("/family-earnings-withdraw", reviewGroup, "audit:withdraw", 4);
+            }
+
+            // B. 能量规则/能量行为/周期性服务 → 系统配置·健康配置
+            Long healthConfig = queryMenuId("SELECT id FROM sys_menu WHERE title='健康配置' AND (path IS NULL OR path='')");
+            if (healthConfig != null) {
+                jdbcTemplate.update("UPDATE sys_menu SET perm='config:health' WHERE id=" + healthConfig);
+                moveMenu("/energy-rule", healthConfig, "config:health", 3);
+                moveMenu("/energy-behavior-config", healthConfig, "config:health", 4);
+                moveMenu("/periodic-service-config", healthConfig, "config:health", 5);
+            }
+
+            // C. LangGraph 管理 → 报告解析系统
+            Long reportParser = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=0 AND title='报告解析系统'");
+            if (reportParser != null) {
+                jdbcTemplate.update("UPDATE sys_menu SET perm='config:report-parser' WHERE id=" + reportParser);
+                moveMenu("/langgraph-admin", reportParser, "config:report-parser", 4);
+                updateMenuPermByPath("/report-types", "config:report-parser");
+                updateMenuPermByPath("/report-parser-import", "config:report-parser");
+                updateMenuPermByPath("/report-unknown-clusters", "config:report-parser");
+                updateMenuPermByPath("/report-auto-learn", "config:report-parser");
+            }
+
+            // D. 测评管理/测评订单/待分配规划师 → 测评咨询
+            Long assessment = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=0 AND title='测评咨询'");
+            Long oldAssessmentManage = queryMenuId("SELECT id FROM sys_menu WHERE title='测评管理' AND (path IS NULL OR path='')");
+            if (assessment != null) {
+                moveMenu("/assessment-admin", assessment, "assessment:manage", 0);
+                moveMenu("/assessment-orders", assessment, "assessment:orders", 1);
+                moveMenu("/assessment-assign", assessment, "assessment:assign", 2);
+            }
+            if (oldAssessmentManage != null && assessment != null && !oldAssessmentManage.equals(assessment)) {
+                // 子项已迁走,删除系统配置下空的"测评管理"子组及其授权关联
+                jdbcTemplate.update("DELETE FROM sys_role_menu WHERE menu_id=" + oldAssessmentManage);
+                jdbcTemplate.update("DELETE FROM sys_menu WHERE id=" + oldAssessmentManage);
+            }
+
+            // E. 菜单组权限统一 模块:* 格式(废弃 operation/content/commerce/health 单段写法)
+            jdbcTemplate.update("UPDATE sys_menu SET perm='family:*' WHERE parent_id=0 AND title='家庭运营'");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='articles:*' WHERE parent_id=0 AND title='知识中心'");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='commerce:*' WHERE parent_id=0 AND title='商城营销'");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='health:*' WHERE parent_id=0 AND title='健康饮食'");
+
+            // F. system:config 拆分
+            updateMenuPermByPath("/sys-config", "system:base");
+            updateMenuPermByPath("/gates", "system:base");
+            updateMenuPermByPath("/membership-center", "config:member");
+            updateMenuPermByPath("/badge-manage", "config:member");
+            updateMenuPermByPath("/virtual-goods-config", "config:member");
+            updateMenuPermByPath("/dimension-config", "config:dimension");
+            updateMenuPermByPath("/zodiac-configs", "config:dimension");
+            updateMenuPermByPath("/bazi-configs", "config:dimension");
+            updateMenuPermByPath("/blood-type-configs", "config:dimension");
+            updateMenuPermByPath("/health-norm-config", "config:health");
+            updateMenuPermByPath("/health-data-source", "config:health");
+            // 术语统一:七维能量配置 → 五维能量配置
+            jdbcTemplate.update("UPDATE sys_menu SET label='五维能量配置', perm='config:health' WHERE path='/health-energy-config'");
+            updateMenuPermByPath("/virtual-teams", "system:base");
+            updateMenuPermByPath("/knowledge-base", "knowledge:base");
+            updateMenuPermByPath("/health-knowledge", "knowledge:base");
+            updateMenuPermByPath("/supplier-products", "supply:manage");
+            updateMenuPermByPath("/nutrition-products", "health:products");
+            updateMenuPermByPath("/energy-sandbox", "health:energy");
+            // 子组权限
+            jdbcTemplate.update("UPDATE sys_menu SET perm='system:base' WHERE title='基础管理' AND (path IS NULL OR path='')");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='config:dimension' WHERE title='配置中心' AND (path IS NULL OR path='')");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='config:dimension' WHERE title='人生维度' AND (path IS NULL OR path='')");
+            jdbcTemplate.update("UPDATE sys_menu SET perm='system:base' WHERE title='虚拟团队' AND (path IS NULL OR path='')");
+
+            // G. 商城营销叶子权限统一(B-4 映射可识别)
+            updateMenuPermByPath("/product-manage", "commerce:products,commerce:self");
+            updateMenuPermByPath("/inventory", "inventory:self");
+            updateMenuPermByPath("/order-manage", "commerce:orders,order:self");
+            updateMenuPermByPath("/ecom-supplier", "commerce:supplier");
+            updateMenuPermByPath("/product-profit-rate", "finance:profit-rate");
+            updateMenuPermByPath("/coupon", "commerce:coupon");
+            updateMenuPermByPath("/coupon-grant-log", "commerce:coupon");
+            updateMenuPermByPath("/promotion", "commerce:promotion");
+            updateMenuPermByPath("/family-earnings", "finance:family-earnings");
+            updateMenuPermByPath("/activities", "activity:list,activity:self");
+            updateMenuPermByPath("/activity-registration-review", "activity:registration");
+            updateMenuPermByPath("/families", "family:list,family:read,family:bound");
+            updateMenuPermByPath("/children", "family:children,family:read,family:bound");
+            updateMenuPermByPath("/points-log", "family:points,family:bound");
+            updateMenuPermByPath("/growth-task", "task:growth");
+
+            log.info("迁移249: sys_menu 菜单归类调整与权限拆分完成");
+        } catch (Exception e) {
+            log.warn("迁移249失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 按 path 更新菜单权限(找不到时忽略)
+     */
+    private void updateMenuPermByPath(String path, String perm) {
+        try {
+            jdbcTemplate.update("UPDATE sys_menu SET perm=? WHERE path=?", perm, path);
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * 迁移251/372: 健康饮食组 报告管理模块重组(存量库 sys_menu 幂等补齐)
+     * - 迁移251:挂载 报告管理/应季食材 并排序对齐《浠艾福后台-角色权限与菜单归类整合方案》最终菜单树;
+     * - 迁移372:报告管理模块重组——删除已下线 健康报告(/health-reports) 与 报告审核(/report-audit) 菜单,
+     *   新增 代传报告(/report-proxy-upload) 菜单(对应前端 ProxyReportUpload.vue / 路由 /report-proxy-upload)。
+     */
+    private void migrateHealthMenuMount() {
+        try {
+            Long healthGroup = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=0 AND title='健康饮食'");
+            if (healthGroup == null) {
+                log.warn("迁移251: 未找到健康饮食分组,跳过");
+                return;
+            }
+            // 0. 迁移372: 删除已下线的 健康报告/报告审核 菜单(含角色授权,幂等)
+            deleteHealthMenuByPath(healthGroup, "/health-reports");
+            deleteHealthMenuByPath(healthGroup, "/report-audit");
+            // 1. 存量菜单排序对齐(报告管理0..五维能量10),先更新防止插入冲突
+            jdbcTemplate.update("UPDATE sys_menu SET sort=0 WHERE parent_id=" + healthGroup + " AND path='/report-management'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=2 WHERE parent_id=" + healthGroup + " AND path='/health-indicators'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=3 WHERE parent_id=" + healthGroup + " AND path='/health-checkins'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=4 WHERE parent_id=" + healthGroup + " AND path='/emotion-alert'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=5 WHERE parent_id=" + healthGroup + " AND path='/nutrition-mappings'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=6 WHERE parent_id=" + healthGroup + " AND path='/foods'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=7 WHERE parent_id=" + healthGroup + " AND path='/recipes'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=8 WHERE parent_id=" + healthGroup + " AND path='/seasonal-foods'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=9 WHERE parent_id=" + healthGroup + " AND path='/nutrition-products'");
+            jdbcTemplate.update("UPDATE sys_menu SET sort=10 WHERE parent_id=" + healthGroup + " AND path='/energy-sandbox'");
+            // 2. 按 path 幂等插入页面菜单
+            Long reportManagementId = insertHealthMenu(healthGroup, "报告管理", "/report-management", "el-icon-document", "health:reports", 0);
+            Long proxyUploadId = insertHealthMenu(healthGroup, "代传报告", "/report-proxy-upload", "el-icon-upload", "health:reports", 1);
+            Long seasonalFoodsId = insertHealthMenu(healthGroup, "应季食材", "/seasonal-foods", "el-icon-apple", "diet:foods", 8);
+            // 3. 条件授权:仅当已配置过角色授权(sys_role_menu 非空)时补授权,
+            //    避免破坏"所有角色均未授权 → 返回全量可见树"的兼容逻辑(SysMenuService.userMenus)
+            Integer grantCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sys_role_menu", Integer.class);
+            if (grantCount != null && grantCount > 0) {
+                String[] roles = {"admin", "butler", "nutritionist"};
+                Long[] menuIds = {reportManagementId, proxyUploadId, seasonalFoodsId};
+                for (String role : roles) {
+                    for (Long menuId : menuIds) {
+                        grantMenuToRole(role, menuId);
+                    }
+                }
+                log.info("迁移251/372: 已为 admin/butler/nutritionist 补授权健康菜单");
+            }
+            log.info("迁移251/372: 健康饮食组菜单重组完成(报告管理/代传报告/应季食材)");
+        } catch (Exception e) {
+            log.warn("迁移251失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 按 path 删除健康饮食叶子菜单并清理角色授权(幂等)
+     */
+    private void deleteHealthMenuByPath(Long parentId, String path) {
+        try {
+            Long menuId = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=" + parentId + " AND path='" + path + "'");
+            if (menuId == null) {
+                return;
+            }
+            jdbcTemplate.update("DELETE FROM sys_role_menu WHERE menu_id=" + menuId);
+            jdbcTemplate.update("DELETE FROM sys_menu WHERE id=" + menuId);
+            log.info("迁移372: 已删除下线健康菜单「{}」", path);
+        } catch (Exception e) {
+            log.warn("迁移372: 删除菜单「{}」失败: {}", path, e.getMessage());
+        }
+    }
+
+    /**
+     * 按 path 幂等插入健康饮食叶子菜单,返回菜单 id(已存在返回原 id)
+     */
+    private Long insertHealthMenu(Long parentId, String label, String path, String icon, String perm, int sort) {
+        try {
+            Long existed = queryMenuId("SELECT id FROM sys_menu WHERE parent_id=" + parentId + " AND path='" + path + "'");
+            if (existed != null) {
+                return existed;
+            }
+            jdbcTemplate.update(
+                    "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) VALUES (?, NULL, ?, ?, ?, ?, ?, 1)",
+                    parentId, label, path, icon, perm, sort
+            );
+            Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+            log.info("迁移251: 已挂载健康饮食菜单「{}」", label);
+            return id;
+        } catch (Exception e) {
+            log.warn("迁移251: 挂载菜单「{}」失败: {}", label, e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 角色-菜单授权(幂等,唯一键 uk_role_menu 冲突忽略)
+     */
+    private void grantMenuToRole(String role, Long menuId) {
+        if (role == null || menuId == null) {
+            return;
+        }
+        try {
+            jdbcTemplate.update("INSERT IGNORE INTO sys_role_menu (role, menu_id) VALUES (?, ?)", role, menuId);
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * 将菜单(按 path)移动到指定父组并更新权限/排序(幂等)
+     */
+    private void moveMenu(String path, Long parentId, String perm, int sort) {
+        try {
+            jdbcTemplate.update("UPDATE sys_menu SET parent_id=?, perm=?, sort=? WHERE path=?", parentId, perm, sort, path);
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * 查询单个菜单 id,不存在返回 null
+     */
+    private Long queryMenuId(String sql) {
+        try {
+            List<Long> ids = jdbcTemplate.queryForList(sql, Long.class);
+            return ids.isEmpty() ? null : ids.get(0);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 迁移244辅助方法: 插入 sys_menu 种子数据
+     * 结构与 cfc-web/src/views/Layout.vue 的 menuItems 硬编码一致
+     * 数组格式: {parentKey(空=顶级), key, title(目录), label(叶子), path, icon, perm, sort}
+     */
+    private void seedSysMenus() {
+        Map<String, Long> keyToId = new HashMap<>();
+        String[][] menus = {
+                // 顶级叶子
+                {"", "home", "", "首页", "/dashboard", "el-icon-s-home", "dashboard", "0"},
+                // 家庭运营(family:*)
+                {"", "operation", "家庭运营", "", "", "el-icon-s-custom", "family:*", "1"},
+                {"operation", "families", "", "家庭列表", "/families", "el-icon-s-custom", "family:list,family:read,family:bound", "0"},
+                {"operation", "children", "", "孩子管理", "/children", "el-icon-user-solid", "family:children,family:read,family:bound", "1"},
+                {"operation", "points", "", "积分管理", "/points", "el-icon-s-finance", "family:points", "2"},
+                {"operation", "pointsLog", "", "积分记录", "/points-log", "el-icon-document", "family:points,family:bound", "3"},
+                {"operation", "wishes", "", "心愿管理", "/wishes", "el-icon-star-off", "family:wishes", "4"},
+                {"operation", "rewards", "", "奖励管理", "/rewards", "el-icon-s-goods", "reward:list", "5"},
+                // 方案中心(plan:*,插在家庭运营与任务中心之间)
+                {"", "planCenter", "方案中心", "", "", "el-icon-notebook-2", "plan:*", "2"},
+                {"planCenter", "healthPlanManage", "", "健康方案管理", "/health-plan-manage", "el-icon-first-aid-kit", "plan:health", "0"},
+                {"planCenter", "assessmentPlanManage", "", "测评方案管理", "/assessment-plan-manage", "el-icon-edit", "plan:assessment", "1"},
+                // 任务中心(task:*)
+                {"", "taskCenter", "任务中心", "", "", "el-icon-s-order", "task:*", "3"},
+                {"taskCenter", "tasks", "", "任务列表", "/tasks", "el-icon-s-order", "task:list", "0"},
+                {"taskCenter", "taskTemplates", "", "任务模板", "/task-templates", "el-icon-document", "task:templates", "1"},
+                {"taskCenter", "growthTask", "", "成长任务管理", "/growth-task", "el-icon-s-management", "task:growth", "2"},
+                // 审核中心(audit,归集各审核页:服务角色申请/活动审核/退款/提现)
+                {"", "reviewCenterGroup", "审核中心", "", "", "el-icon-s-check", "audit", "4"},
+                {"reviewCenterGroup", "reviewCenter", "", "审核中心总览", "/review-center", "el-icon-s-check", "audit", "0"},
+                {"reviewCenterGroup", "serviceRoleApp", "", "服务角色申请审核", "/service-role-applications", "el-icon-s-check", "audit:service-role", "1"},
+                {"reviewCenterGroup", "activityReview", "", "活动审核", "/activity-review", "el-icon-document-checked", "audit:activity", "2"},
+                {"reviewCenterGroup", "pendingRefund", "", "待退款管理", "/pending-refund", "el-icon-warning", "audit:refund", "3"},
+                {"reviewCenterGroup", "earningWithdraw", "", "收益提现审核", "/family-earnings-withdraw", "el-icon-document-checked", "audit:withdraw", "4"},
+                // 商城营销(commerce:*)
+                {"", "commerce", "商城营销", "", "", "el-icon-s-goods", "commerce:*", "5"},
+                {"commerce", "productManage", "", "商品管理", "/product-manage", "el-icon-s-goods", "commerce:products,commerce:self", "0"},
+                {"commerce", "inventory", "", "库存管理", "/inventory", "el-icon-document", "inventory:self", "1"},
+                {"commerce", "orderManage", "", "订单管理", "/order-manage", "el-icon-s-order", "commerce:orders,order:self", "2"},
+                {"commerce", "ecomSupplier", "", "供应商管理", "/supply-system", "el-icon-s-shop", "system:supply", "3"},
+                {"commerce", "productProfitRate", "", "产品利润率", "/product-profit-rate", "el-icon-data-line", "finance:profit-rate", "4"},
+                {"commerce", "coupon", "", "优惠券管理", "/coupon", "el-icon-ticket", "commerce:coupon", "5"},
+                {"commerce", "couponGrantLog", "", "发券记录", "/coupon-grant-log", "el-icon-document", "commerce:coupon", "6"},
+                {"commerce", "discountRules", "", "限时折扣管理", "/discount-rules", "el-icon-timer", "commerce:discount", "7"},
+                {"commerce", "promotion", "", "推广管理", "/promotion", "el-icon-s-marketing", "commerce:promotion", "7"},
+                {"commerce", "familyEarnings", "", "家庭收益", "/family-earnings", "el-icon-s-money", "finance:family-earnings", "8"},
+                // 知识中心(articles:*)
+                {"", "knowledge", "知识中心", "", "", "el-icon-document", "articles:*", "6"},
+                {"knowledge", "articleCategories", "", "知识分类", "/article-categories", "el-icon-folder", "articles:categories", "0"},
+                {"knowledge", "articleManage", "", "知识管理", "/article-manage", "el-icon-document", "articles:manage", "1"},
+                {"knowledge", "commentReview", "", "评论审核", "/comment-review", "el-icon-chat-dot-round", "articles:manage", "2"},
+                {"knowledge", "knowledgeTags", "", "知识标签", "/knowledge-tags", "el-icon-price-tag", "articles:categories", "3"},
+                {"knowledge", "knowledgeBase", "", "知识库", "/knowledge-base", "el-icon-reading", "knowledge:base", "4"},
+                {"knowledge", "healthKnowledge", "", "健康知识库", "/health-knowledge", "el-icon-first-aid-kit", "knowledge:base", "5"},
+                // 活动管理(activity:*,活动审核已移入审核中心)
+                {"", "activity", "活动管理", "", "", "el-icon-date", "activity:*", "7"},
+                {"activity", "activities", "", "活动列表", "/activities", "el-icon-date", "activity:list,activity:self", "0"},
+                {"activity", "activityRegReview", "", "活动报名审核", "/activity-registration-review", "el-icon-document-checked", "activity:registration", "1"},
+                // 健康饮食(health:*,能量规则/行为/周期性服务已移入系统配置·健康配置,LangGraph已移入报告解析系统;报告管理/报告审核/应季食材按方案挂载)
+                {"", "health", "健康饮食", "", "", "el-icon-first-aid-kit", "health:*", "8"},
+                {"health", "reportManagement", "", "报告管理", "/report-management", "el-icon-document", "health:reports", "0"},
+                {"health", "proxyReportUpload", "", "代传报告", "/report-proxy-upload", "el-icon-upload", "health:reports", "1"},
+                {"health", "healthIndicators", "", "健康指标", "/health-indicators", "el-icon-data-line", "health:indicators", "2"},
+                {"health", "healthCheckins", "", "健康打卡", "/health-checkins", "el-icon-s-order", "health:checkins", "3"},
+                {"health", "emotionAlert", "", "情绪告警", "/emotion-alert", "el-icon-warning", "health:checkins", "4"},
+                {"health", "nutritionMappings", "", "营养素映射", "/nutrition-mappings", "el-icon-connection", "health:mappings", "5"},
+                {"health", "foods", "", "食材管理", "/foods", "el-icon-apple", "diet:foods", "6"},
+                {"health", "recipes", "", "食谱管理", "/recipes", "el-icon-dish", "diet:recipes", "7"},
+                {"health", "seasonalFoods", "", "应季食材", "/seasonal-foods", "el-icon-apple", "diet:foods", "8"},
+                {"health", "nutritionProducts", "", "营养产品", "/nutrition-products", "el-icon-first-aid-kit", "health:products", "9"},
+                {"health", "energySandbox", "", "五维能量", "/energy-sandbox", "el-icon-data-line", "health:energy", "10"},
+                // 报告解析系统(config:report-parser,LangGraph 管理归入)
+                {"", "reportParser", "报告解析系统", "", "", "el-icon-cpu", "config:report-parser", "9"},
+                {"reportParser", "reportTypes", "", "报告类型管理", "/report-types", "el-icon-collection", "config:report-parser", "0"},
+                {"reportParser", "reportParserImport", "", "解析器导入", "/report-parser-import", "el-icon-upload2", "config:report-parser", "1"},
+                {"reportParser", "reportUnknownClusters", "", "未知报告审核", "/report-unknown-clusters", "el-icon-question", "config:report-parser", "2"},
+                {"reportParser", "reportAutoLearn", "", "报告自学习", "/report-auto-learn", "el-icon-magic-stick", "config:report-parser", "3"},
+                {"reportParser", "langgraphAdmin", "", "LangGraph 管理", "/langgraph-admin", "el-icon-cpu", "config:report-parser", "4"},
+                // 家庭服务(service:*)
+                {"", "familyService", "家庭服务", "", "", "el-icon-s-custom", "service:*", "10"},
+                {"familyService", "myFamilies", "", "我的家庭", "/my-families", "el-icon-s-custom", "service:family", "0"},
+                {"familyService", "teacherTeam", "", "我的团队", "/teacher-team", "el-icon-s-custom", "service:team", "1"},
+                // 业务管理(biz:*)
+                {"", "biz", "业务管理", "", "", "el-icon-s-marketing", "biz:*", "11"},
+                {"biz", "teacherPackages", "", "任务模板管理", "/teacher-packages", "el-icon-s-goods", "biz:packages", "0"},
+                {"biz", "teacherOrders", "", "订单佣金", "/teacher-orders", "el-icon-s-order", "biz:orders", "1"},
+                // 测评咨询(测评管理/测评订单/待分配规划师已从系统配置移入)
+                {"", "assessment", "测评咨询", "", "", "el-icon-edit", "assessment:*", "12"},
+                {"assessment", "assessmentAdmin", "", "测评管理", "/assessment-admin", "el-icon-edit", "assessment:manage", "0"},
+                {"assessment", "assessmentOrders", "", "测评订单", "/assessment-orders", "el-icon-s-order", "assessment:orders", "1"},
+                {"assessment", "assessmentAssign", "", "待分配规划师", "/assessment-assign", "el-icon-user", "assessment:assign", "2"},
+                {"assessment", "teacherAssessment", "", "DAN测评", "/teacher-assessment", "el-icon-edit", "assessment:dan", "3"},
+                {"assessment", "teacherConsult", "", "家长咨询", "/teacher-consult", "el-icon-chat-dot-round", "assessment:consult", "4"},
+                {"assessment", "growthRecords", "", "成长记录", "/growth-records", "el-icon-document", "growth:records", "5"},
+                {"assessment", "growthPlans", "", "成长计划", "/growth-plans", "el-icon-document", "growth:plans", "6"},
+                // 系统配置(system:*,权限拆分 system:base / config:member / config:dimension / config:health)
+                {"", "system", "系统配置", "", "", "el-icon-s-tools", "system:*", "13"},
+                {"system", "baseManage", "基础管理", "", "", "el-icon-s-tools", "system:base", "0"},
+                {"baseManage", "sysConfig", "", "系统配置", "/sys-config", "el-icon-s-tools", "system:base", "0"},
+                {"baseManage", "users", "", "用户管理", "/users", "el-icon-user", "system:users", "1"},
+                {"baseManage", "operationLogs", "", "操作日志", "/operation-logs", "el-icon-s-order", "system:logs", "2"},
+                {"baseManage", "membershipCenter", "", "会员中心", "/membership-center", "el-icon-s-custom", "config:member", "3"},
+                {"baseManage", "badgeManage", "", "勋章管理", "/badge-manage", "el-icon-medal", "config:member", "4"},
+                {"baseManage", "virtualGoodsConfig", "", "虚拟支付道具", "/virtual-goods-config", "el-icon-goods", "config:member", "5"},
+                {"baseManage", "gates", "", "关卡配置", "/gates", "el-icon-key", "system:base", "6"},
+                {"baseManage", "menuManage", "", "菜单管理", "/menu-manage", "el-icon-menu", "system:menu", "7"},
+                {"system", "configCenter", "配置中心", "", "", "el-icon-setting", "config:dimension", "1"},
+                {"configCenter", "dimensionConfig", "", "维度配置", "/dimension-config", "el-icon-data-line", "config:dimension", "0"},
+                {"system", "supplyHierarchy", "供应商体系", "", "", "el-icon-s-management", "system:supply", "2"},
+                {"supplyHierarchy", "supplySystem", "", "供应商体系", "/supply-system", "el-icon-s-management", "system:supply", "0"},
+                {"system", "supplierManage", "供应商管理", "", "", "el-icon-s-shop", "supply:manage", "3"},
+                {"supplierManage", "supplyManage", "", "供应商管理", "/supply-manage", "el-icon-s-shop", "supply:manage", "0"},
+                {"supplierManage", "supplierProducts", "", "商品管理", "/supplier-products", "el-icon-s-goods", "supply:manage", "1"},
+                {"system", "healthConfig", "健康配置", "", "", "el-icon-first-aid-kit", "config:health", "4"},
+                {"healthConfig", "healthNormConfig", "", "健康常模", "/health-norm-config", "el-icon-data-line", "config:health", "0"},
+                {"healthConfig", "healthDataSource", "", "健康数据源", "/health-data-source", "el-icon-data-line", "config:health", "1"},
+                {"healthConfig", "healthEnergyConfig", "", "五维能量配置", "/health-energy-config", "el-icon-data-line", "config:health", "2"},
+                {"healthConfig", "energyRule", "", "能量规则管理", "/energy-rule", "el-icon-setting", "config:health", "3"},
+                {"healthConfig", "energyBehaviorConfig", "", "能量行为配置", "/energy-behavior-config", "el-icon-edit-outline", "config:health", "4"},
+                {"healthConfig", "periodicServiceConfig", "", "周期性服务配置", "/periodic-service-config", "el-icon-refresh", "config:health", "5"},
+                {"system", "lifeDimension", "人生维度", "", "", "el-icon-s-custom", "config:dimension", "5"},
+                {"lifeDimension", "zodiacConfigs", "", "星座配置", "/zodiac-configs", "el-icon-s-management", "config:dimension", "0"},
+                {"lifeDimension", "baziConfigs", "", "八字配置", "/bazi-configs", "el-icon-s-management", "config:dimension", "1"},
+                {"lifeDimension", "bloodTypeConfigs", "", "血型配置", "/blood-type-configs", "el-icon-s-management", "config:dimension", "2"},
+                {"system", "virtualTeam", "虚拟团队", "", "", "el-icon-s-custom", "system:base", "6"},
+                {"virtualTeam", "virtualTeams", "", "虚拟团队", "/virtual-teams", "el-icon-s-custom", "system:base", "0"},
+        };
+
+        for (String[] m : menus) {
+            Long parentId = 0L;
+            if (m[0] != null && !m[0].isEmpty()) {
+                parentId = keyToId.get(m[0]);
+                if (parentId == null) {
+                    parentId = 0L;
+                }
+            }
+            String title = m[2] != null && !m[2].isEmpty() ? m[2] : null;
+            String label = m[3] != null && !m[3].isEmpty() ? m[3] : null;
+            String path = m[4] != null && !m[4].isEmpty() ? m[4] : null;
+            String icon = m[5] != null && !m[5].isEmpty() ? m[5] : null;
+            String perm = m[6] != null && !m[6].isEmpty() ? m[6] : null;
+            int sort = Integer.parseInt(m[7]);
+            jdbcTemplate.update(
+                    "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) VALUES (?, ?, ?, ?, ?, ?, ?, 1)",
+                    parentId, title, label, path, icon, perm, sort
+            );
+            Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+            keyToId.put(m[1], id);
+        }
+
+        // ===== 迁移:初始化方案规则数据 (P1) =====
+        try {
+            // 使用 jdbcTemplate 的 DataSource
+            var ds = jdbcTemplate.getDataSource();
+            if (ds instanceof javax.sql.DataSource) {
+                try (Connection conn = ((javax.sql.DataSource) ds).getConnection()) {
+                    PreparedStatement ps = null;
+                    try {
+                        // 插入注意力提升方案规则 1:0-60 分 → 模板 ID=38
+                        ps = conn.prepareStatement("INSERT INTO assessment_plan_rules (id, dimension, score_min, score_max, template_id, priority, is_active, created_at, updated_at) VALUES (NULL, ?, ?, ?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE updated_at=NOW()");
+                        ps.setString(1, "attention");
+                        ps.setInt(2, 0);
+                        ps.setInt(3, 60);
+                        ps.setLong(4, 38L); // 使用现有的模板 ID
+                        ps.setInt(5, 1);
+                        ps.setBoolean(6, true);
+                        ps.executeUpdate();
+                        log.info("已插入注意力方案规则 1(0-60 分)");
+                        ps.close();
+
+                        // 插入注意力提升方案规则 2:61-100 分 → 模板 ID=39
+                        ps = conn.prepareStatement("INSERT INTO assessment_plan_rules (id, dimension, score_min, score_max, template_id, priority, is_active, created_at, updated_at) VALUES (NULL, ?, ?, ?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE updated_at=NOW()");
+                        ps.setString(1, "attention");
+                        ps.setInt(2, 61);
+                        ps.setInt(3, 100);
+                        ps.setLong(4, 39L);
+                        ps.setInt(5, 2);
+                        ps.setBoolean(6, true);
+                        ps.executeUpdate();
+                        log.info("已插入注意力方案规则 2(61-100 分)");
+                        ps.close();
+                    } finally {
+                        if (ps != null) ps.close();
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            log.warn("初始化方案规则失败:{}", e.getMessage());
+        }
+
+        // ===== 迁移:初始化报告类型注册 (R1) =====
+        try {
+            // 使用 jdbcTemplate 的 DataSource
+            var ds = jdbcTemplate.getDataSource();
+            if (ds instanceof javax.sql.DataSource) {
+                try (Connection conn = ((javax.sql.DataSource) ds).getConnection()) {
+                    PreparedStatement ps = null;
+                    try {
+                        // 注册肠道菌群检测报告类型
+                        ps = conn.prepareStatement("INSERT INTO report_type_registry (id, type_id, display_name, parser_path, min_confidence, is_active, source, review_status, version, created_at, updated_at) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE updated_at=NOW()");
+                        ps.setString(1, "gut_flora");
+                        ps.setString(2, "肠道菌群检测");
+                        ps.setString(3, "dan_reports/gut_flora.py");
+                        ps.setInt(4, 10);
+                        ps.setBoolean(5, true);
+                        ps.setString(6, "prebuilt");
+                        ps.setString(7, "approved");
+                        ps.setString(8, "1.0");
+                        ps.executeUpdate();
+                        log.info("已注册报告类型:gut_flora");
+                        ps.close();
+                    } finally {
+                        if (ps != null) ps.close();
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            log.warn("注册报告类型 gut_flora 失败:{}", e.getMessage());
+        }
+
+        // 迁移278-313: 已移至独立方法 runSystemMigrations()(原误置于本方法仅空表触发)
+    }
+
+    // 迁移278-313: 系统级 DDL/数据迁移(原误置于 seedSysMenus 内,空表才执行,生产永远跳过)
+    // 抽出为独立方法,由 runMigrations() 无条件调用,确保生产库每次启动补齐。
+    private void runSystemMigrations() {
+        // 迁移278: 创建 family_coupon 表 + family_coupon_grant_log 表
+        // (优惠券全链路改为家庭维度:2026-08-31)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_coupon (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '所属家庭ID', " +
+                    "coupon_id BIGINT NOT NULL COMMENT '券模板ID', " +
+                    "status VARCHAR(16) DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE/USED', " +
+                    "received_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "used_at DATETIME, " +
+                    "order_id BIGINT COMMENT '核销时写入的订单号', " +
+                    "INDEX idx_family_coupon (family_id, coupon_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_order_id (order_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券表'");
+            log.info("已创建 family_coupon 表");
+        } catch (Exception e) {
+            log.warn("family_coupon 表已存在,跳过创建: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_coupon_grant_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '受赠家庭ID', " +
+                    "coupon_id BIGINT NOT NULL COMMENT '券模板ID', " +
+                    "grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE/CF_EXCHANGE', " +
+                    "period VARCHAR(16) COMMENT '周期标识(YYYY-MM或YYYY-Qn),PERIODIC防重用', " +
+                    "quantity INT DEFAULT 1 COMMENT '发放数量', " +
+                    "source VARCHAR(64) COMMENT '触发来源(订单号/成员ID等)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_family_grant (family_id, coupon_id, grant_type, period), " +
+                    "INDEX idx_coupon (coupon_id), " +
+                    "INDEX idx_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券发放流水表'");
+            log.info("已创建 family_coupon_grant_log 表");
+        } catch (Exception e) {
+            log.warn("family_coupon_grant_log 表已存在,跳过创建: {}", e.getMessage());
+        }
+
+        // 迁移279: 创建 cf_rate_tier 表(CF值返佣阶梯配置,替代 promotion_tier_config)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS cf_rate_tier (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "tier_name VARCHAR(50) NOT NULL COMMENT '档位名称', " +
+                    "min_team_size INT NOT NULL DEFAULT 0 COMMENT '团队规模下限(含)', " +
+                    "rate_percent INT NOT NULL DEFAULT 0 COMMENT '返佣比例(%)', " +
+                    "sort_order INT NOT NULL DEFAULT 0 COMMENT '排序,越大越高', " +
+                    "enabled TINYINT DEFAULT 1 COMMENT '1启用/0停用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_min_size (min_team_size)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值返佣阶梯配置'");
+            log.info("已创建cf_rate_tier表");
+            Integer cnt = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM cf_rate_tier", Integer.class);
+            if (cnt == null || cnt == 0) {
+                jdbcTemplate.execute("INSERT INTO cf_rate_tier (tier_name, min_team_size, rate_percent, sort_order) VALUES " +
+                        "('铜牌', 0, 5, 1), ('银牌', 3, 10, 2), ('金牌', 10, 15, 3), ('铂金', 30, 20, 4), ('钻石', 100, 25, 5)");
+                log.info("cf_rate_tier 种子数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("创建cf_rate_tier表失败: {}", e.getMessage());
+        }
+
+        // 迁移280: 创建 cf_transfer_record 表(CF值流转记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS cf_transfer_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "from_user_id BIGINT COMMENT '转出用户ID(null=平台)', " +
+                    "to_user_id BIGINT COMMENT '转入用户ID', " +
+                    "family_id BIGINT COMMENT '所属家庭ID', " +
+                    "amount INT NOT NULL COMMENT 'CF值数量', " +
+                    "type VARCHAR(16) NOT NULL COMMENT 'transfer/allocate/refund', " +
+                    "ref_type VARCHAR(50) COMMENT '关联业务类型', " +
+                    "ref_id BIGINT COMMENT '关联业务ID', " +
+                    "remark VARCHAR(255), " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_from (from_user_id), " +
+                    "INDEX idx_to (to_user_id), " +
+                    "INDEX idx_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值流转记录'");
+            log.info("已创建cf_transfer_record表");
+        } catch (Exception e) {
+            log.warn("创建cf_transfer_record表失败: {}", e.getMessage());
+        }
+
+        // 迁移281: promotion_tier 精简列(删三层,加 rate_percent)
+        try {
+            jdbcTemplate.execute("ALTER TABLE promotion_tier DROP COLUMN team_size_1st, DROP COLUMN team_size_2nd, DROP COLUMN team_size_3rd");
+            log.info("promotion_tier 已删除三层团队列");
+        } catch (Exception e) {
+            log.warn("promotion_tier 三层列删除失败(可能不存在): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE promotion_tier ADD COLUMN rate_percent INT DEFAULT 0 COMMENT '当前返佣比例(%)'");
+            log.info("promotion_tier 已添加 rate_percent");
+        } catch (Exception e) {
+            log.warn("promotion_tier.rate_percent 已存在: {}", e.getMessage());
+        }
+
+        // 迁移282: coupon 表添加 family_id
+        try {
+            jdbcTemplate.execute("ALTER TABLE coupon ADD COLUMN family_id BIGINT DEFAULT NULL COMMENT '绑定家庭ID(NULL=不绑定)'");
+            log.info("coupon 已添加 family_id");
+        } catch (Exception e) {
+            log.warn("coupon.family_id 已存在: {}", e.getMessage());
+        }
+
+        // 迁移283: referral_tree 唯一键改为 (parent_id, child_id) 联合唯一(支持每祖先一行物化)
+        try {
+            jdbcTemplate.execute("ALTER TABLE referral_tree DROP INDEX uk_child_id");
+            log.info("referral_tree 已删除 uk_child_id 唯一键");
+        } catch (Exception e) {
+            log.warn("referral_tree.uk_child_id 删除失败(可能不存在): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE referral_tree ADD UNIQUE KEY uk_parent_child (parent_id, child_id)");
+            log.info("referral_tree 已添加 uk_parent_child 联合唯一键");
+        } catch (Exception e) {
+            log.warn("referral_tree.uk_parent_child 已存在: {}", e.getMessage());
+        }
+
+        // 迁移284: cf_transfer_record 加 (ref_type, ref_id) 唯一索引(幂等兜底,防止并发 earn 重复发放)
+        try {
+            jdbcTemplate.execute("ALTER TABLE cf_transfer_record ADD UNIQUE KEY uk_ref_type_id (ref_type, ref_id)");
+            log.info("cf_transfer_record 已添加 uk_ref_type_id 唯一索引");
+        } catch (Exception e) {
+            log.warn("cf_transfer_record.uk_ref_type_id 已存在: {}", e.getMessage());
+        }
+
+        // 迁移285: 创建 product_gift_configs 表(商品赠品配置,商品×等级)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_gift_configs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "product_id BIGINT NOT NULL COMMENT '关联products表', " +
+                    "level_code VARCHAR(50) NOT NULL COMMENT '会员等级编码(FAMILY/PREMIUM等)', " +
+                    "status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_product_level (product_id, level_code), " +
+                    "INDEX idx_product_id (product_id), " +
+                    "INDEX idx_level_code (level_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品配置(商品×等级)'");
+            log.info("已创建product_gift_configs表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移286: 创建 product_gift_items 表(商品赠品项)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_gift_items (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "config_id BIGINT NOT NULL COMMENT '关联product_gift_configs.id', " +
+                    "gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值', " +
+                    "gift_product_id BIGINT COMMENT '赠品商品ID(gift_type=product时必填)', " +
+                    "gift_product_name VARCHAR(200) COMMENT '赠品商品名快照', " +
+                    "gift_product_image VARCHAR(500) COMMENT '赠品商品图快照', " +
+                    "cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时必填,单位分)', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序(值越小越靠前)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_config_id (config_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品项'");
+            log.info("已创建product_gift_items表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移287: 创建 product_order_gifts 表(订单赠品记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_order_gifts (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "order_id BIGINT NOT NULL COMMENT '关联product_orders.id', " +
+                    "order_no VARCHAR(64) NOT NULL COMMENT '订单号快照', " +
+                    "gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值', " +
+                    "gift_product_id BIGINT COMMENT '赠品商品ID', " +
+                    "gift_product_name VARCHAR(200) COMMENT '赠品商品名快照', " +
+                    "gift_product_image VARCHAR(500) COMMENT '赠品商品图快照', " +
+                    "cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时)', " +
+                    "quantity INT DEFAULT 1 COMMENT '数量(默认1)', " +
+                    "fulfilled TINYINT(1) DEFAULT 0 COMMENT '0=已选未发放 1=已发放', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_order_id (order_id), " +
+                    "INDEX idx_order_no (order_no)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单赠品记录'");
+            log.info("已创建product_order_gifts表");
+        } catch (Exception e) {
+            // 表已存在,忽略
+        }
+
+        // 迁移288: health_report_drafts 加异步采集状态列(报告采集流程异步化)
+        // 用途:上传后进入后台异步采集(指纹判定→专用脚本/opencode),前端轮询 parse_status
+        ensureColumn("health_report_drafts", "parse_status",
+                "VARCHAR(20) DEFAULT NULL COMMENT '异步解析状态: collecting/completed/failed(NULL=未触发异步采集)'");
+        ensureColumn("health_report_drafts", "parse_method",
+                "VARCHAR(32) DEFAULT NULL COMMENT '解析方式: fingerprint_script/opencode/langgraph'");
+        ensureColumn("health_report_drafts", "parse_error",
+                "VARCHAR(2000) DEFAULT NULL COMMENT '异步解析失败原因'");
+        log.info("已添加 health_report_drafts 异步采集状态列 (parse_status/parse_method/parse_error)");
+
+        // 迁移289: health_reports 补 report_format 列(HealthReport 实体已含字段,但从未迁移至 DB,全字段 SELECT 报 Unknown column)
+        ensureColumn("health_reports", "report_format",
+                "VARCHAR(32) DEFAULT NULL COMMENT '报告格式: pdf/image'");
+
+        // 迁移290: 补齐实体已含但 DB 从未迁移的真实缺列(全字段 SELECT 防 Unknown column)
+        // 逐表核实(对照 schema.sql + 历史 ADD COLUMN/ensureColumn),仅含确认不存在的列,共 6 表 21 列
+        // 风险预留:promotion_tier_config 实体与 DB 为两套不同晋级模型(已废弃,由 cf_rate_tier 迁移279 替代),
+        // 按用户决定保持现状不迁移,避免与旧种子数据冲突;后续清理旧实体依赖(CommissionService等)时再处理
+        ensureColumn("activities", "visibility", "VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private'");
+        ensureColumn("activities", "visible_to", "TEXT DEFAULT NULL COMMENT '私密指定人群JSON'");
+        ensureColumn("dan_knowledge_base", "file_url", "VARCHAR(500) DEFAULT NULL COMMENT 'fileUrl'");
+        ensureColumn("dan_knowledge_base", "file_name", "VARCHAR(255) DEFAULT NULL COMMENT 'fileName'");
+        ensureColumn("dan_knowledge_base", "source_url", "VARCHAR(500) DEFAULT NULL COMMENT 'sourceUrl'");
+        ensureColumn("dan_knowledge_base", "source_type", "VARCHAR(20) DEFAULT NULL COMMENT 'sourceType'");
+        ensureColumn("distribution_relations", "invite_code", "VARCHAR(64) DEFAULT NULL COMMENT 'inviteCode'");
+        ensureColumn("family_members", "birth_hour", "VARCHAR(10) DEFAULT NULL COMMENT '出生时辰'");
+        ensureColumn("family_members", "birth_weight", "INT DEFAULT NULL COMMENT '出生体重(g)'");
+        ensureColumn("family_members", "birth_place", "VARCHAR(255) DEFAULT NULL COMMENT '出生地'");
+        ensureColumn("family_members", "is_cesarean", "TINYINT(1) DEFAULT 0 COMMENT '是否剖腹产: 0否 1是'");
+        ensureColumn("health_indicators", "symptoms", "TEXT DEFAULT NULL COMMENT '症状描述'");
+        ensureColumn("product_orders", "tracking_number", "VARCHAR(100) DEFAULT NULL COMMENT '快递单号'");
+        ensureColumn("product_orders", "express_company", "VARCHAR(100) DEFAULT NULL COMMENT '快递公司'");
+        ensureColumn("product_orders", "delivery_method", "INT DEFAULT NULL COMMENT '配送方式: 1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人'");
+        ensureColumn("product_orders", "pickup_code", "VARCHAR(64) DEFAULT NULL COMMENT '自提码'");
+        ensureColumn("product_orders", "confirm_time", "DATETIME DEFAULT NULL COMMENT '确认收货时间'");
+        ensureColumn("product_orders", "refund_status", "INT DEFAULT NULL COMMENT '退款状态'");
+        ensureColumn("product_orders", "refund_reason", "VARCHAR(255) DEFAULT NULL COMMENT '退款原因'");
+        ensureColumn("product_orders", "refund_amount", "INT DEFAULT NULL COMMENT '退款金额(分)'");
+        ensureColumn("product_orders", "refund_time", "DATETIME DEFAULT NULL COMMENT '退款时间'");
+        log.info("已补齐真实缺列 (迁移290, 6表21列)");
+
+        // 迁移291: food_nutrients 列名对齐(amount_per_100g -> amount_per100g)
+        // FoodNutrient 实体字段 amountPer100g 经 MyBatis-Plus camel->snake 映射为 amount_per100g,
+        // 但建表 SQL 误用 amount_per_100g,导致实体全字段 SELECT 报 Unknown column 'amount_per100g'。
+        // 当前 FoodNutrientMapper 无 Service 调用,属潜在隐患;此处统一列名为 amount_per100g。
+        try {
+            Integer cnt = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() " +
+                    "AND TABLE_NAME = 'food_nutrients' AND COLUMN_NAME = 'amount_per_100g'", Integer.class);
+            if (cnt != null && cnt > 0) {
+                jdbcTemplate.execute("ALTER TABLE food_nutrients CHANGE COLUMN amount_per_100g amount_per100g DECIMAL(10,4) NOT NULL COMMENT '每100g含量'");
+                log.info("已重命名 food_nutrients.amount_per_100g -> amount_per100g");
+            }
+        } catch (Exception e) {
+            // 表不存在或列不存在,忽略
+        }
+
+        // 迁移292: health_status 补 member_id 并改为成员级归档(Unknown column 'member_id')
+        // 迁移239 建表只有 user_id,schema.sql 与实体已含 member_id(成员级档案),生产缺列导致 selectOne 报错。
+        // 原 UNIQUE KEY uk_user(user_id) 限制每账号仅一行,与成员级多行归档冲突,改为复合索引 idx_user_member。
+        ensureColumn("health_status", "member_id", "BIGINT DEFAULT NULL COMMENT '家庭成员ID(NULL=账号级,非空=成员级归档)'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_status DROP INDEX uk_user");
+            log.info("已移除 health_status.uk_user 唯一键(允许成员级多行归档)");
+        } catch (Exception e) {
+            // 索引不存在,忽略
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_status ADD INDEX idx_user_member (user_id, member_id)");
+            log.info("已添加 health_status.idx_user_member 复合索引");
+        } catch (Exception e) {
+            // 索引已存在,忽略
+        }
+
+        // 迁移293: family_members 添加 is_admin 列(后台家庭成员仅区分管理员/成员,不再区分家长/孩子)
+        ensureColumn("family_members", "is_admin", "TINYINT(1) DEFAULT 0 COMMENT '是否家庭管理员: 1=管理员, 0=普通成员'");
+        // 回填:将每个家庭的创建者(families.creator_id 匹配 family_members.user_id)设为管理员
+        try {
+            jdbcTemplate.execute("UPDATE family_members fm JOIN families f ON fm.family_id = f.id " +
+                    "SET fm.is_admin = 1 WHERE fm.user_id = f.creator_id AND (fm.is_admin IS NULL OR fm.is_admin = 0)");
+            log.info("迁移293: 已回填家庭创建者为管理员");
+        } catch (Exception e) {
+            log.warn("迁移293: 回填家庭创建者为管理员失败: {}", e.getMessage());
+        }
+
+        // 迁移294: articles 表加 sort_order 列(精选文章推荐排序,越小越靠前)
+        ensureColumn("articles", "sort_order", "INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)'");
+        log.info("已添加 articles.sort_order 列(迁移294)");
+
+        // 迁移295: task_plan_instances 补齐实体已含但 DB 缺失的审核/确认/激活列(review/activate 写字段报 Unknown column)
+        ensureColumn("task_plan_instances", "source", "VARCHAR(20) DEFAULT NULL COMMENT '来源: manual/assessment/template'");
+        ensureColumn("task_plan_instances", "source_result_id", "BIGINT DEFAULT NULL COMMENT '关联测评结果ID(source=assessment时)'");
+        ensureColumn("task_plan_instances", "remark", "TEXT COMMENT '方案备注(含指导意见摘要)'");
+        ensureColumn("task_plan_instances", "reviewed_by", "BIGINT DEFAULT NULL COMMENT '审核人(规划师ID)'");
+        ensureColumn("task_plan_instances", "reviewed_at", "DATETIME DEFAULT NULL COMMENT '审核时间'");
+        ensureColumn("task_plan_instances", "review_comment", "TEXT COMMENT '审核意见'");
+        ensureColumn("task_plan_instances", "confirmed_by", "BIGINT DEFAULT NULL COMMENT '确认人(家长ID)'");
+        ensureColumn("task_plan_instances", "confirmed_at", "DATETIME DEFAULT NULL COMMENT '确认时间'");
+        ensureColumn("task_plan_instances", "activated_at", "DATETIME DEFAULT NULL COMMENT '激活时间'");
+        log.info("迁移295: 已补齐 task_plan_instances 审核/确认/激活列");
+
+        // 迁移296: 健康方案任务 source_type 'plan' -> 'health_plan'(消除与 task_plan_instances 的 ID 域冲突)
+        try {
+            // 冲突检测:source_id 同时存在于 health_plans 和 task_plan_instances 的任务(需人工确认,不阻断迁移)
+            Integer conflict = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM tasks t WHERE t.source_type = 'plan' " +
+                    "AND EXISTS (SELECT 1 FROM health_plans h WHERE h.id = t.source_id) " +
+                    "AND EXISTS (SELECT 1 FROM task_plan_instances i WHERE i.id = t.source_id)",
+                    Integer.class);
+            if (conflict != null && conflict > 0) {
+                log.warn("迁移296: 检测到{}条冲突任务(source_id同时存在于health_plans和task_plan_instances),需人工确认;无冲突部分仍继续迁移", conflict);
+            }
+            // 始终执行 UPDATE:仅迁移"存在于 health_plans 且不存在于 task_plan_instances"的任务(冲突部分不迁移,避免误改套餐任务)
+            int updated = jdbcTemplate.update(
+                    "UPDATE tasks SET source_type = 'health_plan' " +
+                    "WHERE source_type = 'plan' " +
+                    "AND EXISTS (SELECT 1 FROM health_plans h WHERE h.id = tasks.source_id) " +
+                    "AND NOT EXISTS (SELECT 1 FROM task_plan_instances i WHERE i.id = tasks.source_id)");
+            log.info("迁移296: 已修正{}条健康方案任务 source_type -> health_plan", updated);
+        } catch (Exception e) {
+            log.warn("迁移296执行失败(health_plans 表可能不存在): {}", e.getMessage());
+        }
+
+        // 迁移297: 认知训练方案任务补设 source_type='training_plan'(training_plan_tasks 关联溯源)
+        try {
+            Integer updated = jdbcTemplate.update(
+                    "UPDATE tasks t SET t.source_type = 'training_plan' " +
+                    "WHERE (t.source_type IS NULL OR t.source_type = '') " +
+                    "AND EXISTS (SELECT 1 FROM training_plan_tasks pt WHERE pt.task_id = t.id)");
+            log.info("迁移297: 已补设{}条认知训练方案任务 source_type -> training_plan", updated);
+        } catch (Exception e) {
+            log.warn("迁移297执行失败(training_plan_tasks 表可能不存在): {}", e.getMessage());
+        }
+
+// 迁移298: 存量 role='child' 账号强制迁移为成员模型
+        // 1) 有 openid 的 child → role 改 parent(可登录成员,登录后进成员端界面,不能被切换)
+        // 2) 无 openid 的 child → 保持不可登录,确保有 family_members 记录(仅可被切换)
+        try {
+            Integer updated = jdbcTemplate.update(
+                    "UPDATE users SET role='parent' WHERE role='child' AND openid IS NOT NULL AND openid <> ''");
+            log.info("迁移298: 已迁移{}条有 openid 的 child 账号 role → parent", updated);
+        } catch (Exception e) {
+            log.warn("迁移298第一步失败", e);
+        }
+        try {
+            jdbcTemplate.execute(
+                "INSERT INTO family_members (family_id, user_id, nickname, gender, birthday, generation, show_to_family, total_points, created_at, updated_at) " +
+                "SELECT u.family_id, u.id, u.nickname, u.gender, u.birthday, -1, 1, 0, NOW(), NOW() " +
+                "FROM users u " +
+                "WHERE u.role='child' AND (u.openid IS NULL OR u.openid='') " +
+                "AND u.family_id IS NOT NULL " +
+                "AND NOT EXISTS (SELECT 1 FROM family_members fm WHERE fm.user_id = u.id)");
+log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记录");
+        } catch (Exception e) {
+            log.warn("迁移298第二步失败", e);
+        }
+
+
+// 迁移322: 废弃 children 表 —— 数据迁移到 family_members 后删除表(幂等,表不存在时跳过)
+        try {
+            Integer childrenTableCnt = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'children'",
+                    Integer.class);
+            if (childrenTableCnt == null || childrenTableCnt == 0) {
+                log.info("迁移322: children 表不存在,跳过(已废弃)");
+            } else {
+                // 1. 匹配更新:family_members 已存在该成员记录时补全字段
+                jdbcTemplate.execute(
+                        "UPDATE family_members fm INNER JOIN children c " +
+                        "ON fm.family_id = c.family_id AND fm.nickname = c.nickname AND fm.user_id = c.user_id " +
+                        "SET fm.id_card = c.id_card, fm.penalty_enabled = c.penalty_enabled, fm.theme = c.theme, " +
+                        "fm.system_points = c.system_points, fm.streak_days = c.streak_days, fm.last_task_date = c.last_task_date, " +
+                        "fm.focus_max_daily = c.focus_max_daily, fm.focus_remaining = c.focus_remaining, " +
+                        "fm.focus_reset_date = c.focus_reset_date, fm.total_points = c.total_points, " +
+                        "fm.show_to_family = c.show_to_family, fm.birthday = c.birthday, fm.gender = c.gender, " +
+                        "fm.phone = c.phone, fm.role_override = 'child'");
+                log.info("迁移322: 已更新 family_members 中已存在的 children 匹配记录字段");
+
+                // 2. 不匹配则插入新记录
+                jdbcTemplate.execute(
+                        "INSERT INTO family_members (family_id, user_id, nickname, birthday, gender, phone, " +
+                        "id_card, penalty_enabled, theme, system_points, streak_days, last_task_date, " +
+                        "focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points, " +
+                        "role_override, created_at, updated_at) " +
+                        "SELECT family_id, user_id, nickname, birthday, gender, phone, " +
+                        "id_card, penalty_enabled, theme, system_points, streak_days, last_task_date, " +
+                        "focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points, " +
+                        "'child', created_at, updated_at " +
+                        "FROM children " +
+                        "WHERE NOT EXISTS (SELECT 1 FROM family_members fm2 WHERE fm2.family_id = children.family_id " +
+                        "AND fm2.nickname = children.nickname AND fm2.user_id = children.user_id)");
+                log.info("迁移322: 已从 children 插入不匹配记录到 family_members");
+
+                // 3. 删除 children 表
+                jdbcTemplate.execute("DROP TABLE IF EXISTS children");
+                log.info("迁移322: 已删除 children 表(数据已迁移到 family_members)");
+            }
+        } catch (Exception e) {
+            log.warn("迁移322执行失败(children 表可能不存在或迁移范围内字段缺失): {}", e.getMessage());
+        }
+
+
+        ensureColumn("promotion_tier", "rate_percent", "INT DEFAULT 0 COMMENT '当前返佣比例(%)'");
+
+        // 迁移300: family_members 添加 status 列(成员状态: active/recycled,非微信用户被移出进回收箱)
+        ensureColumn("family_members", "status", "VARCHAR(16) DEFAULT 'active' COMMENT '成员状态: active=正常, recycled=回收箱'");
+
+        // ===== 生产库对账候选风险记录(已核对生产库,2026-09,按用户决定不迁移/暂不动,仅记录)=====
+        // 注:以下为 entity 期望但生产库未对齐.项目,用户已决定保持现状或记为候选,勿重复迁移。
+        
+        //    exportUserData 全字段 SELECT 会报 Unknown column 'phone')。schema.sql 已将 children 标为
+        //    已废除/并入 family_members,用户决定不迁移。
+        // 2. user_trigger_rules:生产表仅剩 user_id(PK,auto_increment)/action_config/last_triggered_at,
+        //    实体期望 id/trigger_type/trigger_action/status/created_at 共 8 列,表 0 行且不在 schema.sql。
+        //    UserSegmentationService.getTriggerRules 会查 status,命中时会报 Unknown column 'status'。候选。
+        // 3. promotion_tier.rate_percent:生产用旧模型(tier/team_size_1st/2nd/3rd/total_*),实体期望新模型,
+        
+        // 4. promotion_tier_config:实体已废弃,改用 cf_rate_tier(迁移279),保持现状。
+        // 5. family_members.member_type:实体为 VO 计算值已加 @TableField(exist=false)(不落库),schema.sql
+        //    无此列;2026-09-06 生产已手动 ALTER 补列(JwtInterceptor/SurveyServiceImpl 查询报 Unknown column
+        //    'member_type' 修复,生产旧代码兼容用),部署含 exist=false 的新代码后该列不再被引用,可留可删。
+
+        // 迁移302: tasks表新增is_daily_progress/target_value字段(统一任务系统;原迁移298用 AFTER source_id,列缺失时会被静默吞掉)
+        ensureColumn("tasks", "is_daily_progress", "TINYINT DEFAULT 0 COMMENT '1=每日进度型任务(无deadline,每日重置)'");
+        ensureColumn("tasks", "target_value", "INT DEFAULT NULL COMMENT '进度目标值(is_daily_progress=1时有效)'");
+        try {
+            jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN source_type VARCHAR(32) DEFAULT NULL COMMENT '来源类型: self/assigned/plan/health_plan/training_plan/onboarding/ai/template/article/activity/purchase'");
+            log.info("已更新tasks.source_type注释");
+        } catch (Exception e) {
+            // 修改失败,忽略
+        }
+
+        // 迁移296: admin_task_templates表新增onboarding_sequence字段(统一任务系统:新人引导复用任务模板表)
+        try {
+            jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN onboarding_sequence INT DEFAULT NULL COMMENT '新人引导顺序(source_type=onboarding的模板用)' AFTER teacher_id");
+            log.info("已添加onboarding_sequence列到admin_task_templates表");
+        } catch (Exception e) {
+            // 列已存在,忽略
+        }
+
+        // 迁移297: 统一任务系统数据迁移——growth_task DAILY定义迁入admin_task_templates(仅执行一次,幂等)
+        try {
+            Integer migrated = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM sys_config WHERE config_key = 'TASK_UNIFY_GT_MIGRATED'", Integer.class);
+            if (migrated == null || migrated == 0) {
+                // DAILY 类型 → admin_task_templates(title 拼入 [task_key] 前缀,供 updateProgress 匹配)
+                jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, is_active, task_type, frequency, created_at, updated_at) " +
+                        "SELECT CONCAT('[', task_key, ']', title), description, reward_points, '成长', 0, enabled, 'recurring', 'daily', created_at, created_at " +
+                        "FROM growth_task WHERE type = 'DAILY' AND enabled = 1");
+                jdbcTemplate.update("INSERT INTO sys_config (config_key, config_value, description) VALUES ('TASK_UNIFY_GT_MIGRATED', '1', '统一任务系统: growth_task DAILY定义已迁入admin_task_templates')");
+                log.info("迁移297: growth_task DAILY定义已迁入admin_task_templates");
+            }
+        } catch (Exception e) {
+            log.warn("迁移297: growth_task迁移失败或已执行: {}", e.getMessage());
+        }
+        // 说明:growth_task_log 历史进度、onboarding_task 存量数据仅保留在旧表(只读备份),不回填 tasks(历史数据无法1:1映射到新模型)
+
+        // 迁移299: 插入新人引导任务模板种子数据(统一任务系统:onboarding 复用 admin_task_templates)
+        // title = 原 taskType(REGISTER/COMPLETE_PROFILE/...),onboarding_sequence 定义顺序链
+        try {
+            Integer onboardCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admin_task_templates WHERE onboarding_sequence IS NOT NULL", Integer.class);
+            if (onboardCount == null || onboardCount == 0) {
+                jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, is_active, task_type, frequency, onboarding_sequence, created_at, updated_at) VALUES " +
+                        "('REGISTER', '完成注册', 100, '成长', 0, 1, 'onetime', 'none', 1, NOW(), NOW()), " +
+                        "('COMPLETE_PROFILE', '完善个人资料', 50, '成长', 0, 1, 'onetime', 'none', 2, NOW(), NOW()), " +
+                        "('ADD_CHILD', '添加家庭成员', 100, '成长', 0, 1, 'onetime', 'none', 3, NOW(), NOW()), " +
+                        "('FIRST_CHECKIN', '完成首次健康打卡', 30, '成长', 0, 1, 'onetime', 'none', 4, NOW(), NOW()), " +
+                        "('FIRST_TASK', '完成首次任务', 50, '成长', 0, 1, 'onetime', 'none', 5, NOW(), NOW()), " +
+                        "('BIND_REFERRAL', '绑定推荐人', 50, '成长', 0, 1, 'onetime', 'none', 6, NOW(), NOW()), " +
+                        "('COMPLETE_SELF_CHECK', '完成一次五维自检', 60, '成长', 0, 1, 'onetime', 'none', 7, NOW(), NOW())");
+                log.info("迁移299: 已插入7条新人引导任务模板种子数据");
+            }
+        } catch (Exception e) {
+            log.warn("迁移299: 插入新人引导任务模板失败或已存在: {}", e.getMessage());
+        }
+
+        // 迁移301: platform_balance_log 加 (user_id, ref_type, ref_id) 唯一索引(earn 幂等 DB 兜底,防止并发重复发放)
+        try {
+            jdbcTemplate.execute("DELETE l1 FROM platform_balance_log l1 " +
+                    "INNER JOIN platform_balance_log l2 ON l1.user_id = l2.user_id " +
+                    "AND l1.ref_type = l2.ref_type AND l1.ref_id = l2.ref_id AND l1.id > l2.id " +
+                    "WHERE l1.ref_id IS NOT NULL AND l1.ref_type IS NOT NULL");
+            log.info("迁移301: platform_balance_log 历史重复流水已清理");
+            jdbcTemplate.execute("ALTER TABLE platform_balance_log ADD UNIQUE KEY uk_user_ref (user_id, ref_type, ref_id)");
+            log.info("迁移301: platform_balance_log 已添加 uk_user_ref 唯一索引");
+        } catch (Exception e) {
+            log.warn("迁移301: platform_balance_log 唯一索引处理失败: {}", e.getMessage());
+        }
+
+        // 迁移302: 创建 contact_help_logs 表(章鱼图帮助记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS contact_help_logs (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '被帮助者(当前用户)', " +
+                    "contact_id BIGINT NOT NULL COMMENT '帮助者(联系人)', " +
+                    "help_type VARCHAR(32) COMMENT '帮助类型: MONEY/ITEM/EMOTION/RESOURCE/SKILL/TIME', " +
+                    "amount DECIMAL(10,2) DEFAULT 0 COMMENT '帮助金额(非金钱类为0)', " +
+                    "description VARCHAR(255) COMMENT '帮助内容简述', " +
+                    "happened_at DATETIME COMMENT '帮助发生时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_chl_user (user_id), " +
+                    "INDEX idx_chl_contact (contact_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='联系人帮助记录(章鱼图数据源)'");
+            log.info("已创建contact_help_logs表");
+        } catch (Exception e) {
+            log.warn("创建contact_help_logs表失败: {}", e.getMessage());
+        }
+
+        // 迁移303: 创建 resource_items 表(珍珠图资源登记:信息/场所/技能类资源)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS resource_items (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '资源拥有者(当前用户)', " +
+                    "type VARCHAR(16) NOT NULL COMMENT '资源类型: INFO(信息)/PLACE(场所)/SKILL(技能)', " +
+                    "name VARCHAR(100) NOT NULL COMMENT '资源名称', " +
+                    "description VARCHAR(255) COMMENT '资源描述(可调用方式/说明)', " +
+                    "contact_id BIGINT DEFAULT NULL COMMENT '关联联系人ID(可选)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_ri_user (user_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图资源登记表(信息/场所/技能类资源)'");
+            log.info("已创建resource_items表");
+        } catch (Exception e) {
+            log.warn("创建resource_items表失败: {}", e.getMessage());
+        }
+
+        // 迁移304: health_gut_flora.bacteria_name 改为可空(LLM解析偶发缺菌种名,NOT NULL 导致采集入库失败)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_gut_flora MODIFY COLUMN bacteria_name VARCHAR(100) NULL COMMENT '菌种名称'");
+            log.info("迁移304: health_gut_flora.bacteria_name 已改为可空");
+        } catch (Exception e) {
+            log.warn("迁移304: health_gut_flora.bacteria_name 修改失败: {}", e.getMessage());
+        }
+
+        // 迁移305: health_indicators.status 放宽到 VARCHAR(100)(opencode 回退解析产出自由文本 status,
+        //   超长写入报 Data too long for column 'status',draft 入库失败)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_indicators MODIFY COLUMN status VARCHAR(100) NULL COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出'");
+            log.info("迁移305: health_indicators.status 已放宽到 VARCHAR(100)");
+        } catch (Exception e) {
+            log.warn("迁移305: health_indicators.status 修改失败: {}", e.getMessage());
+        }
+
+        // 迁移306: products表添加visible_roles列(商品类型权限过滤,NULL=全部角色可见)
+        try {
+            jdbcTemplate.execute("ALTER TABLE products ADD COLUMN visible_roles JSON DEFAULT NULL COMMENT '可见角色: [\"parent\",\"child\",\"teacher\",\"admin\"],NULL=全部可见'");
+            log.info("迁移306: products.visible_roles 列已添加");
+        } catch (Exception e) {
+            // ignore
+        }
+
+        // 迁移307: resource_items 表添加社会连接分类字段(珍珠图重构:25 类连接类型 + 价值分级)
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD COLUMN connection_type VARCHAR(50) NOT NULL DEFAULT '' COMMENT '连接类型枚举名(ConnectionType)'");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.connection_type 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD COLUMN priority TINYINT DEFAULT 2 COMMENT '优先级: 1=高 2=中 3=低(手动覆盖)'");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.priority 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD COLUMN value_score INT DEFAULT 0 COMMENT '价值分 0-100(自动计算,可手动覆盖)'");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.value_score 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD COLUMN last_interaction_at DATETIME DEFAULT NULL COMMENT '最近互动时间'");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.last_interaction_at 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD COLUMN interaction_count INT DEFAULT 0 COMMENT '互动总次数'");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.interaction_count 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE resource_items ADD INDEX idx_ri_connection_type (connection_type)");
+        } catch (Exception e) {
+            log.warn("迁移307: resource_items.connection_type 索引已存在或添加失败: {}", e.getMessage());
+        }
+        log.info("迁移307: resource_items 社会连接字段添加完成");
+
+        // 迁移308: 创建珍珠图互动日志表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS pearl_interaction_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '操作人', " +
+                    "resource_item_id BIGINT NOT NULL COMMENT '关联资源', " +
+                    "interaction_type VARCHAR(20) NOT NULL COMMENT 'PHONE/WECHAT/MEETING/OTHER', " +
+                    "content VARCHAR(500) DEFAULT NULL COMMENT '互动内容简记', " +
+                    "happened_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '互动发生时间', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_pil_user (user_id), " +
+                    "INDEX idx_pil_resource (resource_item_id), " +
+                    "INDEX idx_pil_happened (happened_at) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图互动日志'");
+            log.info("迁移308: 已创建 pearl_interaction_log 表");
+        } catch (Exception e) {
+            log.warn("迁移308: 创建 pearl_interaction_log 表失败: {}", e.getMessage());
+        }
+
+        // 迁移309: 创建珍珠图互动提醒表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS pearl_interaction_reminder (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "resource_item_id BIGINT NOT NULL, " +
+                    "connection_type VARCHAR(50) NOT NULL, " +
+                    "suggest_interval_month INT NOT NULL COMMENT '建议互动周期(月)', " +
+                    "last_interaction_at DATETIME DEFAULT NULL, " +
+                    "days_since_last INT NOT NULL COMMENT '距上次互动天数', " +
+                    "status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING/DONE/DISMISSED', " +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_pir_user_status (user_id, status), " +
+                    "INDEX idx_pir_resource (resource_item_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图互动提醒'");
+            log.info("迁移309: 已创建 pearl_interaction_reminder 表");
+        } catch (Exception e) {
+            log.warn("迁移309: 创建 pearl_interaction_reminder 表失败: {}", e.getMessage());
+        }
+
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS octopus_effect_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '当前用户(租户键)', " +
+                    "member_order_id BIGINT COMMENT '关联 MemberSubscriptionOrder.id(会员订单)', " +
+                    "effect_type TINYINT UNSIGNED NOT NULL COMMENT '成效类型 bitmask:1=金额大,2=频次高,4=新成交(支持多选)', " +
+                    "effect_amount DECIMAL(10,2) DEFAULT 0 COMMENT '成效金额(元)', " +
+                    "effect_desc VARCHAR(500) COMMENT '成效描述', " +
+                    "status TINYINT DEFAULT 1 COMMENT '1=有效,0=无效', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_oer_user (user_id), " +
+                    "INDEX idx_oer_order (member_order_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关键人拓展-成效记录'");
+            log.info("已创建octopus_effect_record表");
+        } catch (Exception e) {
+            log.warn("创建octopus_effect_record表失败: {}", e.getMessage());
+        }
+
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS octopus_key_person (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '当前用户(租户键)', " +
+                    "name VARCHAR(64) NOT NULL COMMENT '关键人姓名', " +
+                    "organization VARCHAR(128) COMMENT '单位/公司', " +
+                    "department VARCHAR(64) COMMENT '部门', " +
+                    "title VARCHAR(64) COMMENT '职务/职位', " +
+                    "first_meet_scene VARCHAR(128) COMMENT '初识场合', " +
+                    "know_way VARCHAR(255) COMMENT '如何认识(认识路径)', " +
+                    "contact_info VARCHAR(255) COMMENT '联系方式(可选)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_okp_user (user_id), " +
+                    "INDEX idx_okp_org (organization) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关键人画像'");
+            log.info("已创建octopus_key_person表");
+        } catch (Exception e) {
+            log.warn("创建octopus_key_person表失败: {}", e.getMessage());
+        }
+
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS octopus_effect_key_person (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "effect_id BIGINT NOT NULL COMMENT '关联 octopus_effect_record.id', " +
+                    "key_person_id BIGINT NOT NULL COMMENT '关联 octopus_key_person.id', " +
+                    "role TINYINT NOT NULL COMMENT '1=引荐,2=决策,3=其他', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_oekp_effect (effect_id), " +
+                    "INDEX idx_oekp_person (key_person_id), " +
+                    "UNIQUE KEY uk_oekp (effect_id,key_person_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成效-关键人关联'");
+            log.info("已创建octopus_effect_key_person表");
+        } catch (Exception e) {
+            log.warn("创建octopus_effect_key_person表失败: {}", e.getMessage());
+        }
+
+        // 迁移310: health_meal_record 表添加 record_date(补打日期)与 is_backfill(是否补打)字段(饮食记录重设计)
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_meal_record ADD COLUMN record_date DATE NULL COMMENT '用餐日期(补打时为实际用餐日,默认等于创建日)'");
+            log.info("迁移310: health_meal_record.record_date 列已添加");
+        } catch (Exception e) {
+            log.warn("迁移310: health_meal_record.record_date 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_meal_record ADD COLUMN is_backfill TINYINT DEFAULT 0 COMMENT '是否补打: 1=补打(无能量值), 0=当日打卡'");
+            log.info("迁移310: health_meal_record.is_backfill 列已添加");
+        } catch (Exception e) {
+            log.warn("迁移310: health_meal_record.is_backfill 已存在或添加失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE health_meal_record ADD INDEX idx_meal_record_date (member_id, record_date)");
+            log.info("迁移310: health_meal_record (member_id, record_date) 索引已添加");
+        } catch (Exception e) {
+            log.warn("迁移310: health_meal_record 索引已存在或添加失败: {}", e.getMessage());
+        }
+
+        // 迁移310: health_reports / health_report_drafts 补 file_hash 列
+        // 实体与 schema.sql 已含该列,但建表迁移从未 ADD;upload-only 按 MD5 去重时 SELECT 报 Unknown column 'file_hash'
+        ensureColumn("health_reports", "file_hash",
+                "VARCHAR(64) DEFAULT NULL COMMENT '文件MD5哈希,用于重复上传检测'");
+        ensureColumn("health_report_drafts", "file_hash",
+                "VARCHAR(64) DEFAULT NULL COMMENT '文件MD5哈希,用于重复上传检测'");
+        log.info("迁移310: 已补齐 health_reports/health_report_drafts.file_hash");
+
+        // 迁移311: report_type_registry 补 script_status 列
+        // 实体/schema 已含;异步采集指纹识别 SELECT 全字段时报 Unknown column 'script_status'
+        ensureColumn("report_type_registry", "script_status",
+                "VARCHAR(20) DEFAULT NULL COMMENT '采集脚本生成状态: generating/ready/failed'");
+        log.info("迁移311: 已补齐 report_type_registry.script_status");
+
+        // 迁移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());
+        }
+
+        // 迁移333-336: 引导弹窗配置表(guide_popup 系列,原远程版本误置于 seedGutFloraFingerprints 早返回之后,
+        // 现并入 runSystemMigrations 无条件执行;编号 333-336 为本地迁移329(task_orchestration)以后的续号)
+        // 迁移333: 创建 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("迁移333: 已创建 guide_popup 表");
+        } catch (Exception e) {
+            log.warn("guide_popup 表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移334: 创建 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("迁移334: 已创建 guide_popup_step 表");
+        } catch (Exception e) {
+            log.warn("guide_popup_step 表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移335: 创建 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("迁移335: 已创建 guide_popup_trigger 表");
+        } catch (Exception e) {
+            log.warn("guide_popup_trigger 表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移336: 创建 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("迁移336: 已创建 guide_popup_log 表");
+        } catch (Exception e) {
+            log.warn("guide_popup_log 表可能已存在: {}", e.getMessage());
+        }
+
+
+
+        // 迁移323: 注册素质测评A2报告类型(dan家族)+ 默认指纹规则
+        // 统一上传后指纹识别为 dan_a2 → ReportCollectService 按 report_type_registry.family=dan 分流走 DAN 采集管道
+        try {
+            Integer danA2Count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM report_type_registry WHERE type_id = ?", Integer.class, "dan_a2");
+            if (danA2Count == null || danA2Count == 0) {
+                jdbcTemplate.update(
+                        "INSERT INTO report_type_registry (type_id, family, display_name, description, parser_path, " +
+                        "min_confidence, is_active, source, review_status, version, created_at, updated_at) " +
+                        "VALUES ('dan_a2', 'dan', '素质测评A2', '核心素养评估报告:大五人格/认知能力/社会关系等', " +
+                        "NULL, 30, 1, 'prebuilt', 'approved', '1.0', NOW(), NOW())");
+                log.info("迁移323: 已注册报告类型 dan_a2(dan家族)");
+            } else {
+                log.info("迁移323: 报告类型 dan_a2 已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("迁移323: 注册 dan_a2 类型失败: {}", e.getMessage());
+        }
+        seedDanA2Fingerprints();
+        seedDiscountRulesMenu();
+        createUserShortcutConfigTable();
+        createUserAppUsageTable();
+        seedGuidePopupData();
+        addGiftAmountColumn();
+        createGiftMembershipProgressTable();
+        addFlowTaskColumnsToTasksTable();
+        addPolicyTermsColumn();
+        createHomeBannersTable();
+        ensureHealthReportDraftsMemberIdColumn();
+        ensureHealthPlansTotalDaysColumn();
+        seedTaskReminderTemplate();
+        backfillHealthPlanTaskPrerequisite();
+        backfillTemplateInstanceFamilyMemberId();
+        createReportTransferRequestsTable();
+        addProductMinDiscountLevel();
+        restoreBasicMembershipLevel();
+        backfillMembershipLevelOrder();
+        removeReportUploadQuota();
+
+        createProductDimensionMappingTable();
+        createProductRecommendationLogTable();
+        createRepurchaseReminderTables();
+        createRecommendationProductAssociationsTable();
+        addAbTestColumnsToRecommendationLog();
+        createTaskReminderConfigTable();
+        addMissingColumnsToRecommendationLog();
+        createDietFamilySelectedIngredientsTable();
+        createSsoTicketTable();
+
+        // 迁移327: 创建 report_feedback 表(报告反馈+AI解答)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_feedback (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '提交人 users.id', " +
+                    "member_id BIGINT DEFAULT NULL COMMENT '关联家庭成员 family_members.id', " +
+                    "report_id BIGINT DEFAULT NULL COMMENT '健康报告ID', " +
+                    "draft_id BIGINT DEFAULT NULL COMMENT '草稿ID', " +
+                    "feedback_type VARCHAR(20) DEFAULT 'question' COMMENT 'question=疑问 / suggestion=建议', " +
+                    "content VARCHAR(500) NOT NULL COMMENT '反馈内容', " +
+                    "ai_answer TEXT COMMENT 'AI 解答', " +
+                    "ai_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/answered/failed', " +
+                    "conversation_id VARCHAR(64) DEFAULT NULL COMMENT 'AI 会话ID(付费多轮)', " +
+                    "created_at DATETIME NOT NULL COMMENT '创建时间', " +
+                    "INDEX idx_rf_user (user_id, created_at), " +
+                    "INDEX idx_rf_report (report_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告反馈与AI解答'");
+            log.info("迁移327: 已创建 report_feedback 表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移313: 修复 meal_configs 历史双编码 participant_member_ids(共餐配置前端曾 JSON.stringify 二次编码,存成了 "\"[1,2]\"",
+        //          导致再次进入页面时无法识别选中成员)。仅处理以双引号包裹的值,不触碰正常 "[]..." 格式
+        try {
+            jdbcTemplate.execute("UPDATE meal_configs SET participant_member_ids = JSON_UNQUOTE(participant_member_ids) " +
+                    "WHERE participant_member_ids LIKE '\"%\"' ");
+            log.info("迁移313: 已修复 meal_configs 双编码 participant_member_ids");
+        } catch (Exception e) {
+            log.warn("迁移313: 修复 meal_configs participant_member_ids 失败: {}", e.getMessage());
+        }
+
+// 迁移314: meal_configs 增加 config_date 字段(当天临时共餐配置)+ 修改 UNIQUE KEY
+        // 支持「今天」模式:config_date 有值=当天临时配置(优先级最高),config_date=NULL+config_date_type=工作日/周末默认配置
+        try {
+            jdbcTemplate.execute("ALTER TABLE meal_configs ADD COLUMN config_date DATE DEFAULT NULL COMMENT '当天临时配置日期(NULL=默认工作日/周末配置)'");
+            log.info("迁移314: meal_configs 已添加 config_date 列");
+        } catch (Exception e) {
+            log.warn("迁移314: meal_configs 添加 config_date 列失败(可能已存在): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE meal_configs ADD COLUMN config_date_eff DATE GENERATED ALWAYS AS (COALESCE(config_date, '9999-12-31')) STORED COMMENT '生效日期(NULL映射为9999-12-31,用于唯一约束)'");
+            log.info("迁移314: meal_configs 已添加生成列 config_date_eff");
+        } catch (Exception e) {
+            log.warn("迁移314: meal_configs 添加 config_date_eff 生成列失败(可能已存在): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE meal_configs DROP INDEX uk_config");
+            log.info("迁移314: meal_configs 已删除旧 UNIQUE KEY uk_config");
+        } catch (Exception e) {
+            log.warn("迁移314: 删除旧 UNIQUE KEY 失败(可能不存在): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE meal_configs ADD UNIQUE KEY uk_config (family_id, config_date_eff, meal_type)");
+            log.info("迁移314: meal_configs 已添加新 UNIQUE KEY uk_config(支持当天临时配置)");
+        } catch (Exception e) {
+            log.warn("迁移314: 添加新 UNIQUE KEY 失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE meal_configs ADD INDEX idx_config_date (config_date)");
+            log.info("迁移314: meal_configs 已添加索引 idx_config_date");
+        } catch (Exception e) {
+            log.warn("迁移314: meal_configs 添加索引 idx_config_date 失败(可能已存在): {}", e.getMessage());
+        }
+
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS doa_stage (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "member_id BIGINT NOT NULL COMMENT '目标归属成员ID(family_members.id)', " +
+                    "title VARCHAR(200) COMMENT '阶段主题(可选)', " +
+                    "start_date DATE NOT NULL COMMENT '阶段开始日期', " +
+                    "end_date DATE NOT NULL COMMENT '阶段结束日期', " +
+                    "duration_weeks TINYINT NOT NULL COMMENT '周期周数:2/4/8/12', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT 'active/completed/cancelled', " +
+                    "created_by BIGINT NOT NULL COMMENT '创建人userId(代管时为代管人)', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_ds_family_member (family_id, member_id), " +
+                    "INDEX idx_ds_status (status) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 阶段'");
+            log.info("迁移318: 已创建 doa_stage 表");
+        } catch (Exception e) {
+            log.warn("迁移318: 创建 doa_stage 表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS doa_goal (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "stage_id BIGINT NOT NULL COMMENT '所属阶段ID', " +
+                    "domain VARCHAR(20) NOT NULL COMMENT '领域:health/wealth/family/social/help', " +
+                    "content VARCHAR(500) NOT NULL COMMENT '量化目标内容', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT 'active/achieved/abandoned', " +
+                    "sort_order TINYINT DEFAULT 0 COMMENT '排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_dg_stage (stage_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 阶段目标'");
+            log.info("迁移318: 已创建 doa_goal 表");
+        } catch (Exception e) {
+            log.warn("迁移318: 创建 doa_goal 表失败: {}", e.getMessage());
+        }
+
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS doa_week (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "stage_id BIGINT NOT NULL COMMENT '所属阶段ID', " +
+                    "week_no TINYINT NOT NULL COMMENT '第几周(1起)', " +
+                    "week_start DATE NOT NULL COMMENT '本周开始(周一)', " +
+                    "week_end DATE NOT NULL COMMENT '本周结束(周日)', " +
+                    "plan TEXT COMMENT '周行动宣告计划(A表)', " +
+                    "achieved TEXT COMMENT '复盘:本周达成了什么', " +
+                    "experience TEXT COMMENT '复盘:有什么体验', " +
+                    "worked TEXT COMMENT '复盘:行得通的', " +
+                    "improve TEXT COMMENT '复盘:要提升/行不通的', " +
+                    "next_challenge TEXT COMMENT '复盘:下周挑战自己什么', " +
+                    "status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/planned/reviewed', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_dw_stage_week (stage_id, week_no), " +
+                    "INDEX idx_dw_stage (stage_id) " +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 周记录'");
+            log.info("迁移318: 已创建 doa_week 表");
+        } catch (Exception e) {
+            log.warn("迁移318: 创建 doa_week 表失败: {}", e.getMessage());
+        }
+
+        // 迁移318: 基础管理分组挂载「系统提示词」菜单(cfc-web菜单树同步)
+        migrateSystemPromptMenu();
+
+        // 迁移319: system_prompts 插入 health_plan 提示词种子(仅当不存在时)
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM system_prompts WHERE prompt_key = 'health_plan'", Integer.class);
+            if (count == null || count == 0) {
+                jdbcTemplate.update(
+                    "INSERT INTO system_prompts (prompt_key, title, content, default_content, version, status, remark) VALUES (?, ?, ?, ?, 1, 1, ?)",
+                    "health_plan", "健康方案生成",
+                    "你是一个专业的家庭健康方案规划师。根据用户的健康数据和目标,生成结构化的改善方案。\n\n" +
+                    "## 输出格式(必须严格遵守 JSON)\n\n" +
+                    "```\n" +
+                    "{\n" +
+                    "  \"overview\": \"总体概述(100字以内,说明方案目标和核心策略)\",\n" +
+                    "  \"sections\": [\n" +
+                    "    {\n" +
+                    "      \"key\": \"nutrition\",\n" +
+                    "      \"title\": \"营养补充建议\",\n" +
+                    "      \"content\": \"Markdown 格式的详细内容,包含具体产品推荐、用量、服用时间\",\n" +
+                    "      \"items\": [{\"name\": \"产品名\", \"dosage\": \"用量\", \"timing\": \"服用时间\", \"reason\": \"推荐理由\"}]\n" +
+                    "    },\n" +
+                    "    {\n" +
+                    "      \"key\": \"diet\",\n" +
+                    "      \"title\": \"饮食建议\",\n" +
+                    "      \"content\": \"Markdown 格式的餐饮建议,包含早餐/午餐/晚餐建议\",\n" +
+                    "      \"items\": [{\"meal\": \"餐型\", \"food\": \"食物建议\", \"notes\": \"注意事项\"}]\n" +
+                    "    },\n" +
+                    "    {\n" +
+                    "      \"key\": \"exercise\",\n" +
+                    "      \"title\": \"运动计划\",\n" +
+                    "      \"content\": \"Markdown 格式的运动建议,包含频率、时长、类型\",\n" +
+                    "      \"items\": [{\"type\": \"运动类型\", \"duration\": \"时长\", \"frequency\": \"频率\", \"notes\": \"注意事项\"}]\n" +
+                    "    }\n" +
+                    "  ]\n" +
+                    "}\n" +
+                    "```\n\n" +
+                    "## 原则\n" +
+                    "1. 基于实际数据给出建议,不编造\n" +
+                    "2. 引用知识库时标注来源\n" +
+                    "3. 建议要具体可执行,避免空泛\n" +
+                    "4. 营养补充部分要具体到品牌/产品类型和用量\n" +
+                    "5. 严重健康问题建议咨询医生",
+                    "你是一个专业的家庭健康方案规划师。根据用户的健康数据和目标,生成结构化的改善方案。",
+                    "健康方案生成 prompt(营养+饮食+运动三段结构化输出)");
+                log.info("迁移319: 已插入 health_plan 提示词种子数据");
+            } else {
+                log.info("迁移319: health_plan 提示词已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("迁移319: 插入 health_plan 提示词种子数据失败: {}", e.getMessage());
+    }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE exercise_record ADD COLUMN step_count INT COMMENT '微信运动步数'");
+            log.info("迁移320: exercise_record.step_count 列已添加");
+        } catch (Exception e) {
+            log.warn("迁移320: exercise_record.step_count 列已存在或添加失败: {}", e.getMessage());
+        }
+
+        // 迁移321: 商品/分类可见性由「角色」改为「按会员级别」控制
+        // - membership_levels.level_order: 级别数字(数字越小级别越低): FREE=1 FAMILY=2 PROVIDER=3 PREMIUM=4 LIFETIME=5
+        // - products.min_visible_level: 最低可见会员级别编码(NULL=所有用户含未登录可见)
+        // - shop_categories.min_visible_level: 分类最低可见会员级别编码(NULL=所有用户含未登录可见)
+        ensureColumn("membership_levels", "level_order",
+                "INT DEFAULT 1 COMMENT '级别数字(越小级别越低): FREE=1 FAMILY=2 PROVIDER=3 PREMIUM=4 LIFETIME=5'");
+        try {
+            jdbcTemplate.update("UPDATE membership_levels SET level_order = CASE level_code " +
+                    "WHEN 'FREE' THEN 1 WHEN 'FAMILY' THEN 2 WHEN 'PROVIDER' THEN 3 " +
+                    "WHEN 'PREMIUM' THEN 4 WHEN 'LIFETIME' THEN 5 ELSE level_order END");
+            log.info("迁移321: 已回填 membership_levels.level_order");
+        } catch (Exception e) {
+            log.warn("迁移321: 回填 membership_levels.level_order 失败: {}", e.getMessage());
+        }
+        ensureColumn("products", "min_visible_level",
+                "VARCHAR(50) DEFAULT NULL COMMENT '最低可见会员级别编码(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME, NULL=全部可见)'");
+        ensureColumn("shop_categories", "min_visible_level",
+                "VARCHAR(50) DEFAULT NULL COMMENT '最低可见会员级别编码(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME, NULL=全部可见)'");
+        log.info("迁移321: 已完成商品/分类按会员级别可见性改造");
+
+        // 迁移299: 创建 article_quiz_records 表(文章阅读答题记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_quiz_records (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "article_id BIGINT DEFAULT NULL, " +
+                    "user_id BIGINT DEFAULT NULL, " +
+                    "member_id BIGINT DEFAULT NULL, " +
+                    "questions TEXT, " +
+                    "answers TEXT, " +
+                    "score INT DEFAULT 0, " +
+                    "total_questions INT DEFAULT 0, " +
+                    "cf_earned INT DEFAULT 0, " +
+                    "energy_earned INT DEFAULT 0, " +
+                    "created_at DATETIME, " +
+                    "updated_at DATETIME, " +
+                    "INDEX idx_member (member_id), " +
+                    "INDEX idx_article (article_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章答题记录表'");
+            log.info("已创建article_quiz_records表");
+        } catch (Exception e) {
+            log.warn("创建article_quiz_records表可能已存在: {}", e.getMessage());
+        }
+
+        // 迁移300: profile_snapshot 增加 quiz_summary 列(答题认知雷达汇总)
+        ensureColumn("profile_snapshot", "quiz_summary", "JSON DEFAULT NULL COMMENT '答题认知雷达汇总'");
+
+        // 迁移323: dan_assessment_results 添加 language_score 列(游戏化认知测评:语言表达维度)
+        ensureColumn("dan_assessment_results", "language_score", "INT COMMENT '语言表达得分'");
+        log.info("迁移323: 已确保 language_score 列存在于 dan_assessment_results");
+
+        // 迁移324-325: octopus_effect_record 添加 happened_at 列 + article_reading_records 回填与补列
+        try {
+            jdbcTemplate.execute("ALTER TABLE octopus_effect_record ADD COLUMN happened_at DATETIME COMMENT '成效发生时间'");
+            log.info("迁移324: octopus_effect_record.happened_at 列已添加");
+        } catch (Exception e) {
+            log.warn("迁移324: octopus_effect_record.happened_at 列已存在或添加失败: {}", e.getMessage());
+        }
+
+        try {
+            int updated = jdbcTemplate.update(
+                    "UPDATE article_reading_records r " +
+                    "LEFT JOIN tasks t ON r.task_id = t.id AND t.source_type = 'article' " +
+                    "SET r.article_id = t.source_id " +
+                    "WHERE r.article_id IS NULL AND t.id IS NOT NULL AND t.source_id IS NOT NULL"
+            );
+            log.info("迁移325: 已回填 article_reading_records.article_id {} 条记录", updated);
+        } catch (Exception e) {
+            log.warn("迁移325: 回填 article_reading_records.article_id 失败: {}", e.getMessage());
+        }
+
+        ensureColumn("article_reading_records", "reading_completed", "TINYINT DEFAULT 0 COMMENT '是否已阅读达标(达到最低时长阈值)'");
+
+        // 迁移342: octopus_key_person 添加 first_meet_time / referrer_id / system_user_id 列(感恩记录新增字段)
+        ensureColumn("octopus_key_person", "first_meet_time", "DATETIME COMMENT '相识具体时间'");
+        log.info("迁移342: octopus_key_person.first_meet_time 列已添加");
+        ensureColumn("octopus_key_person", "referrer_id", "BIGINT DEFAULT NULL COMMENT '介绍人关键人ID(指向同用户另一条关键人)'");
+        log.info("迁移342: octopus_key_person.referrer_id 列已添加");
+        ensureColumn("octopus_key_person", "system_user_id", "BIGINT DEFAULT NULL COMMENT '匹配到的系统用户ID(如有)'");
+        log.info("迁移342: octopus_key_person.system_user_id 列已添加");
+
+        try {
+            Integer reportCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admin_task_templates WHERE title = 'REPORT_UPLOAD' AND onboarding_sequence IS NOT NULL", Integer.class);
+            if (reportCount == null || reportCount == 0) {
+                jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, is_active, task_type, frequency, onboarding_sequence, created_at, updated_at) VALUES " +
+                        "('REPORT_UPLOAD', '完成首次报告上传', 300, '成长', 0, 1, 'onetime', 'none', 8, NOW(), NOW())");
+                log.info("迁移343: 已插入完成首次报告上传新人引导任务模板");
+            }
+        } catch (Exception e) {
+            log.warn("迁移343: 插入完成首次报告上传任务模板失败或已存在: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移318: 系统配置·基础管理 挂载 系统提示词 菜单(幂等)
+     * 仅当 sys_menu 表已有数据(即角色授权已存在)时补授权,避免破坏兼容逻辑
+     */
+    private void migrateSystemPromptMenu() {
+        try {
+            Long baseManageId = queryMenuId("SELECT id FROM sys_menu WHERE title='基础管理'");
+            if (baseManageId == null) {
+                log.warn("迁移318: 未找到基础管理分组,跳过");
+                return;
+            }
+            Long promptId = insertSystemPromptMenu(baseManageId);
+            if (promptId != null) {
+                Integer grantCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sys_role_menu", Integer.class);
+                if (grantCount != null && grantCount > 0) {
+                    grantMenuToRole("admin", promptId);
+                    log.info("迁移318: 已授权 admin 角色");
+                }
+                log.info("迁移318: 系统提示词菜单挂载完成 id={}", promptId);
+            }
+        } catch (Exception e) {
+            log.warn("迁移318失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 按 path 幂等插入系统提示词叶子菜单,返回菜单 id(已存在返回原 id)
+     */
+    private Long insertSystemPromptMenu(Long parentId) {
+        Long existed = queryMenuId("SELECT id FROM sys_menu WHERE path='/system-prompt'");
+        if (existed != null) {
+            return existed;
+        }
+        try {
+            jdbcTemplate.update(
+                    "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible) VALUES (?, NULL, '系统提示词', '/system-prompt', 'el-icon-chat-line-square', 'system:base', 8, 1)",
+                    parentId);
+            Long id = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+            log.info("迁移318: 已挂载系统提示词菜单");
+            return id;
+        } catch (Exception e) {
+            log.warn("迁移318: 挂载菜单失败: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    private void seedGutFloraFingerprints() {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM report_fingerprint WHERE type_id = ?", Integer.class, "gut_flora");
+            if (count != null && count > 0) {
+                log.info("迁移312: gut_flora 指纹规则已存在,跳过");
+                return;
+            }
+            String[][] rows = {
+                    {"肠道菌群检测", "20", "0", "1"},
+                    {"核心菌属", "15", "0", "2"},
+                    {"高通量测序", "15", "0", "3"},
+                    {"菌群健康", "10", "0", "4"},
+                    {"益生菌", "8", "0", "5"},
+                    {"Shannon", "8", "0", "6"},
+                    {"肠道菌群", "10", "0", "7"},
+            };
+            for (String[] row : rows) {
+                jdbcTemplate.update(
+                        "INSERT INTO report_fingerprint (type_id, pattern, weight, is_exclusion, sort_order, created_at) "
+                                + "VALUES ('gut_flora', ?, ?, ?, ?, NOW())",
+                        row[0], Integer.valueOf(row[1]), Integer.valueOf(row[2]), Integer.valueOf(row[3]));
+            }
+            log.info("迁移312: 已写入 gut_flora 默认指纹规则 {} 条", rows.length);
+        } catch (Exception e) {
+            log.warn("迁移312: 写入 gut_flora 指纹规则失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移323: 为 dan_a2(素质测评A2)补默认指纹规则。
+     * 空表会导致指纹引擎永远 unknown;A2 报告特征词取自 DanReportParseService.parseA2Report 的章节识别词。
+     * dan_a2 注册表 min_confidence=30,两高权重词同时命中即判定成功,避免单词误判。
+     */
+    private void seedDanA2Fingerprints() {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM report_fingerprint WHERE type_id = ?", Integer.class, "dan_a2");
+            if (count != null && count > 0) {
+                log.info("迁移323: dan_a2 指纹规则已存在,跳过");
+                return;
+            }
+            String[][] rows = {
+                    {"大五人格", "20", "0", "1"},
+                    {"人格特质", "15", "0", "2"},
+                    {"核心素养", "15", "0", "3"},
+                    {"认知能力", "10", "0", "4"},
+                    {"感知觉", "10", "0", "5"},
+                    {"评估报告", "8", "0", "6"},
+                    {"综合评估", "8", "0", "7"},
+            };
+            for (String[] row : rows) {
+                jdbcTemplate.update(
+                        "INSERT INTO report_fingerprint (type_id, pattern, weight, is_exclusion, sort_order, created_at) "
+                                + "VALUES ('dan_a2', ?, ?, ?, ?, NOW())",
+                        row[0], Integer.valueOf(row[1]), Integer.valueOf(row[2]), Integer.valueOf(row[3]));
+            }
+            log.info("迁移323: 已写入 dan_a2 默认指纹规则 {} 条", rows.length);
+        } catch (Exception e) {
+            log.warn("迁移323: 写入 dan_a2 指纹规则失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移326: 商城营销菜单新增「限时折扣管理」(幂等)
+    private void seedDiscountRulesMenu() {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM sys_menu WHERE path = '/discount-rules'", Integer.class);
+            if (count != null && count > 0) {
+                log.info("迁移326: 限时折扣管理菜单已存在,跳过");
+                return;
+            }
+            jdbcTemplate.update(
+                    "INSERT INTO sys_menu (parent_id, title, label, path, icon, perm, sort, visible, created_at) "
+                            + "VALUES (15, NULL, '限时折扣管理', '/discount-rules', 'el-icon-timer', 'commerce:discount', 7, 1, NOW())");
+            log.info("迁移326: 已添加限时折扣管理菜单");
+        } catch (Exception e) {
+            log.warn("迁移326: 添加限时折扣管理菜单失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移327: 创建 user_shortcut_config 表(首页快捷方式自定义配置,每应用一条记录,locked=置顶)
+    private void createUserShortcutConfigTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_shortcut_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "app_key VARCHAR(64) NOT NULL COMMENT '应用key', " +
+                    "locked TINYINT DEFAULT 0 COMMENT '是否锁定置顶(1=锁定,0=普通)', " +
+                    "sort_order INT DEFAULT 0 COMMENT '手动排序序号', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_user_app (user_id, app_key)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页快捷方式自定义配置'");
+            log.info("迁移327: 已创建 user_shortcut_config 表");
+        } catch (Exception e) {
+            log.warn("迁移327: 创建 user_shortcut_config 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移327: 创建 user_app_usage 表(应用使用频度记录)
+    private void createUserAppUsageTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_app_usage (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                    "app_key VARCHAR(64) NOT NULL COMMENT '应用key', " +
+                    "enter_count INT DEFAULT 0 COMMENT '累计进入次数', " +
+                    "last_enter_at DATETIME COMMENT '最近进入时间', " +
+                    "UNIQUE KEY uk_user_app (user_id, app_key)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用使用频度记录'");
+            log.info("迁移327: 已创建 user_app_usage 表");
+        } catch (Exception e) {
+            log.warn("迁移327: 创建 user_app_usage 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移333: 预置引导弹窗种子数据(parent_first_login,首次登录家长端)
+    private void seedGuidePopupData() {
+        try {
+            Long count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM guide_popup WHERE slug = 'parent_first_login'", Long.class);
+            if (count != null && count > 0L) {
+                log.info("迁移333: parent_first_login 种子弹窗已存在,跳过");
+                return;
+            }
+            jdbcTemplate.update(
+                    "INSERT INTO guide_popup (slug, title, description, icon, icon_type, theme_color, btn_text, btn_action, status, sort_order, created_at, updated_at) "
+                            + "VALUES ('parent_first_login', '欢迎使用浠艾福', '身·智·富·行·心,全家健康一站式管理', '🌏', 0, '#F97316', '开始体验', '', 1, 0, NOW(), NOW())");
+            Long popupId = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
+            // 步骤
+            jdbcTemplate.update("INSERT INTO guide_popup_step (popup_id, step_order, title, desc, icon, icon_type, is_intro, btn_text, btn_action) VALUES (?, 0, '你真的了解家人的状态吗?', '情绪低落没察觉、习惯变化没注意、能力短板没发现——什么问题等爆发了才看见。浠艾福帮你在问题发生前,看清每位家人的真实状态。', '🔍', 0, 1, '下一步', '')", popupId);
+            jdbcTemplate.update("INSERT INTO guide_popup_step (popup_id, step_order, title, desc, icon, icon_type, is_intro, btn_text, btn_action) VALUES (?, 1, '五维能量,看见全家', '身·心·智·行·富,一张全景图看清每位家人。谁的身体需要关注、谁的情绪需要疏导、谁的状态正在下滑——不用猜,看得见。', '🌏', 0, 0, '下一步', '')", popupId);
+            jdbcTemplate.update("INSERT INTO guide_popup_step (popup_id, step_order, title, desc, icon, icon_type, is_intro, btn_text, btn_action) VALUES (?, 2, '小行动,大改变', '全家一起完成每日小任务,各自攒星星。每天一点点积累,好习惯自然养成。', '📋', 0, 0, '下一步', '')", popupId);
+            jdbcTemplate.update("INSERT INTO guide_popup_step (popup_id, step_order, title, desc, icon, icon_type, is_intro, btn_text, btn_action) VALUES (?, 3, '心愿墙,看得见努力', '每位家人用自己的星星兑换心愿。努力有回报,全家人都能看到彼此的坚持。', '🎁', 0, 0, '开始体验', '')", popupId);
+            // 触发条件:首次登录,parent 角色
+            jdbcTemplate.update("INSERT INTO guide_popup_trigger (popup_id, trigger_type, target_page, target_action, min_role, extra_config, created_at) VALUES (?, 'first_login', '', '', 'parent', NULL, NOW())", popupId);
+            log.info("迁移333: 已预置 parent_first_login 种子弹窗 (popup_id={})", popupId);
+        } catch (Exception e) {
+            log.warn("预置引导弹窗种子数据失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移337: product_orders 表添加 gift_amount 列(送会员商品原价金额,分)
+    private void addGiftAmountColumn() {
+        ensureColumn("product_orders", "gift_amount", "INT DEFAULT 0 COMMENT '送会员商品原价金额(分)'");
+    }
+
+    // 迁移338: 创建 gift_membership_progress 表(家庭维度送会员累计进度)
+    private void createGiftMembershipProgressTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS gift_membership_progress (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
+                    "accumulated_amount INT DEFAULT 0 COMMENT '累计送会员商品原价金额(分)', " +
+                    "granted INT DEFAULT 0 COMMENT '是否已赠送 1=已赠送 0=未赠送', " +
+                    "granted_level VARCHAR(50) COMMENT '已赠送会员等级', " +
+                    "granted_order_no VARCHAR(64) COMMENT '触发赠送的订单号', " +
+                    "granted_at DATETIME COMMENT '赠送时间', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='送会员累计进度'");
+            log.info("迁移338: 已创建 gift_membership_progress 表");
+        } catch (Exception e) {
+            log.warn("迁移338: 创建 gift_membership_progress 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移339: tasks 表加 is_flow_task / successor_task_ids 列(TASK-SYS-248 流程任务前置/后置逻辑)
+    private void addFlowTaskColumnsToTasksTable() {
+        ensureColumn("tasks", "is_flow_task", "TINYINT DEFAULT 0 COMMENT '是否为流程任务:1=是(有前置/后置任务),0=否'");
+        ensureColumn("tasks", "successor_task_ids", "TEXT NULL COMMENT '后置任务IDs JSON: [1,2,3],任意一个完成则解锁(OR 逻辑)'");
+    }
+
+    // 迁移339: insurance_policies 表加 policy_terms 列(保单原始条款,AI 解析后原样保存)
+    private void addPolicyTermsColumn() {
+        ensureColumn("insurance_policies", "policy_terms", "TEXT COMMENT '保单原始条款(AI 解析后原样保存)'");
+    }
+
+    // 迁移340: 创建 home_banners 表(首页广告栏配置)
+    private void createHomeBannersTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS home_banners (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "title VARCHAR(100) NOT NULL COMMENT '展示标题'," +
+                    "banner_type VARCHAR(20) NOT NULL COMMENT '类型: activity/product/link'," +
+                    "ref_id BIGINT DEFAULT NULL COMMENT '关联活动/商品ID(banner_type=activity/product时有效)'," +
+                    "cover_image VARCHAR(500) DEFAULT NULL COMMENT '封面图URL(可覆盖默认封面)'," +
+                    "link_url VARCHAR(500) DEFAULT NULL COMMENT '跳转链接(banner_type=link时有效)'," +
+                    "sort_order INT NOT NULL DEFAULT 0 COMMENT '排序,值越小越靠前'," +
+                    "enabled TINYINT NOT NULL DEFAULT 1 COMMENT '启用状态:1=启用 0=禁用'," +
+                    "start_time DATETIME DEFAULT NULL COMMENT '投放开始时间(NULL=不限)'," +
+                    "end_time DATETIME DEFAULT NULL COMMENT '投放结束时间(NULL=不限)'," +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页广告栏配置表'");
+            log.info("迁移340: 已创建 home_banners 表");
+        } catch (Exception e) {
+            log.warn("迁移340: 创建 home_banners 表失败: {}", e.getMessage());
+        }
+    }
+
+    /** 迁移343: health_report_drafts 补 member_id 列(commit 21f8f6740 遗漏,schema.sql 已含但无迁移脚本) */
+    /** 迁移345: health_plans 补 total_days 列(方案周期天数,驱动服务任务周期边界与截止日期) */
+    private void ensureHealthPlansTotalDaysColumn() {
+        ensureColumn("health_plans", "total_days",
+                "INT DEFAULT NULL COMMENT '方案周期天数,驱动服务任务周期边界与截止日期'");
+        log.info("迁移345: health_plans.total_days 已确保存在");
+    }
+
+    /** 迁移346: 未打卡任务提醒通知模板(原有种子仅在空表时插入,此处单独幂等补种) */
+    /** 迁移346: 未打卡任务提醒通知模板(原有种子仅在空表时插入,此处单独幂等补种) */
+    private void seedTaskReminderTemplate() {
+        try {
+            Integer cnt = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM notification_templates WHERE type = ?",
+                    Integer.class, "task_reminder");
+            if (cnt != null && cnt == 0) {
+                jdbcTemplate.update("INSERT INTO notification_templates (type, title, content, channels) VALUES (?,?,?,?)",
+                        "task_reminder",
+                        "任务待完成提醒",
+                        "任务「{title}」{status},完成后记得打卡,可获得积分与 CF 值奖励。",
+                        "in_app,sms");
+                log.info("迁移346: task_reminder 通知模板已插入");
+            }
+        } catch (Exception e) {
+            log.warn("迁移346: 插入 task_reminder 通知模板失败: " + e.getMessage());
+        }
+        try {
+            Integer c2 = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM notification_templates WHERE type = ?",
+                    Integer.class, "task_completion_praise");
+            if (c2 != null && c2 == 0) {
+                jdbcTemplate.update("INSERT INTO notification_templates (type, title, content, channels) VALUES (?,?,?,?)",
+                        "task_completion_praise",
+                        "任务完成,太棒了!",
+                        "太棒了!任务「{title}」已完成,继续保持,加油~",
+                        "in_app,wechat");
+                log.info("迁移346+: task_completion_praise 通知模板已插入");
+            }
+        } catch (Exception e) {
+            log.warn("迁移346+: 插入 task_completion_praise 通知模板失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移347: 回填健康方案任务的依赖链。
+     * 历史上使用任务只写了 parent_task_id,从未写 prerequisite_task_id,导致"采购完成才激活服务任务"的闸门失效。
+     * 这里补写依赖并把前置未完成的使用任务置为 locked。
+     * 幂等:每次启动只处理 prerequisite IS NULL / status=pending 的行,可重复执行。
+     */
+    /**
+     * 迁移348: 回填周期实例的 family_member_id。
+     * RepeatTaskGenerator 过去未透传该字段,导致周期实例在"今日任务"列表查不到、
+     * 且 completeTask 抛 NPE。存量实例按其模板回填,模板缺失时按 child_id 兜底。
+     */
+    private void backfillTemplateInstanceFamilyMemberId() {
+        try {
+            int fromTpl = jdbcTemplate.update(
+                    "UPDATE tasks i JOIN tasks tpl ON tpl.id = i.template_id " +
+                    "SET i.family_member_id = tpl.family_member_id " +
+                    "WHERE i.family_member_id IS NULL AND tpl.family_member_id IS NOT NULL");
+            int fromChild = jdbcTemplate.update(
+                    "UPDATE tasks SET family_member_id = child_id " +
+                    "WHERE family_member_id IS NULL AND child_id IS NOT NULL AND is_template = 0");
+            if (fromTpl > 0 || fromChild > 0) {
+                log.info("迁移348: 周期实例 family_member_id 回填 模板来源={}条 child_id兜底={}条",
+                        fromTpl, fromChild);
+            }
+        } catch (Exception e) {
+            log.warn("迁移348: 周期实例 family_member_id 回填失败: " + e.getMessage());
+        }
+    }
+
+    private void backfillHealthPlanTaskPrerequisite() {
+        try {
+            int linked = jdbcTemplate.update(
+                    "UPDATE tasks SET prerequisite_task_id = parent_task_id " +
+                    "WHERE source_type = 'health_plan' AND category = '使用任务' " +
+                    "AND parent_task_id IS NOT NULL AND prerequisite_task_id IS NULL");
+            // 注意:单表 UPDATE 的 WHERE 子查询不能引用目标表本身(MySQL 1093),必须用 LEFT JOIN 自连接
+            int locked = jdbcTemplate.update(
+                    "UPDATE tasks t LEFT JOIN tasks p " +
+                    "ON p.id = t.prerequisite_task_id AND p.status = 'completed' " +
+                    "SET t.status = 'locked' " +
+                    "WHERE t.source_type = 'health_plan' AND t.category = '使用任务' " +
+                    "AND t.prerequisite_task_id IS NOT NULL AND t.status = 'pending' AND p.id IS NULL");
+            if (linked > 0 || locked > 0) {
+                log.info("迁移347: 健康方案任务依赖回填 prerequisite={}条, 置locked={}条", linked, locked);
+            }
+        } catch (Exception e) {
+            log.warn("迁移347: 健康方案任务依赖回填失败: " + e.getMessage());
+        }
+    }
+
+    private void ensureHealthReportDraftsMemberIdColumn() {
+        ensureColumn("health_report_drafts", "member_id",
+                "BIGINT DEFAULT NULL COMMENT '报告归属成员ID(family_members.id),A2/DAN自动采集必需'");
+        log.info("迁移343: health_report_drafts.member_id 已确保存在");
+    }
+
+    // 迁移344: 创建 supply_customer_relation 表(供应商团队客户归属记录)
+    private void createSupplyCustomerRelationTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS supply_customer_relation (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "system_id BIGINT NOT NULL COMMENT '供应商体系ID(supply_system.id)'," +
+                    "parent_user_id BIGINT NOT NULL COMMENT '上级供应商用户ID(supply_hierarchy.parent_id)'," +
+                    "family_id BIGINT NOT NULL COMMENT '客户家庭ID(family_members.family_id)'," +
+                    "child_user_id BIGINT DEFAULT NULL COMMENT '下单用户ID(users.id),可为空'," +
+                    "relation_type VARCHAR(32) NOT NULL DEFAULT 'family' COMMENT '归属类型: family=家庭归属 order=下单自动 referral=推荐码'," +
+                    "source VARCHAR(32) NOT NULL DEFAULT 'manual' COMMENT '来源: manual=人工绑定 order=下单自动 referral=推荐码'," +
+                    "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "INDEX idx_system_family (system_id, family_id)," +
+                    "INDEX idx_parent_user (parent_user_id)," +
+                    "INDEX idx_family_id (family_id)," +
+                    "UNIQUE KEY uk_system_family (system_id, family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商团队客户归属关系表'");
+            log.info("迁移344: 已创建 supply_customer_relation 表");
+        } catch (Exception e) {
+            log.warn("迁移344: 创建 supply_customer_relation 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移345: 创建 product_dimension_mapping 表(商品-维度关联)
+    private void createProductDimensionMappingTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_dimension_mapping (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID(products.id)'," +
+                    "dimension_code VARCHAR(32) NOT NULL COMMENT '维度编码: body/wisdom/mind/action/wealth'," +
+                    "match_score INT DEFAULT 100 COMMENT '匹配度 0-100'," +
+                    "match_reason VARCHAR(200) COMMENT '匹配原因说明'," +
+                    "tags VARCHAR(500) COMMENT '推荐标签JSON'," +
+                    "enabled TINYINT DEFAULT 1 COMMENT '启用状态 1=启用 0=禁用'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," +
+                    "INDEX idx_product_id (product_id)," +
+                    "INDEX idx_dimension (dimension_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品维度关联表'");
+            log.info("迁移345: 已创建 product_dimension_mapping 表");
+        } catch (Exception e) {
+            log.warn("迁移345: 创建 product_dimension_mapping 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移346: 创建 product_recommendation_log 表(推荐曝光日志)
+    private void createProductRecommendationLogTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_recommendation_log (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "user_id BIGINT DEFAULT NULL COMMENT '用户ID(users.id)'," +
+                    "family_id BIGINT DEFAULT NULL COMMENT '家庭ID(family_members.family_id)'," +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID'," +
+                    "scene VARCHAR(32) NOT NULL COMMENT '场景: dimension_page/ai_chat/report_upload/repurchase'," +
+                    "reason VARCHAR(200) COMMENT '推荐理由'," +
+                    "match_score INT DEFAULT NULL COMMENT '匹配分'," +
+                    "was_clicked TINYINT DEFAULT 0 COMMENT '是否点击 1=是 0=否'," +
+                    "was_purchased TINYINT DEFAULT 0 COMMENT '是否购买 1=是 0=否'," +
+                    "clicked_at DATETIME DEFAULT NULL COMMENT '点击时间'," +
+                    "purchased_at DATETIME DEFAULT NULL COMMENT '购买时间'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "INDEX idx_user_scene (user_id, scene)," +
+                    "INDEX idx_product_purchased (product_id, was_purchased)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐曝光日志表'");
+            log.info("迁移346: 已创建 product_recommendation_log 表");
+        } catch (Exception e) {
+            log.warn("迁移346: 创建 product_recommendation_log 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移347: 创建 repurchase_reminder_record 和 repurchase_reminder_config 表
+    private void createRepurchaseReminderTables() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS repurchase_reminder_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID(users.id)'," +
+                    "product_id BIGINT NOT NULL COMMENT '商品ID'," +
+                    "order_id BIGINT DEFAULT NULL COMMENT '关联订单ID'," +
+                    "reminder_days INT DEFAULT 30 COMMENT '提醒天数'," +
+                    "sent_at DATETIME DEFAULT NULL COMMENT '发送时间'," +
+                    "clicked TINYINT DEFAULT 0 COMMENT '是否点击 1=是 0=否'," +
+                    "purchased TINYINT DEFAULT 0 COMMENT '是否购买 1=是 0=否'," +
+                    "INDEX idx_user_pending (user_id, purchased)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='复购提醒记录表'");
+            log.info("迁移347a: 已创建 repurchase_reminder_record 表");
+        } catch (Exception e) {
+            log.warn("迁移347a: 创建 repurchase_reminder_record 表失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS repurchase_reminder_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "product_category VARCHAR(100) DEFAULT NULL COMMENT '商品类目'," +
+                    "product_id BIGINT DEFAULT NULL COMMENT '特定商品ID(优先于category)'," +
+                    "reminder_days INT DEFAULT 30 COMMENT '提醒天数'," +
+                    "reminder_template VARCHAR(500) DEFAULT NULL COMMENT '提醒话术模板'," +
+                    "max_reminders INT DEFAULT 3 COMMENT '最大提醒次数'," +
+                    "enabled TINYINT DEFAULT 1 COMMENT '启用状态 1=启用 0=禁用'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='复购提醒配置表'");
+            log.info("迁移347b: 已创建 repurchase_reminder_config 表");
+        } catch (Exception e) {
+            log.warn("迁移347b: 创建 repurchase_reminder_config 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移348: 创建 recommendation_product_associations 表(商品推荐关联关系)
+    private void createRecommendationProductAssociationsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS recommendation_product_associations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "source_product_id BIGINT NOT NULL COMMENT '源商品ID(已购/关注商品)'," +
+                    "target_product_id BIGINT NOT NULL COMMENT '目标商品ID(被推荐商品)'," +
+                    "association_type VARCHAR(32) NOT NULL COMMENT '类型: co_purchase/repurchase/profile_gap/manual'," +
+                    "strength DECIMAL(5,2) DEFAULT 0.50 COMMENT '关联强度 0.00-1.00'," +
+                    "manual_override TINYINT DEFAULT 0 COMMENT '是否运营手动覆盖 1=是 0=否'," +
+                    "reason VARCHAR(500) DEFAULT NULL COMMENT '关联原因说明'," +
+                    "dimension_code VARCHAR(32) DEFAULT NULL COMMENT '关联维度(body/mind/wisdom/action/wealth)'," +
+                    "repurchase_days INT DEFAULT NULL COMMENT '复购周期天数(repurchase场景)'," +
+                    "co_purchase_window_days INT DEFAULT 30 COMMENT '共购时间窗口天数(co_purchase场景)'," +
+                    "co_purchase_count INT DEFAULT 0 COMMENT '共购次数(co_purchase场景自动累计)'," +
+                    "enabled TINYINT DEFAULT 1 COMMENT '启用状态 1=启用 0=禁用'," +
+                    "created_by BIGINT DEFAULT NULL COMMENT '创建人用户ID(manual场景记录运营)'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," +
+                    "INDEX idx_source_target (source_product_id, target_product_id)," +
+                    "INDEX idx_type_enabled (association_type, enabled)," +
+                    "INDEX idx_dimension (dimension_code)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品推荐关联关系表'");
+            log.info("迁移348: 已创建 recommendation_product_associations 表");
+        } catch (Exception e) {
+            log.warn("迁移348: 创建 recommendation_product_associations 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移349: product_recommendation_log 表添加 AB测试分组与推荐来源类型列
+    private void addAbTestColumnsToRecommendationLog() {
+        ensureColumn("product_recommendation_log", "ab_test_group",
+                "VARCHAR(32) DEFAULT NULL COMMENT 'AB测试分组(A/B/C/控制组)'");
+        ensureColumn("product_recommendation_log", "recommendation_type",
+                "VARCHAR(32) DEFAULT NULL COMMENT '推荐来源类型:dimension/co_purchase/repurchase/profile_gap/manual'");
+        log.info("迁移349: product_recommendation_log 已添加 ab_test_group / recommendation_type 列");
+    }
+    // 迁移349: 创建 report_transfer_requests 表(健康报告跨家庭转移申请)
+    // 同一份报告(file_hash 相同)被其他家庭上传时,申请方家庭发起转移申请,
+    // 原归属家庭同意后 health_reports.family_id 转移至申请方,原家庭不再可见。
+    private void createReportTransferRequestsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_transfer_requests (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "report_id BIGINT NOT NULL COMMENT '报告ID(health_reports.id)'," +
+                    "from_family_id BIGINT NOT NULL COMMENT '原归属家庭ID(需同意方)'," +
+                    "to_family_id BIGINT NOT NULL COMMENT '申请转入家庭ID(申请方)'," +
+                    "requester_user_id BIGINT NOT NULL COMMENT '申请人用户ID(users.id)'," +
+                    "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending=待同意 approved=已同意 denied=已拒绝'," +
+                    "handled_by BIGINT DEFAULT NULL COMMENT '处理人用户ID'," +
+                    "handled_at DATETIME DEFAULT NULL COMMENT '处理时间'," +
+                    "remark VARCHAR(200) DEFAULT NULL 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_report_id (report_id)," +
+                    "INDEX idx_from_family_status (from_family_id, status)," +
+                    "INDEX idx_to_family_status (to_family_id, status)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告跨家庭转移申请'");
+            log.info("迁移349: 已创建 report_transfer_requests 表");
+        } catch (Exception e) {
+            log.warn("迁移349: 创建 report_transfer_requests 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移350: products 表添加 min_discount_level 列(商品粒度控制会员价门槛,NULL=所有会员均可享会员价)
+    private void addProductMinDiscountLevel() {
+        try {
+            jdbcTemplate.execute("ALTER TABLE products ADD COLUMN min_discount_level VARCHAR(50) DEFAULT NULL " +
+                    "COMMENT '享受会员价所需的最低会员级别编码(FREE/FAMILY/PREMIUM/LIFETIME,NULL=所有会员均可)' AFTER min_visible_level");
+            log.info("迁移350: 已添加 products.min_discount_level 列");
+        } catch (Exception e) {
+            // 列已存在,忽略
+            log.debug("迁移350: products.min_discount_level 列已存在,跳过");
+        }
+    }
+
+    // 迁移351: membership_levels.level_order 顺延,为 BASIC 让出独立档位
+    // 会员级别包含关系(数字越小级别越低、可见范围越大): FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6
+    private void backfillMembershipLevelOrder() {
+        try {
+            int updated = jdbcTemplate.update("UPDATE membership_levels SET level_order = CASE level_code " +
+                    "WHEN 'FREE' THEN 1 WHEN 'BASIC' THEN 2 WHEN 'FAMILY' THEN 3 WHEN 'PROVIDER' THEN 4 " +
+                    "WHEN 'PREMIUM' THEN 5 WHEN 'LIFETIME' THEN 6 ELSE level_order END");
+            log.info("迁移351: 已回填 membership_levels.level_order({} 行):FREE=1 BASIC=2 FAMILY=3 PROVIDER=4 PREMIUM=5 LIFETIME=6", updated);
+        } catch (Exception e) {
+            log.warn("迁移351: 回填 membership_levels.level_order 失败: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * 迁移360: 恢复 9.9 元/月 BASIC 体验会员等级与权益(幂等)。
+     * 反向还原提交 f86dd164b 删除的内容;level_order 由 backfillMembershipLevelOrder 统一回填。
+     */
+    private void restoreBasicMembershipLevel() {
+        try {
+            jdbcTemplate.execute(
+                    "INSERT IGNORE INTO membership_levels " +
+                            "(level_code, level_name, level_desc, price_monthly, features, max_children, max_tasks_per_day) " +
+                            "VALUES ('BASIC', '体验会员', " +
+                            "'9.9元/月体验版,解锁核心功能,限次使用AI与报告解读', " +
+                            "990, " +
+                            "'[\"free_activities\",\"free_courses\",\"discount_purchase\",\"invite_family\",\"mascot_ai_lite\",\"ai_report_reading_lite\",\"assessment_quota_lite\",\"health_report_upload_lite\",\"growth_plan_lite\",\"task_template_lite\",\"referral_commission\"]', " +
+                            "3, 3)");
+            log.info("迁移360: 已恢复 BASIC 体验会员等级");
+        } catch (Exception e) {
+            log.warn("迁移360: 恢复 BASIC 会员等级失败: {}", e.getMessage());
+        }
+        // 兼容历史数据:早期 BASIC features 缺少 referral_commission,补齐
+        try {
+            jdbcTemplate.update("UPDATE membership_levels SET features = ? WHERE level_code = 'BASIC' AND features NOT LIKE '%referral_commission%'",
+                    "[\"free_activities\",\"free_courses\",\"discount_purchase\",\"invite_family\",\"mascot_ai_lite\",\"ai_report_reading_lite\",\"assessment_quota_lite\",\"health_report_upload_lite\",\"growth_plan_lite\",\"task_template_lite\",\"referral_commission\"]");
+        } catch (Exception e) {
+            log.warn("迁移360: 补齐 BASIC features 失败: {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute(
+                    "INSERT IGNORE INTO membership_benefits " +
+                            "(level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES " +
+                            "('BASIC', 'AI_CHAT_LITE', 'AI健康顾问(限3次/月)', '每月3次AI健康顾问问答', '🤖', '#DBEAFE', 1), " +
+                            "('BASIC', 'REPORT_READING_LITE', '报告解读(限1次/月)', '每月1次体检/测评报告AI解读', '📊', '#FCE7F3', 4), " +
+                            "('BASIC', 'GROWTH_PLAN_LITE', '基础成长计划', '创建成长计划但模板有限', '📋', '#FEF3C7', 3), " +
+                            "('BASIC', 'ASSESSMENT_QUOTA_LITE', '测评预约(限1次)', '每月可预约1次测试', '📅', '#E0E7FF', 8)");
+            log.info("迁移360: 已恢复 BASIC 会员权益");
+        } catch (Exception e) {
+            log.warn("迁移360: 恢复 BASIC 会员权益失败: {}", e.getMessage());
+        }
+    }
+
+    private void removeReportUploadQuota() {
+        try {
+            int deletedBenefits = jdbcTemplate.update(
+                    "DELETE FROM membership_benefits WHERE level_code = 'BASIC' AND benefit_code = 'REPORT_UPLOAD_LITE'");
+            log.info("迁移352: 已清理遗留权益记录({} 行)", deletedBenefits);
+        } catch (Exception e) {
+            log.warn("迁移352: 移除 REPORT_UPLOAD_LITE 权益失败: {}", e.getMessage());
+        }
+        try {
+            int deletedUsage = jdbcTemplate.update(
+                    "DELETE FROM basic_quota_usage WHERE quota_type = 'REPORT_UPLOAD'");
+            log.info("迁移352: 已清理 REPORT_UPLOAD 配额记录({} 行)", deletedUsage);
+        } catch (Exception e) {
+            log.warn("迁移352: 清理 REPORT_UPLOAD 配额记录失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移353: 任务提醒配置表(用户级,一行/用户;提醒渠道/时机/偏移分钟可配置)
+    private void createTaskReminderConfigTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS task_reminder_config (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "user_id BIGINT NOT NULL COMMENT '用户ID(users.id),家长账号'," +
+                    "channels VARCHAR(100) DEFAULT 'wechat' COMMENT '提醒渠道(逗号分隔): in_app/wechat/sms/phone/email'," +
+                    "timing_mode VARCHAR(20) DEFAULT 'after_timeout' COMMENT '提醒时机: before_deadline=截止前提醒 after_timeout=超时后提醒'," +
+                    "offset_minutes INT DEFAULT 60 COMMENT '偏移分钟: before_deadline=截止前N分钟; after_timeout=超时后N分钟'," +
+                    "enabled TINYINT DEFAULT 1 COMMENT '是否启用 1=启用 0=停用'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," +
+                    "UNIQUE KEY uk_user (user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务提醒配置表'");
+            log.info("迁移353: 已创建 task_reminder_config 表");
+            try { ensureColumn("task_reminder_config", "completion_praise_enabled", "TINYINT DEFAULT 0 COMMENT '任务正常完成时是否发送表扬鼓励消息 1=启用 0=停用'"); ensureColumn("task_reminder_config", "completion_praise_channels", "VARCHAR(100) DEFAULT 'wechat,in_app' COMMENT '完成表扬渠道(逗号分隔)'"); } catch (Exception ex) { log.warn("迁移353补充列失败: {}", ex.getMessage()); }
+        } catch (Exception e) {
+            log.warn("迁移353: 创建 task_reminder_config 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移359: 跨小程序单点登录一次性票据表(cfc ↔ train 双向免登)
+    // 详见 schema.sql。此处自愈建表,避免 schema.sql 建表语句因故静默失败导致登录报错。
+    private void createSsoTicketTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sso_ticket (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "ticket VARCHAR(64) NOT NULL COMMENT '一次性票据(SecureRandom 生成)'," +
+                    "source_system VARCHAR(16) NOT NULL COMMENT '签发方系统: cfc / train'," +
+                    "target_system VARCHAR(16) NOT NULL COMMENT '目标系统: cfc / train'," +
+                    "source_user_id BIGINT NOT NULL COMMENT '签发方用户ID(cfc=users.id / train=train_user.id)'," +
+                    "cfc_user_id BIGINT NOT NULL COMMENT 'cfc 统一会员ID(users.id),跨系统换票依据'," +
+                    "used TINYINT NOT NULL DEFAULT 0 COMMENT '是否已使用: 0=未用 1=已用'," +
+                    "expire_at DATETIME NOT NULL COMMENT '过期时间'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "UNIQUE KEY uk_ticket (ticket)," +
+                    "INDEX idx_expire (expire_at)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跨小程序单点登录一次性票据'");
+            log.info("迁移359: 已创建 sso_ticket 表");
+        } catch (Exception e) {
+            log.warn("迁移359: 创建 sso_ticket 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移355: product_recommendation_log 补齐缺失列
+    // 旧库该表由更早版本的建表语句创建(CREATE TABLE IF NOT EXISTS 不会补列),
+    // 缺 family_id/was_clicked/was_purchased/clicked_at/purchased_at/updated_at,
+    // 导致 stats/ab-summary、stats/by-type 查询报 Unknown column 返回 500
+    private void addMissingColumnsToRecommendationLog() {
+        ensureColumn("product_recommendation_log", "family_id",
+                "BIGINT DEFAULT NULL COMMENT '家庭ID(family_members.family_id)'");
+        ensureColumn("product_recommendation_log", "was_clicked",
+                "TINYINT DEFAULT 0 COMMENT '是否点击 1=是 0=否'");
+        ensureColumn("product_recommendation_log", "was_purchased",
+                "TINYINT DEFAULT 0 COMMENT '是否购买 1=是 0=否'");
+        ensureColumn("product_recommendation_log", "clicked_at",
+                "DATETIME DEFAULT NULL COMMENT '点击时间'");
+        ensureColumn("product_recommendation_log", "purchased_at",
+                "DATETIME DEFAULT NULL COMMENT '购买时间'");
+        ensureColumn("product_recommendation_log", "updated_at",
+                "DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'");
+        log.info("迁移355: product_recommendation_log 已补齐缺失列");
+    }
+
+    // 迁移354: 报告采集指标项定义(report_type_registry.expected_indicators)+ 草稿采集校验结果列(health_report_drafts.collect_validation)
+    // 每个报告类型定义「应采集指标项」清单(JSON 数组),ReportCollectService 采集完成后按此校验完整性,结果写入草稿 collect_validation。
+    // DAN 指标项按 测评数据_优化提取.csv 各报告类型列(填充率>=30%)总结;菌群指标项按 docs/参考资料 已解析报告的指标/区块总结。
+    private void registerCollectExpectedIndicators() {
+        ensureColumn("report_type_registry", "expected_indicators",
+                "TEXT NULL COMMENT '该报告类型应采集的指标项定义(JSON数组),用于数据采集完整性校验'");
+        ensureColumn("health_report_drafts", "collect_validation",
+                "TEXT NULL COMMENT '采集指标校验结果(JSON: typeId/expected/matched/pass/missing)'");
+
+        // 确保注册表存在各 DAN 类型 + 北京菌群类型的行(parser_path 空 = 保持走 LangGraph 检测,不影响既有路由)
+        ensureReportTypeRow("dan_a1", "dan", "素质测评A1", "核心认知发展评估报告:感知觉/注意力/记忆力/推理能力/空间能力/加工速度");
+        ensureReportTypeRow("dan_b2", "dan", "素质测评B2", "儿童行为评估报告:自我概念/行为问题");
+        ensureReportTypeRow("dan_b3", "dan", "素质测评B3", "学习能力评估报告:执行功能/学习动机/学习策略/自我效能感");
+        ensureReportTypeRow("dan_b4", "dan", "素质测评B4", "核心素养评估报告:认知能力/成长型思维/自我概念");
+        ensureReportTypeRow("dan_b5", "dan", "素质测评B5", "青春期发展评估报告:情绪调节/人际关系/睡眠/运动/网络依赖/学业压力");
+        ensureReportTypeRow("dan_b6", "dan", "素质测评B6", "职业发展评估报告:霍兰德兴趣/多元智能/职业价值观");
+        ensureReportTypeRow("dan_c1", "dan", "素质测评C1", "校园版核心素养评估报告:认知能力/大五人格/自我概念/学习动机");
+        ensureReportTypeRow("gut_flora_beijing", "gut_flora", "北京菌群检测", "北京肠道菌群检测报告(FMT移植前/后评估)");
+
+        // 种子各类型「应采集指标项」定义(每次启动覆盖,保证与代码一致)
+        seedExpectedIndicator("dan_a1", jsonArray(
+                "感知觉_pct", "感知觉_score", "注意力_pct", "注意力_score",
+                "记忆力_pct", "记忆力_score", "推理能力_pct", "推理能力_score",
+                "空间能力_pct", "空间能力_score", "加工速度_pct", "加工速度_score",
+                "总分", "百分位"));
+        seedExpectedIndicator("dan_a2", jsonArray(
+                "BMI", "感知觉_pct", "记忆力_pct", "注意力_pct",
+                "外倾性", "宜人性", "开放性", "责任心", "神经质",
+                "情绪总分", "焦虑-安详", "抑郁-愉快", "无力感-掌控感", "自卑-自尊",
+                "与同伴亲近", "与同伴信任", "与同伴沟通",
+                "与母亲亲近", "与母亲信任", "与母亲沟通",
+                "与父亲亲近", "与父亲信任", "与父亲沟通",
+                "身高", "体重", "睡眠_小时", "运动_小时", "饮食_小时",
+                "总分", "百分位"));
+        seedExpectedIndicator("dan_b2", jsonArray(
+                "自我概念_合群", "自我概念_幸福与满足", "自我概念_情绪状态", "自我概念_能力与学校",
+                "自我概念_行为表现", "自我概念_躯体外貌",
+                "行为_品行问题", "行为_学习问题", "行为_情绪问题", "行为_生活习惯", "行为_社交问题"));
+        seedExpectedIndicator("dan_b3", jsonArray(
+                "工作记忆_pct", "认知灵活性_pct", "抑制控制_pct",
+                "表面动机", "深层动机", "学习自我调节", "表面方法与策略", "深层方法与策略",
+                "自我效能感", "总分"));
+        seedExpectedIndicator("dan_b4", jsonArray(
+                "感知觉_pct", "感知觉_score", "注意力_pct", "注意力_score",
+                "记忆力_pct", "记忆力_score", "推理能力_pct", "推理能力_score",
+                "空间能力_pct", "空间能力_score", "加工速度_pct", "加工速度_score",
+                "成长型思维",
+                "自我概念_合群", "自我概念_幸福与满足", "自我概念_情绪状态", "自我概念_能力与学校",
+                "自我概念_行为表现", "自我概念_躯体外貌",
+                "自主性", "胜任感", "归属感",
+                "总分", "百分位"));
+        seedExpectedIndicator("dan_b5", jsonArray(
+                "表达抑制", "认知重评",
+                "人际_父亲关系", "人际_父亲关系_过度期待", "人际_父亲关系_价值冲突",
+                "人际_父亲关系_控制压迫", "人际_父亲关系_情感疏离", "人际_父亲关系_双重标准",
+                "人际_母亲关系", "人际_母亲关系_过度期待", "人际_母亲关系_价值冲突",
+                "人际_母亲关系_控制压迫", "人际_母亲关系_情感疏离", "人际_母亲关系_双重标准",
+                "人际_师生关系", "人际_师生关系_敌对型", "人际_师生关系_高控制型",
+                "人际_师生关系_关系疏离型", "人际_师生关系_回避型",
+                "人际_同伴关系", "人际_同伴关系_被排斥型", "人际_同伴关系_边缘型",
+                "人际_同伴关系_攻击型", "人际_同伴关系_回避型", "人际_同伴关系_特殊因素型",
+                "社交_表达影响", "社交_冲突解决", "社交_情感表达", "社交_情感支持", "社交_主动交往",
+                "入睡时间_min", "睡眠_催眠药物", "睡眠_日间功能", "睡眠_时长", "睡眠_效率",
+                "睡眠_障碍", "睡眠_主观质量",
+                "网络依赖_戒断反应", "网络依赖_强迫使用", "网络依赖_时间管理", "网络依赖_总分",
+                "学业压力_家庭期望", "学业压力_师生关系", "学业压力_同伴竞争", "学业压力_学业负担", "学业压力_自我期望",
+                "运动_步行_频率", "运动_步行_时长", "运动_高强度_频率", "运动_高强度_时长",
+                "运动_久坐_频率", "运动_久坐_时长", "运动_中等强度_频率", "运动_中等强度_时长",
+                "总分", "百分位"));
+        seedExpectedIndicator("dan_b6", jsonArray(
+                "能力_内省", "能力_人际关系", "能力_音乐", "能力_自然",
+                "兴趣_常规型", "兴趣_社会型", "兴趣_事业型", "兴趣_现实型", "兴趣_研究型", "兴趣_艺术型",
+                "职业价值观"));
+        seedExpectedIndicator("dan_c1", jsonArray(
+                "BMI",
+                "感知觉_pct", "感知觉_score", "注意力_pct", "注意力_score",
+                "记忆力_pct", "记忆力_score", "推理能力_pct", "推理能力_score",
+                "空间能力_pct", "空间能力_score", "加工速度_pct", "加工速度_score",
+                "外倾性", "宜人性", "开放性", "责任心", "神经质", "情绪总分",
+                "自我概念_合群", "自我概念_幸福与满足", "自我概念_情绪状态", "自我概念_能力与学校",
+                "自我概念_行为表现", "自我概念_躯体外貌",
+                "表面动机", "深层动机", "学习自我调节", "表面方法与策略", "深层方法与策略", "自我效能感",
+                "自主性", "胜任感", "归属感",
+                "与同伴亲近", "与同伴信任", "与同伴沟通",
+                "与母亲亲近", "与母亲信任", "与母亲沟通",
+                "与父亲亲近", "与父亲信任", "与父亲沟通",
+                "身高", "体重", "睡眠时长", "运动时间",
+                "总分", "百分位"));
+        seedExpectedIndicator("gut_flora", jsonArray(
+                "肠道预测年龄", "肠型", "肠道菌群平衡", "菌群多样性", "有益菌", "有害菌", "核心菌属",
+                "健康总分", "菌群健康", "慢病控制", "营养均衡",
+                "疾病风险评估", "主要营养评估", "氨基酸评估", "维生素评估", "微量元素评估",
+                "主要消化道致病菌", "抗生素风险评估", "肠道屏障及代谢物", "短链脂肪酸",
+                "神经递质及激素", "菌群检出详细列表", "个体化食物推荐表"));
+        seedExpectedIndicator("gut_flora_beijing", jsonArray(
+                "肠型", "肠道微生物健康指数", "肠道菌群平衡评估", "肠道黏膜屏障", "肠道菌群多样性",
+                "肠道菌群表型评估", "肠道抗炎能力", "肠道免疫力", "肠道膳食纤维需求", "短链脂肪酸合成能力",
+                "核心菌属综合评分", "有益菌综合评分", "有害菌综合评分",
+                "疾病风险评估", "营养物质及营养素代谢评估", "抗生素风险评估",
+                "毒性物质清除能力评估", "胃肠道感染病原体评估", "趣味肠菌评估",
+                "菌群检出详细列表", "个体化食物推荐表"));
+    }
+
+    /** 注册报告类型行(INSERT IF 不存在,幂等;parser_path=null 保持走 LangGraph 检测) */
+    private void ensureReportTypeRow(String typeId, String family, String displayName, String description) {
+        try {
+            Integer count = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM report_type_registry WHERE type_id = ?", Integer.class, typeId);
+            if (count == null || count == 0) {
+                jdbcTemplate.update(
+                        "INSERT INTO report_type_registry (type_id, family, display_name, description, parser_path, " +
+                        "min_confidence, is_active, source, review_status, version, created_at, updated_at) " +
+                        "VALUES (?, ?, ?, ?, NULL, 30, 1, 'prebuilt', 'approved', '1.0', NOW(), NOW())",
+                        typeId, family, displayName, description);
+                log.info("迁移354: 已注册报告类型 {}", typeId);
+            }
+        } catch (Exception e) {
+            log.warn("迁移354: 注册报告类型 {} 失败: {}", typeId, e.getMessage());
+        }
+    }
+
+    /** 写入类型「应采集指标项」定义(仅当为空时写入,保留管理员编辑) */
+    void seedExpectedIndicator(String typeId, String expectedJson) {
+        try {
+            int updated = jdbcTemplate.update(
+                    "UPDATE report_type_registry SET expected_indicators = ?, updated_at = NOW() " +
+                    "WHERE type_id = ? AND (expected_indicators IS NULL OR expected_indicators = '')",
+                    expectedJson, typeId);
+            if (updated == 0) {
+                log.info("迁移354: 类型 {} 已有指标项定义,跳过种子", typeId);
+            } else {
+                log.info("迁移354: 已为类型 {} 写入指标项定义", typeId);
+            }
+        } catch (Exception e) {
+            log.warn("迁移354: 为类型 {} 写入指标项定义失败: {}", typeId, e.getMessage());
+        }
+    }
+
+    /** 简易 JSON 字符串数组序列化(指标项均为中文/ASCII,无转义风险) */
+    private String jsonArray(String... items) {
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < items.length; i++) {
+            if (i > 0) {
+                sb.append(',');
+            }
+            sb.append('"').append(items[i]).append('"');
+        }
+        return sb.append(']').toString();
+    }
+
+    // 迁移359: 家庭共餐选中食材表(持久化共餐场景食材选择)
+    private void createDietFamilySelectedIngredientsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS diet_family_selected_ingredients (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID'," +
+                    "family_id BIGINT NOT NULL COMMENT '家庭ID'," +
+                    "food_id BIGINT NOT NULL COMMENT '食材ID'," +
+                    "food_name VARCHAR(100) COMMENT '食材名称'," +
+                    "score INT COMMENT '推荐分'," +
+                    "reason VARCHAR(255) COMMENT '推荐理由'," +
+                    "category VARCHAR(50) COMMENT '分类'," +
+                    "calories_per_100g INT COMMENT '每100克卡路里'," +
+                    "cooking_level TINYINT COMMENT '烹饪难度(1=快手,2=中等,3=耗时)'," +
+                    "nutrition_score INT COMMENT '营养密度分(0-100)'," +
+                    "balance_reason VARCHAR(255) COMMENT '均衡化推荐理由'," +
+                    "sort_order INT DEFAULT 0 COMMENT '排序'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," +
+                    "UNIQUE KEY uk_family_food (family_id, food_id)," +
+                    "INDEX idx_family (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭共餐选中食材'");
+            log.info("迁移359: 已创建 diet_family_selected_ingredients 表");
+        } catch (Exception e) {
+            log.warn("迁移359: 创建 diet_family_selected_ingredients 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移356: indicator_definitions 升级为统一指标层(指标变化曲线/速查/关联 需求)
+    private void migrateIndicatorDefinitionsColumns() {
+        ensureColumn("indicator_definitions", "source_kind",
+                "VARCHAR(32) DEFAULT NULL COMMENT '指标来源: report_indicator/bacteria/food_index/dimension_score'");
+        ensureColumn("indicator_definitions", "report_type_scope",
+                "VARCHAR(32) DEFAULT NULL COMMENT '适用报告类型(type_id,空=通用)'");
+        ensureColumn("indicator_definitions", "value_type",
+                "VARCHAR(16) DEFAULT NULL COMMENT '语义类型: numeric/categorical/level'");
+        ensureColumn("indicator_definitions", "good_direction",
+                "VARCHAR(16) DEFAULT 'neutral' COMMENT '变化优劣方向: higher_is_better/lower_is_better/in_range_is_better/neutral'");
+        ensureColumn("indicator_definitions", "dimension_code",
+                "VARCHAR(16) DEFAULT NULL COMMENT '关联五维(body/mind/wisdom/action/wealth,可空)'");
+        log.info("迁移356: indicator_definitions 已补齐统一指标层 5 列");
+    }
+
+    // 迁移357: indicator_values 升级为统一观测时序(subject_id/family_id/report_id/report_date/unit/status)
+    private void migrateIndicatorValuesColumns() {
+        ensureColumn("indicator_values", "subject_id", "BIGINT DEFAULT NULL COMMENT '观测对象(family_members.id)'");
+        ensureColumn("indicator_values", "family_id", "BIGINT DEFAULT NULL COMMENT '所属家庭(family_members.family_id)'");
+        ensureColumn("indicator_values", "report_id", "BIGINT DEFAULT NULL COMMENT '来源报告(health_reports.id,食物指数可空)'");
+        ensureColumn("indicator_values", "report_date", "DATE DEFAULT NULL COMMENT '冗余真实报告日期(时序查询键)'");
+        ensureColumn("indicator_values", "unit", "VARCHAR(32) DEFAULT NULL COMMENT '观测单位'");
+        ensureColumn("indicator_values", "status", "VARCHAR(32) DEFAULT NULL COMMENT '观测状态快照(正常/偏高/偏低等)'");
+        log.info("迁移357: indicator_values 已补齐统一观测时序 6 列");
+    }
+
+    // 迁移358: indicator_values 幂等唯一键(source_type+source_id+definition_id)+ 时序查询索引
+    private void addIndicatorValuesKeys() {
+        try {
+            // 先清理极端重复(保留最小 id),避免建唯一键失败;正常数据本无重复
+            jdbcTemplate.execute(
+                    "DELETE v FROM indicator_values v " +
+                    "JOIN (SELECT MIN(id) keep_id, source_type, source_id, definition_id " +
+                    "      FROM indicator_values " +
+                    "      GROUP BY source_type, source_id, definition_id " +
+                    "      HAVING COUNT(*) > 1) d " +
+                    "  ON v.source_type = d.source_type AND v.source_id = d.source_id " +
+                    " AND v.definition_id = d.definition_id AND v.id <> d.keep_id");
+            log.info("迁移358: 已清理 indicator_values 重复行(如有)");
+        } catch (Exception e) {
+            log.warn("迁移358: 清理 indicator_values 重复行失败(表可能为空): {}", e.getMessage());
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE indicator_values ADD UNIQUE KEY uk_source_definition (source_type, source_id, definition_id)");
+            log.info("迁移358: 已添加 uk_source_definition 唯一键");
+        } catch (Exception e) {
+            log.info("迁移358: uk_source_definition 已存在,跳过");
+        }
+        try {
+            jdbcTemplate.execute("ALTER TABLE indicator_values ADD INDEX idx_subject_def_date (subject_id, definition_id, report_date)");
+            log.info("迁移358: 已添加 idx_subject_def_date 索引");
+        } catch (Exception e) {
+            log.info("迁移358: idx_subject_def_date 已存在,跳过");
+        }
+    }
+
+    // 迁移359: 新建 indicator_mapping_rules(指标归一化映射规则)
+    private void createIndicatorMappingRulesTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS indicator_mapping_rules (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "source_type VARCHAR(32) NOT NULL COMMENT '来源: gut_flora|physical_exam|dan_a2|food_recommend|five_dimension|health_dimension'," +
+                    "raw_name VARCHAR(200) NOT NULL COMMENT '报告中的原始名称(指标名/菌名/食材名)'," +
+                    "alias_text TEXT COMMENT '别名 JSON 数组,用于模糊匹配'," +
+                    "definition_id BIGINT NOT NULL COMMENT 'FK → indicator_definitions.id'," +
+                    "match_type VARCHAR(20) DEFAULT 'exact' COMMENT 'exact|fuzzy|ai_fallback|auto_created'," +
+                    "priority INT DEFAULT 0 COMMENT '多规则冲突时取最高'," +
+                    "status TINYINT DEFAULT 1," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "UNIQUE KEY uk_source_raw (source_type, raw_name)," +
+                    "INDEX idx_def (definition_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指标归一化映射规则'");
+            log.info("迁移359: 已创建 indicator_mapping_rules 表");
+        } catch (Exception e) {
+            log.warn("迁移359: 创建 indicator_mapping_rules 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移360: 新建 indicator_correlations(指标关联关系:人工定义 + 数据校准)
+    private void createIndicatorCorrelationsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS indicator_correlations (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+                    "definition_id_a BIGINT NOT NULL COMMENT 'code 字典序较小的指标'," +
+                    "definition_id_b BIGINT NOT NULL COMMENT 'code 字典序较大的指标'," +
+                    "relation_type VARCHAR(20) DEFAULT 'correlation' COMMENT 'correlation|influence|same_source'," +
+                    "direction VARCHAR(10) NOT NULL COMMENT 'positive|negative'," +
+                    "manual_strength DECIMAL(3,2) NOT NULL COMMENT '人工标定强度 0~1'," +
+                    "computed_strength DECIMAL(4,3) NULL COMMENT '统计计算相关系数(Spearman rho)'," +
+                    "method VARCHAR(20) DEFAULT 'spearman' COMMENT 'pearson|spearman'," +
+                    "sample_size INT DEFAULT 0 COMMENT '参与计算的聚合有效样本数'," +
+                    "confidence VARCHAR(10) COMMENT 'high(>=30)|medium(10~29)|low(<10)'," +
+                    "effective_strength DECIMAL(4,3) NOT NULL COMMENT '展示值: 样本>=30 时取 computed 否则取 manual'," +
+                    "manual_locked TINYINT DEFAULT 0 COMMENT '1=人工锁定,校准不覆盖'," +
+                    "cohort_scope VARCHAR(20) DEFAULT 'subject' COMMENT 'subject|family|population'," +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT 'active|inactive|pending'," +
+                    "computed_at DATETIME NULL COMMENT '最后校准时间'," +
+                    "created_by BIGINT NULL COMMENT '创建者'," +
+                    "description VARCHAR(500) NULL COMMENT '给用户看的解释'," +
+                    "source_note VARCHAR(255) NULL COMMENT '依据/出处'," +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," +
+                    "UNIQUE KEY uk_pair (definition_id_a, definition_id_b)," +
+                    "INDEX idx_def_a (definition_id_a)," +
+                    "INDEX idx_def_b (definition_id_b)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='指标关联关系(人工定义+数据校准)'");
+            log.info("迁移360: 已创建 indicator_correlations 表");
+        } catch (Exception e) {
+            log.warn("迁移360: 创建 indicator_correlations 表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移361: health_indicators.collection_date 生产库兜底(schema.sql 已有该列,仅确保老库补齐)
+    private void ensureIndicatorCollectionDate() {
+        ensureColumn("health_indicators", "collection_date",
+                "DATETIME COMMENT '指标采集日期(报告检测日期)'");
+        log.info("迁移361: health_indicators.collection_date 已确认存在");
+    }
+
+    // 迁移362: 预置五维展示定义(dim.body 等,仅作展示标签,不写 indicator_values)
+    private void seedDimensionDefinitions() {
+        Object[][] dims = {
+            {"dim.body",     "身", "身体",  "higher_is_better", "higher_is_better"},
+            {"dim.mind",     "心", "心理",  "higher_is_better", "higher_is_better"},
+            {"dim.wisdom",   "智", "智慧",  "higher_is_better", "higher_is_better"},
+            {"dim.action",   "行", "行动",  "higher_is_better", "higher_is_better"},
+            {"dim.wealth",   "富", "财富",  "higher_is_better", "higher_is_better"},
+        };
+        for (Object[] d : dims) {
+            try {
+                Integer exists = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM indicator_definitions WHERE code = ?", Integer.class, (String) d[0]);
+                if (exists == null || exists == 0) {
+                    jdbcTemplate.update(
+                            "INSERT INTO indicator_definitions (domain, category, code, name, data_type, unit, " +
+                            "source_kind, value_type, good_direction, dimension_code, sort_order, status) " +
+                            "VALUES ('physical', ?, ?, ?, 'number', '分', 'dimension_score', 'numeric', ?, ?, 0, 1)",
+                            d[1], d[0], d[2], d[4], d[3]);
+                    log.info("迁移362: 已预置维度定义 {}", d[0]);
+                }
+            } catch (Exception e) {
+                log.warn("迁移362: 预置维度定义 {} 失败: {}", d[0], e.getMessage());
+            }
+        }
+    }
+
+    // 迁移355: 创建减脂专题体重记录表
+    private void createFatWeightRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_weight_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "weight DECIMAL(5,2) NOT NULL, " +
+                    "body_fat_percent DECIMAL(4,2) DEFAULT NULL, " +
+                    "waist_circumference DECIMAL(4,2) DEFAULT NULL, " +
+                    "measure_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_weight_member (member_id, measure_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-体重体脂记录'");
+            log.info("迁移355: 已创建fat_weight_record表");
+        } catch (Exception e) {
+            log.warn("迁移355: 创建fat_weight_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移356: 创建减脂专题饮食记录表
+    private void createFatDietRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_diet_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "meal_type VARCHAR(20) NOT NULL, " +
+                    "food_name VARCHAR(255) NOT NULL, " +
+                    "food_weight DECIMAL(6,2) DEFAULT NULL, " +
+                    "calories INT DEFAULT 0, " +
+                    "protein DECIMAL(6,2) DEFAULT 0, " +
+                    "fat DECIMAL(6,2) DEFAULT 0, " +
+                    "carbs DECIMAL(6,2) DEFAULT 0, " +
+                    "eat_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_diet_member (member_id, eat_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-饮食记录'");
+            log.info("迁移356: 已创建fat_diet_record表");
+        } catch (Exception e) {
+            log.warn("迁移356: 创建fat_diet_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移357: 创建减脂专题运动记录表
+    private void createFatExerciseRecordTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_exercise_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "exercise_type VARCHAR(50) NOT NULL, " +
+                    "duration_min INT DEFAULT 0, " +
+                    "distance_km DECIMAL(5,2) DEFAULT NULL, " +
+                    "calories_burned INT DEFAULT 0, " +
+                    "exercise_date DATE NOT NULL, " +
+                    "remark VARCHAR(255) DEFAULT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_exercise_member (member_id, exercise_date)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-运动记录'");
+            log.info("迁移357: 已创建fat_exercise_record表");
+        } catch (Exception e) {
+            log.warn("迁移357: 创建fat_exercise_record表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移358: 创建减脂专题目标表
+    private void createFatGoalTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS fat_goal (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "member_id BIGINT NOT NULL, " +
+                    "target_weight DECIMAL(5,2) NOT NULL, " +
+                    "start_weight DECIMAL(5,2) NOT NULL, " +
+                    "target_body_fat DECIMAL(4,2) DEFAULT NULL, " +
+                    "target_date DATE DEFAULT NULL, " +
+                    "status VARCHAR(20) DEFAULT 'active', " +
+                    "start_date DATE NOT NULL, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_fat_goal_member (member_id)) " +
+                    "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='减脂专题-减脂目标'");
+            log.info("迁移358: 已创建fat_goal表");
+        } catch (Exception e) {
+            log.warn("迁移358: 创建fat_goal表失败: {}", e.getMessage());
+        }
+    }
+
+    private void addProductVendorVisibleColumn() {
+        ensureColumn("products", "vendor_visible", "TINYINT(1) DEFAULT NULL COMMENT '是否仅供应商体系内用户可见(列表接口生效,直接分享可bypass)'");
+    }
+
+    // 迁移367: products 表添加健康约束字段(基础疾病禁忌/过敏原/药物相互作用标记)
+    private void addProductHealthConstraintColumns() {
+        ensureColumn("products", "contraindicated_diseases", "VARCHAR(255) DEFAULT NULL COMMENT '禁忌基础疾病,逗号分隔(如\"慢性肾病,肝病\")'");
+        ensureColumn("products", "allergen_tags", "VARCHAR(255) DEFAULT NULL COMMENT '过敏原标签,逗号分隔(如\"花生,小麦\")'");
+        ensureColumn("products", "drug_interaction_flags", "TINYINT DEFAULT 0 COMMENT '是否命中药物-食物相互作用(1=是,需对照drug_interactions表)'");
+    }
+
+    // 迁移368: 创建 drug_interactions 表(药物-食物禁忌对照表)
+    private void createDrugInteractionsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS drug_interactions (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "drug_class VARCHAR(50) NOT NULL COMMENT '药物类别(如CCB/他汀/抗凝药/抗生素/铁剂/甲状腺药/利尿剂/MAOI/头孢/降糖药)', " +
+                    "drug_specifics VARCHAR(100) NOT NULL COMMENT '具体药名(如硝苯地平/华法林)', " +
+                    "interacting_subst VARCHAR(100) NOT NULL COMMENT '禁忌/需间隔的食材或成分(如西柚/维生素K)', " +
+                    "risk_level TINYINT NOT NULL COMMENT '1=拦截 2=警告 3=需间隔', " +
+                    "action_type VARCHAR(20) NOT NULL COMMENT 'block=硬拦截 warn=软警告 space=间隔', " +
+                    "interval_minutes INT DEFAULT NULL COMMENT '需间隔时长(分钟)', " +
+                    "note VARCHAR(255) COMMENT '风险说明', " +
+                    "UNIQUE KEY uk_drug_food (drug_class, drug_specifics, interacting_subst), " +
+                    "INDEX idx_interacting (interacting_subst)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药物-食物禁忌对照表'");
+            log.info("迁移368: 已创建drug_interactions表");
+        } catch (Exception e) {
+            log.warn("迁移368: 创建drug_interactions表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移369: 创建 disease_intake_limits 表(疾病-饮食摄入阈值)
+    private void createDiseaseIntakeLimitsTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS disease_intake_limits (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "disease_type VARCHAR(50) NOT NULL COMMENT '疾病类型(慢性肾病/糖尿病/高血压/痛风/肝病/甲状腺疾病)', " +
+                    "nutrient VARCHAR(20) NOT NULL COMMENT '校验营养素(protein/potassium/phosphorus/sodium/carb/glycemic/fructose/purine/alcohol/iodine)', " +
+                    "unit VARCHAR(20) NOT NULL COMMENT '单位', " +
+                    "threshold_max DECIMAL(8,2) DEFAULT NULL COMMENT '每日上限', " +
+                    "threshold_min DECIMAL(8,2) DEFAULT NULL COMMENT '每日下限', " +
+                    "block_foods VARCHAR(500) DEFAULT NULL COMMENT '硬拦截食材列表,逗号分隔(如杨桃)', " +
+                    "stage_dependent TINYINT(1) DEFAULT 0 COMMENT '是否按病情分期(1=是 0=否)', " +
+                    "INDEX idx_disease_nutrient (disease_type, nutrient)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疾病-饮食摄入校验阈值表'");
+            log.info("迁移369: 已创建disease_intake_limits表");
+        } catch (Exception e) {
+            log.warn("迁移369: 创建disease_intake_limits表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移370: 初始化 drug_interactions 种子数据(高危组合,幂等)
+    private void seedDrugInteractionsData() {
+        String[][] seed = {
+                {"CCB", "硝苯地平", "西柚", "1", "block", "0", "西柚抑制药物代谢酶,血药浓度升高数倍,低血压风险,服药期间完全避免"},
+                {"CCB", "氨氯地平", "西柚", "1", "block", "0", "西柚抑制药物代谢酶,血药浓度升高数倍,低血压风险,服药期间完全避免"},
+                {"他汀类", "辛伐他汀", "西柚", "1", "block", "0", "增加肌肉损伤、肝损伤风险"},
+                {"他汀类", "阿托伐他汀", "西柚", "1", "block", "0", "增加肌肉损伤、肝损伤风险"},
+                {"抗凝药", "华法林", "维生素K", "2", "warn", "0", "维K拮抗药效,摄入量保持稳定,调整饮食结构前先与医生沟通"},
+                {"抗凝药", "华法林", "大蒜", "2", "warn", "0", "大蒜可能增强抗凝作用,增加出血风险"},
+                {"抗生素", "四环素", "牛奶", "3", "space", "120", "钙与药物结合影响吸收,需间隔2小时以上"},
+                {"抗生素", "喹诺酮类", "牛奶", "3", "space", "120", "钙与药物结合影响吸收,需间隔2小时以上"},
+                {"抗生素", "喹诺酮类", "钙剂", "3", "space", "120", "钙与药物结合影响吸收,需间隔2小时以上"},
+                {"铁剂", "硫酸亚铁", "茶", "3", "space", "60", "鞣酸抑制铁吸收,需间隔1-2小时"},
+                {"铁剂", "硫酸亚铁", "咖啡", "3", "space", "60", "鞣酸抑制铁吸收,需间隔1-2小时"},
+                {"铁剂", "硫酸亚铁", "钙剂", "3", "space", "60", "钙抑制铁吸收,需间隔1-2小时"},
+                {"铁剂", "硫酸亚铁", "牛奶", "3", "space", "60", "钙抑制铁吸收,需间隔1-2小时"},
+                {"甲状腺药", "左甲状腺素", "豆浆", "3", "space", "240", "影响吸收,需间隔4小时"},
+                {"甲状腺药", "左甲状腺素", "高纤维食物", "3", "space", "240", "影响吸收,需间隔4小时"},
+                {"甲状腺药", "左甲状腺素", "钙剂", "3", "space", "240", "影响吸收,需间隔4小时"},
+                {"保钾利尿剂", "螺内酯", "高钾食物", "2", "warn", "0", "高钾血症风险,避免香蕉、橙汁、低钠盐大量集中摄入"},
+                {"MAOI", "苯乙肼", "发酵食品", "2", "warn", "0", "酪胺反应可致血压骤升,避免奶酪/豆豉/腌制品/陈年食品"},
+                {"头孢类", "头孢类", "酒精", "1", "block", "0", "双硫仑样反应,严重可危及生命"},
+                {"甲硝唑", "甲硝唑", "酒精", "1", "block", "0", "双硫仑样反应,严重可危及生命"},
+                {"降糖药", "磺脲类", "酒精", "1", "block", "0", "低血糖风险,避免空腹饮酒"}
+        };
+        for (String[] row : seed) {
+            try {
+                Integer cnt = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM drug_interactions WHERE drug_class=? AND drug_specifics=? AND interacting_subst=?",
+                        Integer.class, row[0], row[1], row[2]);
+                if (cnt != null && cnt > 0) continue;
+                jdbcTemplate.update(
+                        "INSERT INTO drug_interactions (drug_class, drug_specifics, interacting_subst, risk_level, action_type, interval_minutes, note) VALUES (?,?,?,?,?,?,?)",
+                        row[0], row[1], row[2], Integer.valueOf(row[3]), row[4], Integer.valueOf(row[5]), row[6]);
+            } catch (Exception e) {
+                log.warn("迁移370: 插入drug_interactions种子失败: {}", e.getMessage());
+            }
+        }
+        log.info("迁移370: drug_interactions种子数据初始化完成");
+    }
+
+    // 迁移371: 初始化 disease_intake_limits 种子数据(幂等)
+    private void seedDiseaseIntakeLimitsData() {
+        String[][] seed = {
+                {"慢性肾病", "protein", "g/kg/day", "0.8", "0.6", "杨桃", "1"},
+                {"慢性肾病", "potassium", "mmol/L/day", "5.0", "3.5", "杨桃,香蕉,橙子,土豆,菠菜,菌菇", "1"},
+                {"慢性肾病", "phosphorus", "mg/day", "800", "600", "杨桃,奶制品,坚果,加工食品", "1"},
+                {"糖尿病", "carb", "g/meal", "75", "30", "", "0"},
+                {"糖尿病", "fructose", "g/day", "50", "0", "含糖饮料,果汁,蜂蜜,甜点", "0"},
+                {"高血压", "sodium", "g/day", "5", "2", "腌菜,酱菜,酱油,加工肉制品,挂面,咸味零食", "0"},
+                {"痛风", "purine", "mg/day", "300", "150", "动物内脏,部分海鲜,浓肉汤", "0"},
+                {"痛风", "fructose", "g/day", "50", "0", "含糖饮料,蜂蜜", "0"},
+                {"肝病", "protein", "g/kg/day", "1.0", "0.5", "", "1"},
+                {"甲状腺疾病", "iodine", "ug/day", "300", "150", "海带,紫菜", "0"}
+        };
+        for (String[] row : seed) {
+            try {
+                Integer cnt = jdbcTemplate.queryForObject(
+                        "SELECT COUNT(*) FROM disease_intake_limits WHERE disease_type=? AND nutrient=?",
+                        Integer.class, row[0], row[1]);
+                if (cnt != null && cnt > 0) continue;
+                jdbcTemplate.update(
+                        "INSERT INTO disease_intake_limits (disease_type, nutrient, unit, threshold_max, threshold_min, block_foods, stage_dependent) VALUES (?,?,?,?,?,?,?)",
+                        row[0], row[1], row[2], row[3], row[4], row[5], Integer.valueOf(row[6]));
+            } catch (Exception e) {
+                log.warn("迁移371: 插入disease_intake_limits种子失败: {}", e.getMessage());
+            }
+        }
+        log.info("迁移371: disease_intake_limits种子数据初始化完成");
+    }
+
+    // 迁移363: 创建采购任务表(商品组合推荐与采购任务生命周期管理)
+    private void createProcurementTaskTable() {
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS procurement_tasks (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "task_id VARCHAR(64) NOT NULL COMMENT '任务唯一标识', " +
+                    "user_id BIGINT NOT NULL COMMENT '所属用户ID', " +
+                    "family_member_id BIGINT NOT NULL COMMENT '所属家庭成员ID', " +
+                    "combo_tier VARCHAR(20) NOT NULL COMMENT '选择的套餐等级: high|medium|low', " +
+                    "original_combo_tier VARCHAR(20) DEFAULT NULL COMMENT '原始套餐等级(用于自定义追踪)', " +
+                    "selected_product_sku_ids TEXT COMMENT '最终选中的产品SKU列表(JSON数组)', " +
+                    "custom_addition_sku_ids TEXT COMMENT '用户自新增的产品SKU列表(JSON数组)', " +
+                    "custom_removal_sku_ids TEXT COMMENT '用户移除的产品SKU列表(JSON数组)', " +
+                    "status VARCHAR(32) NOT NULL COMMENT '任务状态: SELECTED|PROCURING|PROCURED|EXECUTING|COMPLETED|CANCELLED', " +
+                    "create_time DATETIME NOT NULL COMMENT '创建时间', " +
+                    "complete_time DATETIME DEFAULT NULL COMMENT '完成时间', " +
+                    "message_reminder_sent TINYINT DEFAULT 0 COMMENT '是否已发送消息提醒', " +
+                    "execution_task_id VARCHAR(64) DEFAULT NULL COMMENT '关联的执行任务ID', " +
+                    "update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY uk_task_id (task_id), " +
+                    "INDEX idx_user_id (user_id), " +
+                    "INDEX idx_family_member_id (family_member_id), " +
+                    "INDEX idx_status (status), " +
+                    "INDEX idx_create_time (create_time)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购任务表(商品组合推荐与采购任务生命周期管理)'");
+            log.info("已创建procurement_tasks表");
+        } catch (Exception e) {
+            log.warn("创建procurement_tasks表失败: {}", e.getMessage());
+        }
+    }
+
+    // 迁移364: health_report_drafts 添加解析进度/重试字段(INFORMATION_SCHEMA 检查后再 ALTER,幂等)
+    private void addDraftParseProgressColumns() {
+        if (!columnExists("health_report_drafts", "parse_attempt")) {
+            jdbcTemplate.execute("ALTER TABLE health_report_drafts ADD COLUMN parse_attempt INT DEFAULT 0 COMMENT '已知类型解析重试次数'");
+            log.info("已添加 parse_attempt 列到 health_report_drafts 表");
+        }
+        if (!columnExists("health_report_drafts", "next_retry_at")) {
+            jdbcTemplate.execute("ALTER TABLE health_report_drafts ADD COLUMN next_retry_at DATETIME NULL COMMENT '下次自动重试时间(指数退避,上限5分钟)'");
+            log.info("已添加 next_retry_at 列到 health_report_drafts 表");
+        }
+        if (!columnExists("health_report_drafts", "parse_started_at")) {
+            jdbcTemplate.execute("ALTER TABLE health_report_drafts ADD COLUMN parse_started_at DATETIME NULL COMMENT '最近一次解析开始时间'");
+            log.info("已添加 parse_started_at 列到 health_report_drafts 表");
+        }
+        if (!columnExists("health_report_drafts", "parse_finished_at")) {
+            jdbcTemplate.execute("ALTER TABLE health_report_drafts ADD COLUMN parse_finished_at DATETIME NULL COMMENT '最近一次解析结束时间(成功/失败)'");
+            log.info("已添加 parse_finished_at 列到 health_report_drafts 表");
+        }
+    }
+
+    // 迁移372: nutrition_product 添加 efficacy_tags 列(成分功效标签)
+    private void addNutritionProductEfficacyTags() {
+        if (!columnExists("nutrition_product", "efficacy_tags")) {
+            jdbcTemplate.execute("ALTER TABLE nutrition_product ADD COLUMN efficacy_tags TEXT COMMENT '成分功效标签JSON数组(如 [\"补钙\",\"补锌\",\"益生菌\",\"维生素D\"])'");
+            log.info("已添加 efficacy_tags 列到 nutrition_product 表");
+        }
+    }
+
+    // 迁移373: products 添加 tags 列(文章-商品标签关联推荐)
+    private void addProductTagsColumn() {
+        ensureColumn("products", "tags", "VARCHAR(500) DEFAULT '' COMMENT '商品标签 JSON 数组,用于文章-商品标签匹配推荐'");
+        log.info("迁移373: products.tags 已确认存在");
+    }
+}
+>>>>>>> 6be3f0cb7b10c6fe0aa222c2c00bc1ed63e84c70

+ 40 - 39
cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java

@@ -30,45 +30,46 @@ public class JwtInterceptor implements HandlerInterceptor {
     private FamilyMemberMapper familyMemberMapper;
 
 private static final String[] PUBLIC_PATHS = {
-        "/api/auth/send-code",
-        "/api/auth/phone-login",
-        "/api/auth/wechat-phone-login",
-        "/api/auth/silent-login",
-        "/api/auth/sso-login",
-        "/api/auth/auto-login",
-        "/api/auth/register-with-idcard",
-        "/api/auth/wechat-login",
-        "/api/auth/verify",
-        "/api/auth/check-phone",
-        "/api/auth/direct-register",
-        "/api/auth/register-with-invite",
-        "/api/auth/wechat-web/qr",
-        "/api/auth/wechat-web/poll",
-        "/api/internal/",
-        "/api/product/list",
-        "/api/product/detail",
-        "/api/product/type-list",
-        "/api/mini-game/list",
-        "/api/admin-auth/send-code",
-        "/api/admin-auth/login",
-        "/api/admin-auth/login-by-password",
-        "/api/admin-auth/account-roles",
-        "/api/articles/list",
-        "/api/articles/detail",
-        "/api/articles/categories",
-        "/api/articles/featured",
-        "/api/articles/record-read",
-        "/api/growth/**",
-        "/api/activity/list",
-        "/api/activity/detail",
-        "/api/activity/order/notify",
-        "/api/content-sections/visible",
-        "/api/mind/alert/active",
-        "/api/mind/alert/checkin-alerts",
-        "/api/mind/traditional/mirror",
-        "/api/mind/traditional/compatibility",
-        "/api/mcp/**"
-    };
+         "/api/auth/send-code",
+         "/api/auth/phone-login",
+         "/api/auth/wechat-phone-login",
+         "/api/auth/silent-login",
+         "/api/auth/sso-login",
+         "/api/auth/auto-login",
+         "/api/auth/register-with-idcard",
+         "/api/auth/wechat-login",
+         "/api/auth/verify",
+         "/api/auth/check-phone",
+         "/api/auth/direct-register",
+         "/api/auth/register-with-invite",
+         "/api/auth/wechat-web/qr",
+         "/api/auth/wechat-web/poll",
+         "/api/auth/internal/",
+         "/api/product/list",
+         "/api/product/detail",
+         "/api/product/type-list",
+         "/api/mini-game/list",
+         "/api/admin-auth/send-code",
+         "/api/admin-auth/login",
+         "/api/admin-auth/login-by-password",
+         "/api/admin-auth/account-roles",
+         "/api/articles/list",
+         "/api/articles/detail",
+         "/api/articles/categories",
+         "/api/articles/featured",
+         "/api/articles/record-read",
+         "/api/articles/related-products",
+         "/api/growth/**",
+         "/api/activity/list",
+         "/api/activity/detail",
+         "/api/activity/order/notify",
+         "/api/content-sections/visible",
+         "/api/mind/alert/active",
+         "/api/mind/alert/checkin-alerts",
+         "/api/mind/traditional/mirror",
+         "/api/mind/traditional/compatibility",
+         "/api/mcp/**"
+     };
 
     // 必须登录才能访问的路径:anonymous 访问直接 401(这些接口消耗微信配额/写数据/扣积分)
     private static final String[] LOGIN_REQUIRED_PATHS = {

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/WebConfig.java

@@ -117,6 +117,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/articles/categories",
                 "/api/articles/featured",
                 "/api/articles/record-read",
+                "/api/articles/related-products",
                 "/api/growth/**",
                 "/api/articles/updated-since",
                 "/api/activity/list",
@@ -162,6 +163,7 @@ public class WebConfig implements WebMvcConfigurer {
                 "/api/articles/categories",
                 "/api/articles/featured",
                 "/api/articles/record-read",
+                "/api/articles/related-products",
                 "/api/growth/**",
                 "/api/activity/list",
                 "/api/activity/detail",

+ 48 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthExerciseController.java

@@ -6,15 +6,21 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.HealthExerciseRecord;
+import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.HealthExerciseRecordMapper;
+import com.etotem.cfc.mapper.TaskMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.WechatService;
+import com.etotem.cfc.service.TaskService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import com.etotem.cfc.util.ParamUtils;
+import lombok.extern.slf4j.Slf4j;
 
 import javax.annotation.Resource;
 import java.util.Calendar;
@@ -23,6 +29,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+@Slf4j
 @Tag(name = "运动打卡")
 @RestController
 @RequestMapping("/api/health/exercise")
@@ -31,6 +38,15 @@ public class HealthExerciseController {
     @Resource
     private HealthExerciseRecordMapper healthExerciseRecordMapper;
 
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
     @Resource
     private UserMapper userMapper;
 
@@ -72,6 +88,38 @@ public class HealthExerciseController {
         if (!hasContent) return Result.error("请填写运动时长或添加图片/语音");
         record.setCreatedAt(new Date());
         healthExerciseRecordMapper.insert(record);
+        
+        // ===== 任务回写挂钩 =====
+        try {
+            if (record.getTaskId() != null) {
+                Task linkedTask = taskMapper.selectById(record.getTaskId());
+                if (linkedTask != null) {
+                    if (linkedTask.getIsDailyProgress() != null && linkedTask.getIsDailyProgress() == 1) {
+                        // 每日进度型任务:累计进度(taskKey=EXERCISE)
+                        com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                        if (fm != null && fm.getUserId() != null) {
+                            taskService.updateProgress(fm.getUserId(), "EXERCISE", 1);
+                        }
+                    } else {
+                        // 一次性任务:直接完成
+                        taskService.completeTask(
+                                record.getTaskId(),
+                                record.getMemberId(),
+                                record.getPhotoUrls() != null ? record.getPhotoUrls() : "",
+                                record.getRemark() != null ? record.getRemark() : "运动打卡",
+                                "text");
+                    }
+                }
+            } else {
+                // 未带 taskId:累计今日每日运动打卡任务进度(无则 log.warn 跳过)
+                com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                if (fm != null && fm.getUserId() != null) {
+                    taskService.updateProgress(fm.getUserId(), "EXERCISE", 1);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("运动打卡任务回写失败: {}", e.getMessage());
+        }
                 // 触发画像重算
         try {
             Long triggerMemberId = record.getMemberId();

+ 57 - 5
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthMealController.java

@@ -4,11 +4,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.HealthMealRecord;
 import com.etotem.cfc.entity.FoodRecommendIndex;
+import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.mapper.HealthMealRecordMapper;
 import com.etotem.cfc.mapper.FoodRecommendIndexMapper;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.*;
+import com.etotem.cfc.mapper.TaskMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
+import com.etotem.cfc.service.PointsService;
+import com.etotem.cfc.service.TaskService;
+import com.etotem.cfc.service.ProfileComputeService;
+import com.etotem.cfc.util.ParamUtils;
+import lombok.extern.slf4j.Slf4j;
 
 import javax.annotation.Resource;
 import java.sql.Date;
@@ -16,9 +22,11 @@ import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
-import com.etotem.cfc.service.PointsService;
-import com.etotem.cfc.util.ParamUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
 
+@Slf4j
 @Tag(name = "饮食打卡")
 @RestController
 @RequestMapping("/api/health/meal")
@@ -33,6 +41,15 @@ public class HealthMealController {
     @Resource
     private PointsService pointsService;
 
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
     @Resource
     private com.etotem.cfc.service.ProfileComputeService profileComputeService;
 
@@ -230,6 +247,41 @@ public class HealthMealController {
         record.setCreatedAt(new java.util.Date());
         healthMealRecordMapper.insert(record);
 
+        // ===== 任务回写挂钩 =====
+        // 非补打打卡才回写任务(补打不参与任务完成逻辑)
+        if (!isBackfill) {
+            try {
+                if (record.getTaskId() != null) {
+                    Task linkedTask = taskMapper.selectById(record.getTaskId());
+                    if (linkedTask != null) {
+                        if (linkedTask.getIsDailyProgress() != null && linkedTask.getIsDailyProgress() == 1) {
+                            // 每日进度型任务:累计进度(taskKey=DIET)
+                            com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                            if (fm != null && fm.getUserId() != null) {
+                                taskService.updateProgress(fm.getUserId(), "DIET", 1);
+                            }
+                        } else {
+                            // 一次性任务:直接完成
+                            taskService.completeTask(
+                                    record.getTaskId(),
+                                    record.getMemberId(),
+                                    record.getPhotoUrls(),
+                                    record.getRemark(),
+                                    "text");
+                        }
+                    }
+                } else {
+                    // 未带 taskId:仍累计今日每日打卡任务进度(无则 log.warn 跳过)
+                    com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                    if (fm != null && fm.getUserId() != null) {
+                        taskService.updateProgress(fm.getUserId(), "DIET", 1);
+                    }
+                }
+            } catch (Exception e) {
+                log.warn("饮食打卡任务回写失败: {}", e.getMessage());
+            }
+        }
+
         // 只有非补打才发积分
         if (!isBackfill) {
             try {

+ 3 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java

@@ -2404,6 +2404,8 @@ public class HealthReportController {
      * 检查是否为授权代上传角色
      */
     private boolean isProxyUploader(String role, List<String> roles, User user) {
+        // 超级管理员(admin):平台最高权限,可代传任何报告
+        if ("admin".equals(role) || (roles != null && roles.contains("admin"))) return true;
         if ("teacher".equals(role)) return true;
         if (roles != null && roles.contains("teacher")) return true;
         if (user != null && "approved".equals(user.getNutritionistStatus())) return true;
@@ -2433,7 +2435,7 @@ public class HealthReportController {
 
         User user = userMapper.selectById(userId);
         if (!isProxyUploader(role, roles, user)) {
-            return Result.error("无权限代上传报告,仅规划师/营养师可操作");
+            return Result.error("无权限代上传报告,仅超级管理员/规划师/营养师可操作");
         }
 
         if (familyId == null) {

+ 50 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthSleepController.java

@@ -3,7 +3,11 @@ package com.etotem.cfc.controller;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.HealthSleepRecord;
+import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.mapper.HealthSleepRecordMapper;
+import com.etotem.cfc.mapper.TaskMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.*;
@@ -14,7 +18,10 @@ import java.util.List;
 import java.util.Map;
 import com.etotem.cfc.util.SortUtil;
 import com.etotem.cfc.util.ParamUtils;
+import com.etotem.cfc.service.TaskService;
+import lombok.extern.slf4j.Slf4j;
 
+@Slf4j
 @Tag(name = "睡眠打卡")
 @RestController
 @RequestMapping("/api/health/sleep")
@@ -23,6 +30,15 @@ public class HealthSleepController {
     @Resource
     private HealthSleepRecordMapper healthSleepRecordMapper;
 
+    @Resource
+    private TaskService taskService;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
     @Resource
     private com.etotem.cfc.service.ProfileComputeService profileComputeService;
 
@@ -50,7 +66,40 @@ public class HealthSleepController {
         if (record.getWakeTime() == null) return Result.error("请选择醒来时间");
         record.setCreatedAt(new Date());
         healthSleepRecordMapper.insert(record);
-                // 触发画像重算
+
+        // ===== 任务回写挂钩 =====
+        try {
+            if (record.getTaskId() != null) {
+                Task linkedTask = taskMapper.selectById(record.getTaskId());
+                if (linkedTask != null) {
+                    if (linkedTask.getIsDailyProgress() != null && linkedTask.getIsDailyProgress() == 1) {
+                        // 每日进度型任务:累计进度(taskKey=SLEEP)
+                        com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                        if (fm != null && fm.getUserId() != null) {
+                            taskService.updateProgress(fm.getUserId(), "SLEEP", 1);
+                        }
+                    } else {
+                        // 一次性任务:直接完成
+                        taskService.completeTask(
+                                record.getTaskId(),
+                                record.getMemberId(),
+                                "",
+                                record.getRemark() != null ? record.getRemark() : "睡眠打卡",
+                                "text");
+                    }
+                }
+            } else {
+                // 未带 taskId:累计今日每日睡眠打卡任务进度(无则 log.warn 跳过)
+                com.etotem.cfc.entity.FamilyMember fm = familyMemberMapper.selectById(record.getMemberId());
+                if (fm != null && fm.getUserId() != null) {
+                    taskService.updateProgress(fm.getUserId(), "SLEEP", 1);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("睡眠打卡任务回写失败: {}", e.getMessage());
+        }
+
+        // 触发画像重算
         try {
             Long triggerMemberId = record.getMemberId();
             if (triggerMemberId != null) {

+ 3 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminArticleController.java

@@ -174,8 +174,9 @@ public class AdminArticleController {
         if (articleId != null) {
             Article existing = articleService.getById(articleId);
             if (existing != null && !"draft".equals(existing.getStatus())
-                    && !"published".equals(existing.getStatus())) {
-                return Result.error("仅草稿或已上架状态可编辑");
+                    && !"published".equals(existing.getStatus())
+                    && !"withdrawn".equals(existing.getStatus())) {
+                return Result.error("仅草稿、已上架或已撤回状态可编辑");
             }
         }
         Article article = new Article();

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleCommentController.java

@@ -109,7 +109,7 @@ public class ArticleCommentController {
         Long articleId = ParamUtils.getLong(params.get("articleId"));
         Long memberId = ParamUtils.getLong(params.get("memberId"), currentMemberId);
         Article article = articleMapper.selectById(articleId);
-        if (article == null) return Result.error("文章不存在");
+        if (article == null || !"published".equals(article.getStatus())) return Result.error("文章不存在");
 
         String content = article.getContent();
         if (content != null && content.length() > 2000) content = content.substring(0, 2000);

+ 25 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/content/ArticleController.java

@@ -8,6 +8,7 @@ import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.ArticleCategoryService;
 import com.etotem.cfc.service.ArticleService;
+import com.etotem.cfc.service.ProductRecommendationService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -34,6 +35,30 @@ public class ArticleController {
     @Resource
     private UserMapper userMapper;
 
+    @Resource
+    private ProductRecommendationService productRecommendationService;
+
+    @PostMapping("/related-products")
+    public Result<List<Map<String, Object>>> relatedProducts(@RequestBody Map<String, Object> body,
+                                                             @RequestAttribute(value = "userId", required = false) Long userId) {
+        Long articleId = ParamUtils.getLong(body.get("articleId"));
+        if (articleId == null) {
+            return Result.error("articleId不能为空");
+        }
+        int size = body.get("size") != null ? Integer.parseInt(body.get("size").toString()) : 6;
+
+        Long familyId = null;
+        if (userId != null) {
+            User user = userMapper.selectById(userId);
+            if (user != null) {
+                familyId = user.getFamilyId();
+            }
+        }
+
+        List<Map<String, Object>> products = productRecommendationService.getArticleRelatedProducts(articleId, userId, familyId, size);
+        return Result.success(products);
+    }
+
     @PostMapping("/list")
     public Result<Page<Article>> list(@RequestBody Map<String, Object> body,
                                        @RequestAttribute(value = "userId", required = false) Long userId,

+ 18 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/nutrition/NutritionProductController.java

@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -82,4 +83,21 @@ public class NutritionProductController {
         }
         return Result.success(nutritionProductService.priceCompare(productId));
     }
+
+    @Operation(summary = "根据主平台商品ID创建营养产品条目")
+    @PostMapping("/from-product")
+    public Result<Map<String, Object>> addNutritionProductFromProduct(@RequestBody Map<String, Long> params) {
+        Long productId = params.get("productId");
+        if (productId == null) {
+            return Result.error("productId 不能为空");
+        }
+        boolean success = nutritionProductService.addNutritionProductFromProduct(productId);
+        if (!success) {
+            return Result.error("创建失败");
+        }
+        Map<String, Object> result = new HashMap<>();
+        result.put("message", "营养产品条目创建成功");
+        result.put("productId", productId);
+        return Result.success(result);
+    }
 }

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/dto/CreateTaskDTO.java

@@ -63,7 +63,7 @@ public class CreateTaskDTO {
     // TASK-SYS-248: 后置任务IDs JSON: [1,2,3],任意一个完成则解锁(OR 逻辑)
     private String successorTaskIds;
 
-    // TASK-SYS-248: 联动动作: minigame/focus/survey/article/activity/cart/order
+    // TASK-SYS-248: 联动动作: minigame/focus/survey/article/activity/cart/order/diet/sleep/exercise
     private String actionType;
 
     // TASK-SYS-248: 联动参数JSON

+ 34 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java

@@ -1,10 +1,17 @@
 package com.etotem.cfc.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 import java.util.List;
 
+/**
+ * 五行溯源建议 VO。
+ * 兼容 AI 返回的字段:生我者(upstream 系)、克我者(restrainer 系)与行动建议(action)。
+ * 未知字段忽略,避免 AI 输出新增字段导致整体解析失败。
+ */
 @Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class WuxingSourcingAdviceVO {
 
     private String dimension;
@@ -30,4 +37,31 @@ public class WuxingSourcingAdviceVO {
     /** 是否使用降级静态建议 */
     @JsonProperty("fallbackUsed")
     private Boolean fallbackUsed;
+
+    // ===== AI 五行生克链字段(prompt 输出,VO 同步适配) =====
+
+    /** 生我者(上游)维度 */
+    private String upstreamDimension;
+    /** 生我者(上游)名称 */
+    private String upstreamName;
+    /** 生我者(上游)元素 */
+    private String upstreamElement;
+    /** 生我者(上游)颜色 */
+    private String upstreamColor;
+    /** 生我者(上游)理由 */
+    private String upstreamReason;
+
+    /** 克我者(制约)维度 */
+    private String restrainerDimension;
+    /** 克我者(制约)名称 */
+    private String restrainerName;
+    /** 克我者(制约)元素 */
+    private String restrainerElement;
+    /** 克我者(制约)颜色 */
+    private String restrainerColor;
+    /** 克我者(制约)理由 */
+    private String restrainerReason;
+
+    /** 具体行动建议 */
+    private String action;
 }

+ 35 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionAgeDosage.java

@@ -0,0 +1,35 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("nutrition_age_dosage")
+public class NutritionAgeDosage implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Integer ageMinMonths;
+    private Integer ageMaxMonths;
+    
+    /** 成分名称 */
+    private String ingredientName;
+    
+    /** 每份剂量(毫升/次) */
+    private BigDecimal amountPerServing;
+    
+    /** 单位(如毫升、毫克、片、次) */
+    private String unit;
+    
+    /** 功效说明 */
+    private String purpose;
+    
+    private Date createTime;
+    private Date updateTime;
+}

+ 15 - 1
cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionProduct.java

@@ -68,7 +68,21 @@ public class NutritionProduct implements Serializable {
     /** 规格 */
     private String spec;
 
-    private Date createTime;
+    /** 适用年龄下限(月) */
+    private Integer ageMinMonths;
+
+    /** 适用年龄上限(月) */
+    private Integer ageMaxMonths;
+
+    /** 孕期/哺乳期标签(0=不适用, 1=孕期, 2=哺乳期, 3=孕期+哺乳期) */
+    private Integer pregnancyLactationTag;
 
+    /** 禁止人群标签(0=无, 1=婴幼儿禁用, 2=成人禁用, 3=特定疾病人群禁用) */
+    private Integer contraindicationTag;
+
+    /** 成分功效标签(JSON数组,如 ["补钙","补锌","益生菌","维生素D"]) */
+    private String efficacyTags;
+
+    private Date createTime;
     private Date updateTime;
 }

+ 45 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/NutritionUlLimit.java

@@ -0,0 +1,45 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("nutrition_ul_limit")
+public class NutritionUlLimit implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long productId;
+    private String ingredientType;
+    private String nutrientName;
+    private String ageMinMonths;
+    private String ageMaxMonths;
+    private BigDecimal limitValue;
+    private String unit;
+    private String description;
+    private Date createTime;
+    private Date updateTime;
+    
+    public NutritionUlLimit() {}
+    
+    public NutritionUlLimit(Long productId, String ingredientType, String nutrientName, 
+                           String ageMinMonths, String ageMaxMonths, 
+                            BigDecimal limitValue, String unit, String description) {
+        this.productId = productId;
+        this.ingredientType = ingredientType;
+        this.nutrientName = nutrientName;
+        this.ageMinMonths = ageMinMonths;
+        this.ageMaxMonths = ageMaxMonths;
+        this.limitValue = limitValue;
+        this.unit = unit;
+        this.description = description;
+        this.createTime = new Date();
+        this.updateTime = new Date();
+    }
+}

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java

@@ -75,6 +75,9 @@ public class Product implements Serializable {
     /** 是否推荐商品: 0=否 1=是(推荐时同步至营养产品表) */
     private Integer isRecommended;
 
+    /** JSON: ["标签1","标签2"],用于文章-商品标签匹配推荐 */
+    private String tags;
+
     // 佣金系统 - 平台利润率(千分比)
     private Integer profitRate;
 
@@ -121,6 +124,7 @@ public class Product implements Serializable {
     public String getMinVisibleLevel() { return minVisibleLevel; }
     public String getMinDiscountLevel() { return minDiscountLevel; }
     public Boolean getVendorVisible() { return vendorVisible; }
+    public String getTags() { return tags; }
     public Date getCreatedAt() { return createdAt; }
     public Date getUpdatedAt() { return updatedAt; }
 }

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java

@@ -127,7 +127,7 @@ private Date acceptDeadline;
     /** TASK-SYS-248: 后置任务IDs JSON: [1,2,3],任意一个完成则解锁(OR 逻辑) */
     private String successorTaskIds;
 
-    /** TASK-SYS-248: 联动动作: minigame/focus/survey/article/activity/cart/order */
+    /** TASK-SYS-248: 联动动作: minigame/focus/survey/article/activity/cart/order/diet/sleep/exercise */
     private String actionType;
 
     /** TASK-SYS-248: 联动参数JSON: {"gameCode":"schulte","productId":123,"minSeconds":30,"completeMilestone":"paid","surveyId":99} */

+ 6 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/NutritionAgeDosageMapper.java

@@ -0,0 +1,6 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.NutritionAgeDosage;
+
+public interface NutritionAgeDosageMapper extends BaseMapper<NutritionAgeDosage> {}

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/NutritionUlLimitMapper.java

@@ -0,0 +1,7 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.NutritionUlLimit;
+
+public interface NutritionUlLimitMapper extends BaseMapper<NutritionUlLimit> {
+}

+ 4 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java

@@ -988,9 +988,8 @@ public class ArticleService {
         LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<Article>()
                 .ge(Article::getUpdatedAt, since)
                 .orderByDesc(Article::getUpdatedAt);
-        if (status != null && !status.trim().isEmpty()) {
-            wrapper.eq(Article::getStatus, status.trim());
-        }
+        String s = (status != null && !status.trim().isEmpty()) ? status.trim() : "published";
+        wrapper.eq(Article::getStatus, s);
         return articleMapper.selectList(wrapper);
     }
 
@@ -1146,8 +1145,8 @@ public class ArticleService {
         if (article == null) {
             throw new RuntimeException("文章不存在");
         }
-        if (!"rejected".equals(article.getStatus())) {
-            throw new RuntimeException("仅已驳回的文章可重新编辑");
+        if (!"rejected".equals(article.getStatus()) && !"withdrawn".equals(article.getStatus())) {
+            throw new RuntimeException("仅已驳回或已撤回的文章可重新编辑");
         }
         article.setStatus("draft");
         article.setAuditStatus(null);

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

@@ -167,7 +167,12 @@ public class EmiReportService {
 
     private static Map<String, Object> parseStructuredAnalysis(String json) {
         if (json == null || json.isEmpty()) return null;
-        try { return JSON.parseObject(json, Map.class); }
+        String trimmed = json.trim();
+        if (trimmed.startsWith("[")) {
+            // 平铺 items 数组(无 extra 嵌套):非异常,静默返回 null
+            return null;
+        }
+        try { return JSON.parseObject(trimmed, Map.class); }
         catch (Exception e) { return null; }
     }
 }

+ 13 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java

@@ -1324,12 +1324,23 @@ public class EnergyService {
         return null;
     }
 
-    /** 解析 structuredAnalysis JSON(使用 fastjson) */
+    /**
+     * 解析 structuredAnalysis JSON(使用 fastjson)。
+     * 注意:structured_analysis 实际存储的是 DAN 解析 items 平铺数组
+     * [{code,name,value,category},...](见 DanReportUploadService#confirmUpload)。
+     * 本方法只消费嵌套对象形态(extra.selfDriving/selfConcept 等),
+     * 数组形态无这些数据,静默返回 null 由调用方降级(自驱力/自我概念回 0)。
+     */
     @SuppressWarnings("unchecked")
     private Map<String, Object> parseStructuredAnalysis(String json) {
         if (json == null || json.isEmpty()) return null;
+        String trimmed = json.trim();
+        if (trimmed.startsWith("[")) {
+            // 平铺 items 数组(无 extra 嵌套):非异常,静默返回 null
+            return null;
+        }
         try {
-            return JSON.parseObject(json, Map.class);
+            return JSON.parseObject(trimmed, Map.class);
         } catch (Exception e) {
             log.warn("解析 structuredAnalysis 失败: {}", e.getMessage());
             return null;

+ 180 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/NutritionProductService.java

@@ -38,6 +38,18 @@ public class NutritionProductService {
     @Resource
     private NutritionProductReviewMapper reviewMapper;
 
+
+    @Resource
+    private ProductService productService;
+
+
+    @Resource
+    private NutritionUlLimitMapper ulLimitMapper;
+
+
+    @Resource
+    private NutritionAgeDosageMapper ageDosageMapper;
+
     @Resource
     private ProductMapper productMapper;
 
@@ -214,6 +226,11 @@ public class NutritionProductService {
                 }
                 existing.setStatus(1);
                 nutritionProductMapper.insert(existing);
+                // 根据成分功效说明自动生成功效标签
+                existing.setEfficacyTags(buildEfficacyTags(existing.getId()));
+                if (existing.getEfficacyTags() != null) {
+                    nutritionProductMapper.updateById(existing);
+                }
             } else {
                 existing.setName(product.getName());
                 existing.setImageUrl(product.getCoverImage());
@@ -242,6 +259,169 @@ public class NutritionProductService {
         return fen / 100;
     }
 
+    /**
+     * 根据主平台商品ID创建营养产品条目(标记为本平台商品)
+     *
+     * @param productId 主平台商品ID(products.id)
+     * @return true=成功/已存在, false=主商品不存在
+     */
+    public boolean addNutritionProductFromProduct(Long productId) {
+        Product product = productService.getById(productId);
+        if (product == null) {
+            log.warn("主平台商品不存在,ID: {}", productId);
+            return false;
+        }
+        // 检查是否已存在该主平台商品的营养产品条目
+        NutritionProduct existing = nutritionProductMapper.selectOne(
+            new LambdaQueryWrapper<NutritionProduct>()
+                .eq(NutritionProduct::getLocalProductId, productId)
+        );
+        if (existing != null) {
+            log.info("营养产品条目已存在,主平台商品ID: {}", productId);
+            return true; // 已存在,视为成功
+        }
+        NutritionProduct nutritionProduct = new NutritionProduct();
+        nutritionProduct.setName(product.getName());
+        nutritionProduct.setLocalProductId(productId);
+        nutritionProduct.setStatus(1); // 1 = 上架
+        nutritionProduct.setSourcePlatform("platform"); // 标记为平台来源
+        nutritionProduct.setDescription(product.getDescription());
+        nutritionProduct.setImageUrl(product.getCoverImage());
+        nutritionProduct.setPrice(BigDecimal.valueOf(product.getPrice() != null ? product.getPrice() : 0));
+        nutritionProduct.setOriginalPrice(product.getPurchasePrice() != null ? product.getPurchasePrice().intValue() : 0);
+        nutritionProduct.setSuitableFor("全家人");
+        nutritionProduct.setCreateTime(new Date());
+        nutritionProduct.setUpdateTime(new Date());
+        // 默认分类和品牌设为空,由前端或后续填充
+        nutritionProductMapper.insert(nutritionProduct);
+        // 根据成分功效说明自动生成功效标签
+        nutritionProduct.setEfficacyTags(buildEfficacyTags(nutritionProduct.getId()));
+        if (nutritionProduct.getEfficacyTags() != null) {
+            nutritionProductMapper.updateById(nutritionProduct);
+        }
+        log.info("已创建营养产品条目,主平台商品ID: {}, 营养产品ID: {}", productId, nutritionProduct.getId());
+        return true;
+    }
+
+    /**
+     * 根据产品的成分功效说明生成成分功效标签(JSON数组字符串,如 ["补钙","补锌"])
+     *
+     * @param nutritionProductId 营养产品ID
+     * @return JSON数组字符串;无成分或无法提取时返回 null
+     */
+    private String buildEfficacyTags(Long nutritionProductId) {
+        List<NutritionIngredient> ingredients = ingredientMapper.selectList(
+            new LambdaQueryWrapper<NutritionIngredient>()
+                .eq(NutritionIngredient::getProductId, nutritionProductId)
+        );
+        if (ingredients.isEmpty()) {
+            return null;
+        }
+        List<String> tags = new ArrayList<>();
+        for (NutritionIngredient ingredient : ingredients) {
+            String purpose = ingredient.getPurpose();
+            if (purpose == null || purpose.trim().isEmpty()) {
+                continue;
+            }
+            // 按常见分隔符拆分功效描述,取有效片段作为标签(去重、去空、截断过长)
+            for (String part : purpose.split("[,,、;;/|\\s]+")) {
+                String tag = part.trim();
+                if (tag.isEmpty() || tag.length() > 20 || tags.contains(tag)) {
+                    continue;
+                }
+                tags.add(tag);
+            }
+        }
+        if (tags.isEmpty()) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder("[");
+        for (int i = 0; i < tags.size(); i++) {
+            if (i > 0) {
+                sb.append(",");
+            }
+            sb.append("\"").append(tags.get(i)).append("\"");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /**
+     * 验证营养产品是否符合年龄和剂量要求
+     *
+     * @param productId 营养产品ID
+     * @param ageInMonths 当前年龄(月)
+     * @param ageMinMonths 最小年龄(月)
+     * @param ageMaxMonths 最大年龄(月)
+     * @param dosageRequirement 剂量要求描述(可为 null)
+     * @param dosageUnit 剂量单位(可为 null)
+     * @return 是否通过验证
+     */
+    public boolean validateProductForAgeAndDosage(Long productId, Integer ageInMonths,
+                                                  Integer ageMinMonths, Integer ageMaxMonths,
+                                                  String dosageRequirement, String dosageUnit) {
+        // 1. 检查年龄是否在允许范围内
+        if (ageMinMonths != null && ageInMonths != null
+                && (ageInMonths < ageMinMonths || ageInMonths > ageMaxMonths)) {
+            log.info("产品{} 年龄{}个月不在允许范围[{}, {}]", productId, ageInMonths, ageMinMonths, ageMaxMonths);
+            return false;
+        }
+        // 2. 检查剂量是否超过 UL 限制
+        return validateDosageAgainstUlLimit(productId);
+    }
+
+    /**
+     * 检查产品剂量是否超过 UL 限制(多来源累加自动校验)
+     */
+    private boolean validateDosageAgainstUlLimit(Long productId) {
+        // 1. 获取该产品的所有成分
+        List<NutritionIngredient> ingredients = ingredientMapper.selectList(
+            new LambdaQueryWrapper<NutritionIngredient>()
+                .eq(NutritionIngredient::getProductId, productId)
+        );
+        if (ingredients.isEmpty()) {
+            return true; // 没有成分则视为通过
+        }
+
+        // 2. 按营养成分分组,累加同一成分多来源剂量
+        Map<String, BigDecimal> accumulatedDosage = new HashMap<>();
+
+        for (NutritionIngredient ingredient : ingredients) {
+            String nutrientKey = ingredient.getName() != null
+                ? ingredient.getName().toLowerCase() : ingredient.getIngredientType();
+            if (nutrientKey == null) {
+                continue;
+            }
+            // 累加同一成分剂量(假设所有成分来自同一产品内的多个成分条目)
+            BigDecimal amount = ingredient.getAmountPerServing() != null
+                ? ingredient.getAmountPerServing() : BigDecimal.ZERO;
+            accumulatedDosage.merge(nutrientKey, amount, BigDecimal::add);
+        }
+
+        // 3. 检查每个成分的 UL 限制
+        for (Map.Entry<String, BigDecimal> entry : accumulatedDosage.entrySet()) {
+            // 查找该成分的 UL 限制(按营养名称匹配)
+            List<NutritionUlLimit> ulLimits = ulLimitMapper.selectList(
+                new LambdaQueryWrapper<NutritionUlLimit>()
+                    .eq(NutritionUlLimit::getNutrientName, entry.getKey())
+            );
+            if (ulLimits.isEmpty()) {
+                continue;
+            }
+            NutritionUlLimit ulLimit = ulLimits.get(0);
+            if (ulLimit.getLimitValue() != null && entry.getValue() != null) {
+                double ulValue = ulLimit.getLimitValue().doubleValue();
+                double actualDose = entry.getValue().doubleValue();
+                if (actualDose > ulValue) {
+                    log.warn("成分{}剂量{}超过UL限制{}", entry.getKey(), actualDose, ulValue);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
     // ---- 评分私有方法 ----
 
     private double calcIngredientScore(NutritionProduct product, NutritionRecommendDTO.RecommendQuery query) {

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

@@ -1,820 +1 @@
-package com.etotem.cfc.service;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.*;
-import com.etotem.cfc.mapper.*;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.*;
-import java.util.stream.Collectors;
-import com.etotem.cfc.util.SortUtil;
-
-/**
- * 商品推荐服务
- * 支持三类推荐场景:
- * 1. 维度页推荐(基于维度匹配 + 关联关系)
- * 2. AI对话推荐(基于画像缺口 + 购买关联)
- * 3. 报告关联推荐(健康报告/认知测评触发)
- * <p>
- * 核心思路:<strong>静态维度映射</strong> 为基础,<strong>动态关联关系表</strong> 为补充,
- * 实现<strong>画像驱动</strong>与<strong>购买关联</strong>双轮驱动的个性化推荐。
- */
-@Slf4j
-@Service
-public class ProductRecommendationService {
-
-    @Resource
-    private ProductMapper productMapper;
-
-    @Resource
-    private ProductDimensionMappingMapper dimensionMappingMapper;
-
-    @Resource
-    private ProductOrderMapper orderMapper;
-
-    @Resource
-    private FiveDimensionScoreMapper fiveDimensionScoreMapper;
-
-    @Resource
-    private ProductRecommendationLogMapper productRecommendationLogMapper;
-
-    @Resource
-    private RecommendationProductAssociationMapper recommendationProductAssociationMapper;
-
-    @Resource
-    private HealthFilterService healthFilterService;
-
-    @Resource
-    private ReferralTreeMapper referralTreeMapper;
-
-    @Resource
-    private SupplySystemMemberMapper supplySystemMemberMapper;
-
-    @Resource
-    private UserMapper userMapper;
-
-    @Resource
-    private SupplySystemService supplySystemService;
-
-    @Resource
-    private UserContentWeightService userContentWeightService;
-
-    /**
-     * 维度页推荐
-     *
-     * @param dimensionCode    维度编码: body/wisdom/mind/action/wealth
-     * @param familyId         家庭ID
-     * @param memberId         成员ID(可选)
-     * @param excludeProductIds 排除的商品ID列表
-     * @param limit            返回数量
-     * @param userId           当前登录用户ID(用于供应商优先级计算,可为null)
-     * @return 推荐商品列表
-     */
-    public List<Map<String, Object>> getDimensionRecommendations(
-            String dimensionCode, Long familyId, Long memberId,
-            List<Long> excludeProductIds, int limit, Long userId) {
-
-        // 0. 加载成员健康画像(硬拦截基础)
-        HealthFilterService.HealthProfile healthProfile = healthFilterService.loadProfile(memberId, null, null);
-
-        // 1. 获取已购商品ID(90天内)
-        Set<Long> purchasedIds = getPurchasedProductIds(familyId, 90);
-        if (excludeProductIds != null) {
-            purchasedIds.addAll(excludeProductIds);
-        }
-
-        // 2. 获取成员维度得分(画像)
-        Map<String, Integer> memberScores = getMemberDimensionScores(familyId, memberId);
-
-        // 3. 从维度匹配表获取候选商品(静态基础)
-        List<Map<String, Object>> candidates = new ArrayList<>();
-
-        // 从 mapping 表获取关联商品
-        LambdaQueryWrapper<ProductDimensionMapping> mappingQw = new LambdaQueryWrapper<ProductDimensionMapping>()
-                .eq(ProductDimensionMapping::getEnabled, 1);
-        if (dimensionCode != null && !dimensionCode.isEmpty()) {
-            mappingQw.eq(ProductDimensionMapping::getDimensionCode, dimensionCode);
-        }
-        SortUtil.applySort(mappingQw);
-        List<ProductDimensionMapping> mappings = dimensionMappingMapper.selectList(mappingQw);
-
-        if (mappings != null && !mappings.isEmpty()) {
-            List<Long> mappedProductIds = mappings.stream()
-                    .map(ProductDimensionMapping::getProductId)
-                    .filter(id -> !purchasedIds.contains(id))
-                    .collect(Collectors.toList());
-
-            if (!mappedProductIds.isEmpty()) {
-                List<Product> mapped = productMapper.selectList(
-                        new LambdaQueryWrapper<Product>()
-                        .in(Product::getId, mappedProductIds)
-                                .eq(Product::getStatus, "on_shelf")
-                                .and(w -> w.ne(Product::getProductType, "physical").or().isNull(Product::getStock).or().gt(Product::getStock, 0))
-                                .orderByAsc(Product::getSortOrder)
-                );
-
-                HealthFilterService.FilterResult mappedFilter = healthFilterService.filterProducts(healthProfile, mapped);
-                mapped = mappedFilter.allowed;
-
-                // 构建 productId → mapping 得分
-                Map<Long, Integer> mappingScores = mappings.stream()
-                        .collect(Collectors.toMap(
-                                ProductDimensionMapping::getProductId,
-                                ProductDimensionMapping::getMatchScore,
-                                (a, b) -> a
-                        ));
-                Map<Long, String> mappingReasons = mappings.stream()
-                        .collect(Collectors.toMap(
-                                ProductDimensionMapping::getProductId,
-                                m -> m.getMatchReason() != null ? m.getMatchReason() : "",
-                                (a, b) -> a
-                        ));
-
-                for (Product p : mapped) {
-                    Integer baseScore = mappingScores.getOrDefault(p.getId(), 100);
-                    String reason = mappingReasons.getOrDefault(p.getId(), "");
-                    double finalScore = applyDimensionBoost(baseScore, dimensionCode, memberScores);
-                    Map<String, Object> item = new LinkedHashMap<>();
-                    item.put("id", p.getId());
-                    item.put("productId", p.getId());
-                    item.put("name", p.getName());
-                    item.put("coverImage", p.getCoverImage());
-                    item.put("price", p.getPrice());
-                    item.put("memberPrice", p.getPrice());
-                    item.put("productType", p.getProductType());
-                    item.put("source", "dimension");
-                    item.put("url", "/pages/discover/product-detail/product-detail?id=" + p.getId());
-
-                    // 推荐理由
-                    String dimReason = buildRecommendationReason(p, dimensionCode, memberScores);
-                    item.put("reason", dimReason);
-
-                    // 匹配分
-                    double matchScore = calculateMatchScore(p, dimensionCode, memberScores);
-                    item.put("matchScore", matchScore);
-
-                    candidates.add(item);
-                }
-            }
-        }
-
-        // 3.5 fallback: 当 dimension_mapping 表无数据时,从 Product.domain 匹配
-        if (candidates.isEmpty()) {
-            LambdaQueryWrapper<Product> domainQw = new LambdaQueryWrapper<Product>()
-                    .eq(Product::getStatus, "on_shelf")
-                    .and(w -> w.ne(Product::getProductType, "physical").or().isNull(Product::getStock).or().gt(Product::getStock, 0))
-                    .orderByAsc(Product::getSortOrder);
-            if (dimensionCode != null && !dimensionCode.isEmpty()) {
-                domainQw.eq(Product::getDomain, dimensionCode);
-            }
-            if (!purchasedIds.isEmpty()) {
-                domainQw.notIn(Product::getId, purchasedIds);
-            }
-            SortUtil.applySort(domainQw);
-            List<Product> domainMatched = productMapper.selectList(domainQw);
-             HealthFilterService.FilterResult domainFilter = healthFilterService.filterProducts(healthProfile, domainMatched);
-             domainMatched = domainFilter.allowed;
-            for (Product p : domainMatched) {
-                Map<String, Object> item = new LinkedHashMap<>();
-                item.put("id", p.getId());
-                item.put("productId", p.getId());
-                item.put("name", p.getName());
-                item.put("coverImage", p.getCoverImage());
-                item.put("price", p.getPrice());
-                item.put("memberPrice", p.getPrice());
-                item.put("productType", p.getProductType());
-                item.put("source", "dimension");
-                item.put("url", "/pages/discover/product-detail/product-detail?id=" + p.getId());
-                String reason = buildRecommendationReason(p, dimensionCode, memberScores);
-                item.put("reason", reason);
-                double matchScore = calculateMatchScore(p, dimensionCode, memberScores);
-                item.put("matchScore", matchScore);
-                candidates.add(item);
-            }
-        }
-
-        // 4. 关联关系推荐(动态:画像缺口、共购、复购)
-        List<Map<String, Object>> assocResults = queryAssociationRecommendations(
-                dimensionCode, familyId, memberId, purchasedIds, excludeProductIds, limit - candidates.size(), healthProfile);
-
-        // 合并并去重(关联推荐优先级高于维度映射,但保留维度的reason/matchScore)
-        if (assocResults != null && !assocResults.isEmpty()) {
-            for (Map<String, Object> item : assocResults) {
-                Long prodId = (Long) item.get("id");
-                // 已在 candidates 中则跳过(由 limit 控制数量,这里不再二次去重复用 id,而是追加)
-                if (candidates.size() >= limit) break;
-                candidates.add(item);
-            }
-        }
-
-        // 5. 最终排序并返回
-        return sortAndReturn(candidates, limit, userId, familyId);
-    }
-
-    /**
-     * 查询关联关系推荐(画像缺口 + 共购 + 复购)
-     */
-    private List<Map<String, Object>> queryAssociationRecommendations(
-            String dimensionCode, Long familyId, Long memberId,
-            Set<Long> purchasedIds, List<Long> excludeProductIds, int reqCount,
-            HealthFilterService.HealthProfile healthProfile) {
-
-        if (reqCount <= 0) return Collections.emptyList();
-
-        Map<String, Integer> memberScores = getMemberDimensionScores(familyId, memberId);
-        List<Map<String, Object>> results = new ArrayList<>();
-
-        // 4.1 画像缺口关联:维度得分低时推荐对应商品
-        if (dimensionCode != null && !dimensionCode.isEmpty()) {
-            List<Map<String, Object>> profileAssocs = queryProfileGapAssociations(
-                    dimensionCode, memberScores, purchasedIds, excludeProductIds, reqCount / 3, healthProfile);
-            results.addAll(profileAssocs);
-            if (results.size() >= reqCount) return results.subList(0, reqCount);
-        }
-
-        // 4.2 共购关联:买了A的用户也买了B(从订单数据挖掘)
-        if (purchasedIds != null && !purchasedIds.isEmpty()) {
-            List<Map<String, Object>> coPurchaseAssocs = queryCoPurchaseAssociations(
-                    purchasedIds, excludeProductIds, reqCount - results.size(), healthProfile);
-            results.addAll(coPurchaseAssocs);
-            if (results.size() >= reqCount) return results.subList(0, reqCount);
-        }
-
-        // 4.3 复购关联:买了A后X天再买A的关联商品
-        if (purchasedIds != null && !purchasedIds.isEmpty()) {
-            List<Map<String, Object>> repurchaseAssocs = queryRepurchaseAssociations(
-                    purchasedIds, excludeProductIds, reqCount - results.size(), healthProfile);
-            results.addAll(repurchaseAssocs);
-        }
-
-        return results;
-    }
-
-    /**
-     * 画像缺口关联:某维度得分低时,从关联关系表推荐商品
-     */
-    private List<Map<String, Object>> queryProfileGapAssociations(
-            String dimensionCode, Map<String, Integer> memberScores,
-            Set<Long> purchasedIds, List<Long> excludeProductIds, int reqCount,
-            HealthFilterService.HealthProfile healthProfile) {
-
-        if (reqCount <= 0) return Collections.emptyList();
-
-        // 寻找关联关系:association_type='profile_gap' 且 dimension_code 匹配
-        LambdaQueryWrapper<RecommendationProductAssociation> qw = new LambdaQueryWrapper<RecommendationProductAssociation>()
-                .eq(RecommendationProductAssociation::getAssociationType, "profile_gap")
-                .eq(RecommendationProductAssociation::getDimensionCode, dimensionCode)
-                .eq(RecommendationProductAssociation::getEnabled, 1);
-
-        // 如果有成员得分,筛选“得分低于阈值”才触发推荐
-        if (memberScores != null && !memberScores.isEmpty()) {
-            Integer score = memberScores.get(dimensionCode);
-            if (score != null && score < 70) {
-                // 分数越低,关联强度越高(可选逻辑,此处直接查出所有并按 strength 排序)
-                qw.orderByDesc(RecommendationProductAssociation::getStrength);
-            }
-        } else {
-            // 无画像数据时,默认中等强度
-            qw.orderByDesc(RecommendationProductAssociation::getStrength);
-        }
-
-        List<Map<String, Object>> results = new ArrayList<>();
-        List<RecommendationProductAssociation> assocs = recommendationProductAssociationMapper.selectList(qw);
-        for (RecommendationProductAssociation assoc : assocs) {
-            if (reqCount <= 0 || results.size() >= reqCount) break;
-            // 跳过已购商品
-            if (purchasedIds != null && purchasedIds.contains(assoc.getTargetProductId())) continue;
-            // 跳过被排除
-            if (excludeProductIds != null && excludeProductIds.contains(assoc.getTargetProductId())) continue;
-
-            Product product = productMapper.selectById(assoc.getTargetProductId());
-            if (product == null || !"on_shelf".equals(product.getStatus()) || !hasSellableStock(product)) continue;
-            if (!healthFilterService.checkProductBlocks(healthProfile, product).isEmpty()) continue;
-
-            Map<String, Object> item = new LinkedHashMap<>();
-            item.put("id", product.getId());
-            item.put("productId", product.getId());
-            item.put("name", product.getName());
-            item.put("coverImage", product.getCoverImage());
-            item.put("price", product.getPrice());
-            item.put("memberPrice", product.getPrice());
-            item.put("productType", product.getProductType());
-            item.put("source", "assoc_profile_gap");
-
-            // 关联强度作为匹配分
-            java.math.BigDecimal strength = assoc.getStrength();
-            item.put("matchScore", strength != null ? strength.doubleValue() / 10.0 : 0.5);
-
-            // 关联理由
-            String reason = assoc.getReason();
-            if (reason == null || reason.isEmpty()) {
-                Integer score = memberScores != null ? memberScores.get(dimensionCode) : null;
-                if (score != null && score < 50) {
-                    reason = "该维度得分偏低,重点推荐";
-                } else if (score != null && score < 70) {
-                    reason = "该维度有提升空间,推荐关注";
-                } else {
-                    reason = "根据维度匹配推荐";
-                }
-            }
-            item.put("reason", reason);
-
-            results.add(item);
-        }
-        return results;
-    }
-
-    /**
-     * 共购关联:从订单数据挖掘“买了A的用户也买了B”
-     */
-    private List<Map<String, Object>> queryCoPurchaseAssociations(
-            Set<Long> purchasedIds, List<Long> excludeProductIds, int reqCount,
-            HealthFilterService.HealthProfile healthProfile) {
-
-        if (reqCount <= 0 || purchasedIds == null || purchasedIds.isEmpty()) return Collections.emptyList();
-
-        List<Map<String, Object>> results = new ArrayList<>();
-
-        // 找出非空的 purchasedIds,查找关联关系
-        LambdaQueryWrapper<RecommendationProductAssociation> qw = new LambdaQueryWrapper<RecommendationProductAssociation>()
-                .eq(RecommendationProductAssociation::getAssociationType, "co_purchase")
-                .eq(RecommendationProductAssociation::getEnabled, 1)
-                .gt(RecommendationProductAssociation::getCoPurchaseCount, 0) // 只取已挖掘的
-                .orderByDesc(RecommendationProductAssociation::getCoPurchaseCount);
-
-        // 限制数量
-        if (reqCount > 0) {
-            qw.last("LIMIT " + reqCount);
-        }
-
-        List<RecommendationProductAssociation> assocs = recommendationProductAssociationMapper.selectList(qw);
-        for (RecommendationProductAssociation assoc : assocs) {
-            Long targetId = assoc.getTargetProductId();
-            if (targetId == null) continue;
-            // 跳过已购和被排除
-            if (purchasedIds.contains(targetId)) continue;
-            if (excludeProductIds != null && excludeProductIds.contains(targetId)) continue;
-
-            Product product = productMapper.selectById(targetId);
-            if (product == null || !"on_shelf".equals(product.getStatus()) || !hasSellableStock(product)) continue;
-            if (!healthFilterService.checkProductBlocks(healthProfile, product).isEmpty()) continue;
-
-            Map<String, Object> item = new LinkedHashMap<>();
-            item.put("id", product.getId());
-            item.put("productId", product.getId());
-            item.put("name", product.getName());
-            item.put("coverImage", product.getCoverImage());
-            item.put("price", product.getPrice());
-            item.put("memberPrice", product.getPrice());
-            item.put("productType", product.getProductType());
-            item.put("source", "assoc_co_purchase");
-
-            // 共购次数作为强度:次数越多 strength 越高
-            double strength = Math.min(1.0, assoc.getCoPurchaseCount() / 20.0); // 假设20次以上算很强
-            item.put("matchScore", strength);
-
-            // 理由:共购 X 次
-            item.put("reason", "与您常购商品共购 " + assoc.getCoPurchaseCount() + " 次");
-
-            results.add(item);
-        }
-        return results;
-    }
-
-    /**
-     * 复购关联:买了A后X天再买相关商品的关联
-     */
-    private List<Map<String, Object>> queryRepurchaseAssociations(
-            Set<Long> purchasedIds, List<Long> excludeProductIds, int reqCount,
-            HealthFilterService.HealthProfile healthProfile) {
-
-        if (reqCount <= 0 || purchasedIds == null || purchasedIds.isEmpty()) return Collections.emptyList();
-
-        List<Map<String, Object>> results = new ArrayList<>();
-
-        LambdaQueryWrapper<RecommendationProductAssociation> qw = new LambdaQueryWrapper<RecommendationProductAssociation>()
-                .eq(RecommendationProductAssociation::getAssociationType, "repurchase")
-                .eq(RecommendationProductAssociation::getEnabled, 1);
-
-        if (reqCount > 0) {
-            qw.last("LIMIT " + reqCount);
-        }
-
-        List<RecommendationProductAssociation> assocs = recommendationProductAssociationMapper.selectList(qw);
-        for (RecommendationProductAssociation assoc : assocs) {
-            Long targetId = assoc.getTargetProductId();
-            if (targetId == null) continue;
-            if (purchasedIds.contains(targetId)) continue;
-            if (excludeProductIds != null && excludeProductIds.contains(targetId)) continue;
-
-            Product product = productMapper.selectById(targetId);
-            if (product == null || !"on_shelf".equals(product.getStatus()) || !hasSellableStock(product)) continue;
-            if (!healthFilterService.checkProductBlocks(healthProfile, product).isEmpty()) continue;
-
-            Map<String, Object> item = new LinkedHashMap<>();
-            item.put("id", product.getId());
-            item.put("productId", product.getId());
-            item.put("name", product.getName());
-            item.put("coverImage", product.getCoverImage());
-            item.put("price", product.getPrice());
-            item.put("memberPrice", product.getPrice());
-            item.put("productType", product.getProductType());
-            item.put("source", "assoc_repurchase");
-
-            // 复购天数:days越小说明周期越短,推荐意图越强
-            int days = assoc.getRepurchaseDays() != null ? assoc.getRepurchaseDays() : 30;
-            double strength = 1.0 / Math.max(1, days / 7.0); // 每周递减
-            item.put("matchScore", Math.min(1.0, strength));
-
-            item.put("reason", "复购周期 " + days + " 天内关联推荐");
-
-            results.add(item);
-        }
-        return results;
-    }
-
-    /**
-     * 计算供应商优先级加分
-     * <p>
-     * 规则1:若直接引荐人是某产品供应商(supply_system.admin),优先推荐其体系内产品
-     * 规则2:沿引荐链向上追溯,找到任一层级是供应商的引荐人,优先推荐其产品
-     * 规则3:同一供应商体系引荐的用户越多,权重越高(按体系成员数加权)
-     * 补充规则:用户所属多个供应商体系时,其关联产品均优先推荐
-     */
-    private Map<Long, Double> getSupplierPriorityScores(Long userId) {
-        if (userId == null) return Collections.emptyMap();
-
-        // 1. 收集引荐链上的所有祖先(直接推荐人、间接推荐人)
-        List<ReferralTree> ancestorNodes = referralTreeMapper.selectList(
-                new LambdaQueryWrapper<ReferralTree>()
-                        .eq(ReferralTree::getChildId, userId)
-                        .orderByAsc(ReferralTree::getLevel));
-        if (ancestorNodes.isEmpty()) return Collections.emptyMap();
-
-        // 祖先 userId -> 层级(level 越小=关系越近,权重越高)
-        Map<Long, Integer> ancestorLevels = new HashMap<>();
-        for (ReferralTree node : ancestorNodes) {
-            ancestorLevels.putIfAbsent(node.getParentId(), node.getLevel());
-        }
-
-        // 2. 找出引荐链中的供应商(adminId 对应 supply_system)
-        //    supplySystemId -> 引荐层级(取最小即最近层级,权重最高)
-        Map<Long, Integer> supplierSystemToNearestLevel = new HashMap<>();
-        for (Map.Entry<Long, Integer> entry : ancestorLevels.entrySet()) {
-            Long ancestorId = entry.getKey();
-            Integer level = entry.getValue();
-            SupplySystem system = supplySystemService.getByAdminId(ancestorId);
-            if (system != null) {
-                supplierSystemToNearestLevel.merge(system.getId(), level,
-                        (existing, newVal) -> Math.min(existing, newVal));
-            }
-        }
-
-        // 3. 也检查当前用户本身是否属于某些供应商体系(补充规则)
-        List<SupplySystemMember> userSystems = supplySystemMemberMapper.selectList(
-                new LambdaQueryWrapper<SupplySystemMember>()
-                        .eq(SupplySystemMember::getUserId, userId)
-                        .eq(SupplySystemMember::getStatus, "active"));
-        for (SupplySystemMember mem : userSystems) {
-            supplierSystemToNearestLevel.merge(mem.getSystemId(), 0,
-                    (existing, newVal) -> Math.min(existing, newVal));
-        }
-
-        if (supplierSystemToNearestLevel.isEmpty()) return Collections.emptyMap();
-
-        // 4. 计算每个供应商体系的成员数作为权重(规则3)
-        //    supplySystemId -> 加分系数 = memberCount * 0.1 / levelPenalty
-        //    levelPenalty: 直接推荐人(level=1) 系数为1.0,每远一层衰减
-        Map<Long, Double> systemBoosts = new HashMap<>();
-        for (Map.Entry<Long, Integer> sysEntry : supplierSystemToNearestLevel.entrySet()) {
-            Long systemId = sysEntry.getKey();
-            Integer level = sysEntry.getValue();
-            // 成员数
-            Long memberCount = supplySystemMemberMapper.selectCount(
-                    new LambdaQueryWrapper<SupplySystemMember>()
-                            .eq(SupplySystemMember::getSystemId, systemId)
-                            .eq(SupplySystemMember::getStatus, "active"));
-            int count = memberCount == null ? 0 : memberCount.intValue();
-            // 层级衰减:level=1(直接推荐人)不打折,level越大折扣越大
-            double levelFactor = Math.max(0.1, 1.0 - (level - 1) * 0.15);
-            // 加分系数 = 成员数 * 0.1 * 层级因子,基础分50,最大加成5分
-            double boost = Math.min(5.0, count * 0.1 * levelFactor);
-            systemBoosts.put(systemId, boost);
-        }
-
-        // 5. 按 distributionSystemId 聚合加分
-        //    同一个产品的 distributionSystemId 对应多个体系时,取最高加分
-        Map<Long, Double> productIdBoost = new HashMap<>();
-        for (Map.Entry<Long, Double> sysEntry : systemBoosts.entrySet()) {
-            Long systemId = sysEntry.getKey();
-            Double boost = sysEntry.getValue();
-            List<Product> products = productMapper.selectList(
-                    new LambdaQueryWrapper<Product>()
-                            .eq(Product::getDistributionSystemId, systemId)
-                            .eq(Product::getStatus, "on_shelf"));
-            for (Product p : products) {
-                productIdBoost.merge(p.getId(), boost, Math::max);
-            }
-        }
-
-        return productIdBoost;
-    }
-
-    /**
-     * 根据 matchScore 和供应商优先级进行最终排序
-     * 权重表有数据时按家庭权重总和排序,无权重数据时回退 matchScore
-     */
-    private List<Map<String, Object>> sortAndReturn(List<Map<String, Object>> candidates, int limit, Long userId, Long familyId) {
-        if (candidates == null || candidates.isEmpty()) return Collections.emptyList();
-        
-        // 计算供应商优先级加分(规则1~3)
-        Map<Long, Double> supplierPriorityScores = getSupplierPriorityScores(userId);
-        
-        // 将供应商优先级加入 matchScore
-        if (!supplierPriorityScores.isEmpty()) {
-            for (Map<String, Object> item : candidates) {
-                Long productId = (Long) item.get("productId");
-                Double priorityBoost = productId != null ? supplierPriorityScores.get(productId) : null;
-                Double currentScore = (Double) item.get("matchScore");
-                if (priorityBoost != null && priorityBoost > 0) {
-                    double boostedScore = (currentScore != null ? currentScore : 0) + priorityBoost;
-                    item.put("matchScore", boostedScore);
-                }
-                item.put("supplierPriorityBoost", priorityBoost != null ? priorityBoost : 0);
-            }
-        }
-        
-        // 计算每个商品的家庭权重总和(家庭聚合:Σ成员权重,方向一致均为正向)
-        Map<Long, Double> productWeightSum = new HashMap<>();
-        if (familyId != null) {
-            for (Map<String, Object> item : candidates) {
-                Long productId = (Long) item.get("productId");
-                if (productId == null) continue;
-                // 查该家庭所有成员对该商品的权重,求和
-                List<UserContentWeight> weights = userContentWeightService.listWeights(null, familyId, "product", productId);
-                double sum = 0.0;
-                if (weights != null) {
-                    for (UserContentWeight w : weights) {
-                        sum += w.getWeight() != null ? w.getWeight().doubleValue() : 0.0;
-                    }
-                }
-                productWeightSum.put(productId, sum);
-                item.put("userWeight", sum);
-            }
-        }
-        
-        // 排序:优先按用户权重(降序),权重相同或无权重数据时回退 matchScore(降序)
-        candidates.sort((a, b) -> {
-            Double weightA = productWeightSum.getOrDefault((Long) a.get("productId"), 0.0);
-            Double weightB = productWeightSum.getOrDefault((Long) b.get("productId"), 0.0);
-            int cmp = weightB.compareTo(weightA);
-            if (cmp != 0) return cmp;
-            
-            Double scoreA = (Double) a.get("matchScore");
-            Double scoreB = (Double) b.get("matchScore");
-            if (scoreA == null && scoreB == null) return 0;
-            if (scoreA == null) return 1;
-            if (scoreB == null) return -1;
-            return scoreB.compareTo(scoreA);
-        });
-        
-        // 去重(按 productId 去重)
-        Set<Long> seenIds = new HashSet<>();
-        List<Map<String, Object>> result = new ArrayList<>();
-        for (Map<String, Object> item : candidates) {
-            Long id = (Long) item.get("id");
-            if (id == null || seenIds.contains(id)) continue;
-            seenIds.add(id);
-            if (result.size() >= limit) break;
-            result.add(item);
-        }
-        return result;
-    }
-
-    // ── 原有私有辅助方法(保持不变)──
-
-    // 仅实物商品要求库存>0(库存NULL视为可购),与 ProductOrderService 下单校验口径一致;虚拟商品不看库存
-    private boolean hasSellableStock(Product product) {
-        if (!"physical".equals(product.getProductType())) {
-            return true;
-        }
-        return product.getStock() == null || product.getStock() > 0;
-    }
-
-    /**
-     * 获取家庭成员在90天内购买过的商品ID
-     */
-    private Set<Long> getPurchasedProductIds(Long familyId, int daysAgo) {
-        Set<Long> ids = new HashSet<>();
-        if (familyId == null) return ids;
-
-        Calendar cal = Calendar.getInstance();
-        cal.add(Calendar.DAY_OF_YEAR, -daysAgo);
-        Date cutoff = cal.getTime();
-
-        List<ProductOrder> orders = orderMapper.selectList(
-                new LambdaQueryWrapper<ProductOrder>()
-                        .eq(ProductOrder::getFamilyId, familyId)
-                        .in(ProductOrder::getStatus, "paid", "pending_receipt", "shipped", "completed")
-                        .gt(ProductOrder::getPaidAt, cutoff)
-        );
-
-        for (ProductOrder order : orders) {
-            if (order.getProductId() != null) {
-                ids.add(order.getProductId());
-            }
-        }
-        return ids;
-    }
-
-    /**
-     * 获取成员维度得分
-     */
-    private Map<String, Integer> getMemberDimensionScores(Long familyId, Long memberId) {
-        Map<String, Integer> scores = new HashMap<>();
-
-        LambdaQueryWrapper<FiveDimensionScore> qw = new LambdaQueryWrapper<>();
-        qw.eq(familyId != null, FiveDimensionScore::getFamilyId, familyId);
-        qw.eq(memberId != null, FiveDimensionScore::getMemberId, memberId);
-        qw.orderByDesc(FiveDimensionScore::getAssessedAt);
-
-        SortUtil.applySort(qw);
-        List<FiveDimensionScore> records = fiveDimensionScoreMapper.selectList(qw);
-
-        // 取每个维度的最新得分
-        Set<String> seen = new HashSet<>();
-        for (FiveDimensionScore s : records) {
-            if (s.getDimensionCode() != null && !seen.contains(s.getDimensionCode())) {
-                seen.add(s.getDimensionCode());
-                scores.put(s.getDimensionCode(), s.getScore() != null ? s.getScore() : 0);
-            }
-        }
-        return scores;
-    }
-
-    /**
-     * 对低得分维度加权 boost
-     */
-    private double applyDimensionBoost(int baseScore, String dimensionCode, Map<String, Integer> memberScores) {
-        if (dimensionCode == null || dimensionCode.isEmpty()) {
-            return baseScore;
-        }
-        Integer score = memberScores.get(dimensionCode);
-        if (score == null || score >= 70) {
-            return baseScore;
-        } else if (score >= 50) {
-            return baseScore * 1.1;
-        } else {
-            return baseScore * 1.2;
-        }
-    }
-
-    /**
-     * 计算综合匹配分
-     */
-    private double calculateMatchScore(Product product, String dimensionCode, Map<String, Integer> memberScores) {
-        int base = 70;
-        if (dimensionCode == null || dimensionCode.isEmpty()) {
-            Integer minScore = memberScores.values().stream().min(Integer::compareTo).orElse(null);
-            if (minScore != null) {
-                if (minScore < 50) base = 90;
-                else if (minScore < 70) base = 80;
-            }
-            return base;
-        }
-        Integer myScore = memberScores.get(dimensionCode);
-        if (myScore != null) {
-            if (myScore < 50) base = 90;
-            else if (myScore < 70) base = 80;
-            else base = 70;
-        }
-        return base;
-    }
-
-    /**
-     * 构建推荐理由
-     */
-    private String buildRecommendationReason(Product product, String dimensionCode, Map<String, Integer> memberScores) {
-        if (dimensionCode == null || dimensionCode.isEmpty()) {
-            return "为您推荐的好物";
-        }
-        Integer score = memberScores.get(dimensionCode);
-        if (score == null) {
-            return "根据您的维度匹配为您推荐";
-        }
-        if (score < 50) {
-            return "该维度得分偏低,重点推荐";
-        } else if (score < 70) {
-            return "该维度有提升空间,推荐关注";
-        }
-        return "丰富您的维度生活";
-    }
-
-    /**
-     * 从健康分析结果提取维度需求
-     */
-    @SuppressWarnings("unchecked")
-    private List<String> extractDimensionNeedsFromHealth(Object analysis) {
-        List<String> dims = new ArrayList<>();
-        if (analysis == null) return dims;
-
-        try {
-            if (analysis instanceof Map) {
-                Map<String, Object> map = (Map<String, Object>) analysis;
-                Object nutritionScore = map.get("nutritionScore");
-                Object gutScore = map.get("gutHealthScore");
-                Object overallScore = map.get("overallScore");
-
-                if (nutritionScore instanceof Number && ((Number) nutritionScore).intValue() < 60) {
-                    dims.add("body");
-                }
-                if (gutScore instanceof Number && ((Number) gutScore).intValue() < 60) {
-                    dims.add("body");
-                }
-                if (overallScore instanceof Number && ((Number) overallScore).intValue() < 60) {
-                    dims.add("body");
-                    dims.add("mind");
-                }
-            }
-        } catch (Exception e) {
-            log.warn("解析健康分析结果维度失败: {}", e.getMessage());
-        }
-        return dims;
-    }
-
-    /**
-     * 从认知测评结果提取弱维度
-     */
-    @SuppressWarnings("unchecked")
-    private List<String> extractDimensionNeedsFromCognitive(Object analysis) {
-        List<String> dims = new ArrayList<>();
-        if (analysis == null) return dims;
-
-        try {
-            if (analysis instanceof List) {
-                List<String> weakDims = (List<String>) analysis;
-                for (String dim : weakDims) {
-                    if ("focusScore".equals(dim) || "processingSpeedScore".equals(dim)) {
-                        dims.add("wisdom");
-                    } else if ("attentionScore".equals(dim)) {
-                        dims.add("mind");
-                    } else if ("memoryScore".equals(dim)) {
-                        dims.add("wisdom");
-                    } else {
-                        dims.add(dim);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            log.warn("解析认知测评结果维度失败: {}", e.getMessage());
-        }
-        return dims;
-    }
-
-    /**
-     * 报告关联推荐(健康报告/认知测评触发)
-     *
-     * @param reportType 报告类型: "health_report" 或 "cognitive_assessment"
-     * @param analysis   分析结果(可为null,由内部推断维度)
-     * @param userId     用户ID
-     * @param limit      返回数量
-     * @return 推荐商品列表
-     */
-    public List<Map<String, Object>> getReportRelatedProducts(
-            String reportType, Object analysis, Long userId, int limit) {
-
-        // 从用户画像中提取弱维度
-        List<String> dimensionCodes = new ArrayList<>();
-        if ("health_report".equals(reportType)) {
-            dimensionCodes = extractDimensionNeedsFromHealth(analysis);
-        } else if ("cognitive_assessment".equals(reportType)) {
-            dimensionCodes = extractDimensionNeedsFromCognitive(analysis);
-        }
-
-        if (dimensionCodes.isEmpty()) {
-            dimensionCodes.add("body");
-        }
-
-        // 去重后为每个维度查询关联推荐
-        List<String> uniqueDims = dimensionCodes.stream().distinct().collect(Collectors.toList());
-        Set<Long> seenProductIds = new HashSet<>();
-
-        List<Map<String, Object>> result = new ArrayList<>();
-        for (String dim : uniqueDims) {
-            if (result.size() >= limit) break;
-            // 获取该维度的关联推荐(含画像缺口、共购、复购)
-            List<Map<String, Object>> prods = getDimensionRecommendations(dim, null, null, null, limit - result.size(), null);
-            for (Map<String, Object> p : prods) {
-                Long pid = (Long) p.get("id");
-                if (pid == null || seenProductIds.contains(pid)) continue;
-                seenProductIds.add(pid);
-                p.put("triggerDimension", dim);
-                result.add(p);
-            }
-        }
-
-        return result;
-    }
-}
+"213: return sortAndReturn(candidates, limit, userId, familyId);"

+ 80 - 9
cfc-backend/src/main/java/com/etotem/cfc/service/ReportCollectService.java

@@ -25,6 +25,7 @@ import java.io.FileInputStream;
 import java.io.InputStreamReader;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.concurrent.Executor;
@@ -152,9 +153,10 @@ public class ReportCollectService {
         if (STATUS_COMPLETED.equals(draft.getParseStatus())) {
             return;
         }
+        String pdfPath = null;
         try {
             healthReportDraftService.markParseStarted(draftId);
-            String pdfPath = resolvePdfPath(draft);
+            pdfPath = resolvePdfPath(draft);
             if (pdfPath == null) {
                 healthReportDraftService.markCollectFailed(draftId, "无法获取报告文件路径");
                 return;
@@ -185,6 +187,16 @@ public class ReportCollectService {
                 }
             }
 
+            // 加固③:指纹成功识别出的类型与用户所选报告类型不一致(非 DAN)→ 阻止自动入库,
+            // 防止认知能力测评等报告误入健康管道(如 gut_flora)后以错误类型静默入库
+            if (!"unknown".equals(typeId) && !typeId.equals(draft.getReportType())) {
+                log.warn("报告类型不匹配 draftId={}, 指纹识别={}, 所选={}", draftId, typeId, draft.getReportType());
+                healthReportDraftService.markCollectFailed(draftId,
+                        "报告内容与所选类型不符(指纹识别为「" + typeId + "」,所选「" + draft.getReportType()
+                                + "」),请核对报告类型后重新上传");
+                return;
+            }
+
             // 采集指标清单(用于驱动非 DAN 的 LangGraph LLM 解析)
             String validationTypeId = (!"unknown".equals(typeId)) ? typeId : draft.getReportType();
             List<String> expectedIndicators = loadExpectedIndicators(validationTypeId);
@@ -223,11 +235,29 @@ public class ReportCollectService {
             }
 
             harmReportPayloadValid(payload);
+            // 加固②:已知肠道菌群家族类型必须含菌群实质数据(菌群检出列表/指标清单),
+            // 防止认知测评等非菌群报告只解析出 overview 也静默入库
+            if (isGutFloraType(validationTypeId) && !hasGutContent(payload)) {
+                log.warn("肠道菌群报告解析结果缺少菌群数据,阻止入库 draftId={}, typeId={}",
+                        draftId, validationTypeId);
+                healthReportDraftService.markCollectFailed(draftId,
+                        "解析结果缺少菌群检测数据(报告内容与所选类型不符),请在解析状态页人工处理或重新上传");
+                return;
+            }
             // 采集指标完整性校验(report_type_registry.expected_indicators 定义驱动)
+            // 加固①:校验不通过(pass=false)阻止自动入库,标记失败由管理端人工处理
+            boolean validationPass;
             try {
-                runCollectValidation(draftId, validationTypeId, payload);
+                validationPass = runCollectValidation(draftId, validationTypeId, payload);
             } catch (Exception e) {
                 log.warn("采集指标校验失败 draftId={}, typeId={}", draftId, validationTypeId, e);
+                validationPass = true;
+            }
+            if (!validationPass) {
+                log.warn("采集指标校验未通过,阻止入库 draftId={}, typeId={}", draftId, validationTypeId);
+                healthReportDraftService.markCollectFailed(draftId,
+                        "采集指标校验未通过(报告内容与所选类型不符或解析不完整),请在解析状态页人工处理或重新上传");
+                return;
             }
             healthReportDraftService.updateParseMethod(draftId, method);
             healthReportDraftService.updatePayload(draftId, objectMapper.writeValueAsString(payload));
@@ -247,6 +277,8 @@ public class ReportCollectService {
         } catch (Exception e) {
             log.error("异步采集失败 draftId={}", draftId, e);
             healthReportDraftService.markCollectFailed(draftId, e.getMessage());
+        } finally {
+            cleanupPdfCache(pdfPath);
         }
     }
 
@@ -405,6 +437,25 @@ public class ReportCollectService {
         }
     }
 
+    /**
+     * 解析完成后清理 OSS 下载中转缓存。OSS 为持久存储,本地缓存仅为解析中转,用后即删。
+     * 仅清理 .jdcloud-cache 缓存文件,本地磁盘存储(uploadBaseDir 直存)的原文件不动。
+     */
+    private void cleanupPdfCache(String pdfPath) {
+        if (pdfPath == null) {
+            return;
+        }
+        File f = new File(pdfPath);
+        if (f.exists() && f.getAbsolutePath().contains(".jdcloud-cache")) {
+            try {
+                Files.deleteIfExists(f.toPath());
+                log.info("已清理OSS下载中转缓存 {}", pdfPath);
+            } catch (Exception e) {
+                log.warn("清理OSS下载中转缓存失败 {}", pdfPath, e);
+            }
+        }
+    }
+
     /**
      * 检查文件是否为 PDF(通过魔数 %PDF- 判断)。
      * 指纹引擎、LangGraph 解析、opencode 均要求 PDF 输入。
@@ -1045,14 +1096,14 @@ public class ReportCollectService {
     /**
      * 健康(GUT/混检)管道校验入口:汇总 payload 中全部可采集指标名称后按定义比对
      */
-    private void runCollectValidation(Long draftId, String typeId, ParsedReportPayload.Payload payload) {
-        runCollectValidation(draftId, typeId, collectHealthNames(payload));
+    private boolean runCollectValidation(Long draftId, String typeId, ParsedReportPayload.Payload payload) {
+        return runCollectValidation(draftId, typeId, collectHealthNames(payload));
     }
 
     /**
      * DAN 管道校验入口:汇总解析 items 的 name/code 后按定义比对
      */
-    private void runCollectValidation(Long draftId, String typeId, DanReportParseService.DanParsedReport parsed) {
+    private boolean runCollectValidation(Long draftId, String typeId, DanReportParseService.DanParsedReport parsed) {
         Set<String> names = new HashSet<>();
         if (parsed != null && parsed.getItems() != null) {
             for (DanReportParseService.DataItem item : parsed.getItems()) {
@@ -1060,7 +1111,7 @@ public class ReportCollectService {
                 addCollectName(names, item.getCode());
             }
         }
-        runCollectValidation(draftId, typeId, names);
+        return runCollectValidation(draftId, typeId, names);
     }
 
     /**
@@ -1068,13 +1119,13 @@ public class ReportCollectService {
      * 该类型无指标项定义(expected_indicators 为空 / 类型未注册)时跳过,不影响采集主流程。
      * 结果 JSON(typeId/expected/matched/pass/missing)写入草稿 collect_validation,缺失项记入 WARN 日志。
      */
-    private void runCollectValidation(Long draftId, String typeId, Set<String> collectedNames) {
+    private boolean runCollectValidation(Long draftId, String typeId, Set<String> collectedNames) {
         if (draftId == null || typeId == null || collectedNames == null || collectedNames.isEmpty()) {
-            return;
+            return true;
         }
         List<String> expected = loadExpectedIndicators(typeId);
         if (expected == null || expected.isEmpty()) {
-            return;
+            return true;
         }
         List<String> missing = new ArrayList<>();
         int matched = 0;
@@ -1102,6 +1153,26 @@ public class ReportCollectService {
         } catch (Exception e) {
             log.warn("记录采集校验结果失败 draftId={}, typeId={}", draftId, typeId, e);
         }
+        return missing.isEmpty();
+    }
+
+    /** 是否为肠道菌群家族类型(report_type_registry.family = gut_flora) */
+    private boolean isGutFloraType(String typeId) {
+        if (typeId == null) {
+            return false;
+        }
+        ReportTypeRegistry registry = reportTypeRegistryMapper.selectOne(
+                new LambdaQueryWrapper<ReportTypeRegistry>()
+                        .eq(ReportTypeRegistry::getTypeId, typeId)
+                        .last("LIMIT 1"));
+        return registry != null && "gut_flora".equals(registry.getFamily());
+    }
+
+    /** 菌群实质数据判定:菌群检出列表或指标清单至少一项非空 */
+    private boolean hasGutContent(ParsedReportPayload.Payload payload) {
+        return payload != null
+                && ((payload.getGutFlora() != null && !payload.getGutFlora().isEmpty())
+                        || (payload.getIndicators() != null && !payload.getIndicators().isEmpty()));
     }
 
     /** 读取类型指标项定义(report_type_registry.expected_indicators JSON 数组);未定义返回 null */

+ 75 - 0
cfc-backend/src/main/java/com/etotem/cfc/task/RepeatTaskGenerator.java

@@ -2,12 +2,15 @@ package com.etotem.cfc.task;
 import javax.annotation.Resource;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Task;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
@@ -22,6 +25,7 @@ import com.etotem.cfc.config.DatabaseInitializer;
  * - weekly: 每周生成(按模板 deadline 的星期几)
  * - monthly: 每月生成(按模板 deadline 的日)
  * 兼容旧版 repeat_type (none/daily/weekly):repeat_type=daily 等价 frequency=daily,weekly 等价 weekly
+ * 额外:每日为能登录用户生成 3 条内置健康打卡任务([DIET]/[SLEEP]/[EXERCISE],is_daily_progress=1)
  */
 @Slf4j
 @Component
@@ -30,6 +34,16 @@ public class RepeatTaskGenerator {
     @Resource
     private TaskMapper taskMapper;
 
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
+    /** 内置健康打卡任务模板定义(title 前缀约定与 TaskService.updateProgress 的 taskKey 匹配一致) */
+    private static final List<String[]> CHECKIN_TEMPLATES = Arrays.asList(
+            new String[]{"[DIET]饮食打卡", "diet"},
+            new String[]{"[SLEEP]睡眠打卡", "sleep"},
+            new String[]{"[EXERCISE]运动打卡", "exercise"}
+    );
+
     /**
      * 每小时第5分执行,按频率分派生成重复任务
      */
@@ -134,11 +148,72 @@ public class RepeatTaskGenerator {
             }
             log.info("每小时重复任务生成完成,处理{}个模板", hourlyTasks.size());
 
+            // 6. 内置健康打卡任务([DIET]/[SLEEP]/[EXERCISE])——每日为能登录用户生成
+            generateBuiltinCheckinTasks(today);
+
         } catch (Exception e) {
             log.error("生成重复任务失败", e);
         }
     }
 
+    /**
+     * 每日为所有能登录用户生成 3 条内置健康打卡任务(is_daily_progress=1)
+     * title 前缀约定:[DIET]/[SLEEP]/[EXERCISE],与 TaskService.updateProgress 的 taskKey 匹配一致
+     * 幂等:当天已有对应实例则跳过
+     */
+    private void generateBuiltinCheckinTasks(Date today) {
+        Date tomorrow = new Date(today.getTime() + 86400000L);
+        // 所有能登录用户(family_members.userId 非空)
+        List<FamilyMember> members = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .isNotNull(FamilyMember::getUserId)
+        );
+        int created = 0;
+        for (FamilyMember member : members) {
+            for (String[] tpl : CHECKIN_TEMPLATES) {
+                String title = tpl[0];
+                String actionType = tpl[1];
+                Long exists = taskMapper.selectCount(
+                        new LambdaQueryWrapper<Task>()
+                                .eq(Task::getFamilyMemberId, member.getId())
+                                .eq(Task::getTitle, title)
+                                .eq(Task::getIsDailyProgress, 1)
+                                .ge(Task::getCreatedAt, today)
+                                .lt(Task::getCreatedAt, tomorrow)
+                );
+                if (exists != null && exists > 0) {
+                    continue;
+                }
+                Task checkinTask = new Task();
+                checkinTask.setFamilyId(member.getFamilyId());
+                checkinTask.setFamilyMemberId(member.getId());
+                checkinTask.setCreatorId(member.getUserId());
+                checkinTask.setExecutorType("child");
+                checkinTask.setExecutorId(member.getId());
+                checkinTask.setChildId(member.getId());
+                checkinTask.setTitle(title);
+                checkinTask.setDescription("每日健康打卡任务");
+                checkinTask.setPoints(5);
+                checkinTask.setCategory("健康打卡");
+                checkinTask.setStatus("pending");
+                checkinTask.setNeedReview(0);
+                checkinTask.setIsTemplate(0);
+                checkinTask.setIsDailyProgress(1);
+                checkinTask.setTargetValue(1);
+                checkinTask.setActionType(actionType);
+                checkinTask.setMemberOnly(0);
+                checkinTask.setSourceType("self");
+                checkinTask.setCreatedAt(new Date());
+                checkinTask.setUpdatedAt(new Date());
+                taskMapper.insert(checkinTask);
+                created++;
+            }
+        }
+        if (created > 0) {
+            log.info("内置健康打卡任务生成完成,新增{}条(覆盖{}个能登录用户)", created, members.size());
+        }
+    }
+
     /**
      * 隔天判定:模板创建时间到今天的间隔天数为偶数 → 今天生成
      */

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

@@ -1,5 +1,6153 @@
+<<<<<<< HEAD
 -- 迁移354: 用户内容偏好权重表(user_content_weights)
 CREATE TABLE IF NOT EXISTS user_content_weights (
+=======
+-- 浠艾福数据库初始化脚本
+-- 自动创建表
+
+-- 推荐规则表(统一推荐引擎规则配置)
+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='商品推荐规则配置';
+
+-- 推荐规则表索引
+CREATE INDEX idx_rules_enabled ON recommendation_rules(enabled);
+CREATE INDEX idx_rules_scene ON recommendation_rules(scene_codes);
+CREATE INDEX idx_rules_priority ON recommendation_rules(priority);
+
+-- 浠艾福数据库初始化脚本
+-- 自动创建表
+
+-- 家庭表
+CREATE TABLE IF NOT EXISTS families (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100),
+    invite_code VARCHAR(8) UNIQUE,
+    butler_id BIGINT COMMENT '绑定的管家userId',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_invite_code (invite_code),
+    INDEX idx_butler_id (butler_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 用户表
+CREATE TABLE IF NOT EXISTS users (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    openid VARCHAR(64) NOT NULL,
+    unionid VARCHAR(64),
+    family_id BIGINT,
+    role VARCHAR(20) NOT NULL,
+    roles VARCHAR(100) COMMENT '角色列表JSON/逗号分隔(多角色)',
+    nickname VARCHAR(50),
+    avatar VARCHAR(255),
+    password VARCHAR(128),
+    session_key VARCHAR(64) COMMENT '微信登录会话密钥(虚拟支付签名用)',
+    id_card VARCHAR(18),
+    real_name VARCHAR(50),
+    phone VARCHAR(11),
+    birthday DATE,
+    gender ENUM('male','female'),
+    birth_hour VARCHAR(10) COMMENT '出生时辰: 子丑寅卯辰巳午未申酉戌亥',
+    mascot VARCHAR(20) DEFAULT NULL COMMENT 'AI助手形象: xibao(浠宝)/fubao(福宝)',
+    portrait_prompt TEXT COMMENT '用户自定义画像 prompt(为空时自动从 profile_snapshot 渲染)',
+    family_inviter_id BIGINT DEFAULT NULL COMMENT '家庭邀请人ID(首次注册时绑定,用于追踪邀请来源)',
+    province VARCHAR(50) DEFAULT NULL COMMENT '省份',
+    city VARCHAR(50) DEFAULT NULL COMMENT '城市',
+    district VARCHAR(50) DEFAULT NULL COMMENT '区县',
+    street VARCHAR(100) DEFAULT NULL COMMENT '详细地址',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_openid (openid),
+    INDEX idx_family_id (family_id),
+    INDEX idx_phone (phone)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+-- 注:role 字段值 'child' 已废弃,存量数据通过迁移298统一归一为 'parent'。
+-- 可登录成员(有 openid)= 原 role='child' 迁移后的账号;
+-- 不可登录成员(无 openid)= 家长手动创建的子账号。
+
+-- ============================================================
+-- 【已删除】children 表已废弃:数据由 DatabaseInitializer 迁移322 迁移至 family_members 后 DROP。
+-- 保留此处仅作参考,勿执行
+-- ============================================================
+-- CREATE TABLE IF NOT EXISTS children (
+--     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+--     user_id BIGINT NOT NULL,
+--     family_id BIGINT NOT NULL,
+--     nickname VARCHAR(50) NOT NULL,
+--     age TINYINT NOT NULL,
+--     birthday DATE,
+--     gender ENUM('male','female'),
+--     penalty_enabled TINYINT DEFAULT 1 COMMENT '是否开启扣分(0否1是)',
+--     theme VARCHAR(32) DEFAULT 'default',
+--     total_points INT DEFAULT 0,
+--     system_points INT DEFAULT 0 COMMENT 'CF值(测评建档/平台发放)',
+--     streak_days INT DEFAULT 0,
+--     last_task_date DATE,
+--     focus_max_daily TINYINT DEFAULT 3,
+--     focus_remaining TINYINT DEFAULT 3,
+--     focus_reset_date DATE,
+--     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+--     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+--     INDEX idx_user_id (user_id),
+--     INDEX idx_family_id (family_id)
+-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 任务表
+CREATE TABLE IF NOT EXISTS tasks (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL,
+    creator_id BIGINT NOT NULL,
+    child_id BIGINT COMMENT '执行者ID(child任务时必填,parent任务时可空)',
+    executor_type VARCHAR(20) COMMENT '执行者类型: child=非登录用户/parent=可登录家长/member=其他成员',
+    executor_id BIGINT COMMENT '执行者ID',
+    title VARCHAR(200) NOT NULL,
+    description TEXT,
+    points TINYINT DEFAULT 2 COMMENT '任务积分1-10',
+    deadline DATETIME NOT NULL,
+    accept_deadline DATETIME DEFAULT NULL COMMENT '接受截止时间(分配给其他家庭成员的任务)',
+    repeat_type ENUM('none', 'daily', 'weekly') DEFAULT 'none',
+    category VARCHAR(32),
+    need_review TINYINT DEFAULT 0 COMMENT '是否需要审核',
+    review_by_category TINYINT DEFAULT 0 COMMENT '是否按分类审核',
+    status ENUM('pending', 'assigned', 'in_progress', 'completed', 'rejected', 'overdue', 'cancelled', 'locked', 'pending_review') DEFAULT 'pending',
+    completed_at DATETIME,
+    bonus_rule JSON COMMENT '积分奖励规则',
+    points_expire_days INT DEFAULT 0 COMMENT '积分有效期天数',
+    dimension_code VARCHAR(20) COMMENT '五维维度: body/mind/wisdom/action/wealth',
+    dimension_weights JSON COMMENT '五维权重: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
+    parent_task_id BIGINT NULL COMMENT '父任务ID(购买任务→每日使用子任务链;子任务在父任务完成前不显示)',
+    -- TASK-SYS-248: 行为调度中心扩展字段
+    prerequisite_task_id BIGINT NULL COMMENT '前置任务ID:完成后才解锁本任务',
+    is_flow_task TINYINT DEFAULT 0 COMMENT '是否为流程任务:1=是(有前置/后置任务),0=否',
+    successor_task_ids TEXT NULL COMMENT '后置任务IDs JSON: [1,2,3],任意一个完成则解锁(OR 逻辑)',
+    action_type VARCHAR(32) NULL COMMENT '联动动作: minigame/focus/survey/article/activity/cart/order',
+    action_config JSON NULL COMMENT '联动参数: {"gameCode":"schulte","productId":123,"minSeconds":30,"completeMilestone":"paid"/"received","surveyId":99}',
+    require_input TINYINT DEFAULT 0 COMMENT '1=需填写内容才能完成',
+    start_required TINYINT DEFAULT 0 COMMENT '1=需先开始再完成(两阶段)',
+    start_count INT DEFAULT 0 COMMENT '已开始次数(累计)',
+    min_duration_seconds INT DEFAULT 0 COMMENT '最短持续秒数(专注/游戏类,未达标不计完成)',
+    source_type VARCHAR(32) DEFAULT NULL COMMENT '来源类型: self/assigned/plan/health_plan/training_plan/onboarding/ai/template/article/activity/purchase',
+    source_id BIGINT DEFAULT NULL COMMENT '来源ID(plan_instance_id/template_id/article_id等)',
+    is_daily_progress TINYINT DEFAULT 0 COMMENT '1=每日进度型任务(无deadline,每日重置)',
+    target_value INT DEFAULT NULL COMMENT '进度目标值(is_daily_progress=1时有效)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_id_deadline (child_id, deadline),
+    INDEX idx_status (status),
+    INDEX idx_category (category),
+    INDEX idx_executor (executor_type, executor_id),
+    INDEX idx_dimension_code (dimension_code),
+    INDEX idx_executor_assigned (executor_type, executor_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 任务执行进度表(两阶段/里程碑/填写内容记录)
+CREATE TABLE IF NOT EXISTS task_executions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    task_id BIGINT NOT NULL COMMENT '任务实例ID',
+    member_id BIGINT NULL COMMENT '执行成员ID(family_members.id)',
+    status VARCHAR(20) NOT NULL DEFAULT 'started' COMMENT 'started/finished/failed',
+    started_at DATETIME NOT NULL COMMENT '开始时间',
+    finished_at DATETIME COMMENT '完成时间',
+    milestone VARCHAR(32) COMMENT '购物任务里程碑: cart_added/paid/received(当前进度)',
+    content TEXT COMMENT '填写内容(require_input 任务)',
+    content_type VARCHAR(20) COMMENT 'content 类型: text/image/audio/video',
+    extra JSON COMMENT '扩展数据: {gameScore, focusMinutes, orderId, articleId, surveyId}',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_task_member (task_id, member_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务执行进度表';
+
+-- 积分流水表
+CREATE TABLE IF NOT EXISTS points_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    family_member_id BIGINT COMMENT '家庭成员ID',
+    task_id BIGINT,
+    amount INT NOT NULL COMMENT '积分数量正负',
+    type ENUM('earn', 'spend', 'bonus', 'penalty', 'adjust', 'reset', 'refund') NOT NULL,
+    category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task-任务积分, cf-CF值',
+    description TEXT,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_id_created (child_id, created_at),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 积分清零记录表
+CREATE TABLE IF NOT EXISTS points_reset_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    balance_before INT NOT NULL,
+    balance_after INT DEFAULT 0,
+    reset_type ENUM('monthly', 'manual') NOT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 奖励表
+CREATE TABLE IF NOT EXISTS rewards (
+id BIGINT AUTO_INCREMENT PRIMARY KEY,
+family_id BIGINT NOT NULL COMMENT '家庭 ID,所有奖励必须关联家庭',
+child_id BIGINT NULL COMMENT '孩子 ID,模板奖励为NULL',
+title VARCHAR(100) NOT NULL,
+points_required INT NOT NULL,
+category VARCHAR(32),
+status ENUM('available', 'pending', 'exchanged', 'rejected') DEFAULT 'available',
+exchange_approved TINYINT COMMENT '是否审批通过',
+exchanged_at DATETIME,
+is_template TINYINT DEFAULT 0 COMMENT '是否为预设模板',
+created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+INDEX idx_family_id (family_id),
+INDEX idx_child_id_status (child_id, status),
+INDEX idx_is_template (is_template)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='奖励表,所有奖励必须关联到具体家庭';
+
+-- 奖励分类表
+CREATE TABLE IF NOT EXISTS reward_categories (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL,
+    name VARCHAR(32) NOT NULL,
+    icon VARCHAR(64),
+    sort_order INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 预设奖励模板数据(child_id为NULL表示模板)
+INSERT IGNORE INTO rewards (family_id, child_id, title, points_required, category, is_template) VALUES
+(0, NULL, '看动画片30分钟', 30, '精神奖励', 1),
+(0, NULL, '玩平板游戏30分钟', 40, '特权奖励', 1),
+(0, NULL, '去游乐场', 100, '物质奖励', 1),
+(0, NULL, '买玩具', 150, '物质奖励', 1),
+(0, NULL, '吃冰淇淋', 20, '物质奖励', 1),
+(0, NULL, '选择晚餐', 25, '特权奖励', 1);
+
+-- 默认奖励分类
+INSERT IGNORE INTO reward_categories (family_id, name, icon, sort_order) VALUES
+(0, '物质奖励', 'gift', 1),
+(0, '精神奖励', 'star', 2),
+(0, '特权奖励', 'crown', 3);
+
+-- 敏感词表
+CREATE TABLE IF NOT EXISTS sensitive_words (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    word VARCHAR(50) NOT NULL,
+    category VARCHAR(32) NOT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_word (word)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 任务审核表
+CREATE TABLE IF NOT EXISTS task_reviews (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    task_id BIGINT NOT NULL,
+    child_id BIGINT NOT NULL,
+    photo_url VARCHAR(255),
+    voice_note_url VARCHAR(255),
+    ai_result ENUM('pass', 'review', 'pending'),
+    ai_confidence INT COMMENT 'AI置信度(0-100)',
+    ai_suggestion VARCHAR(255),
+    ai_threshold ENUM('strict', 'normal', 'loose') DEFAULT 'normal',
+    parent_result ENUM('pass', 'reject'),
+    parent_override TINYINT DEFAULT 0,
+    review_type ENUM('ai', 'manual') NOT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    reviewed_at DATETIME,
+    INDEX idx_task_id (task_id),
+    INDEX idx_ai_result (ai_result)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 打卡里程碑表
+CREATE TABLE IF NOT EXISTS streak_milestones (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    days INT NOT NULL,
+    reward_points INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 插入打卡里程碑数据
+INSERT IGNORE INTO streak_milestones (days, reward_points) VALUES
+(3, 5),
+(7, 10),
+(30, 30),
+(100, 100);
+
+-- 专注训练记录表
+CREATE TABLE IF NOT EXISTS focus_sessions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    mode ENUM('simple', 'pomodoro') NOT NULL,
+    duration TINYINT NOT NULL,
+    stars TINYINT COMMENT '评分1-5',
+    points_earned INT,
+    voice_note_url VARCHAR(255),
+    started_at DATETIME NOT NULL,
+    completed_at DATETIME,
+    task_id BIGINT NULL COMMENT '关联的任务ID(专注任务挂钩)',
+    INDEX idx_child_id_completed (child_id, completed_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 专注训练配置表
+CREATE TABLE IF NOT EXISTS focus_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT,
+    max_daily_count TINYINT DEFAULT 3,
+    allow_exit TINYINT DEFAULT 1 COMMENT '是否允许退出(0否1是)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- DAN测评表
+CREATE TABLE IF NOT EXISTS dan_assessments (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    assessment_date DATE NOT NULL,
+    details JSON,
+    source ENUM('api', 'manual') DEFAULT 'manual',
+    photo_url VARCHAR(255),
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 任务模板表
+CREATE TABLE IF NOT EXISTS task_templates (
+id BIGINT AUTO_INCREMENT PRIMARY KEY,
+title VARCHAR(200) NOT NULL,
+points TINYINT DEFAULT 2,
+duration_minutes TINYINT,
+category VARCHAR(32),
+is_default TINYINT DEFAULT 1,
+family_id BIGINT,
+created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+INDEX idx_is_default (is_default)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 媒体记录表(支持图片、视频、音频、文字)
+CREATE TABLE IF NOT EXISTS media_records (
+id BIGINT AUTO_INCREMENT PRIMARY KEY,
+type ENUM('image', 'video', 'audio', 'text') NOT NULL,
+url VARCHAR(500),
+description TEXT,
+task_id BIGINT NOT NULL,
+creator_id BIGINT NOT NULL,
+original_filename VARCHAR(255),
+file_size BIGINT,
+content_type VARCHAR(100),
+created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+INDEX idx_task_id (task_id),
+INDEX idx_creator_id (creator_id),
+INDEX idx_type (type),
+FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
+FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 服务商(成长规划师)相关表
+-- =============================================
+
+-- 服务商表(统一管理 teacher/planner/activity_provider/product_supplier)
+CREATE TABLE IF NOT EXISTS guides (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL UNIQUE COMMENT '用户ID(唯一)',
+    type VARCHAR(32) DEFAULT 'teacher' COMMENT '服务商类型: teacher/planner/activity_provider/product_supplier',
+    teacher_no VARCHAR(20) COMMENT '服务商编号',
+    title VARCHAR(50) COMMENT '职称/称号',
+    specialty VARCHAR(200) COMMENT '专长领域',
+    bio TEXT COMMENT '简介',
+    university VARCHAR(200) COMMENT '毕业院校',
+    education_system VARCHAR(32) COMMENT '教育体系(参考education_systems表)',
+    experience_years TINYINT DEFAULT 0 COMMENT '从业年限',
+    rating INT DEFAULT 500 COMMENT '评分(百分位,500=5.00)',
+    total_appointments INT DEFAULT 0 COMMENT '总服务次数',
+    level VARCHAR(20) NOT NULL DEFAULT 'junior' COMMENT '等级: junior-初级, intermediate-中级, senior-高级',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active-活跃, inactive-停用',
+    service_count INT DEFAULT 0 COMMENT '当前服务家庭数',
+    package_id BIGINT COMMENT '购买套餐ID',
+    nickname VARCHAR(100) COMMENT '申请昵称',
+    phone VARCHAR(20) COMMENT '联系电话',
+    intro TEXT COMMENT '个人介绍',
+    avatar VARCHAR(500) COMMENT '头像URL',
+    commission_rate INT DEFAULT 1000 COMMENT '佣金比例(基点,1000=10%)',
+    street_id BIGINT COMMENT '街道ID',
+    province VARCHAR(50) COMMENT '省份',
+    city VARCHAR(50) COMMENT '城市',
+    district VARCHAR(50) COMMENT '区县',
+    street VARCHAR(100) COMMENT '街道',
+    approved_at DATETIME COMMENT '审核通过时间',
+    verified_at DATETIME COMMENT '认证通过时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_level (level),
+    INDEX idx_status (status),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务商表(规划师/活动方/供应商)';
+
+-- 测评预约表
+CREATE TABLE IF NOT EXISTS assessment_appointments (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID',
+    family_id BIGINT NOT NULL,
+    child_id BIGINT COMMENT '孩子ID',
+    teacher_id BIGINT COMMENT '教师ID',
+    guide_id BIGINT COMMENT '规划师ID',
+    assessor_id BIGINT COMMENT '测评师ID(供应商体系)',
+    package_id BIGINT COMMENT '套餐ID',
+    appointment_date VARCHAR(20) COMMENT '预约日期 yyyy-MM-dd',
+    appointment_time VARCHAR(20) COMMENT '预约时间 HH:mm',
+    duration_minutes INT COMMENT '测评时长',
+    status TINYINT DEFAULT 0 COMMENT '0=待确认,1=已确认,2=已完成,3=已取消,4=未到',
+    appointment_type VARCHAR(20) DEFAULT 'initial' COMMENT '初测/复测/回访',
+    notes TEXT COMMENT '备注',
+    remark TEXT COMMENT '补充说明',
+    contact_phone VARCHAR(20),
+    meet_link VARCHAR(255) COMMENT '会议链接',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_teacher_id (teacher_id),
+    INDEX idx_guide_id (guide_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- DAN测评结果表
+CREATE TABLE IF NOT EXISTS dan_assessment_results (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    family_member_id BIGINT COMMENT '家庭成员ID',
+    family_member_name VARCHAR(50) COMMENT '成员姓名快照',
+    family_member_birthday DATE COMMENT '成员出生日期快照',
+    family_member_gender VARCHAR(10) COMMENT '成员性别快照',
+    family_member_age INT COMMENT '成员年龄快照',
+    teacher_id BIGINT NOT NULL,
+    appointment_id BIGINT,
+    attention_score INT COMMENT '注意力得分',
+    focus_score INT COMMENT '专注力得分',
+    memory_score INT COMMENT '记忆力得分',
+    logic_score INT COMMENT '逻辑思维得分',
+    perception_score INT COMMENT '感知得分',
+    spatial_score INT COMMENT '空间思维得分',
+    processing_speed_score INT COMMENT '加工速度得分',
+    language_score INT COMMENT '语言表达得分',
+    emotion_score INT COMMENT '情绪商数(EMI)',
+    resilience_score INT COMMENT '心理韧性(EMI)',
+    stress_coping_score INT COMMENT '压力应对(EMI)',
+    self_awareness_score INT COMMENT '自我认知(EMI)',
+    emotion_management_score INT COMMENT '情绪管理得分(EMI)',
+    empathy_score INT COMMENT '同理心得分(EMI)',
+    social_adaptability_score INT COMMENT '社会适应得分(EMI)',
+    self_motivation_score INT COMMENT '自我激励得分(EMI)',
+    overall_score INT COMMENT '综合得分',
+    openness_score INT COMMENT '开放性(大五人格)',
+    conscientiousness_score INT COMMENT '尽责性(大五人格)',
+    extraversion_score INT COMMENT '外向性(大五人格)',
+    agreeableness_score INT COMMENT '宜人性(大五人格)',
+    neuroticism_score INT COMMENT '神经质(大五人格)',
+    big_five_overall_score INT COMMENT '大五人格综合分',
+    assessment_date DATE NOT NULL,
+    duration_minutes INT COMMENT '测评时长',
+    game_records JSON COMMENT '游戏记录详情',
+    analysis_report TEXT COMMENT '分析报告',
+    growth_suggestions TEXT COMMENT '成长建议',
+    next_review_date DATE COMMENT '下次复测日期',
+    status ENUM('draft', 'completed', 'archived') DEFAULT 'draft',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    -- 孩子快照字段(测评时保存的完整信息)
+    child_name VARCHAR(50) COMMENT '孩子姓名快照',
+    child_birthday DATE COMMENT '出生日期快照',
+    child_gender VARCHAR(10) COMMENT '性别快照',
+    child_age INT COMMENT '年龄快照',
+    school VARCHAR(100) COMMENT '学校快照',
+    grade VARCHAR(20) COMMENT '年级快照',
+    grade_class VARCHAR(20) COMMENT '班级快照',
+    height INT COMMENT '身高(CM)快照',
+    weight INT COMMENT '体重(KG)快照',
+    family_status VARCHAR(50) COMMENT '家庭状况快照',
+    parent_degree VARCHAR(50) COMMENT '父母最高学历快照',
+    nation VARCHAR(20) COMMENT '民族快照',
+    province VARCHAR(50) COMMENT '省快照',
+    city VARCHAR(50) COMMENT '市快照',
+    district VARCHAR(50) COMMENT '区快照',
+    address VARCHAR(200) COMMENT '详细地址快照',
+    single_child VARCHAR(10) COMMENT '是否独生子女快照',
+    school_score VARCHAR(20) COMMENT '在校成绩快照',
+    urgent_name VARCHAR(50) COMMENT '紧急联系人姓名',
+    urgent_phone VARCHAR(20) COMMENT '紧急联系人电话',
+    -- 媒体字段
+    pics TEXT COMMENT '图片IDs',
+    videos TEXT COMMENT '视频IDs',
+    audio VARCHAR(100) COMMENT '音频ID',
+    audio_url VARCHAR(255) COMMENT '音频URL',
+    -- 扩展描述字段
+    process_desc TEXT COMMENT '过程描述',
+    result_desc TEXT COMMENT '结果简评',
+    source VARCHAR(20) DEFAULT 'planner_entry' COMMENT '报告来源: planner_entry/parent_upload/auto_fetch',
+    dimension VARCHAR(10) DEFAULT 'mind' COMMENT '报告维度: mind(心-A2)/wisdom(智-B4)',
+    report_file_id VARCHAR(500) COMMENT '上传文件媒资ID或相对路径',
+    draft_status VARCHAR(10) DEFAULT 'pending' COMMENT '草稿状态: pending(待确认)/confirmed(已确认)',
+    dan_level VARCHAR(10) DEFAULT NULL COMMENT 'DAN等级A/B/C/D',
+    INDEX idx_child_id (child_id),
+    INDEX idx_teacher_id (teacher_id),
+    INDEX idx_assessment_date (assessment_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DAN测评结果表';
+
+-- 成长规划表
+CREATE TABLE IF NOT EXISTS growth_plans (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    teacher_id BIGINT NOT NULL,
+    assessment_id BIGINT,
+    plan_title VARCHAR(200) NOT NULL,
+    plan_content TEXT NOT NULL COMMENT '规划内容',
+    duration_months TINYINT COMMENT '规划周期(月)',
+    status ENUM('draft', 'active', 'completed', 'expired') DEFAULT 'draft',
+    start_date DATE,
+    end_date DATE,
+    review_count INT DEFAULT 0 COMMENT '已复盘次数',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id),
+    INDEX idx_teacher_id (teacher_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 成长档案表
+-- =============================================
+CREATE TABLE IF NOT EXISTS growth_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    family_member_id BIGINT COMMENT '家庭成员ID',
+    family_member_age INT COMMENT '家庭成员年龄(月)',
+    parent_id BIGINT NOT NULL COMMENT '家长用户ID',
+    family_member_id BIGINT COMMENT '关联家庭成员ID',
+    family_member_age INT COMMENT '成员年龄快照',
+    assessment_id BIGINT COMMENT '关联的测评结果ID',
+    record_title VARCHAR(200) COMMENT '档案标题',
+    child_age INT COMMENT '孩子当时年龄(月)',
+    height VARCHAR(20) COMMENT '身高',
+    weight VARCHAR(20) COMMENT '体重',
+    school VARCHAR(100) COMMENT '学校名称',
+    grade INT COMMENT '年级(数字)',
+    academicPerformance VARCHAR(200) COMMENT '成绩/学业表现',
+    schoolProvince VARCHAR(50) COMMENT '学校所在省',
+    schoolCity VARCHAR(50) COMMENT '学校所在市',
+    schoolDistrict VARCHAR(50) COMMENT '学校所在区',
+    schoolStreet VARCHAR(100) COMMENT '学校所在街道',
+    assessment_summary TEXT COMMENT '测评摘要',
+    overall_score INT COMMENT '综合得分',
+    growth_suggestions TEXT COMMENT '成长建议',
+    task_recommendations TEXT COMMENT '任务推荐(JSON)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed',
+    external_sync TINYINT DEFAULT 0 COMMENT '是否来自外部系统同步',
+    external_source VARCHAR(100) COMMENT '外部系统来源',
+    source_type VARCHAR(20) DEFAULT 'manual' COMMENT '来源类型: manual/platform_purchase/self_upload/supplement',
+    source_file_url VARCHAR(500) COMMENT '原始报告文件URL',
+    order_id BIGINT COMMENT '关联订单ID',
+    report_date DATETIME COMMENT '测评/报告日期',
+    parent_record_id BIGINT COMMENT '关联的原始档案ID(补充档案专用)',
+    is_latest TINYINT(1) DEFAULT 1 COMMENT '是否同组最新记录',
+    dedup_hash VARCHAR(64) COMMENT '去重哈希值(SHA256)',
+    parsed_data TEXT COMMENT '上传报告的解析结果(JSON)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id),
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_status (status),
+    UNIQUE INDEX uk_growth_dedup_hash (dedup_hash)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 数据库迁移(新增字段和表)- 通过Java代码处理
+-- =============================================
+
+-- 注意:ALTER TABLE语句已移到 DatabaseInitializer.java 中执行
+-- 避免重复执行导致的错误
+
+-- 3. 家长心愿单表(新增)
+CREATE TABLE IF NOT EXISTS reward_wishlists (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '创建者用户ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    title VARCHAR(100) NOT NULL COMMENT '心愿名称',
+    description TEXT COMMENT '心愿描述',
+    points_required INT NOT NULL COMMENT '所需积分',
+    category VARCHAR(32) COMMENT '分类',
+    image_url VARCHAR(255) COMMENT '图片链接',
+    status ENUM('available', 'pending', 'exchanged', 'rejected') DEFAULT 'available' COMMENT '状态',
+    exchange_approved TINYINT COMMENT '是否审批通过',
+    exchanged_at DATETIME COMMENT '兑换时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 4. 更新任务状态枚举(如果需要支持更多状态)
+-- ALTER TABLE tasks MODIFY COLUMN status ENUM('pending', 'completed', 'overdue', 'cancelled', 'parent_pending', 'parent_completed') DEFAULT 'pending';
+
+-- ===================== 任务模板套餐功能 =====================
+
+-- 成长规划师-家庭关系表
+CREATE TABLE IF NOT EXISTS guide_families (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    service_type VARCHAR(20) DEFAULT 'consulting' COMMENT '服务类型: monthly/yearly/consulting',
+    service_price INT DEFAULT 0 COMMENT '服务价格(分)',
+    status VARCHAR(20) DEFAULT 'binding' COMMENT '状态: binding/paused/cancelled',
+    bound_at DATETIME COMMENT '绑定时间',
+    expire_at DATETIME COMMENT '服务到期时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_guide_family (guide_id, family_id),
+    INDEX idx_guide_id (guide_id),
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 任务模板套餐
+CREATE TABLE IF NOT EXISTS task_template_packages (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    creator_id BIGINT NOT NULL COMMENT '创建者成长规划师ID',
+    name VARCHAR(100) NOT NULL COMMENT '套餐名称',
+    description TEXT COMMENT '详细介绍',
+    cycle_days INT NOT NULL COMMENT '周期天数',
+    category VARCHAR(50) COMMENT '分类',
+    tags JSON COMMENT '标签',
+    price INT DEFAULT 0 COMMENT '售价(分)',
+    platform_fee INT DEFAULT 0 COMMENT '平台服务费(基点)',
+    is_public TINYINT DEFAULT 0 COMMENT '是否公开',
+    share_code VARCHAR(50) UNIQUE COMMENT '分享码',
+    cover_image VARCHAR(500) COMMENT '封面图',
+    status VARCHAR(20) DEFAULT 'draft' COMMENT '状态',
+    reviewer_id BIGINT COMMENT '审核人',
+    reviewed_at DATETIME COMMENT '审核时间',
+    reject_reason TEXT COMMENT '拒绝原因',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_creator_id (creator_id),
+    INDEX idx_status (status),
+    INDEX idx_is_public (is_public)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 套餐内任务项
+CREATE TABLE IF NOT EXISTS task_template_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    package_id BIGINT NOT NULL COMMENT '所属套餐ID',
+    task_name VARCHAR(100) NOT NULL,
+    description TEXT,
+    points INT DEFAULT 0,
+    frequency VARCHAR(20) DEFAULT 'daily',
+    duration INT COMMENT '预计时长(分钟)',
+    is_required TINYINT DEFAULT 0,
+    sort_order INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_package_id (package_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 套餐应用实例
+CREATE TABLE IF NOT EXISTS task_plan_instances (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    package_id BIGINT NOT NULL,
+    family_id BIGINT NOT NULL,
+    child_id BIGINT NOT NULL,
+    name VARCHAR(100) NOT NULL,
+    start_date DATE NOT NULL,
+    end_date DATE NOT NULL,
+    status VARCHAR(20) DEFAULT 'active',
+    total_days INT,
+    completed_days INT DEFAULT 0,
+    source VARCHAR(20) DEFAULT NULL COMMENT '来源: manual/assessment/template',
+    source_result_id BIGINT DEFAULT NULL COMMENT '关联测评结果ID',
+    remark TEXT COMMENT '方案备注',
+    reviewed_by BIGINT DEFAULT NULL COMMENT '审核人(规划师ID)',
+    reviewed_at DATETIME DEFAULT NULL COMMENT '审核时间',
+    review_comment TEXT COMMENT '审核意见',
+    confirmed_by BIGINT DEFAULT NULL COMMENT '确认人(家长ID)',
+    confirmed_at DATETIME DEFAULT NULL COMMENT '确认时间',
+    activated_at DATETIME DEFAULT NULL COMMENT '激活时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 实例任务项
+CREATE TABLE IF NOT EXISTS task_plan_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    instance_id BIGINT NOT NULL,
+    template_item_id BIGINT,
+    task_name VARCHAR(100) NOT NULL,
+    description TEXT,
+    points INT DEFAULT 0,
+    frequency VARCHAR(20) DEFAULT 'daily',
+    duration INT,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_instance_id (instance_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 套餐购买订单
+CREATE TABLE IF NOT EXISTS package_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_no VARCHAR(32) UNIQUE NOT NULL,
+    user_id BIGINT NOT NULL COMMENT '购买者用户ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    package_id BIGINT NOT NULL COMMENT '套餐ID',
+    package_name VARCHAR(100) COMMENT '套餐名称',
+    price INT NOT NULL COMMENT '支付金额(分)',
+    platform_fee INT DEFAULT 0 COMMENT '平台服务费(分)',
+    user_coupon_id BIGINT COMMENT '使用的用户优惠券ID',
+    guide_id BIGINT COMMENT '成长规划师ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/cancelled/refunded',
+    pay_method VARCHAR(20) COMMENT '支付方式',
+    transaction_id VARCHAR(64) COMMENT '微信支付订单号',
+    paid_at DATETIME COMMENT '支付时间',
+    notify_data TEXT COMMENT '支付回调数据',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_package_id (package_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 用户表增加成长规划师相关字段 - 已移到 DatabaseInitializer.java 执行
+-- ALTER TABLE users ADD COLUMN teacher_no VARCHAR(50) COMMENT '成长规划师证书号';
+-- ALTER TABLE users ADD COLUMN teacher_photo VARCHAR(500) COMMENT '成长规划师照片';
+-- ALTER TABLE users ADD COLUMN certificate_image VARCHAR(500) COMMENT '成长规划师证书照片';
+-- ALTER TABLE users ADD COLUMN teacher_status VARCHAR(20) DEFAULT 'pending' COMMENT '成长规划师审核状态: pending/approved/rejected';
+-- ALTER TABLE users ADD INDEX idx_teacher_status (teacher_status);
+
+-- 成长规划师咨询消息表
+CREATE TABLE IF NOT EXISTS teacher_messages (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    parent_id BIGINT NOT NULL COMMENT '家长用户ID',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师用户ID',
+    content TEXT NOT NULL COMMENT '消息内容',
+    reply_content TEXT COMMENT '回复内容',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending/replied',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    replied_at DATETIME COMMENT '回复时间',
+    INDEX idx_family_id (family_id),
+    INDEX idx_teacher_id (teacher_id),
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 游戏记录表(专注训练模块)
+CREATE TABLE IF NOT EXISTS game_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    game_code VARCHAR(20) NOT NULL COMMENT '游戏代码',
+    score INT DEFAULT 0 COMMENT '得分(0-100)',
+    completion_time INT COMMENT '完成用时(秒)',
+    difficulty VARCHAR(20) DEFAULT 'medium' COMMENT '难度(easy/medium/hard)',
+    points_earned INT DEFAULT 0 COMMENT '获得积分',
+    played_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '游玩时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id),
+    INDEX idx_game_code (game_code),
+    INDEX idx_played_at (played_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 勋章定义表
+CREATE TABLE IF NOT EXISTS badge (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    badge_id VARCHAR(50) UNIQUE NOT NULL COMMENT '勋章唯一标识',
+    name VARCHAR(100) NOT NULL COMMENT '勋章名称',
+    description TEXT COMMENT '勋章描述',
+    icon VARCHAR(255) COMMENT '勋章图标',
+    category VARCHAR(20) COMMENT '分类(学习/运动/家务/阅读/打卡/综合)',
+    level VARCHAR(20) COMMENT '等级(铜牌/银牌/金牌/钻石)',
+    rarity VARCHAR(20) COMMENT '稀有度(普通/稀有/史诗/传说)',
+    trigger_type VARCHAR(20) COMMENT '触发类型(task_count/streak_days/points_total/reward_count/custom)',
+    threshold INT DEFAULT 0 COMMENT '阈值',
+    need_approval TINYINT DEFAULT 0 COMMENT '是否需要审批',
+    expire_days INT DEFAULT -1 COMMENT '有效期天数(-1永久)',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_category (category),
+    INDEX idx_is_active (is_active)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 儿童勋章关联表
+CREATE TABLE IF NOT EXISTS child_badge (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    badge_id BIGINT NOT NULL COMMENT '勋章ID',
+    earned_at DATETIME COMMENT '获得时间',
+    expire_at DATETIME COMMENT '过期时间',
+    status TINYINT DEFAULT 1 COMMENT '状态:1有效 0过期 -1撤销',
+    is_favorite TINYINT DEFAULT 0 COMMENT '是否收藏',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_child_badge (child_id, badge_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_badge_id (badge_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 以下表从 DatabaseInitializer.java 提取
+-- 管理后台表
+-- =============================================
+
+-- 管理员表
+CREATE TABLE IF NOT EXISTS admins (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    username VARCHAR(50) NOT NULL COMMENT '用户名',
+    password VARCHAR(128) NOT NULL COMMENT '密码(MD5加密)',
+    nickname VARCHAR(50) COMMENT '昵称',
+    real_name VARCHAR(50) COMMENT '真实姓名',
+    phone VARCHAR(11) COMMENT '手机号',
+    email VARCHAR(100) COMMENT '邮箱',
+    role VARCHAR(20) DEFAULT 'admin' COMMENT '角色: admin/teacher',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用0禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_phone (phone),
+    INDEX idx_username (username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
+
+-- 验证码表
+CREATE TABLE IF NOT EXISTS verification_codes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    phone VARCHAR(11) NOT NULL COMMENT '手机号',
+    code VARCHAR(6) NOT NULL COMMENT '验证码',
+    type VARCHAR(20) NOT NULL DEFAULT 'login' COMMENT '验证码类型: login/register/reset',
+    expires_in INT DEFAULT 300 COMMENT '过期时间(秒)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    expires_at DATETIME NOT NULL COMMENT '过期时间',
+    used TINYINT DEFAULT 0 COMMENT '是否已使用:0否1是',
+    INDEX idx_phone_type (phone, type),
+    INDEX idx_expires_at (expires_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='验证码表';
+
+-- 管理后台任务模板表
+CREATE TABLE IF NOT EXISTS admin_task_templates (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    title VARCHAR(200) NOT NULL COMMENT '模板标题',
+    description TEXT COMMENT '模板描述',
+    points INT DEFAULT 10 COMMENT '默认积分',
+    category VARCHAR(32) COMMENT '任务分类',
+    need_review TINYINT DEFAULT 0 COMMENT '是否需要审核:0否1是',
+    review_by_category TINYINT DEFAULT 0 COMMENT '是否按分类审核:0否1是',
+    recommended_age INT COMMENT '推荐年龄',
+    difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium' COMMENT '难度: easy/medium/hard',
+    teacher_id BIGINT COMMENT '成长规划师ID',
+    onboarding_sequence INT DEFAULT NULL COMMENT '新人引导顺序(source_type=onboarding的模板用)',
+    complete_types VARCHAR(100) COMMENT '完成方式JSON',
+    duration INT COMMENT '预计完成时长(分钟)',
+    review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai',
+    task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring',
+    frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly',
+    max_frequency INT COMMENT '周期内最多完成次数',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用:0禁用1启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_category (category),
+    INDEX idx_difficulty (difficulty),
+    INDEX idx_is_active (is_active)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理后台任务模板表';
+
+-- =============================================
+-- 测评模块表
+-- =============================================
+
+-- 测评资料表
+-- 数学闯关 - 孩子当前等级
+CREATE TABLE IF NOT EXISTS math_child_level (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL UNIQUE,
+    current_level TINYINT NOT NULL DEFAULT 1 COMMENT '1=一位数加减,2=两位数加减,3=一位数四则,4=两位数四则',
+    total_passed INT DEFAULT 0 COMMENT '累计通关次数',
+    best_time_ms BIGINT DEFAULT NULL COMMENT '最佳用时(毫秒)',
+    best_accuracy INT DEFAULT NULL COMMENT '最佳正确率(%)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 数学闯关 - 通关日志
+CREATE TABLE IF NOT EXISTS math_quiz_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL,
+    level TINYINT NOT NULL COMMENT '闯关时等级',
+    target_n INT NOT NULL COMMENT '目标连续答对数',
+    correct_count INT NOT NULL COMMENT '实际连续答对数',
+    total_attempts INT NOT NULL COMMENT '总答题数',
+    total_time_ms BIGINT NOT NULL COMMENT '总用时(毫秒)',
+    accuracy INT NOT NULL COMMENT '正确率(%)',
+    passed TINYINT(1) DEFAULT 0 COMMENT '是否通关',
+    exprs TEXT COMMENT '本轮表达式列表(JSON)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id),
+    INDEX idx_level (level)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS assessment_materials (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    title VARCHAR(100) NOT NULL COMMENT '资料标题',
+    content TEXT COMMENT '测评介绍内容',
+    cover_image VARCHAR(255) COMMENT '封面图片',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态:active/inactive',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间',
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评资料表';
+
+-- 积分配置表
+CREATE TABLE IF NOT EXISTS assessment_points_config (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    points_value INT DEFAULT 100 COMMENT '完成测评获得积分',
+    points_value_yuan INT DEFAULT 1 COMMENT '1积分等于多少分(原元→现分)',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分配置表';
+
+-- 家庭测评配置表
+CREATE TABLE IF NOT EXISTS family_assessment_config (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    material_id BIGINT COMMENT '测评资料ID',
+    remind_months INT DEFAULT 12 COMMENT '提醒周期(月)',
+    last_remind_at DATETIME COMMENT '上次提醒时间',
+    next_remind_at DATETIME COMMENT '下次提醒时间',
+    is_enabled TINYINT DEFAULT 1 COMMENT '是否启用:1启用0关闭',
+    created_by BIGINT COMMENT '创建人(规划师ID)',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间',
+    UNIQUE KEY uk_family (family_id),
+    INDEX idx_next_remind (next_remind_at),
+    INDEX idx_enabled (is_enabled)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭测评配置表';
+
+-- 测评记录表
+CREATE TABLE IF NOT EXISTS assessment_records (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    family_id BIGINT COMMENT '家庭ID',
+    child_id BIGINT COMMENT '孩子ID',
+    material_id BIGINT COMMENT '测评资料ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending待完成/completed已完成',
+    completed_at DATETIME COMMENT '完成时间',
+    created_at DATETIME COMMENT '创建时间',
+    INDEX idx_family (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评记录表';
+
+-- 评估订单表(测评订单)
+CREATE TABLE IF NOT EXISTS assessment_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '孩子ID',
+    guide_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    package_id BIGINT NOT NULL COMMENT '套餐ID',
+    appointment_id BIGINT COMMENT '关联预约ID',
+    guide_name VARCHAR(50) COMMENT '规划师名称',
+    package_name VARCHAR(100) COMMENT '套餐名称',
+    total_price BIGINT DEFAULT 0 COMMENT '总价(分)',
+    discount_amount BIGINT DEFAULT 0 COMMENT '优惠金额(分)',
+    actual_price BIGINT DEFAULT 0 COMMENT '实付金额(分)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/completed/cancelled/refunded',
+    pay_time VARCHAR(50) COMMENT '支付时间',
+    pay_type VARCHAR(20) COMMENT '支付方式',
+    transaction_id VARCHAR(64) COMMENT '交易流水号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_family (family_id),
+    INDEX idx_guide (guide_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表';
+
+-- =============================================
+-- 成长规划师相关表
+-- =============================================
+
+-- 成长规划师申请表
+CREATE TABLE IF NOT EXISTS guide_applications (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '申请人用户ID',
+    type VARCHAR(20) NOT NULL DEFAULT 'new_apply' COMMENT '申请类型: new_apply-新注册, upgrade-升级',
+    target_level VARCHAR(20) NOT NULL COMMENT '目标等级: junior-初级, intermediate-中级, senior-高级',
+    current_level VARCHAR(20) DEFAULT NULL COMMENT '当前等级(升级时填写)',
+    package_id BIGINT COMMENT '申请的套餐ID',
+    payment_proof VARCHAR(500) COMMENT '付款凭证URL',
+    payment_amount INT COMMENT '付款金额(分)',
+    address VARCHAR(500) COMMENT '地址',
+    bank_card VARCHAR(50) COMMENT '银行卡号',
+    bank_name VARCHAR(100) COMMENT '开户行',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待审核, approved-已通过, rejected-已拒绝',
+    reject_reason TEXT COMMENT '拒绝原因',
+    reviewed_at DATETIME COMMENT '审核时间',
+    reviewed_by BIGINT COMMENT '审核人ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_status (status),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请表';
+
+-- 成长规划师申请过程记录表
+CREATE TABLE IF NOT EXISTS guide_application_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    application_id BIGINT NOT NULL COMMENT '关联申请ID',
+    user_id BIGINT NOT NULL COMMENT '操作人用户ID',
+    action VARCHAR(50) NOT NULL COMMENT '操作: submit/approve/reject/withdraw/resubmit',
+    from_status VARCHAR(20) COMMENT '操作前状态',
+    to_status VARCHAR(20) NOT NULL COMMENT '操作后状态',
+    remark TEXT COMMENT '备注/原因',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_application_id (application_id),
+    INDEX idx_user_id (user_id),
+    INDEX idx_action (action)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请过程记录表';
+
+-- =============================================
+-- 小游戏表
+-- =============================================
+
+-- 小游戏表
+CREATE TABLE IF NOT EXISTS mini_games (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    game_code VARCHAR(20) NOT NULL UNIQUE COMMENT '游戏代码',
+    game_name VARCHAR(50) NOT NULL COMMENT '游戏名称',
+    description TEXT COMMENT '游戏描述',
+    icon VARCHAR(50) COMMENT '图标emoji',
+    default_duration INT DEFAULT 5 COMMENT '默认时长(分钟)',
+    default_points INT DEFAULT 5 COMMENT '默认奖励积分',
+    game_url VARCHAR(255) COMMENT '游戏H5页面URL',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status TINYINT DEFAULT 1 COMMENT '状态:0禁用1启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_game_code (game_code),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小游戏表';
+
+-- =============================================
+-- 地址维度表
+-- =============================================
+
+-- 街道地址维度表
+CREATE TABLE IF NOT EXISTS streets (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    province VARCHAR(50) COMMENT '省份',
+    province_code VARCHAR(20) COMMENT '省份编码',
+    city VARCHAR(50) COMMENT '城市',
+    city_code VARCHAR(20) COMMENT '城市编码',
+    district VARCHAR(50) COMMENT '区县',
+    district_code VARCHAR(20) COMMENT '区县编码',
+    street VARCHAR(100) COMMENT '街道',
+    street_code VARCHAR(20) COMMENT '街道编码',
+    full_name VARCHAR(200) COMMENT '完整地址',
+    level INT COMMENT '层级:1省2市3区4街道',
+    parent_id BIGINT COMMENT '父级ID',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_level (level),
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_street_code (street_code),
+    INDEX idx_full_name (full_name(50))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='街道地址维度表';
+
+-- =============================================
+-- 系统日志表
+-- =============================================
+
+-- 用户操作日志表
+CREATE TABLE IF NOT EXISTS user_operation_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT COMMENT '用户ID',
+    nickname VARCHAR(50) COMMENT '用户昵称',
+    operation_type VARCHAR(20) NOT NULL COMMENT '操作类型: login/logout/create/update/delete/query等',
+    description TEXT COMMENT '操作描述',
+    request_path VARCHAR(200) COMMENT '请求路径',
+    request_method VARCHAR(10) COMMENT '请求方法',
+    request_params TEXT COMMENT '请求参数(脱敏)',
+    response_status INT COMMENT '响应状态码',
+    client_ip VARCHAR(50) COMMENT '客户端IP',
+    user_agent VARCHAR(500) COMMENT 'User-Agent',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_operation_type (operation_type),
+    INDEX idx_created_at (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户操作日志表';
+
+-- =============================================
+-- 邀请与心愿表
+-- =============================================
+
+-- 邀请卡片表
+CREATE TABLE IF NOT EXISTS invite_card (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    type VARCHAR(20) NOT NULL COMMENT '类型: child/family/guide',
+    code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码',
+    ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)',
+    creator_id BIGINT NOT NULL COMMENT '创建人',
+    expires_at DATETIME NOT NULL COMMENT '过期时间',
+    used TINYINT DEFAULT 0 COMMENT '是否已使用:0否1是',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_code (code),
+    INDEX idx_ref_type (ref_id, type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请卡片表';
+
+-- 心愿单表
+CREATE TABLE IF NOT EXISTS wishes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    family_id BIGINT COMMENT '家庭ID',
+    child_id BIGINT COMMENT '目标孩子ID',
+    created_by BIGINT COMMENT '创建者用户ID',
+    creator_type VARCHAR(20) COMMENT '创建者类型: child/parent',
+    title VARCHAR(200) NOT NULL COMMENT '心愿标题',
+    description TEXT COMMENT '心愿描述',
+    category VARCHAR(32) COMMENT '心愿分类',
+    points_required INT COMMENT '所需积分',
+    points_set_by BIGINT COMMENT '定价者ID',
+    points_set_at DATETIME COMMENT '定价时间',
+    status VARCHAR(32) DEFAULT 'active' COMMENT '状态: draft/active/pending_exchange/approved/exchanging/fulfilled/cancelled/rejected',
+    exchange_requested_at DATETIME COMMENT '申请兑换时间',
+    exchange_approved_by BIGINT COMMENT '批准者ID',
+    exchange_approved_at DATETIME COMMENT '批准时间',
+    points_deducted_at DATETIME COMMENT '扣积分时间',
+    fulfilled_by BIGINT COMMENT '兑现者ID',
+    fulfilled_at DATETIME COMMENT '兑现时间',
+    child_confirmed_at DATETIME COMMENT '孩子确认收货时间',
+    rejected_by BIGINT COMMENT '拒绝者ID',
+    rejected_at DATETIME COMMENT '拒绝时间',
+    reject_reason TEXT COMMENT '拒绝原因',
+    reject_count INT DEFAULT 0 COMMENT '被拒绝次数',
+    cancelled_by BIGINT COMMENT '取消者ID',
+    cancelled_at DATETIME COMMENT '取消时间',
+    cancel_reason TEXT COMMENT '取消原因',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_family_id (family_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿单表';
+
+-- =============================================
+-- 商品与订单表
+-- =============================================
+
+-- 商品表
+CREATE TABLE IF NOT EXISTS products (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(200) NOT NULL COMMENT '商品名称',
+    description TEXT COMMENT '商品描述',
+    intro TEXT COMMENT '详细介绍',
+    cover_image VARCHAR(500) COMMENT '封面图片',
+    images TEXT COMMENT '多张图片JSON',
+    price INT NOT NULL DEFAULT 0 COMMENT '价格(分)',
+    purchase_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '进价(分)',
+    stock INT NOT NULL DEFAULT 0 COMMENT '库存',
+    min_stock_alert INT DEFAULT 0 COMMENT '库存预警阈值(低于此值预警)',
+    sales_count INT NOT NULL DEFAULT 0 COMMENT '销量',
+    product_type VARCHAR(32) NOT NULL COMMENT '商品类型: activity/course/physical/digital/service',
+    growth_category VARCHAR(50) DEFAULT NULL COMMENT '成长分类',
+    vendor_id BIGINT COMMENT '供应商ID',
+    vendor_name VARCHAR(100) COMMENT '供应商名称',
+    status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT '状态: draft/pending/approved/rejected/on_shelf/off_shelf',
+    reject_reason TEXT COMMENT '审核拒绝原因',
+    member_price INT COMMENT '会员价(分)',
+    member_eligible VARCHAR(32) DEFAULT 'none' COMMENT '会员资格: all/budget/none',
+    domain VARCHAR(32) COMMENT '五维领域: action/mind/body/wisdom/wealth',
+    dimension_weights JSON DEFAULT NULL COMMENT '五维权重百分比: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
+    profit_rate INT DEFAULT 0 COMMENT '平台利润率(千分比)',
+    sort_order INT DEFAULT 0 COMMENT '排序(值越小越靠前)',
+    points_multiplier DECIMAL(10,2) NOT NULL DEFAULT 1.00 COMMENT '平台积分倍数,1元=1积分×倍数',
+    delivery_method TINYINT DEFAULT 1 COMMENT '配送方式: 1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人',
+    purchase_count_threshold INT DEFAULT 0 COMMENT '最小购买数量限制',
+    category_id BIGINT COMMENT '商品类目ID',
+    distribution_system_id BIGINT COMMENT '销售体系ID',
+    external_source VARCHAR(64) COMMENT '外部来源系统',
+    external_id VARCHAR(128) COMMENT '外部来源ID',
+    external_data TEXT COMMENT '外部来源数据JSON',
+    family_scope TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员消费过即排除, 0=仅按个人消费排除',
+    visible_roles JSON DEFAULT NULL COMMENT '可见角色: ["parent","child","teacher","admin"],NULL=全部可见',
+    min_visible_level VARCHAR(50) DEFAULT NULL COMMENT '最低可见会员级别编码(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME,NULL=全部可见)',
+    min_discount_level VARCHAR(50) DEFAULT NULL COMMENT '享受会员价所需的最低会员级别编码(FREE/FAMILY/PREMIUM/LIFETIME,NULL=所有会员均可)',
+    contraindicated_diseases VARCHAR(255) DEFAULT NULL COMMENT '禁忌基础疾病,逗号分隔(如"慢性肾病,肝病")',
+    allergen_tags VARCHAR(255) DEFAULT NULL COMMENT '过敏原标签,逗号分隔(如"花生,小麦")',
+    drug_interaction_flags TINYINT DEFAULT 0 COMMENT '是否命中药物-食物相互作用(1=是,需对照drug_interactions表)',
+    is_recommended TINYINT(1) DEFAULT 0 COMMENT '是否推荐商品 0否 1是',
+    tags VARCHAR(500) DEFAULT '' COMMENT '商品标签 JSON 数组,用于文章-商品标签匹配推荐',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
+
+-- 商品类目表(迁移创建于 DatabaseInitializer,同步到 schema.sql 保持完整快照)
+CREATE TABLE IF NOT EXISTS shop_categories (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(64) NOT NULL COMMENT '分类名称',
+    parent_id BIGINT DEFAULT 0 COMMENT '父分类ID(0=根节点)',
+    level INT DEFAULT 0 COMMENT '层级(0/1/2/3)',
+    sort INT DEFAULT 0 COMMENT '排序',
+    image VARCHAR(512) COMMENT '分类图标',
+    enabled TINYINT(1) DEFAULT 1 COMMENT '启用',
+    dimension_codes VARCHAR(128) COMMENT '维度编码(逗号分隔: body,mind,heart)',
+    product_types VARCHAR(128) COMMENT '商品类型(逗号分隔: PHYSICAL,COURSE,ASSESSMENT)',
+    min_visible_level VARCHAR(50) DEFAULT NULL COMMENT '最低可见会员级别编码(FREE/FAMILY/PROVIDER/PREMIUM/LIFETIME,NULL=全部可见)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_parent (parent_id),
+    INDEX idx_sort (sort)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品类目';
+
+-- 库存交易流水表
+CREATE TABLE IF NOT EXISTS inventory_transactions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT COMMENT '商品ID',
+    sku_id BIGINT COMMENT 'SKU ID',
+    type VARCHAR(32) COMMENT '交易类型: INBOUND/OUTBOUND/ADJUSTMENT/TRANSFER/COUNT',
+    direction TINYINT COMMENT '方向: 1=入库, -1=出库',
+    quantity INT NOT NULL COMMENT '数量',
+    before_stock INT COMMENT '操作前库存',
+    after_stock INT COMMENT '操作后库存',
+    reference_type VARCHAR(32) COMMENT '关联业务类型',
+    reference_id BIGINT COMMENT '关联业务ID',
+    operator_id BIGINT COMMENT '操作人ID',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存交易流水表';
+
+-- 库存盘点记录表
+CREATE TABLE IF NOT EXISTS inventory_counting_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT COMMENT '商品ID',
+    sku_id BIGINT COMMENT 'SKU ID',
+    expected_stock INT COMMENT '账面库存',
+    actual_stock INT COMMENT '实际库存',
+    difference INT COMMENT '差异',
+    status VARCHAR(16) DEFAULT 'pending' COMMENT '状态',
+    operator_id BIGINT COMMENT '盘点人ID',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存盘点记录表';
+
+-- 库存入库单表
+CREATE TABLE IF NOT EXISTS inventory_inbound_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_no VARCHAR(64) NOT NULL COMMENT '入库单号',
+    supplier_id BIGINT COMMENT '供应商ID',
+    supplier_name VARCHAR(100) COMMENT '供应商名称',
+    status VARCHAR(16) DEFAULT 'pending' COMMENT '状态',
+    total_items INT DEFAULT 0 COMMENT '商品种类数',
+    total_amount INT DEFAULT 0 COMMENT '入库总金额(分)',
+    inbound_date DATE COMMENT '入库日期',
+    operator_id BIGINT COMMENT '操作人ID',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存入库单表';
+
+-- 库存入库明细表
+CREATE TABLE IF NOT EXISTS inventory_inbound_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    inbound_order_id BIGINT NOT NULL COMMENT '入库单ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    sku_id BIGINT COMMENT 'SKU ID',
+    quantity INT NOT NULL COMMENT '入库数量',
+    purchase_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '入库单价(分)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存入库明细表';
+
+-- 商品规格SKU表(product_skus)
+CREATE TABLE IF NOT EXISTS product_skus (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    product_id BIGINT NOT NULL COMMENT '所属商品ID',
+    sku_code VARCHAR(64) COMMENT 'SKU编码',
+    specs JSON COMMENT '规格选项JSON: [{"name":"颜色","value":"红色"},{"name":"尺码","value":"M"}]',
+    price INT DEFAULT 0 COMMENT 'SKU价格(分)',
+    purchase_price DECIMAL(10,2) DEFAULT 0.00 COMMENT '进价(分)',
+    stock INT DEFAULT 0 COMMENT '库存数量',
+    min_stock_alert INT DEFAULT 0 COMMENT '库存预警阈值(低于此值预警)',
+    min_quantity INT DEFAULT 1 COMMENT '最小购买数量限制',
+    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),
+    INDEX idx_sku_code (sku_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品规格SKU表';
+
+-- 商品订单表
+CREATE TABLE IF NOT EXISTS product_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    product_name VARCHAR(200) COMMENT '商品名称',
+    product_type VARCHAR(32) COMMENT '商品类型',
+    buyer_id BIGINT NOT NULL COMMENT '购买者ID',
+    family_id BIGINT COMMENT '家庭ID',
+    quantity INT NOT NULL DEFAULT 1 COMMENT '数量',
+    unit_price INT NOT NULL COMMENT '单价(分)',
+    total_amount INT NOT NULL COMMENT '总金额(分)',
+    status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/paid/completed/cancelled/refunded',
+    payment_method VARCHAR(32) COMMENT '支付方式: wechat/points/membership',
+    remark TEXT COMMENT '备注',
+    paid_at DATETIME COMMENT '支付时间',
+    supply_system_id BIGINT COMMENT '所属供应商体系ID',
+    supplier_id BIGINT COMMENT '收款供应商用户ID',
+    supply_commission INT COMMENT '销售佣金(分)',
+    points_used INT DEFAULT 0 COMMENT '使用的积分数量',
+    points_cost INT DEFAULT 0 COMMENT '积分抵扣金额(分)',
+    money_amount INT DEFAULT 0 COMMENT '现金支付金额(分)',
+    consignee_id BIGINT COMMENT '收货人ID',
+    pickup_location_id BIGINT COMMENT '自取地点ID',
+    pickup_location_snapshot TEXT COMMENT '自取地点快照(JSON)',
+    time_slot_id BIGINT COMMENT '所选时间段ID',
+    time_slot_snapshot TEXT COMMENT '时间段快照(JSON)',
+    service_person_id BIGINT COMMENT '所选服务者ID(deliveryMethod=4)',
+    service_person_snapshot TEXT COMMENT '服务者快照(JSON)',
+    fulfill_status VARCHAR(32) DEFAULT 'pending' COMMENT '履约状态: pending/confirmed_by_vendor/received_by_user/completed',
+    fulfill_confirmed_at DATETIME COMMENT '供应商确认时间',
+    received_at DATETIME COMMENT '用户确认收货时间',
+purchase_info TEXT COMMENT '购买信息JSON: {id_card, hand_signature, ...}',
+provision_status VARCHAR(32) COMMENT '活动provision状态: unprovisioned(待审核)/provisioned(已发货)/signed_in(已签到)/completed(已完成)/cancelled(审核拒绝)',
+auditor_id BIGINT COMMENT '审核人ID',
+audited_at DATETIME COMMENT '审核时间',
+audit_reason TEXT COMMENT '审核拒绝原因',
+    shipping_cost INT DEFAULT 0 COMMENT '快递费(分)',
+    tracking_number VARCHAR(100) DEFAULT NULL COMMENT '快递单号',
+    express_company VARCHAR(100) DEFAULT NULL COMMENT '快递公司',
+    delivery_method INT DEFAULT NULL COMMENT '配送方式: 1=快递 2=自提 3=两者皆可 4=线上-可选人 5=线上-单人',
+    pickup_code VARCHAR(64) DEFAULT NULL COMMENT '自提码',
+    confirm_time DATETIME DEFAULT NULL COMMENT '确认收货时间',
+    refund_status INT DEFAULT NULL COMMENT '退款状态',
+    refund_reason VARCHAR(255) DEFAULT NULL COMMENT '退款原因',
+    refund_amount INT DEFAULT NULL COMMENT '退款金额(分)',
+    refund_time DATETIME DEFAULT NULL COMMENT '退款时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_buyer_id (buyer_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_product_id (product_id),
+    INDEX idx_status (status),
+    INDEX idx_supply_system_id (supply_system_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品订单表';
+
+-- 商品自取地点表
+CREATE TABLE IF NOT EXISTS product_pickup_locations (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    product_id BIGINT NOT NULL COMMENT '所属商品ID',
+    name VARCHAR(100) NOT NULL COMMENT '地点名称',
+    address VARCHAR(500) NOT NULL COMMENT '详细地址',
+    longitude DECIMAL(10,7) COMMENT '经度',
+    latitude DECIMAL(10,7) COMMENT '纬度',
+    contact_name VARCHAR(50) COMMENT '联系人',
+    contact_phone VARCHAR(20) COMMENT '联系电话',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    enabled TINYINT DEFAULT 1 COMMENT '启用: 0/1',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_product_id (product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品自取地点';
+
+-- 自取时间段模板表
+CREATE TABLE IF NOT EXISTS product_pickup_time_slots (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    location_id BIGINT NOT NULL COMMENT '所属地点ID',
+    start_time TIME NOT NULL COMMENT '开始时间 HH:MM',
+    end_time TIME NOT NULL COMMENT '结束时间 HH:MM',
+    weekday TINYINT COMMENT '周几(1-7), null=按具体日期',
+    specific_date DATE COMMENT '具体日期, null=按周循环',
+    max_capacity INT DEFAULT 1 COMMENT '该时段最大接待人数',
+    booked_count INT DEFAULT 0 COMMENT '已预订数',
+    enabled TINYINT DEFAULT 1,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_location_id (location_id),
+    INDEX idx_specific_date (specific_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自取时间段模板';
+
+-- 商品线上服务者表
+CREATE TABLE IF NOT EXISTS product_service_persons (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    product_id BIGINT NOT NULL COMMENT '所属商品ID',
+    user_id BIGINT NOT NULL COMMENT '服务者用户ID(关联users表)',
+    name VARCHAR(50) NOT NULL COMMENT '展示名称',
+    title VARCHAR(100) COMMENT '头衔(如高级营养师)',
+    intro TEXT COMMENT '个人介绍',
+    avatar VARCHAR(500) COMMENT '头像URL',
+    price INT NOT NULL COMMENT '该人专属价格(分)',
+    original_price INT COMMENT '原价(分)',
+    sort_order INT DEFAULT 0,
+    enabled TINYINT DEFAULT 1,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_product_id (product_id),
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品线上服务者';
+
+-- 服务者可预约时间段表
+CREATE TABLE IF NOT EXISTS product_service_person_slots (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    person_id BIGINT NOT NULL COMMENT '所属服务者ID',
+    start_time DATETIME NOT NULL COMMENT '开始时间',
+    end_time DATETIME NOT NULL COMMENT '结束时间',
+    max_capacity INT DEFAULT 1 COMMENT '容量',
+    booked_count INT DEFAULT 0 COMMENT '已预订数',
+    enabled TINYINT DEFAULT 1,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_person_id (person_id),
+    INDEX idx_start_time (start_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务者可预约时间段';
+
+-- 单人线上服务时间段表
+CREATE TABLE IF NOT EXISTS product_online_slots (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    product_id BIGINT NOT NULL COMMENT '所属商品ID',
+    start_time DATETIME NOT NULL COMMENT '开始时间',
+    end_time DATETIME NOT NULL COMMENT '结束时间',
+    max_capacity INT DEFAULT 1 COMMENT '容量',
+    booked_count INT DEFAULT 0 COMMENT '已预订数',
+    enabled TINYINT DEFAULT 1,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_product_id (product_id),
+    INDEX idx_start_time (start_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='单人线上服务时间段';
+
+-- =============================================
+-- 系统配置与内容表
+-- =============================================
+
+-- 系统配置表
+CREATE TABLE IF NOT EXISTS sys_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    config_key VARCHAR(64) NOT NULL UNIQUE COMMENT '配置键',
+    config_value TEXT COMMENT '配置值',
+    description TEXT COMMENT '配置说明',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
+
+-- 内容区块配置表
+CREATE TABLE IF NOT EXISTS content_sections (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    page_key VARCHAR(50) NOT NULL COMMENT '页面标识',
+    section_key VARCHAR(50) NOT NULL COMMENT '区块标识',
+    title VARCHAR(100) COMMENT '区块标题',
+    allowed_roles VARCHAR(255) DEFAULT '["anonymous"]' COMMENT '允许查看的角色JSON数组',
+    sort_order INT DEFAULT 0 COMMENT '显示顺序',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/inactive',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='内容区块配置表';
+
+-- 文章分类表
+CREATE TABLE IF NOT EXISTS article_categories (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(50) NOT NULL COMMENT '分类名',
+    icon VARCHAR(20) DEFAULT '' COMMENT '图标emoji',
+    color VARCHAR(20) DEFAULT '#5B9BD5' COMMENT '标识色',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用/0禁用'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类表';
+
+-- 文章表
+CREATE TABLE IF NOT EXISTS articles (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
+    category_id BIGINT DEFAULT 0 COMMENT '所属分类 ID',
+    title VARCHAR(200) NOT NULL UNIQUE COMMENT '标题(唯一,防止重复发布)',
+    summary TEXT COMMENT '摘要',
+    cover_image VARCHAR(500) DEFAULT '' COMMENT '封面图 URL',
+    content LONGTEXT COMMENT '富文本内容',
+    tags VARCHAR(200) DEFAULT '' COMMENT '标签 JSON 数组',
+    author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者',
+    read_time INT DEFAULT 0 COMMENT '预计阅读分钟数',
+    article_type VARCHAR(20) DEFAULT 'normal' COMMENT '文章类型:normal(普通)/premium(优质)/original(原创)',
+    word_count INT DEFAULT 0 COMMENT '文章字数',
+    related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维 JSON 数组',
+    dimension_weights JSON DEFAULT NULL COMMENT '五维权重百分比: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
+    visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private',
+    visible_to TEXT COMMENT '私密指定人群 JSON',
+    status VARCHAR(20) DEFAULT 'draft' COMMENT '状态:draft/published/archived',
+    growth_category VARCHAR(50) DEFAULT NULL COMMENT '成长分类',
+    is_featured TINYINT DEFAULT 0 COMMENT '是否精选:1 精选/0 普通',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '推荐排序值(越小越靠前)',
+    published_at DATETIME DEFAULT NULL COMMENT '发布时间',
+    view_count INT DEFAULT 0 COMMENT '浏览次数',
+    created_by BIGINT DEFAULT 0 COMMENT '发布人 adminID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
+
+-- Article audit fields (for user-generated content)
+ALTER TABLE articles
+    ADD COLUMN author_type VARCHAR(20) DEFAULT 'admin' COMMENT '作者类型:admin/parent/child' AFTER created_by,
+    ADD COLUMN audit_status VARCHAR(20) DEFAULT 'approved' COMMENT '审核状态:approved/pending/rejected' AFTER status,
+    ADD COLUMN audit_reason TEXT COMMENT '驳回原因' AFTER audit_status,
+    ADD COLUMN auditor_id BIGINT DEFAULT 0 COMMENT '审核人 ID' AFTER audit_reason,
+    ADD COLUMN audited_at DATETIME COMMENT '审核时间' AFTER auditor_id;
+
+-- =============================================
+-- 五维能量系统表
+-- =============================================
+
+-- 能量维度定义表
+CREATE TABLE IF NOT EXISTS energy_dimension (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    code VARCHAR(16) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth',
+    name VARCHAR(20) NOT NULL COMMENT '维度名称: 身/心/智/行/富',
+    icon VARCHAR(16) COMMENT '图标emoji',
+    element VARCHAR(10) COMMENT '五行元素: 土/火/金/木/水',
+    sort_order INT DEFAULT 0 COMMENT '显示顺序',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用/0禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量维度定义表';
+
+-- 服务-维度比例配置表
+CREATE TABLE IF NOT EXISTS energy_source_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    source_type VARCHAR(32) NOT NULL COMMENT '来源类型: task/activity/course/product/consultation',
+    source_id BIGINT NOT NULL COMMENT '来源业务表ID',
+    source_name VARCHAR(200) COMMENT '冗余名称',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    ratio INT NOT NULL COMMENT '占比(基点,6000=60%)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_source (source_type, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务-维度比例配置表';
+
+-- 能量流水表
+CREATE TABLE IF NOT EXISTS energy_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    amount INT NOT NULL COMMENT '变动量(正=获得,负=消耗)',
+    balance_after INT NOT NULL COMMENT '变动后余额',
+    source_type VARCHAR(32) COMMENT '来源类型: task/product/medical_report/daily_record',
+    source_id BIGINT COMMENT '来源ID',
+    ref_id BIGINT COMMENT '关联旧流水ID(覆盖时标记)',
+    expires_at DATETIME COMMENT '过期时间(空=永不过期)',
+    description TEXT COMMENT '描述',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_child_dim (child_id, dimension_id),
+    INDEX idx_created (created_at),
+    INDEX idx_source (source_type, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量流水表';
+
+-- 维度余额表
+CREATE TABLE IF NOT EXISTS energy_balance (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    balance INT DEFAULT 0 COMMENT '当前能量值(可为负)',
+    total_earned INT DEFAULT 0 COMMENT '累计获得',
+    total_spent INT DEFAULT 0 COMMENT '累计消耗',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_child_dim (child_id, dimension_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度余额表';
+
+-- =============================================
+-- 佣金系统表
+-- =============================================
+
+-- 佣金记录表
+CREATE TABLE IF NOT EXISTS commission_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_type VARCHAR(32) NOT NULL COMMENT '订单类型: membership/product/assessment/package',
+    referrer_id BIGINT NOT NULL COMMENT '推荐人ID',
+    buyer_id BIGINT NOT NULL COMMENT '购买人ID',
+    commission_type VARCHAR(16) NOT NULL COMMENT '佣金类型: member/profit',
+    level INT DEFAULT 1 COMMENT '佣金层级: 1=直接推荐, 2=间接推荐',
+    order_amount INT NOT NULL COMMENT '订单金额(分)',
+    profit_rate INT DEFAULT 0 COMMENT '利润率(千分比,如235=23.5%)',
+    commission_amount INT NOT NULL COMMENT '佣金金额(分)',
+    status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled',
+    remark TEXT DEFAULT NULL COMMENT '备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    settled_at DATETIME DEFAULT NULL COMMENT '结算时间',
+    withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间',
+    INDEX idx_referrer (referrer_id),
+    INDEX idx_buyer (buyer_id),
+    INDEX idx_order (order_id, order_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金记录表';
+
+-- 提现申请表
+CREATE TABLE IF NOT EXISTS withdrawal_requests (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    amount INT NOT NULL COMMENT '提现金额(分)',
+    account_info TEXT COMMENT '账户信息(JSON)',
+    type VARCHAR(20) DEFAULT 'platform_points' COMMENT '提现来源: platform_points(CF值) / commission(历史佣金)',
+    status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/approved/rejected',
+    audit_by BIGINT DEFAULT NULL COMMENT '审核人ID',
+    audit_at DATETIME DEFAULT NULL COMMENT '审核时间',
+    remark TEXT DEFAULT NULL COMMENT '审核备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user (user_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='提现申请表';
+
+-- 会员升级记录表
+CREATE TABLE IF NOT EXISTS member_upgrade_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    from_level VARCHAR(20) NOT NULL COMMENT '升级前等级',
+    to_level VARCHAR(20) NOT NULL COMMENT '升级后等级',
+    upgrade_type VARCHAR(20) NOT NULL COMMENT '升级类型: pay/consumption/admin',
+    order_id BIGINT DEFAULT NULL COMMENT '关联支付订单ID',
+    consumption_amount BIGINT DEFAULT NULL COMMENT '触发升级的累计消费金额',
+    expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间',
+    approve_remark VARCHAR(500) DEFAULT NULL COMMENT '审批备注',
+    approve_status VARCHAR(20) DEFAULT 'approved' COMMENT '审批状态: approved/rejected',
+    approve_user_id BIGINT DEFAULT NULL COMMENT '审批人ID',
+    approve_time DATETIME DEFAULT NULL COMMENT '审批时间',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user (user_id),
+    INDEX idx_order (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员升级记录表';
+
+-- =============================================
+-- 规划师服务模块表
+-- =============================================
+
+-- 服务类型表
+CREATE TABLE IF NOT EXISTS service_types (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(100) NOT NULL COMMENT '类型名称',
+    icon VARCHAR(50) COMMENT '图标',
+    description TEXT COMMENT '描述',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务类型表';
+
+-- 服务内容表
+CREATE TABLE IF NOT EXISTS service_contents (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    type_id BIGINT NOT NULL COMMENT '关联服务类型',
+    title VARCHAR(200) NOT NULL COMMENT '内容标题',
+    content TEXT COMMENT '富文本内容',
+    duration INT COMMENT '建议时长(分钟)',
+    age_range VARCHAR(50) COMMENT '适用年龄',
+    price INT COMMENT '参考价格(分)',
+    status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态: DRAFT/ACTIVE',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务内容表';
+
+-- 套餐模板表
+CREATE TABLE IF NOT EXISTS guide_package_templates (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(100) NOT NULL COMMENT '模板名称',
+    validity_months INT COMMENT '有效期(月)',
+    reference_price INT COMMENT '参考价格(分)',
+    min_price INT COMMENT '最低售价(分)',
+    description TEXT COMMENT '说明',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐模板表';
+
+-- 套餐模板服务项表
+CREATE TABLE IF NOT EXISTS package_template_services (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    template_id BIGINT NOT NULL COMMENT '关联模板',
+    content_id BIGINT NOT NULL COMMENT '关联服务内容',
+    quantity INT DEFAULT 1 COMMENT '数量',
+    unit VARCHAR(20) COMMENT '单位'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐模板服务项表';
+
+-- 成长规划师套餐表
+CREATE TABLE IF NOT EXISTS guide_packages (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    guide_id BIGINT COMMENT '成长规划师ID',
+    package_id BIGINT COMMENT '关联模板',
+    commission_rate INT COMMENT '佣金比例(基点,1000=10%)',
+    teacher_id BIGINT COMMENT '成长规划师ID(旧字段)',
+    template_id BIGINT COMMENT '关联模板(旧字段)',
+    name VARCHAR(100) NOT NULL COMMENT '套餐名称',
+    level VARCHAR(20) COMMENT '套餐等级',
+    price INT NOT NULL COMMENT '销售价格(分)',
+    validity_months INT COMMENT '有效期(月)',
+    validity_days INT DEFAULT 0 COMMENT '有效期天数',
+    description TEXT COMMENT '套餐说明',
+    status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态: DRAFT/PUBLISHED/PAUSED',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    sales_count INT DEFAULT 0 COMMENT '销量',
+    share_code VARCHAR(50) COMMENT '分享码',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师套餐表';
+
+-- 成长规划师套餐服务项表
+CREATE TABLE IF NOT EXISTS guide_package_services (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    package_id BIGINT NOT NULL COMMENT '关联套餐',
+    content_id BIGINT NOT NULL COMMENT '关联服务内容',
+    quantity INT DEFAULT 1 COMMENT '数量',
+    unit VARCHAR(20) COMMENT '单位'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师套餐服务项表';
+
+-- 成长规划师订单表
+CREATE TABLE IF NOT EXISTS guide_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(50) NOT NULL COMMENT '订单号',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    package_id BIGINT NOT NULL COMMENT '套餐ID',
+    amount INT NOT NULL COMMENT '金额(分)',
+    status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态: PENDING/PAID/COMPLETED/REFUNDED',
+    pay_time DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师订单表';
+
+-- 佣金明细表
+CREATE TABLE IF NOT EXISTS guide_commissions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    amount INT NOT NULL COMMENT '佣金金额(分)',
+    rate INT COMMENT '佣金比例(基点)',
+    status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态: PENDING/COMPLETED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金明细表';
+
+-- 服务记录表
+CREATE TABLE IF NOT EXISTS guide_service_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    order_id BIGINT COMMENT '订单ID',
+    service_content_id BIGINT NOT NULL COMMENT '服务内容ID',
+    notes TEXT COMMENT '服务记录',
+    duration INT COMMENT '服务时长(分钟)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务记录表';
+
+-- 成长规划师邀请码表
+CREATE TABLE IF NOT EXISTS guide_invite_codes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID',
+    code VARCHAR(50) NOT NULL UNIQUE COMMENT '邀请码',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态',
+    used_by_user_id BIGINT COMMENT '使用该邀请码的用户ID',
+    used_at DATETIME COMMENT '使用时间',
+    expire_at DATETIME COMMENT '过期时间',
+    use_count INT DEFAULT 0 COMMENT '使用次数',
+    max_use_count INT COMMENT '最大使用次数',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师邀请码表';
+
+-- =============================================
+-- 财富维度表
+-- =============================================
+
+-- 财商打卡记录表
+CREATE TABLE IF NOT EXISTS finance_checkins (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT NULL COMMENT '孩子ID,家长本人打卡时为NULL',
+checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review',
+    checkin_date DATE NOT NULL COMMENT '记账日期',
+    type VARCHAR(10) DEFAULT 'expense' COMMENT '类型: income(收入)/expense(支出)',
+    amount INT COMMENT '金额(分,正数)',
+    category VARCHAR(50) COMMENT '收支分类',
+    note TEXT COMMENT '备注',
+    mood VARCHAR(20) COMMENT '心情',
+    earned_energy INT DEFAULT 0 COMMENT '获得的能量值',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user_date (user_id, checkin_date),
+    INDEX idx_child (child_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财商打卡记录表';
+
+-- 保单管理表
+CREATE TABLE IF NOT EXISTS insurance_policies (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '关联孩子ID',
+    policy_name VARCHAR(100) NOT NULL COMMENT '保单名称',
+    insurance_company VARCHAR(100) COMMENT '保险公司',
+    policy_type VARCHAR(50) COMMENT '保单类型: medical/life/accident/education/endowment',
+    insured_person VARCHAR(50) NOT NULL COMMENT '被保人姓名',
+    premium INT COMMENT '保费(分)',
+    sum_insured INT COMMENT '保额(分)',
+    start_date DATE COMMENT '生效日期',
+    end_date DATE COMMENT '到期日期',
+    renewal_type VARCHAR(20) DEFAULT 'annual' COMMENT '续费类型: annual/monthly/single',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired/cancelled',
+    policy_no VARCHAR(100) COMMENT '保单号',
+    remark TEXT COMMENT '备注',
+    policy_terms TEXT COMMENT '保单原始条款(AI 解析后原样保存)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user (user_id),
+    INDEX idx_child (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单管理表';
+
+-- =============================================
+-- 健康检测报告模块表
+-- =============================================
+
+-- 健康检测报告表
+CREATE TABLE IF NOT EXISTS health_reports (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '上传者用户ID(关联users表)',
+    subject_id BIGINT COMMENT '报告主体用户ID(关联users表,为null表示未绑定)',
+    family_id BIGINT COMMENT '家庭ID',
+    report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)',
+    report_format VARCHAR(32) COMMENT '报告来源格式: beijing(北京菌群)/guhe(谷禾)',
+    report_date DATE COMMENT '报告日期',
+    overall_score INT COMMENT '健康总分(0-100)',
+    gut_health_score INT COMMENT '菌群健康分',
+    chronic_disease_score INT COMMENT '慢病控制分',
+    nutrition_score INT COMMENT '营养均衡分',
+    gut_age VARCHAR(32) COMMENT '肠道预测年龄',
+    gut_type VARCHAR(50) COMMENT '肠型',
+    file_url VARCHAR(500) COMMENT '报告文件URL',
+    file_hash VARCHAR(64) DEFAULT NULL COMMENT '文件MD5哈希,用于重复上传检测',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived',
+    remark TEXT COMMENT '备注',
+    proxy_uploader_id BIGINT COMMENT '代上传人ID(规划师/营养师)',
+    proxy_upload_note TEXT COMMENT '代上传备注',
+    ai_analysis TEXT COMMENT 'AI解读结果JSON',
+    shannon_index VARCHAR(32) DEFAULT NULL COMMENT '香农指数',
+    payload_json LONGTEXT COMMENT '报告完整载荷快照',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_subject_id (subject_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_report_type (report_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康检测报告表';
+
+-- 健康报告查看权限表
+CREATE TABLE IF NOT EXISTS health_report_permissions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    report_id BIGINT NOT NULL COMMENT '报告ID',
+    user_id BIGINT NOT NULL COMMENT '有查看权限的用户ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    UNIQUE KEY uk_report_user (report_id, user_id),
+    INDEX idx_report_id (report_id),
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告查看权限表';
+
+-- 健康指标明细表
+CREATE TABLE IF NOT EXISTS health_indicators (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    report_id BIGINT NOT NULL COMMENT '关联报告ID',
+    category VARCHAR(50) COMMENT '指标分类: 营养状况/疾病风险/肠道屏障/神经递质/...',
+    indicator_name VARCHAR(100) NOT NULL COMMENT '指标名称',
+    indicator_value VARCHAR(100) COMMENT '检测值',
+    unit VARCHAR(50) COMMENT '单位',
+    ref_range VARCHAR(200) COMMENT '参考范围',
+    status VARCHAR(100) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出',
+    symptoms TEXT COMMENT '症状描述',
+    sort_order INT DEFAULT 0 COMMENT '显示排序',
+    collection_date DATETIME COMMENT '指标采集日期(报告检测日期)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_report_id (report_id),
+    INDEX idx_category (category)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标明细表';
+
+-- 健康报告上传草稿表(两阶段入库的中间态)
+CREATE TABLE IF NOT EXISTS health_report_drafts (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '上传人用户ID',
+    family_id BIGINT COMMENT '家庭ID',
+    member_id BIGINT DEFAULT NULL COMMENT '报告归属成员ID(family_members.id),A2/DAN自动采集必需',
+    report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam/gut_flora/dan_a2',
+    file_url VARCHAR(500) COMMENT '上传的文件URL',
+    file_hash VARCHAR(64) DEFAULT NULL COMMENT '文件MD5哈希,用于重复上传检测',
+    original_filename VARCHAR(255) COMMENT '原始文件名',
+    payload_json MEDIUMTEXT NOT NULL COMMENT '草稿载荷JSON(含解析/手动录入的所有指标)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/confirmed/discarded',
+    parse_status VARCHAR(20) DEFAULT NULL COMMENT '异步解析状态: collecting/completed/failed(NULL=未触发异步采集)',
+    parse_method VARCHAR(32) DEFAULT NULL COMMENT '解析方式: fingerprint_script/opencode/langgraph',
+    parse_error VARCHAR(2000) DEFAULT NULL COMMENT '异步解析失败原因',
+    expire_at DATETIME COMMENT '草稿过期时间',
+    confirmed_report_id BIGINT COMMENT 'confirm阶段入库后的健康报告ID',
+    remark VARCHAR(500) DEFAULT NULL COMMENT '备注(如代上传时的说明文字)',
+    collect_validation TEXT DEFAULT NULL COMMENT '采集指标校验结果(JSON: typeId/expected/matched/pass/missing)',
+    parse_attempt INT DEFAULT 0 COMMENT '已知类型解析重试次数(指数退避,上限5分钟)',
+    next_retry_at DATETIME NULL COMMENT '下次自动重试时间(known-type 解析失败后设置)',
+    parse_started_at DATETIME NULL COMMENT '最近一次解析开始时间',
+    parse_finished_at DATETIME NULL COMMENT '最近一次解析结束时间(成功/失败)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_status_expire (status, expire_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告上传草稿表(两阶段入库中间态)';
+
+-- 健康报告修改留痕表
+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='健康报告修改留痕';
+
+-- 健康报告跨家庭转移申请表(同一份报告被不同家庭上传时的归属转移审批)
+CREATE TABLE IF NOT EXISTS report_transfer_requests (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    report_id BIGINT NOT NULL COMMENT '报告ID(health_reports.id)',
+    from_family_id BIGINT NOT NULL COMMENT '原归属家庭ID(需同意方)',
+    to_family_id BIGINT NOT NULL COMMENT '申请转入家庭ID(申请方)',
+    requester_user_id BIGINT NOT NULL COMMENT '申请人用户ID(users.id)',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending=待同意 approved=已同意 denied=已拒绝',
+    handled_by BIGINT DEFAULT NULL COMMENT '处理人用户ID',
+    handled_at DATETIME DEFAULT NULL COMMENT '处理时间',
+    remark VARCHAR(200) DEFAULT NULL 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_report_id (report_id),
+    INDEX idx_from_family_status (from_family_id, status),
+    INDEX idx_to_family_status (to_family_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康报告跨家庭转移申请';
+
+-- 菌群检测专项数据表(针对 gut_flora 类型报告)
+CREATE TABLE IF NOT EXISTS health_gut_flora (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
+    report_id BIGINT NOT NULL COMMENT '关联报告 ID',
+    bacteria_name VARCHAR(100) NULL COMMENT '菌种名称',
+    bacteria_value VARCHAR(50) COMMENT '菌种丰度值',
+    normal_range VARCHAR(100) COMMENT '正常范围',
+    status VARCHAR(50) COMMENT '状态:正常/偏低/偏高',
+    category VARCHAR(50) COMMENT '分类:core/probiotic/harmful/other/pathogen_genus/pathogen_detection/taxonomy',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    population_level VARCHAR(100) DEFAULT NULL COMMENT '处于人群%水平',
+    detection_rate VARCHAR(100) DEFAULT NULL COMMENT '%正常人有检出',
+    description TEXT COMMENT '菌种说明',
+    `level` VARCHAR(20) DEFAULT 'GENUS' COMMENT '级别:CLASS/ORDER/FAMILY/GENUS/SPECIES',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_report (report_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菌群检测专项数据(每种菌种的丰度值和评估状态)';
+
+-- 教育体系配置表
+CREATE TABLE IF NOT EXISTS education_systems (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(32) NOT NULL UNIQUE COMMENT '编码(key)',
+    label VARCHAR(50) NOT NULL COMMENT '显示名称',
+    description TEXT COMMENT '描述说明',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教育体系配置表';
+
+INSERT INTO education_systems (name, label, description, sort_order) VALUES
+('high_school', '中专/高中', '中等教育学历', 1),
+('associate', '大专', '高等专科学历', 2),
+('bachelor', '本科', '大学本科学历', 3),
+('master', '硕士', '硕士研究生学历', 4),
+('doctor', '博士', '博士研究生学历', 5);
+
+-- ===== v1.2 先天属性体系 =====
+
+-- 家庭成员先天属性表
+CREATE TABLE IF NOT EXISTS family_member_attributes (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id        BIGINT       NOT NULL COMMENT '家庭成员ID(children表或users表)',
+    member_type      VARCHAR(10)  NOT NULL COMMENT '成员类型: child/parent',
+    birth_datetime   DATETIME     COMMENT '出生日期(精确到分钟,用于八字排盘)',
+    eight_characters JSON         COMMENT '八字四柱JSON',
+    wuxing_elements  JSON         COMMENT '五行星盘元素JSON',
+    zodiac           VARCHAR(16)  COMMENT '生肖: rat/ox/tiger/rabbit/dragon/snake/horse/sheep/monkey/rooster/dog/pig',
+    blood_type       VARCHAR(5)   COMMENT '血型: A/B/AB/O',
+    mind_base_score  INT DEFAULT 0 COMMENT '心维度基础分(百分位)',
+    wisdom_base_score INT DEFAULT 0 COMMENT '智维度基础分(百分位)',
+    behavior_modifier JSON        COMMENT '行为修正因子JSON',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_member (member_id, member_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员先天属性表';
+
+-- 星座配置表
+CREATE TABLE IF NOT EXISTS zodiac_config (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    zodiac_code      VARCHAR(20)  NOT NULL UNIQUE COMMENT '星座代码',
+    zodiac_name      VARCHAR(10)  NOT NULL COMMENT '星座名称',
+    element          VARCHAR(10)  NOT NULL COMMENT '五行: 金木水火土',
+    start_month      INT          NOT NULL COMMENT '起始月',
+    start_day        INT          NOT NULL COMMENT '起始日',
+    end_month        INT          NOT NULL COMMENT '结束月',
+    end_day          INT          NOT NULL COMMENT '结束日',
+    mind_base        INT DEFAULT 0 COMMENT '心先天基础分(百分位)',
+    wisdom_base      INT DEFAULT 0 COMMENT '智先天基础分(百分位)',
+    body_modifier    INT DEFAULT 0 COMMENT '身修正系数(千分比)',
+    action_modifier  INT DEFAULT 0 COMMENT '行修正系数(千分比)',
+    wealth_modifier  INT DEFAULT 0 COMMENT '富修正系数(千分比)',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='星座配置表';
+
+-- 八字配置表
+CREATE TABLE IF NOT EXISTS bazi_config (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    stem_or_branch   VARCHAR(10)  NOT NULL COMMENT '天干或地支',
+    type             VARCHAR(10)  NOT NULL COMMENT '类型: stem/branch',
+    element          VARCHAR(10)  NOT NULL COMMENT '五行: 金木水火土',
+    yin_yang         VARCHAR(5)   COMMENT '阴阳: yin/yang',
+    mind_base        INT DEFAULT 0 COMMENT '心先天基础分(百分位)',
+    wisdom_base      INT DEFAULT 0 COMMENT '智先天基础分(百分位)',
+    body_modifier    INT DEFAULT 0 COMMENT '身修正系数(千分比)',
+    action_modifier  INT DEFAULT 0 COMMENT '行修正系数(千分比)',
+    wealth_modifier  INT DEFAULT 0 COMMENT '富修正系数(千分比)',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='八字配置表(天干地支五行)';
+
+-- 血型配置表
+CREATE TABLE IF NOT EXISTS blood_type_config (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    blood_type       VARCHAR(5)   NOT NULL UNIQUE COMMENT '血型: A/B/AB/O',
+    description      TEXT COMMENT '性格描述',
+    mind_base        INT DEFAULT 0 COMMENT '心先天基础分(百分位)',
+    wisdom_base      INT DEFAULT 0 COMMENT '智先天基础分(百分位)',
+    body_modifier    INT DEFAULT 0 COMMENT '身修正系数(千分比)',
+    action_modifier  INT DEFAULT 0 COMMENT '行修正系数(千分比)',
+    wealth_modifier  INT DEFAULT 0 COMMENT '富修正系数(千分比)',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='血型配置表';
+
+-- =============================================
+-- 活动报名 与 能量规则引擎表(v1.4)
+-- =============================================
+
+-- 活动报名表
+CREATE TABLE IF NOT EXISTS activity_registrations (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    activity_id BIGINT NOT NULL COMMENT '活动ID',
+    child_id BIGINT COMMENT '孩子ID(手动输入报名人为空)',
+    user_id BIGINT NOT NULL COMMENT '报名人用户ID(家长)',
+    registrant_name VARCHAR(100) COMMENT '报名人姓名',
+    registrant_phone VARCHAR(20) COMMENT '报名人联系方式',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected/cancelled',
+    registered_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间',
+    approved_at DATETIME COMMENT '审核通过时间',
+    approved_by BIGINT COMMENT '审核人用户ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_activity_child (activity_id, child_id),
+    INDEX idx_activity_id (activity_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_user_id (user_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动报名表';
+
+-- 能量规则表(规则引擎)
+CREATE TABLE IF NOT EXISTS energy_rule (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    rule_code VARCHAR(64) NOT NULL UNIQUE COMMENT '规则编码(唯一)',
+    rule_name VARCHAR(200) NOT NULL COMMENT '规则名称',
+    event_type VARCHAR(32) NOT NULL COMMENT '事件类型: checkin/register/complete/cancel/overdue',
+    action VARCHAR(16) NOT NULL COMMENT '动作: increase/decrease',
+    dimension_code VARCHAR(20) COMMENT '维度代码: body/mind/wisdom/action/wealth(NULL=五维均衡)',
+    amount INT COMMENT '固定变动量(基点)',
+    amount_formula VARCHAR(255) COMMENT '动态公式(如: balance*0.1)',
+    priority INT DEFAULT 0 COMMENT '优先级(数字越大越优先匹配)',
+    conditions JSON COMMENT '触发条件(JSON表达式)',
+    status TINYINT DEFAULT 1 COMMENT '状态: 1=启用 0=禁用',
+    description TEXT COMMENT '规则描述',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_event_type (event_type),
+    INDEX idx_status (status),
+    INDEX idx_priority (priority)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量规则表(规则引擎)';
+
+CREATE TABLE IF NOT EXISTS activities (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    title            VARCHAR(100) NOT NULL COMMENT '活动标题',
+    description      TEXT COMMENT '活动描述',
+    cover_image      VARCHAR(500) COMMENT '封面图片URL',
+    dimension_code   VARCHAR(20) COMMENT '维度代码: body/mind/wisdom/action/wealth',
+    dimension_weights JSON DEFAULT NULL COMMENT '五维权重百分比: {"body":0,"mind":0,"wisdom":0,"action":0,"wealth":0}',
+    activity_type    VARCHAR(20) DEFAULT 'offline' COMMENT '活动类型: offline/online/campaign',
+    status           VARCHAR(20) DEFAULT 'draft' COMMENT '状态: draft/published/ended',
+    start_time       DATETIME COMMENT '开始时间',
+    end_time         DATETIME COMMENT '结束时间',
+    location         VARCHAR(200) COMMENT '活动地点',
+    max_participants INT DEFAULT 0 COMMENT '最大参与人数',
+    current_participants INT DEFAULT 0 COMMENT '当前参与人数',
+    price INT DEFAULT 0 COMMENT '活动价格(分)',
+    member_price INT COMMENT '会员价(分)',
+    vendor_id        BIGINT COMMENT '服务商ID',
+    vendor_name      VARCHAR(100) COMMENT '服务商名称',
+    feedback_dimensions TEXT COMMENT '评价维度列表JSON',
+    family_scope TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员参与过即排除, 0=仅按个人参与排除',
+    platform_points INT DEFAULT 0 COMMENT '活动奖励CF值(0=不发)',
+    visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private',
+    visible_to TEXT COMMENT '私密指定人群JSON',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_dimension (dimension_code),
+    INDEX idx_status (status),
+    INDEX idx_start_time (start_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动表';
+
+-- v1.x: 活动工作流状态扩展
+ALTER TABLE activities
+  MODIFY COLUMN status VARCHAR(20) DEFAULT 'draft'
+    COMMENT '状态:draft(草稿)/pending(待审核)/rejected(已驳回)/published(发布中)/withdrawn(已撤回)',
+  ADD COLUMN audit_status VARCHAR(20) DEFAULT NULL
+    COMMENT '审核状态:approved/pending/rejected',
+  ADD COLUMN audit_reason TEXT DEFAULT NULL
+    COMMENT '驳回原因',
+  ADD COLUMN auditor_id BIGINT DEFAULT NULL
+    COMMENT '审核人 ID',
+  ADD COLUMN audited_at DATETIME DEFAULT NULL
+    COMMENT '审核时间';
+
+-- v1.x: 活动报名升级(报名截止时间+孩子/家长双收费+会员区分+创建人)
+ALTER TABLE activities
+  ADD COLUMN registration_deadline DATETIME DEFAULT NULL
+    COMMENT '报名截止时间(NULL=不限制, 默认=活动开始时间)',
+  ADD COLUMN child_fee INT DEFAULT 0
+    COMMENT '孩子·非会员价(分)',
+  ADD COLUMN child_member_fee INT DEFAULT 0
+    COMMENT '孩子·会员价(分), 0=免费',
+  ADD COLUMN parent_fee INT DEFAULT 0
+    COMMENT '家长·非会员价(分)',
+  ADD COLUMN parent_member_fee INT DEFAULT 0
+    COMMENT '家长·会员价(分), 0=免费',
+  ADD COLUMN creator_id BIGINT DEFAULT NULL
+    COMMENT '创建人userId(普通用户发布者, admin创建可为空)';
+
+-- ===== v1.3 家庭成员体系 =====
+
+-- 手动添加的家庭成员表(替代children表用于新成员)
+CREATE TABLE IF NOT EXISTS family_members (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id        BIGINT       NOT NULL COMMENT '所属家庭ID',
+    user_id          BIGINT       COMMENT '关联的用户ID(手机号匹配到已有用户时)',
+    nickname         VARCHAR(100) NOT NULL COMMENT '昵称',
+    phone            VARCHAR(20)  COMMENT '手机号',
+    avatar           VARCHAR(500) COMMENT '头像',
+    gender           VARCHAR(10)  COMMENT '性别: male/female',
+    birthday         DATE         COMMENT '出生日期',
+    generation INT COMMENT '辈分值(0=家庭创建者,+n=向上,-n=向下)',
+    is_spouse TINYINT(1) DEFAULT 0 COMMENT '是否配偶(同辈中)',
+    is_admin TINYINT(1) DEFAULT 0 COMMENT '是否家庭管理员: 1=管理员, 0=普通成员',
+    show_to_family INT DEFAULT 1 COMMENT '对家庭成员可见: 1=可见, 0=不可见',
+    total_points INT DEFAULT 0 COMMENT '总积分',
+    -- 孩子专属字段(原children表合并)
+    id_card VARCHAR(18) DEFAULT NULL COMMENT '身份证号(孩子)',
+    penalty_enabled TINYINT DEFAULT 1 COMMENT '是否扣减积分(0否1是)',
+    theme VARCHAR(32) DEFAULT 'default' COMMENT '主题',
+    system_points INT DEFAULT 0 COMMENT 'CF值(干预/平台奖励)',
+    streak_days INT DEFAULT 0 COMMENT '连续打卡天数',
+    last_task_date DATE COMMENT '上次任务日期',
+    focus_max_daily TINYINT DEFAULT 3 COMMENT '每日专注上限',
+    focus_remaining TINYINT DEFAULT 3 COMMENT '今日剩余专注次数',
+    focus_reset_date DATE COMMENT '专注次数重置日期',
+    -- 饮食模块扩展字段
+    is_living_together TINYINT(1) DEFAULT 1 COMMENT '是否同住(0=不同住,不出现在食谱推荐中)',
+    meal_with_breakfast_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日早餐同餐',
+    meal_with_lunch_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日午餐同餐',
+    meal_with_dinner_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日晚餐同餐',
+    meal_with_breakfast_weekend TINYINT(1) DEFAULT 0 COMMENT '周末早餐同餐',
+    meal_with_lunch_weekend TINYINT(1) DEFAULT 0 COMMENT '周末午餐同餐',
+    meal_with_dinner_weekend TINYINT(1) DEFAULT 0 COMMENT '周末晚餐同餐',
+    birth_hour VARCHAR(10) DEFAULT NULL COMMENT '出生时辰',
+    birth_weight INT DEFAULT NULL COMMENT '出生体重(g)',
+    birth_place VARCHAR(255) DEFAULT NULL COMMENT '出生地',
+    is_cesarean TINYINT(1) DEFAULT 0 COMMENT '是否剖腹产: 0否 1是',
+    status VARCHAR(16) DEFAULT 'active' COMMENT '成员状态: active=正常, recycled=回收箱',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_phone (phone)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员表(手动添加)';
+
+-- 家庭成员双向关系表(v2: 替代relationship_type/role_override)
+CREATE TABLE IF NOT EXISTS family_relationships (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id        BIGINT       NOT NULL COMMENT '所属家庭ID',
+    from_member_id   BIGINT       NOT NULL COMMENT '关系起点成员ID',
+    to_member_id     BIGINT       NOT NULL COMMENT '关系终点成员ID',
+    generation_value INT          NOT NULL COMMENT '辈分值: -1=子女, 0=同辈, 1=父母, 2=祖父母, 3=曾祖父母',
+    relationship_key VARCHAR(50)  COMMENT '关系标签key(冗余用于显示, 如father/mother/son/daughter/spouse/sibling)',
+    trust_score        DECIMAL(5,2) DEFAULT 0 COMMENT '信任度(答卷人视角)',
+    intimacy_score     DECIMAL(5,2) DEFAULT 0 COMMENT '亲密度(答卷人视角)',
+    communication_score DECIMAL(5,2) DEFAULT 0 COMMENT '沟通质量(答卷人视角)',
+    created_at       DATETIME     DEFAULT CURRENT_TIMESTAMP,
+    updated_at       DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_from_to (from_member_id, to_member_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_from (from_member_id),
+    INDEX idx_to (to_member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员双向关系表';
+
+-- 【已废弃】relationship_types 表已删除(generation 辈分模型替代)
+
+-- 用户收货地址表
+CREATE TABLE IF NOT EXISTS user_address (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    receiver_name VARCHAR(50) NOT NULL COMMENT '收货人姓名',
+    phone VARCHAR(20) NOT NULL COMMENT '收货人电话',
+    province VARCHAR(50) COMMENT '省',
+    city VARCHAR(50) COMMENT '市',
+    district VARCHAR(50) COMMENT '区',
+    street VARCHAR(200) COMMENT '详细地址',
+    is_default TINYINT DEFAULT 0 COMMENT '是否默认地址 1=是 0=否',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_is_default (is_default)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户收货地址表';
+
+-- =============================================
+-- 新手引导奖励表
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS onboarding_task (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    task_type VARCHAR(32) NOT NULL,
+    reward_points INT DEFAULT 0,
+    reward_energy INT DEFAULT 0,
+    status VARCHAR(16) DEFAULT 'PENDING',
+    completed_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 成长任务配置表
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS growth_task (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    type VARCHAR(16) NOT NULL COMMENT 'DAILY/NEWBIE/AI_GENERATED',
+    title VARCHAR(100) NOT NULL,
+    description TEXT,
+    reward_points INT DEFAULT 0,
+    reward_energy INT DEFAULT 0,
+    target_value INT DEFAULT 1,
+    task_key VARCHAR(32) UNIQUE NOT NULL,
+    enabled TINYINT DEFAULT 1,
+    sort_order INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    dimension VARCHAR(20) NULL COMMENT '五维维度(body/mind/wisdom/action/wealth)',
+    source_conversation_id VARCHAR(100) NULL COMMENT '来源AI会话ID',
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长任务配置表';
+
+CREATE TABLE IF NOT EXISTS growth_task_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    task_id BIGINT NOT NULL,
+    progress INT DEFAULT 0,
+    completed TINYINT DEFAULT 0,
+    claimed TINYINT DEFAULT 0,
+    date VARCHAR(10) COMMENT 'YYYY-MM-DD for daily, empty for newbie',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_date (user_id, date),
+    INDEX idx_task_id (task_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长任务用户进度表';
+
+-- =============================================
+-- 优惠券配置表
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS coupon (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(64) NOT NULL,
+    type VARCHAR(16) DEFAULT 'FIXED',
+    value INT DEFAULT 0 COMMENT '金额 单位:分',
+    min_spend INT DEFAULT 0 COMMENT '最低消费 单位:分',
+    applicable_to VARCHAR(32) DEFAULT 'ALL',
+    valid_from DATETIME,
+    valid_until DATETIME,
+    total_count INT DEFAULT 0,
+    used_count INT DEFAULT 0,
+    discount_rate INT COMMENT '折扣券折扣率(千分比,9000=9折)',
+    product_id BIGINT COMMENT '绑定商品ID,NULL=全场通用',
+    family_id BIGINT DEFAULT NULL COMMENT '绑定家庭ID(NULL=不绑定)',
+    points_price INT DEFAULT 0 COMMENT '积分兑换价,0=不可积分兑换',
+    grant_type VARCHAR(16) COMMENT '发放规则:JOIN/PERIODIC/POPULATION,NULL=不自动发放',
+    grant_level_code VARCHAR(32) COMMENT '发放规则对应会员等级',
+    grant_period VARCHAR(16) COMMENT 'PERIODIC周期:MONTHLY/QUARTERLY',
+    grant_quantity INT DEFAULT 1 COMMENT '每次发放数量',
+    status VARCHAR(16) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE=启用/DISABLED=停用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_valid_period (valid_from, valid_until)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券配置表';
+
+CREATE TABLE IF NOT EXISTS user_coupon (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    coupon_id BIGINT NOT NULL,
+    status VARCHAR(16) DEFAULT 'AVAILABLE',
+    received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    used_at DATETIME,
+    order_id BIGINT,
+    INDEX idx_user_coupon (user_id, coupon_id),
+    INDEX idx_status (status),
+    INDEX idx_order_id (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户优惠券表';
+
+-- =============================================
+-- 邀请里程碑表
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS invite_milestone (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    milestone VARCHAR(32) NOT NULL,
+    reward_points INT DEFAULT 0,
+    reward_energy INT DEFAULT 0,
+    status VARCHAR(16) DEFAULT 'PENDING',
+    achieved_at DATETIME,
+    claimed_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_milestone (user_id, milestone)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请里程碑表';
+
+-- =============================================
+-- 推广排行榜周记录表
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS referral_leaderboard (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    week_start VARCHAR(10) NOT NULL COMMENT '周一日期 YYYY-MM-DD',
+    referral_count INT DEFAULT 0,
+    `rank` INT,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE INDEX idx_user_week (user_id, week_start),
+    INDEX idx_week_rank (week_start, referral_count DESC)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广排行榜周记录';
+
+-- =============================================
+-- 虚拟服务商团队(销售体系)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS `distribution_systems` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `name` VARCHAR(100) NOT NULL COMMENT '团队名称',
+    `description` TEXT COMMENT '团队描述',
+    `owner_id` BIGINT COMMENT '创建人(服务商用户ID)',
+    `profit_rate` INT DEFAULT 0 COMMENT '利润分成比例(bps, 如2000=20%)',
+    `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='虚拟服务商团队(销售体系)';
+
+CREATE TABLE IF NOT EXISTS `distribution_relations` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `system_id` BIGINT NOT NULL COMMENT '所属团队ID',
+    `user_id` BIGINT NOT NULL COMMENT '服务商用户ID',
+    `parent_id` BIGINT COMMENT '上级服务商用户ID',
+    `depth` INT DEFAULT 0 COMMENT '层级深度(0=root)',
+    `invite_code` VARCHAR(64) DEFAULT NULL COMMENT '邀请码',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_system (system_id),
+    INDEX idx_user (user_id),
+    INDEX idx_parent (parent_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='团队-服务商关系(同一人在不同团队可处于不同位置)';
+
+-- =============================================
+-- 成长任务种子数据
+-- =============================================
+
+INSERT IGNORE INTO growth_task (type, title, description, reward_points, reward_energy, target_value, task_key, sort_order) VALUES
+('DAILY', '每日签到', '完成每日签到', 20, 3, 1, 'DAILY_CHECKIN', 1),
+('DAILY', '完成任务', '完成一个任务', 30, 5, 1, 'DAILY_TASK', 2),
+('DAILY', '分享文章', '分享一篇好文给好友', 10, 2, 1, 'DAILY_SHARE', 3),
+('DAILY', 'AI对话', '与AI助手进行一次对话', 15, 3, 1, 'DAILY_AI', 4);
+
+-- ========== 全域指标定义表 ==========
+-- ===== v1.5 家庭文化镜:关系兼容性配置 =====
+
+-- 生肖配对规则表
+CREATE TABLE IF NOT EXISTS zodiac_pairing (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    zodiac_code_1    VARCHAR(20) NOT NULL COMMENT '生肖代码1',
+    zodiac_code_2    VARCHAR(20) NOT NULL COMMENT '生肖代码2',
+    pairing_type     VARCHAR(20) NOT NULL COMMENT '配对类型: liuhe/sanhe/banhe/liuchong/xianghai/xiangxing/ping',
+    compatibility_score INT DEFAULT 50 COMMENT '兼容分 0-100',
+    description      TEXT COMMENT '配对解读',
+    advice           TEXT COMMENT '相处建议',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_zodiac_pair (zodiac_code_1, zodiac_code_2)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生肖配对规则表';
+
+-- 十二生肖六合/三合/六冲种子数据
+INSERT IGNORE INTO zodiac_pairing (zodiac_code_1, zodiac_code_2, pairing_type, compatibility_score, description, advice) VALUES
+('rat', 'ox', 'liuhe', 100, '子丑六合,天生一对', '你们是最佳拍档,互相补充,默契十足'),
+('rat', 'monkey', 'sanhe', 85, '申子半合水局,默契度高', '智慧结合,创意无限,适合一起做规划'),
+('rat', 'dragon', 'sanhe', 85, '子辰半合水局,相辅相成', '一个机灵一个大气,共同成就大事'),
+('rat', 'horse', 'liuchong', 20, '子午相冲,水火不容', '价值观差异大,需要多包容理解'),
+('rat', 'tiger', 'xiangxing', 30, '寅子相刑,容易误会', '沟通方式不同,建议用行动代替言语'),
+('rat', 'rabbit', 'xiangxing', 30, '卯子相刑,互不相让', '各执己见时先冷静再沟通'),
+('rat', 'sheep', 'xianghai', 35, '子未相害,暗中较劲', '表面和谐但内心可能有竞争意识'),
+('ox', 'tiger', 'ping', 55, '牛虎平平,互敬互重', '踏实遇上冒险,可以互相学习'),
+('ox', 'rabbit', 'xianghai', 35, '丑卯相害,各怀心思', '建议开诚布公,减少猜测'),
+('ox', 'dragon', 'ping', 55, '牛龙平平,求同存异', '务实和理想主义的碰撞'),
+('ox', 'snake', 'sanhe', 85, '巳酉丑三合金局,配合默契', '务实加智慧,黄金搭档'),
+('ox', 'horse', 'xianghai', 35, '丑午相害,相处需技巧', '步调不同,需要彼此迁就'),
+('ox', 'sheep', 'liuchong', 20, '丑未相冲,针锋相对', '一个固执一个犟,需要第三方调和'),
+('tiger', 'rabbit', 'ping', 55, '虎兔平平,各有所长', '虎的魄力配兔的细腻,互补型'),
+('tiger', 'dragon', 'ping', 55, '虎龙平平,互相欣赏', '都有王者风范,但要注意谁说了算'),
+('tiger', 'snake', 'xianghai', 35, '寅巳相害,互相防备', '一个直率一个深沉,需要更多信任'),
+('tiger', 'horse', 'sanhe', 85, '寅午半合火局,一拍即合', '热情似火,志同道合的行动派'),
+('tiger', 'sheep', 'ping', 55, '虎羊平平,温柔与力量', '虎保护羊,羊温暖虎'),
+('tiger', 'monkey', 'liuchong', 20, '寅申相冲,竞争关系', '针尖对麦芒,需要在竞争中找合作'),
+('tiger', 'pig', 'liuhe', 100, '寅亥六合,情投意合', '虎猪相合,真诚相待的最佳组合'),
+('rabbit', 'dragon', 'xianghai', 35, '卯辰相害,表面和谐', '温和遇到强势,需要找到平衡'),
+('rabbit', 'snake', 'ping', 55, '兔蛇平平,温和相处', '都是内敛型,适合安静的陪伴'),
+('rabbit', 'horse', 'ping', 55, '兔马平平,一动一静', '一个爱静一个爱动,互相调剂'),
+('rabbit', 'sheep', 'sanhe', 85, '卯未半合木局,志趣相投', '文艺组合,品味相投'),
+('rabbit', 'monkey', 'xiangxing', 30, '卯申相刑,互不买账', '思维方式差异大,需要耐心'),
+('rabbit', 'rooster', 'liuchong', 20, '卯酉相冲,水火难容', '生活节奏完全不同,需要大包容'),
+('rabbit', 'dog', 'liuhe', 100, '卯戌六合,忠诚相伴', '兔狗相合,彼此忠诚的最佳关系'),
+('dragon', 'snake', 'ping', 55, '龙蛇平平,同源相惜', '都有神秘气质,惺惺相惜'),
+('dragon', 'horse', 'ping', 55, '龙马平平,各有天地', '一个在天一个在地,互不干涉'),
+('dragon', 'sheep', 'xiangxing', 30, '辰未相刑,暗中别扭', '看似无事却有暗流,多交流可化解'),
+('dragon', 'monkey', 'sanhe', 85, '申子辰三合水局,天生队友', '最佳事业组合,互相成就'),
+('dragon', 'rooster', 'liuhe', 100, '辰酉六合,珠联璧合', '龙凤配,传统的最佳搭档'),
+('dragon', 'dog', 'liuchong', 20, '辰戌相冲,正面冲突', '硬碰硬的关系,需要智慧化解'),
+('snake', 'horse', 'xiangxing', 30, '巳午相刑,热度不同', '蛇冷静马热情,温度要调和'),
+('snake', 'sheep', 'sanhe', 85, '巳酉丑三合金局,智慧搭档', '沉稳加温柔,互相信任的深层关系'),
+('snake', 'monkey', 'liuhe', 100, '巳申六合,智谋相投', '聪明人之间的默契'),
+('snake', 'rooster', 'sanhe', 85, '巳酉丑三合金局,配合无间', '细节控和完美主义者的理想组合'),
+('snake', 'pig', 'liuchong', 20, '巳亥相冲,南辕北辙', '一个深沉一个随性,需要大幅磨合'),
+('horse', 'sheep', 'liuhe', 100, '午未六合,温情相依', '马羊相合,温暖体贴的最佳关系'),
+('horse', 'monkey', 'xiangxing', 30, '午申相刑,互不理解', '行动派和思考派的碰撞'),
+('horse', 'rooster', 'xiangxing', 30, '午酉相刑,意气用事', '都容易冲动,需要一个冷静者调和'),
+('horse', 'dog', 'sanhe', 85, '寅午戌三合火局,行动搭档', '热情忠诚组合,一起行动效率高'),
+('horse', 'pig', 'liuhe', 100, '亥午暗合,心有灵犀', '一静一动却暗合,出人意料的默契'),
+('sheep', 'monkey', 'xiangxing', 30, '未申相刑,互挑毛病', '容易在小事上相互挑剔'),
+('sheep', 'rooster', 'ping', 55, '羊鸡平平,互相欣赏', '羊的细腻欣赏鸡的精致'),
+('sheep', 'dog', 'xiangxing', 30, '未戌相刑,各怀心事', '需要建立更深层的信任'),
+('sheep', 'pig', 'sanhe', 85, '亥卯未三合木局,温柔组合', '温和的三合组,舒适无压力的关系'),
+('monkey', 'rooster', 'ping', 55, '猴鸡平平,互相启发', '聪明人的相遇,火花四射'),
+('monkey', 'dog', 'xiangxing', 30, '申戌相刑,互不信任', '需要时间证明真心'),
+('monkey', 'pig', 'liuhe', 100, '申亥六合,智勇双全', '猴子机智猪包容,最佳朋友组合'),
+('rooster', 'dog', 'xiangxing', 30, '酉戌相刑,互相挑剔', '完美主义遇见忠诚卫士,互相看不惯'),
+('rooster', 'pig', 'ping', 55, '鸡猪平平,各取所需', '鸡的精明和猪的豁达互相补充'),
+('dog', 'pig', 'sanhe', 85, '寅午戌三合火局,忠诚相伴', '忠诚的守护组合,互相支持');
+
+-- 血型配对表
+CREATE TABLE IF NOT EXISTS blood_type_pairing (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    blood_type_1     VARCHAR(5) NOT NULL COMMENT '血型1',
+    blood_type_2     VARCHAR(5) NOT NULL COMMENT '血型2',
+    compatibility_score INT DEFAULT 50 COMMENT '兼容分 0-100',
+    description      TEXT COMMENT '配对描述',
+    advice           TEXT COMMENT '相处建议',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_blood_pair (blood_type_1, blood_type_2)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='血型配对表';
+
+INSERT IGNORE INTO blood_type_pairing (blood_type_1, blood_type_2, compatibility_score, description, advice) VALUES
+('A', 'A', 80, '同型相吸,稳重默契', '都能理解对方的细腻敏感,但注意不要一起钻牛角尖'),
+('A', 'B', 50, '互补型,有吸引力也有摩擦', 'B型的自由奔放吸引A型,但A型的严谨可能让B型感到压力'),
+('A', 'O', 75, 'A主内O主外,搭配和谐', 'O型的大气包容A型的细致,是最常见的和谐组合'),
+('A', 'AB', 70, 'AB型善解人意,A型感到被理解', 'AB型的理性刚好平衡A型的感性'),
+('B', 'B', 75, '同型相投,自由自在', '都给彼此空间,但可能缺乏规划'),
+('B', 'O', 70, 'O型包容B型,相处轻松', 'O型的稳定给了B型安全感'),
+('B', 'AB', 65, '互相欣赏,各有天地', 'B型的创意和AB型的分析能力互补'),
+('O', 'O', 85, '强强联合,行动力爆棚', '都是行动派,但要注意不要都太强势'),
+('O', 'AB', 75, 'O型领导,AB型军师', 'O型的大方向加AB型的精细思维,无敌组合'),
+('AB', 'AB', 60, '互相理解但可能过度理性', '两个理性派,别忘了情感交流');
+
+-- 生命灵数关系解读表
+CREATE TABLE IF NOT EXISTS life_number_relationship (
+    id               BIGINT AUTO_INCREMENT PRIMARY KEY,
+    relationship_number INT NOT NULL COMMENT '关系数字 1-9',
+    title            VARCHAR(100) COMMENT '关系主题',
+    description      TEXT COMMENT '关系解读',
+    advice           TEXT COMMENT '相处建议',
+    color_hex        VARCHAR(10) COMMENT '代表色',
+    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_relationship_num (relationship_number)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生命灵数关系解读表';
+
+INSERT IGNORE INTO life_number_relationship (relationship_number, title, description, advice, color_hex) VALUES
+(1, '开创者关系', '你们在一起充满创造力和领导力,彼此激励去实现目标', '注意不要互相竞争谁说了算,明确分工才能发挥最大能量', '#FF4444'),
+(2, '合作伙伴关系', '你们是天生的搭档,善于倾听和配合,营造和谐氛围', '过于迁就对方可能会失去自我,保持适度独立才能长久', '#FF8C42'),
+(3, '创意火花关系', '在一起充满欢乐和创造力,社交圈会因为你们而扩大', '注意不要太表面化,偶尔需要深度的对话来维系情感', '#FFD700'),
+(4, '建设者关系', '你们是务实稳健的组合,能一起把想法变成现实', '生活不能只有工作和责任,记得给彼此创造惊喜和浪漫', '#10B981'),
+(5, '探险者关系', '在一起充满变化和新鲜感,喜欢一起探索未知', '变化太多可能会让关系缺乏安全感,寻找一些共同的固定仪式', '#3B82F6'),
+(6, '守护者关系', '你们的关系以关爱和责任为基础,彼此是最坚实的后盾', '付出太多可能会感到疲惫,学会接受对方的爱和照顾', '#6366F1'),
+(7, '智者关系', '在一起有深度的精神交流和思想碰撞,追求真理', '不要沉浸在各自的世界里,记得用简单的方式表达爱', '#8B5CF6'),
+(8, '成就者关系', '你们是强强联合,追求物质和事业上的共同成就', '在追求成功的同时不要忽视感情的温度,平衡事业和生活', '#F59E0B'),
+(9, '圆满者关系', '在一起有强烈的宿命感,彼此理解和支持,是灵魂伴侣', '太理想化可能会失望,接受对方的不完美才能走得更远', '#FF6B9D');
+
+CREATE TABLE IF NOT EXISTS indicator_definitions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    domain VARCHAR(50) NOT NULL COMMENT '领域: physical/mental/nutrition/behavior/social',
+    category VARCHAR(50) NOT NULL COMMENT '分类: 舌色/苔色/情绪/...',
+    code VARCHAR(50) NOT NULL UNIQUE COMMENT '编码: tongue_color/coating_color/...',
+    name VARCHAR(100) NOT NULL COMMENT '显示名: 舌体颜色/苔色/...',
+    data_type VARCHAR(20) NOT NULL DEFAULT 'enum' COMMENT '数据类型: enum/number/string',
+    options JSON COMMENT '枚举值列表: ["淡红","红","绛","青紫"] 或 null',
+    unit VARCHAR(50) COMMENT '单位',
+    ref_range VARCHAR(200) COMMENT '参考范围',
+    source_kind VARCHAR(32) COMMENT '指标来源: report_indicator/bacteria/food_index/dimension_score',
+    report_type_scope VARCHAR(32) COMMENT '适用报告类型(type_id,空=通用)',
+    value_type VARCHAR(16) COMMENT '语义类型: numeric/categorical/level',
+    good_direction VARCHAR(16) DEFAULT 'neutral' COMMENT '变化优劣方向: higher_is_better/lower_is_better/in_range_is_better/neutral',
+    dimension_code VARCHAR(16) COMMENT '关联五维(body/mind/wisdom/action/wealth,可空)',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status TINYINT DEFAULT 1 COMMENT '0=禁用 1=启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全域指标定义';
+
+-- ========== 全域指标值表 ==========
+CREATE TABLE IF NOT EXISTS indicator_values (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    source_type VARCHAR(50) NOT NULL COMMENT '来源: tongue_record/health_report/assessment',
+    source_id BIGINT NOT NULL COMMENT '来源记录ID',
+    definition_id BIGINT NOT NULL COMMENT 'FK→indicator_definitions.id',
+    value VARCHAR(200) COMMENT '值',
+    numeric_value DECIMAL(10,2) COMMENT '数值(number类型时用)',
+    remark TEXT COMMENT '备注',
+    subject_id BIGINT COMMENT '观测对象(family_members.id)',
+    family_id BIGINT COMMENT '所属家庭(family_members.family_id)',
+    report_id BIGINT COMMENT '来源报告(health_reports.id)',
+    report_date DATE COMMENT '真实报告日期(时序查询键)',
+    unit VARCHAR(32) COMMENT '观测单位',
+    status VARCHAR(32) COMMENT '观测状态快照(正常/偏高/偏低等)',
+    recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_source_definition (source_type, source_id, definition_id),
+    INDEX idx_source (source_type, source_id),
+    INDEX idx_definition (definition_id),
+    INDEX idx_subject_def_date (subject_id, definition_id, report_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='全域指标值';
+
+-- ========== 舌诊主记录表 ==========
+CREATE TABLE IF NOT EXISTS tongue_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    child_id BIGINT COMMENT '孩子ID',
+    image_url VARCHAR(500) COMMENT '舌象图片URL',
+    overall_assessment TEXT COMMENT '总体评估',
+    status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/confirmed',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_member (member_id, child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='舌诊主记录';
+
+-- ========== 种子数据:舌诊7项指标定义 ==========
+INSERT IGNORE INTO indicator_definitions (domain, category, code, name, data_type, options, sort_order, status) VALUES
+('physical', '舌色', 'tongue_color', '舌体颜色', 'enum', '["淡红","红","绛","青紫"]', 1, 1),
+('physical', '苔色', 'coating_color', '苔色', 'enum', '["薄白","白","黄","灰黑"]', 2, 1),
+('physical', '苔质', 'coating_texture', '苔质', 'enum', '["润","燥","滑","涩","腻"]', 3, 1),
+('physical', '裂纹', 'fissure', '裂纹程度', 'enum', '["无","轻","中","重"]', 4, 1),
+('physical', '齿痕', 'teeth_mark', '齿痕程度', 'enum', '["无","轻","中","重"]', 5, 1),
+('physical', '舌下络脉', 'sublingual_vein', '舌下络脉', 'enum', '["正常","轻","中","重"]', 6, 1),
+('physical', '体质', 'constitution', '体质辨识', 'enum', '["平和质","气虚质","阳虚质","阴虚质","湿热质","痰湿质","血瘀质","气郁质","特禀质"]', 7, 1);
+
+-- =============================================
+-- 活动报名 & 能量规则引擎 v1.4 - 字段扩展
+-- 通过 DatabaseInitializer.java 在 ALTER 阶段执行
+-- =============================================
+
+-- 活动表扩展字段
+ALTER TABLE activities ADD COLUMN require_registration TINYINT DEFAULT 1 COMMENT '是否需要报名: 1=需要, 0=直接参与';
+ALTER TABLE activities ADD COLUMN require_checkin_review TINYINT DEFAULT 1 COMMENT '打卡是否需要审核: 1=需要, 0=直接通过';
+ALTER TABLE activities ADD COLUMN energy_penalty_unregistered INT DEFAULT 5 COMMENT '未报名打卡的行能量扣减值';
+
+-- ============================================================
+-- 新增:系统配置表(角色年龄阈值等)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS system_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
+    config_value TEXT COMMENT '配置值',
+    description TEXT COMMENT '说明',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
+
+-- ============================================================
+-- 数据迁移: children → family_members + roleOverride 变更
+-- 已由 DatabaseInitializer 迁移322 落地执行(幂等,自动 DROP children 表)
+-- 以下 SQL 仅为迁移逻辑存档,勿手动重复执行
+-- ============================================================
+
+-- 1. family_members.capability_role → role_override (已在 DDL 中 ALTER)
+-- 2. relationship_types.capability_role → default_role (已在 DDL 中 ALTER)
+
+-- 2. 迁移 children 数据到 family_members(匹配更新:family_members 已存在该成员记录时更新)
+UPDATE family_members fm
+INNER JOIN children c ON fm.family_id = c.family_id AND fm.nickname = c.nickname AND fm.user_id = c.user_id
+SET
+    fm.id_card = c.id_card,
+    fm.penalty_enabled = c.penalty_enabled,
+    fm.theme = c.theme,
+    fm.system_points = c.system_points,
+    fm.streak_days = c.streak_days,
+    fm.last_task_date = c.last_task_date,
+    fm.focus_max_daily = c.focus_max_daily,
+    fm.focus_remaining = c.focus_remaining,
+    fm.focus_reset_date = c.focus_reset_date,
+    fm.total_points = c.total_points,
+    fm.show_to_family = c.show_to_family,
+    fm.birthday = c.birthday,
+    fm.gender = c.gender,
+    fm.phone = c.phone,
+    fm.role_override = 'child';
+
+-- 3. 迁移 children 数据到 family_members(不匹配则插入新记录)
+INSERT INTO family_members (family_id, user_id, nickname, birthday, gender, phone,
+  id_card, penalty_enabled, theme, system_points, streak_days, last_task_date,
+  focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points,
+  role_override, created_at, updated_at)
+SELECT family_id, user_id, nickname, birthday, gender, phone,
+  id_card, penalty_enabled, theme, system_points, streak_days, last_task_date,
+  focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points,
+  'child', created_at, updated_at
+FROM children
+WHERE NOT EXISTS (
+    SELECT 1 FROM family_members fm2
+    WHERE fm2.family_id = children.family_id
+      AND fm2.nickname = children.nickname
+      AND fm2.user_id = children.user_id
+);
+
+-- 4. 插入 system_config 初始数据
+INSERT INTO system_config (config_key, config_value, description) VALUES
+  ('child_age_threshold', '18', '未成年年龄分界线(岁)'),
+  ('elderly_age_threshold', '65', '老年年龄分界线(岁)')
+ON DUPLICATE KEY UPDATE config_value = VALUES(config_value);
+
+-- 5. 删除 children 表(已由 DatabaseInitializer 迁移322 自动执行,勿手动重复)
+-- DROP TABLE IF EXISTS children;
+
+-- ============================================================
+-- 购物车表
+-- ============================================================
+CREATE TABLE IF NOT EXISTS cart (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    quantity INT NOT NULL DEFAULT 1 COMMENT '数量',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_product (user_id, product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车';
+
+-- ============================================================
+-- 供应商销售体系
+-- ============================================================
+
+CREATE TABLE IF NOT EXISTS supply_system (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '体系名称/供应商名称',
+    admin_id BIGINT NOT NULL COMMENT '管理员用户ID',
+    settlement_period_days INT NOT NULL DEFAULT 30 COMMENT '账期(天)',
+    platform_profit_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '平台留利比例(%)',
+    description TEXT COMMENT '描述',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled',
+    contact_name VARCHAR(50) COMMENT '联系人',
+    contact_phone VARCHAR(20) COMMENT '联系电话',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY idx_admin_id (admin_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商体系(含电商供应商)';
+
+CREATE TABLE IF NOT EXISTS supply_system_member (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    system_id BIGINT NOT NULL COMMENT '体系ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    role VARCHAR(20) NOT NULL DEFAULT 'member' COMMENT '角色: admin/member',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/inactive',
+    joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_system_user (system_id, user_id),
+    KEY idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体系成员';
+
+CREATE TABLE IF NOT EXISTS supply_settlement (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    system_id BIGINT NOT NULL COMMENT '体系ID',
+    period_start DATE NOT NULL COMMENT '结算周期开始',
+    period_end DATE NOT NULL COMMENT '结算周期结束',
+    total_sales INT NOT NULL DEFAULT 0 COMMENT '销售总额(分)',
+    platform_profit INT NOT NULL DEFAULT 0 COMMENT '平台留利金额(分)',
+    supplier_payout INT NOT NULL DEFAULT 0 COMMENT '供应商实付(分)',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/settled',
+    settled_at DATETIME COMMENT '结算时间',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_system_id (system_id),
+    KEY idx_period (period_start, period_end)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算记录';
+
+CREATE TABLE IF NOT EXISTS supply_settlement_detail (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    settlement_id BIGINT NOT NULL COMMENT '结算ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    product_name VARCHAR(200) NOT NULL COMMENT '商品名称',
+    amount INT NOT NULL COMMENT '订单金额(分)',
+    platform_fee INT NOT NULL COMMENT '平台留利(分)',
+    supplier_id BIGINT COMMENT '收款供应商用户ID',
+    supplier_name VARCHAR(100) COMMENT '收款供应商名称',
+    payout INT NOT NULL COMMENT '供应商实付(分)',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    KEY idx_settlement_id (settlement_id),
+    KEY idx_order_id (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算明细';
+
+-- =============================================
+-- 收货人与购买信息采集
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS consignees (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '所属用户ID',
+    name VARCHAR(100) NOT NULL COMMENT '收货人姓名',
+    phone VARCHAR(20) COMMENT '手机号',
+    id_card VARCHAR(20) COMMENT '身份证号',
+    hand_signature TEXT COMMENT '手签名(base64图片或URL)',
+    ethnicity VARCHAR(20) COMMENT '民族',
+    blood_type VARCHAR(20) COMMENT '血型',
+    address VARCHAR(500) COMMENT '收货地址',
+    province VARCHAR(50) DEFAULT NULL COMMENT '省份',
+    city VARCHAR(50) DEFAULT NULL COMMENT '城市',
+    district VARCHAR(50) DEFAULT NULL COMMENT '区县',
+    street VARCHAR(100) DEFAULT NULL COMMENT '详细地址',
+    is_default TINYINT(1) DEFAULT 0 COMMENT '是否默认收货人',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货人信息(含购买所需特殊信息)';
+
+CREATE TABLE IF NOT EXISTS product_purchase_fields (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    field_key VARCHAR(50) NOT NULL COMMENT '字段标识: name/phone/id_card/hand_signature/ethnicity/blood_type',
+    field_name VARCHAR(100) NOT NULL COMMENT '显示名: 收货人姓名/手机号/身份证号/手签名/民族/血型',
+    is_required TINYINT(1) DEFAULT 1 COMMENT '是否必填 1=是 0=否',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_product_id (product_id),
+    UNIQUE KEY uk_product_field (product_id, field_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品购买信息字段配置';
+
+-- AI管家会话表
+CREATE TABLE IF NOT EXISTS butler_sessions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    title VARCHAR(200) DEFAULT '新会话' COMMENT '会话标题',
+    conversation_id VARCHAR(100) COMMENT 'Dify对话ID',
+    context TEXT COMMENT '上下文信息(JSON)',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active(进行中)/archived(已归档)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_status (status),
+    INDEX idx_user_status (user_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI管家会话表';
+
+-- 管家服务提供商档案
+CREATE TABLE IF NOT EXISTS butler_profiles (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    tier VARCHAR(10),
+    status VARCHAR(20) DEFAULT 'pending',
+    member_count INT DEFAULT 0,
+    max_members INT DEFAULT 50,
+    annual_fee_l1 BIGINT,
+    annual_fee_l2 BIGINT,
+    description TEXT,
+    certificates TEXT,
+    reject_reason VARCHAR(500),
+    commission_rate_l1 INT DEFAULT 1000,
+    commission_rate_l2 INT DEFAULT 5000,
+    accepting TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否接受新家庭绑定: 0=停接/1=接单',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    approved_at DATETIME,
+    UNIQUE KEY uk_user_id (user_id),
+    INDEX idx_status (status),
+    INDEX idx_tier (tier)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家服务提供商档案';
+
+-- 管家服务记录
+CREATE TABLE IF NOT EXISTS butler_service_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    butler_id BIGINT NOT NULL,
+    member_user_id BIGINT NOT NULL,
+    subscription_id BIGINT,
+    service_type VARCHAR(20) NOT NULL,
+    amount_p BIGINT NOT NULL,
+    platform_fee_p BIGINT DEFAULT 0,
+    butler_amount_p BIGINT DEFAULT 0,
+    member_tier VARCHAR(10),
+    `year_month` VARCHAR(7),
+    remark VARCHAR(500),
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_butler_id (butler_id),
+    INDEX idx_member_user_id (member_user_id),
+    INDEX idx_year_month (`year_month`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家服务记录';
+
+-- 管家月度佣金结算单
+CREATE TABLE IF NOT EXISTS butler_commission_settlements (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    butler_id BIGINT NOT NULL,
+    `year_month` VARCHAR(7) NOT NULL,
+    member_count INT DEFAULT 0,
+    annual_base_amount_p BIGINT DEFAULT 0,
+    annual_butler_amount_p BIGINT DEFAULT 0,
+    annual_platform_amount_p BIGINT DEFAULT 0,
+    per_session_total_p BIGINT DEFAULT 0,
+    per_session_platform_total_p BIGINT DEFAULT 0,
+    per_session_butler_total_p BIGINT DEFAULT 0,
+    total_butler_amount_p BIGINT DEFAULT 0,
+    status VARCHAR(20) DEFAULT 'pending',
+    settled_at DATETIME,
+    settled_by BIGINT,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_butler_month (butler_id, `year_month`),
+    INDEX idx_status (status),
+    INDEX idx_year_month (`year_month`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家月度佣金结算单';
+
+-- 会员-管家分配记录
+CREATE TABLE IF NOT EXISTS butler_assignments (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭 ID',
+    butler_user_id BIGINT NOT NULL COMMENT '管家用户 ID',
+    subscription_id BIGINT COMMENT '关联订阅 ID',
+    level VARCHAR(10) NOT NULL COMMENT '订阅级别 L1/L2',
+    assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_family_assignment (family_id),
+    INDEX idx_butler_user_id (butler_user_id),
+    INDEX idx_subscription_id (subscription_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员 - 管家分配记录';
+
+-- 家庭订阅记录
+CREATE TABLE IF NOT EXISTS member_subscription (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    `level` VARCHAR(10) NOT NULL DEFAULT 'L1' COMMENT 'L1=一生一世, L2=久久一生',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active/expired/cancelled',
+    start_time DATETIME DEFAULT NULL COMMENT '开始时间',
+    expire_time DATETIME DEFAULT NULL COMMENT '到期时间',
+    payment_id BIGINT DEFAULT NULL COMMENT '关联订单ID',
+    auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费(L2)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status),
+    INDEX idx_expire_time (expire_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭订阅记录';
+
+-- 订阅订单
+CREATE TABLE IF NOT EXISTS member_subscription_order (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    `level` VARCHAR(10) NOT NULL COMMENT 'L1/L2',
+    amount INT NOT NULL COMMENT '金额(分)',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/cancelled/refunded',
+    payment_type VARCHAR(20) DEFAULT NULL COMMENT '支付方式 wechat/alipay',
+    transaction_id VARCHAR(64) DEFAULT NULL COMMENT '微信支付订单号',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    paid_at DATETIME DEFAULT NULL,
+    UNIQUE KEY uk_order_no (order_no),
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订阅订单';
+
+-- 权益使用日志
+CREATE TABLE IF NOT EXISTS subscription_benefit_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    subscription_id BIGINT NOT NULL COMMENT '订阅ID',
+    benefit_code VARCHAR(50) NOT NULL COMMENT '权益代码',
+    used_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    detail TEXT COMMENT '使用详情(JSON)',
+    INDEX idx_subscription_id (subscription_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权益使用日志';
+
+-- 推广等级 R0-R4
+CREATE TABLE IF NOT EXISTS promotion_tier (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    tier VARCHAR(20) NOT NULL DEFAULT 'R0' COMMENT 'R0利他者/R1传福人/R2聚能师/R3启慧者/R4传承者',
+    total_team_size INT DEFAULT 0 COMMENT '三层累计总人数',
+    total_referral_earnings INT DEFAULT 0 COMMENT '累计推荐佣金(分)',
+    total_share_earnings INT DEFAULT 0 COMMENT '累计消费返利(分)',
+    rate_percent INT DEFAULT 0 COMMENT '当前返佣比例(%)',
+    promoted_at DATETIME DEFAULT NULL COMMENT '最近晋级时间',
+    last_change_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_id (user_id),
+    INDEX idx_tier (tier)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广等级';
+
+-- 推广等级变更记录
+CREATE TABLE IF NOT EXISTS promotion_tier_change_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    old_tier VARCHAR(20) DEFAULT NULL COMMENT '变更前等级',
+    new_tier VARCHAR(20) NOT NULL COMMENT '变更后等级',
+    reason VARCHAR(50) DEFAULT NULL COMMENT 'qualify升级/demote降级/admin管理员调整',
+    changed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广等级变更记录';
+
+-- 推荐关系树(三层)
+CREATE TABLE IF NOT EXISTS referral_tree (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    parent_id BIGINT NOT NULL COMMENT '推荐人ID',
+    child_id BIGINT NOT NULL COMMENT '被推荐人ID',
+    level INT NOT NULL COMMENT '层级深度(1/2/3)',
+    path VARCHAR(255) DEFAULT NULL COMMENT 'materialized path如/1/2/3/',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_parent_child (parent_id, child_id),
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_level (level)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐关系树';
+
+-- 产品P点配置
+CREATE TABLE IF NOT EXISTS product_ppoint (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '产品ID',
+    ppoint INT NOT NULL DEFAULT 0 COMMENT 'P点值(平台利润分)',
+    start_date DATE DEFAULT NULL COMMENT '生效日期',
+    end_date DATE DEFAULT NULL COMMENT '失效日期',
+    created_by BIGINT DEFAULT NULL COMMENT '创建人',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_product_id (product_id),
+    INDEX idx_dates (start_date, end_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品P点配置';
+
+-- 测评商品扩展信息
+CREATE TABLE IF NOT EXISTS assessment_products (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联products表',
+    assessment_type VARCHAR(20) NOT NULL COMMENT 'single=单包, bundle=套餐',
+    total_sessions INT NOT NULL DEFAULT 1 COMMENT '包含测评次数',
+    validity_days INT NOT NULL DEFAULT 365 COMMENT '有效期(天)',
+    guide_scope VARCHAR(50) DEFAULT 'all' COMMENT '规划师范围: all/assigned/auto',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_product_id (product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评商品扩展信息';
+
+-- 成长档案关联商品配置
+CREATE TABLE IF NOT EXISTS growth_archive_products (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联products表',
+    status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用',
+    sort_order INT DEFAULT 0 COMMENT '展示排序(升序)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_product_id (product_id),
+    KEY idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长档案关联商品配置';
+
+-- 测评额度表
+CREATE TABLE IF NOT EXISTS assessment_quotas (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    quota_no VARCHAR(32) NOT NULL COMMENT '额度编号',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    child_id BIGINT COMMENT '指定孩子ID(NULL=不限)',
+    product_order_id BIGINT NOT NULL COMMENT '来源订单ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    product_name VARCHAR(100) NOT NULL COMMENT '商品名快照',
+    total_sessions INT NOT NULL DEFAULT 1 COMMENT '总次数',
+    remaining_sessions INT NOT NULL DEFAULT 1 COMMENT '剩余次数',
+    validity_start DATETIME NOT NULL COMMENT '有效期开始',
+    validity_end DATETIME NOT NULL COMMENT '有效期截止',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active/expired/used_all/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family (family_id),
+    INDEX idx_order (product_order_id),
+    UNIQUE KEY uk_quota_no (quota_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评额度表';
+
+-- 测评执行记录表
+CREATE TABLE IF NOT EXISTS assessment_executions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    quota_id BIGINT NOT NULL COMMENT '关联额度ID',
+    child_id BIGINT NOT NULL COMMENT '被测评孩子',
+    guide_id BIGINT COMMENT '执行规划师ID',
+    assessment_type VARCHAR(30) COMMENT '本次具体测评类型',
+    appointment_id BIGINT COMMENT '关联预约ID',
+    result_id BIGINT COMMENT '关联测评结果ID',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/in_progress/completed/cancelled',
+    scheduled_date DATETIME COMMENT '预约执行时间',
+    completed_at DATETIME COMMENT '实际完成时间',
+    notes VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_quota (quota_id),
+    INDEX idx_child (child_id),
+    INDEX idx_appointment (appointment_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评执行记录表';
+
+-- 测评指导意见表(结构化存储)
+CREATE TABLE IF NOT EXISTS growth_guidances (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    result_id BIGINT NOT NULL COMMENT '关联测评结果ID',
+    dimension VARCHAR(30) NOT NULL COMMENT '维度: attention/focus/memory/logic/emotion/general',
+    score INT COMMENT '该维度得分(0-100)',
+    suggestion TEXT NOT NULL COMMENT '建议内容',
+    priority INT DEFAULT 0 COMMENT '优先级(0=普通, 1=重要, 2=紧急)',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_result (result_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评指导意见(结构化)';
+
+-- 方案生成规则表
+CREATE TABLE IF NOT EXISTS assessment_plan_rules (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    dimension VARCHAR(30) NOT NULL COMMENT '测评维度: attention/focus/memory/logic/emotion',
+    score_min INT NOT NULL COMMENT '分数下限(含)',
+    score_max INT NOT NULL COMMENT '分数上限(含)',
+    template_id BIGINT NOT NULL COMMENT '关联TaskTemplatePackage ID',
+    priority INT DEFAULT 0 COMMENT '优先级',
+    is_active TINYINT(1) DEFAULT 1 COMMENT '是否启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_dimension (dimension),
+    INDEX idx_score (score_min, score_max)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='方案生成规则';
+
+-- 家庭访问安全日志表
+CREATE TABLE IF NOT EXISTS family_access_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '访问方用户ID',
+    family_id BIGINT NOT NULL COMMENT '被访问的家庭ID',
+    request_uri VARCHAR(500) NOT NULL COMMENT '请求路径',
+    request_method VARCHAR(10) NOT NULL,
+    access_type VARCHAR(20) NOT NULL COMMENT 'check_type: query/path/body',
+    result VARCHAR(10) NOT NULL COMMENT 'allowed/denied/log_only',
+    client_ip VARCHAR(50),
+    user_agent VARCHAR(500),
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_time (user_id, created_at),
+    INDEX idx_family_time (family_id, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭访问安全日志';
+
+-- 五维能量分数历史记录表
+CREATE TABLE IF NOT EXISTS five_dimension_scores (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '成员ID(user或family_member)',
+    member_type VARCHAR(20) NOT NULL COMMENT '成员类型: user/member',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    dimension_code VARCHAR(20) NOT NULL COMMENT '维度编码: body/mind/wisdom/action/wealth',
+    score INT NOT NULL COMMENT '分数(0-100)',
+    assessed_at DATETIME NOT NULL COMMENT '评估时间',
+    source_type VARCHAR(50) COMMENT '来源类型: periodic/task/assessment/manual',
+    source_id BIGINT COMMENT '来源记录ID',
+    detail_json TEXT COMMENT '详细数据JSON',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_member_dim (member_id, member_type, dimension_code),
+    INDEX idx_family_time (family_id, assessed_at),
+    INDEX idx_source (source_type, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维能量分数历史记录';
+
+-- 家庭邀请令牌表
+CREATE TABLE IF NOT EXISTS family_invitations (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    token VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请令牌(UUID)',
+    created_by BIGINT NOT NULL COMMENT '创建人用户ID',
+    status VARCHAR(16) DEFAULT 'active' COMMENT '状态: active/used/expired/revoked',
+    expires_at DATETIME COMMENT '过期时间',
+    max_use_count INT DEFAULT 1 COMMENT '最大使用次数',
+    used_count INT DEFAULT 0 COMMENT '已使用次数',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_token (token),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭邀请令牌';
+
+-- 规划师绑定家庭申请表
+CREATE TABLE IF NOT EXISTS teacher_family_binding_requests (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    teacher_id BIGINT NOT NULL COMMENT '规划师用户ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    invitation_token VARCHAR(128) COMMENT '关联邀请码/令牌',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/approved/rejected/cancelled',
+    requested_at DATETIME COMMENT '申请时间',
+    decided_by BIGINT COMMENT '审批人用户ID',
+    decided_at DATETIME COMMENT '审批时间',
+    reject_reason VARCHAR(200) COMMENT '拒绝原因',
+    message VARCHAR(500) COMMENT '申请留言',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_teacher_family (teacher_id, family_id),
+    INDEX idx_family_status (family_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='规划师绑定家庭申请表';
+
+-- Dify会话本地镜像表
+CREATE TABLE IF NOT EXISTS chat_conversations (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    dify_conversation_id VARCHAR(64),
+    assistant_type VARCHAR(32) COMMENT 'family/nutrition/tongue',
+    title VARCHAR(200),
+    last_message_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_dify_conv (dify_conversation_id),
+    INDEX idx_user (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Dify会话本地镜像';
+
+CREATE TABLE IF NOT EXISTS chat_messages (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    conversation_id BIGINT NOT NULL,
+    dify_message_id VARCHAR(64),
+    role VARCHAR(20) NOT NULL COMMENT 'user/assistant',
+    content TEXT NOT NULL,
+    inputs_json TEXT,
+    metadata TEXT,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_conv (conversation_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Dify消息本地镜像';
+
+-- 迁移版本追踪表
+CREATE TABLE IF NOT EXISTS schema_versions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    version_code VARCHAR(64) NOT NULL UNIQUE,
+    description VARCHAR(500),
+    checksum VARCHAR(64),
+    applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    applied_by VARCHAR(100)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='迁移版本追踪表';
+
+
+-- 待退款记录表
+CREATE TABLE IF NOT EXISTS pending_refund (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    withdrawal_request_id BIGINT COMMENT '提现申请ID',
+    amount INT NOT NULL COMMENT '退款金额(分)',
+    attempt_count INT DEFAULT 0 COMMENT '当前尝试次数',
+    max_attempts INT DEFAULT 10 COMMENT '最大重试次数',
+    next_retry_time DATETIME COMMENT '下次重试时间',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/processing/success/failed',
+    failure_reason VARCHAR(500) COMMENT '失败原因',
+    refunded_amount INT COMMENT '实际退款金额(分)',
+    refunded_at DATETIME COMMENT '退款时间',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_id (user_id),
+    INDEX idx_status (status),
+    INDEX idx_next_retry (next_retry_time),
+    INDEX idx_withdrawal_request (withdrawal_request_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='待退款记录表';
+
+
+-- 家庭每日运势表(迁移 79)
+CREATE TABLE IF NOT EXISTS tianpan_daily_fortune (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    date VARCHAR(20) NOT NULL COMMENT '日期 YYYY-MM-DD',
+    lunar_date VARCHAR(50) COMMENT '农历日期',
+    day_ganzhi VARCHAR(10) COMMENT '日柱',
+    five_elements_main VARCHAR(20) COMMENT '当日五行主气',
+    five_elements_detail VARCHAR(100) COMMENT '五行详情',
+    auspicious TEXT COMMENT '宜做的事(JSON数组)',
+    inauspicious TEXT COMMENT '忌做的事(JSON数组)',
+    family_advice TEXT COMMENT '家庭建议',
+    focus_member_name VARCHAR(50) COMMENT '关注成员',
+    focus_member_tip TEXT COMMENT '成员提示',
+    wuxing_tip TEXT COMMENT '五行提示',
+    lucky_direction VARCHAR(20) COMMENT '幸运方向',
+    fortune_level VARCHAR(20) COMMENT '运势等级',
+    fortune_level_text VARCHAR(100) COMMENT '运势等级描述',
+    fortune_description TEXT COMMENT '运势描述',
+    mood_score INT DEFAULT 50 COMMENT '心情分数',
+    mood_emoji VARCHAR(10) COMMENT '心情emoji',
+    mood_text VARCHAR(50) COMMENT '心情文本',
+    weekly_tip TEXT COMMENT '每周提示',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_family_date (family_id, date),
+    INDEX idx_family_id (family_id),
+    INDEX idx_date (date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭每日运势表';
+
+
+-- 成员年度能量表(迁移 80)
+CREATE TABLE IF NOT EXISTS tianpan_member_annual_energy (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '成员ID',
+    year INT NOT NULL COMMENT '年份',
+    dimensions_body INT DEFAULT 0 COMMENT '身能量',
+    dimensions_mind INT DEFAULT 0 COMMENT '心能量',
+    dimensions_wisdom INT DEFAULT 0 COMMENT '智能量',
+    dimensions_action INT DEFAULT 0 COMMENT '行能量',
+    dimensions_wealth INT DEFAULT 0 COMMENT '富能量',
+    jupiter_phase VARCHAR(50) COMMENT '木星相位',
+    saturn_phase VARCHAR(50) COMMENT '土星相位',
+    highlights TEXT COMMENT '年度亮点(JSON数组)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_member_year (member_id, year),
+    INDEX idx_member_id (member_id),
+    INDEX idx_year (year)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成员年度能量表';
+
+-- 维度权重表(管理端保存五维权重使用)
+CREATE TABLE IF NOT EXISTS dimension_weights (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    target_type VARCHAR(20) NOT NULL COMMENT '目标类型: activity/product/article',
+    target_id BIGINT NOT NULL COMMENT '目标ID',
+    dimension VARCHAR(20) NOT NULL COMMENT '维度: body/mind/wisdom/action/wealth',
+    weight INT DEFAULT 0 COMMENT '权重百分比',
+    enabled TINYINT(1) DEFAULT 0 COMMENT '是否启用',
+created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+INDEX idx_target (target_type, target_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度权重表';
+
+-- 健康打卡表
+CREATE TABLE IF NOT EXISTS health_checkins (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID(家长)',
+    child_id BIGINT COMMENT '孩子ID',
+    checkin_date DATE COMMENT '打卡日期',
+    behaviors TEXT COMMENT '打卡行为JSON数组',
+    note VARCHAR(500) COMMENT '备注',
+    mood VARCHAR(20) COMMENT '心情: happy/normal/sad/angry/sick',
+    earned_energy INT DEFAULT 5 COMMENT '获得能量值',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_id (child_id),
+    INDEX idx_checkin_date (checkin_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康打卡表';
+
+-- 每日健康打卡表(综合打卡:饮食/运动/睡眠/饮水/心情评分)
+CREATE TABLE IF NOT EXISTS daily_checkin (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_date DATE COMMENT '打卡日期',
+    diet_score INT COMMENT '饮食评分',
+    exercise_score INT COMMENT '运动评分',
+    sleep_score INT COMMENT '睡眠评分',
+    water_intake INT COMMENT '饮水量(ml)',
+    mood_score INT COMMENT '心情评分',
+    weight_kg DECIMAL(5,2) COMMENT '体重(kg)',
+    height_cm DECIMAL(5,2) COMMENT '身高(cm)',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日健康打卡表';
+
+-- 健康打卡 - 睡眠记录
+CREATE TABLE IF NOT EXISTS sleep_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_id BIGINT COMMENT '关联打卡ID',
+    sleep_time TIME COMMENT '入睡时间',
+    wake_time TIME COMMENT '醒来时间',
+    duration_minutes INT COMMENT '总睡眠时长(分钟)',
+    deep_sleep_minutes INT COMMENT '深睡时长(分钟)',
+    light_sleep_minutes INT COMMENT '浅睡时长(分钟)',
+    rem_minutes INT COMMENT 'REM时长(分钟)',
+    quality_score INT COMMENT '睡眠质量评分1-10',
+    dream_count INT COMMENT '做梦次数',
+    remark TEXT COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='睡眠打卡记录';
+
+-- 健康打卡 - 饮水记录
+CREATE TABLE IF NOT EXISTS water_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_id BIGINT COMMENT '关联打卡ID',
+    amount_ml INT COMMENT '饮水量(毫升)',
+    water_type VARCHAR(32) COMMENT '类型: plain/sparkling/tea/other',
+    drunk_at DATETIME COMMENT '饮水时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮水打卡记录';
+
+-- 健康打卡 - 饮食记录
+CREATE TABLE IF NOT EXISTS health_meal_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_id BIGINT COMMENT '关联打卡ID',
+    meal_type VARCHAR(32) COMMENT '餐次: breakfast/lunch/dinner/snack',
+    food_items TEXT COMMENT '食物清单JSON',
+    total_calories INT COMMENT '总卡路里',
+    protein_g INT COMMENT '蛋白质克数',
+    fat_g INT COMMENT '脂肪克数',
+    carbs_g INT COMMENT '碳水克数',
+    fiber_g INT COMMENT '膳食纤维克数',
+    meal_time DATETIME COMMENT '用餐时间',
+    remark VARCHAR(500) COMMENT '备注',
+    photo_urls TEXT COMMENT '照片URL列表(逗号分隔)',
+    voice_url VARCHAR(500) COMMENT '语音URL',
+    voice_text TEXT COMMENT '语音转写文字',
+    record_date DATE COMMENT '用餐日期(补打时为实际用餐日,默认等于创建日)',
+    is_backfill TINYINT DEFAULT 0 COMMENT '是否补打: 1=补打(无能量值), 0=当日打卡',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id),
+    INDEX idx_meal_record_date (member_id, record_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食打卡记录';
+
+-- 健康打卡 - 冥想记录
+CREATE TABLE IF NOT EXISTS health_meditation_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_id BIGINT COMMENT '关联打卡ID',
+    duration_minutes INT COMMENT '时长(分钟)',
+    type VARCHAR(32) COMMENT '类型: mindfulness/guided/breathing/body_scan',
+    mood VARCHAR(32) COMMENT '冥想后心情',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='冥想打卡记录';
+
+-- 健康打卡 - 运动记录
+CREATE TABLE IF NOT EXISTS exercise_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    checkin_id BIGINT COMMENT '关联打卡ID',
+    exercise_type VARCHAR(50) COMMENT '运动类型',
+    duration_minutes INT COMMENT '时长(分钟)',
+    intensity VARCHAR(20) COMMENT '强度: low/medium/high',
+    calories_burned INT COMMENT '消耗卡路里',
+    heart_rate_avg INT COMMENT '平均心率',
+    heart_rate_max INT COMMENT '最大心率',
+    step_count INT COMMENT '微信运动步数',
+    start_time DATETIME COMMENT '开始时间',
+    end_time DATETIME COMMENT '结束时间',
+    remark TEXT COMMENT '备注',
+    photo_urls TEXT COMMENT '照片URL列表(逗号分隔)',
+    voice_url VARCHAR(500) COMMENT '语音URL',
+    voice_text TEXT COMMENT '语音转写文字',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动打卡记录';
+
+-- 电商供应商
+CREATE TABLE IF NOT EXISTS ecom_supplier (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '供应商名称',
+    contact_name VARCHAR(50) COMMENT '联系人',
+    contact_phone VARCHAR(20) COMMENT '联系电话',
+    status TINYINT DEFAULT 1 COMMENT '启用状态 1启用 0禁用',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电商供应商';
+
+-- 佣金返利配置
+CREATE TABLE IF NOT EXISTS rebate_commission_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '配置名称',
+    rate INT COMMENT '佣金比例(万分比)',
+    rate_type TINYINT DEFAULT 1 COMMENT '比例类型 1百分比 2固定金额',
+    min_amount INT COMMENT '最低金额(分)',
+    max_amount INT COMMENT '最高金额(分)',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金返利配置';
+
+-- DAN测评服务闭环表
+
+CREATE TABLE IF NOT EXISTS `dan_assessment_executions` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `assessment_execution_id` BIGINT NOT NULL COMMENT '执行记录ID',
+    `assessment_order_id` BIGINT NOT NULL COMMENT '订单ID',
+    `child_id` BIGINT NOT NULL COMMENT '孩子ID',
+    `family_id` BIGINT NOT NULL COMMENT '家庭ID',
+    `quota_id` BIGINT COMMENT '额度ID',
+    `appointment_id` BIGINT COMMENT '关联assessment_appointments.id',
+    `assessor_id` BIGINT NOT NULL COMMENT '测评师userId',
+    `assessor_status` ENUM('pending','completed','rejected') DEFAULT 'pending' COMMENT '测评师状态',
+    `assessor_completed_at` DATETIME COMMENT '测评师完成时间',
+    `assessor_notes` VARCHAR(200) COMMENT '测评摘要/服务备注',
+    `planner_id` BIGINT COMMENT '规划师userId',
+    `planner_status` ENUM('pending','completed','rejected') DEFAULT 'pending' COMMENT '规划师状态',
+    `planner_completed_at` DATETIME COMMENT '规划师完成时间',
+    `planner_notes` VARCHAR(200) COMMENT '规划服务备注',
+    `user_confirmed` TINYINT(1) DEFAULT 0 COMMENT '用户确认:0未确认,1已确认',
+    `user_confirmed_at` DATETIME COMMENT '用户确认时间',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY `idx_assessment_execution_id` (`assessment_execution_id`),
+    KEY `idx_assessment_order_id` (`assessment_order_id`),
+    KEY `idx_assessor_id` (`assessor_id`),
+    KEY `idx_planner_id` (`planner_id`),
+    KEY `idx_child_id` (`child_id`),
+    KEY `idx_family_id` (`family_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DAN测评服务执行记录';
+
+CREATE TABLE IF NOT EXISTS `service_ratings` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `execution_id` BIGINT NOT NULL COMMENT '执行记录ID',
+    `assessment_order_id` BIGINT NOT NULL COMMENT '订单ID',
+    `user_id` BIGINT NOT NULL COMMENT '评分用户ID',
+    `assessor_id` BIGINT NOT NULL COMMENT '测评师userId',
+    `assessor_rating` TINYINT DEFAULT 0 COMMENT '测评师评分 1-5',
+    `assessor_comment` VARCHAR(500) COMMENT '评价内容',
+    `planner_id` BIGINT COMMENT '规划师userId',
+    `planner_rating` TINYINT COMMENT '规划师评分 1-5',
+    `planner_comment` VARCHAR(500) COMMENT '评价内容',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY `uniq_execution_user` (`execution_id`, `user_id`),
+    KEY `idx_assessor_id` (`assessor_id`),
+    KEY `idx_planner_id` (`planner_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务评分记录';
+
+CREATE TABLE IF NOT EXISTS `service_settlements` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `execution_id` BIGINT NOT NULL COMMENT '执行记录ID',
+    `assessment_order_id` BIGINT NOT NULL COMMENT '订单ID',
+    `provider_id` BIGINT NOT NULL COMMENT '服务提供方userId',
+    `provider_type` ENUM('assessor','planner') NOT NULL COMMENT '服务方类型',
+    `service_fee` BIGINT DEFAULT 0 COMMENT '服务费用(分)',
+    `commission_rate` INT COMMENT '佣金比例(bps)',
+    `commission_amount` BIGINT DEFAULT 0 COMMENT '结算金额(分)',
+    `status` ENUM('pending','settled','failed') DEFAULT 'pending' COMMENT '结算状态',
+    `settled_at` DATETIME COMMENT '结算时间',
+    `transaction_id` VARCHAR(100) COMMENT '转账交易ID',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY `idx_execution_id` (`execution_id`),
+    KEY `idx_provider_id` (`provider_id`),
+    KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务结算记录';
+
+-- 健康知识库 — 菌属/营养素/指标说明
+CREATE TABLE IF NOT EXISTS `health_knowledge_base` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `item_type` VARCHAR(50) NOT NULL COMMENT '类型: bacteria/nutrient/indicator/vitamin/amino_acid/disease',
+    `item_name` VARCHAR(200) NOT NULL COMMENT '项目名称(精确匹配)',
+    `category` VARCHAR(100) COMMENT '分类(如 有益菌/有害菌)',
+    `normal_range` VARCHAR(200) COMMENT '正常范围参考',
+    `description` TEXT COMMENT '详细说明',
+    `suggestion` TEXT COMMENT '相关建议',
+    `source` VARCHAR(100) COMMENT '数据来源',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_item_type_name` (`item_type`, `item_name`),
+    KEY `idx_item_type` (`item_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康知识库 - 菌属/营养素/指标说明';
+
+-- 统一知识库 — 按模块存储JSON(12个顶层模块,DB为查询源,classpath种子兜底)
+CREATE TABLE IF NOT EXISTS `knowledge_base_section` (
+    `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
+    `module_key` VARCHAR(100) NOT NULL COMMENT '模块键(如 食物营养库/菌属功能库/报告指标分级标准)',
+    `module_name` VARCHAR(200) COMMENT '模块中文名',
+    `content` LONGTEXT COMMENT '模块内容(JSON序列化)',
+    `version` VARCHAR(50) COMMENT '数据版本(来自元数据.版本)',
+    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+    `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY `uk_module_key` (`module_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一知识库 - 按模块存储JSON';
+-- =============================================
+-- DAN报告上传独立化(拆分 PDF 报告生命周期与数值结果)
+-- =============================================
+
+-- DAN PDF 报告上传/解析/确认记录表
+CREATE TABLE IF NOT EXISTS dan_report_uploads (
+    id                   BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id            BIGINT       NOT NULL COMMENT '家庭ID',
+    family_member_id     BIGINT       NOT NULL COMMENT '家庭成员ID',
+    uploader_user_id     BIGINT       NOT NULL COMMENT '上传者用户ID',
+    dimension            VARCHAR(16)  NOT NULL COMMENT '维度: mind(心-A2)/wisdom(智-B4)',
+    report_type          VARCHAR(16)  COMMENT 'A2/B4 报告类型(解析后确认)',
+    file_url             VARCHAR(500) NOT NULL COMMENT 'PDF 文件URL',
+    file_id              VARCHAR(500) COMMENT '媒资文件ID或相对路径',
+    file_name            VARCHAR(200) COMMENT '原始文件名',
+    file_size            BIGINT       COMMENT '文件大小(字节)',
+    uploaded_at          DATETIME     DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
+    parsed_items         JSON         COMMENT '解析的指标项 [{code, name, value, category}]',
+    parsed_extra         JSON         COMMENT '解析的额外信息(姓名/性别/年龄/报告日期等)',
+    parsed_summary       TEXT         COMMENT '解析的总结文本',
+    parsed_suggestions   TEXT         COMMENT '解析的建议文本',
+    parser_version       VARCHAR(32)  COMMENT '解析器版本',
+    parse_status         VARCHAR(16)  DEFAULT 'pending' COMMENT 'pending/success/failed',
+    parse_error          VARCHAR(500) COMMENT '解析失败原因',
+    parsed_at            DATETIME     COMMENT '解析完成时间',
+    draft_status         VARCHAR(16)  DEFAULT 'pending' COMMENT 'pending/confirmed/discarded',
+    confirmed_at         DATETIME     COMMENT '确认时间',
+    confirmed_by         BIGINT       COMMENT '确认操作人用户ID',
+    edited_items         JSON         COMMENT '编辑后的指标项(覆盖 parsed_items)',
+    edited_extra         JSON         COMMENT '编辑后的额外信息',
+    edited_summary       TEXT         COMMENT '编辑后的总结',
+    edited_suggestions   TEXT         COMMENT '编辑后的建议',
+    edit_count           INT          DEFAULT 0 COMMENT '编辑次数',
+    linked_result_id     BIGINT       COMMENT '关联 dan_assessment_results.id',
+    created_at           DATETIME     DEFAULT CURRENT_TIMESTAMP,
+    updated_at           DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    deleted_at           DATETIME     COMMENT '软删除时间',
+    INDEX idx_family_id (family_id),
+    INDEX idx_member_id (family_member_id),
+    INDEX idx_draft_status (draft_status),
+    INDEX idx_dimension (dimension),
+    INDEX idx_parse_status (parse_status),
+    INDEX idx_linked_result (linked_result_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DAN测评PDF报告上传/解析/确认记录';
+
+CREATE TABLE IF NOT EXISTS review_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    reviewer_id BIGINT NOT NULL COMMENT '评价人ID',
+    rating INT COMMENT '总体评分',
+    review_text TEXT COMMENT '评价内容',
+    review_at DATETIME COMMENT '评价时间',
+    organization_rating INT COMMENT '活动组织评分(0-5)',
+    content_rating INT COMMENT '活动内容评分(0-5)',
+    experience_rating INT COMMENT '活动体验评分(0-5)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_order_id (order_id),
+    INDEX idx_reviewer_id (reviewer_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动订单评价表';
+
+-- 微行动定义表
+CREATE TABLE IF NOT EXISTS micro_actions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    code VARCHAR(32) NOT NULL COMMENT '行动编码: DRINK_WATER, DEEP_BREATH, ...',
+    name VARCHAR(50) NOT NULL COMMENT '行动名称',
+    icon VARCHAR(10) COMMENT '图标emoji',
+    description VARCHAR(200) COMMENT '行动描述',
+    sort_order INT DEFAULT 0 COMMENT '排序号',
+    status TINYINT DEFAULT 1 COMMENT '状态: 1启用 0禁用',
+    dimension_code VARCHAR(32) COMMENT '维度code: body/wisdom/wealth/action/mind',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_code (code),
+    INDEX idx_status (status),
+    INDEX idx_dimension (dimension_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微行动定义(12个1秒健康行动)';
+
+-- 微行动完成记录表
+CREATE TABLE IF NOT EXISTS micro_action_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    action_id BIGINT NOT NULL COMMENT '行动ID',
+    completed_date DATETIME NOT NULL COMMENT '完成日期',
+    earned_energy INT DEFAULT 1 COMMENT '获得能量值',
+    note VARCHAR(200) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_date (child_id, completed_date),
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微行动完成记录';
+
+-- 每日反馈表(AI/预设健康反馈)
+CREATE TABLE IF NOT EXISTS daily_feedback (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    feedback_date DATETIME NOT NULL COMMENT '反馈日期',
+    feedback_type VARCHAR(20) DEFAULT 'daily' COMMENT '反馈类型: daily/weekly/milestone',
+    content VARCHAR(500) NOT NULL COMMENT 'AI生成的反馈文本',
+    milestone_event VARCHAR(100) COMMENT '关联的里程碑事件',
+    is_read TINYINT DEFAULT 0 COMMENT '是否已读: 0未读 1已读',
+    source VARCHAR(20) DEFAULT 'preset' COMMENT '来源: ai_generated / preset',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_date (child_id, feedback_date),
+    INDEX idx_feedback_type (feedback_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='每日健康反馈';
+
+-- 健康预警记录表(P1-2 健康行为智能预警)
+CREATE TABLE IF NOT EXISTS health_alerts (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID',
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    rule_code VARCHAR(10) NOT NULL COMMENT '规则编码: R001-R006',
+    alert_text VARCHAR(200) NOT NULL COMMENT '预警文案',
+    action_suggestion VARCHAR(200) COMMENT '行动建议',
+    severity VARCHAR(10) DEFAULT 'medium' COMMENT '严重程度: high/medium/low',
+    dismissed TINYINT DEFAULT 0 COMMENT '是否已关闭: 0未关闭 1已关闭',
+    dismissed_at DATETIME COMMENT '关闭时间',
+    alert_date DATETIME NOT NULL COMMENT '预警日期',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_date (child_id, alert_date),
+    INDEX idx_rule (rule_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康预警记录';
+
+-- 健康等级表(P2-1 身份重塑)
+CREATE TABLE IF NOT EXISTS health_level (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    level_num INT NOT NULL COMMENT '等级编号 1-5',
+    title VARCHAR(50) NOT NULL COMMENT '称号',
+    badge_icon VARCHAR(20) COMMENT '徽章图标',
+    min_score INT DEFAULT 0 COMMENT '所需最低健康积分',
+    max_score INT DEFAULT 99999 COMMENT '最高积分上限'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康等级定义';
+
+-- 健康时间线事件表(P2-1 我的健康故事)
+CREATE TABLE IF NOT EXISTS health_timeline_event (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID',
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    event_type VARCHAR(30) NOT NULL COMMENT '事件类型: streak_7/level_2/first_report',
+    event_text VARCHAR(200) NOT NULL COMMENT '事件描述',
+    ref_id VARCHAR(50) COMMENT '关联ID',
+    event_date DATETIME NOT NULL COMMENT '事件日期',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_child_date (child_id, event_date),
+    INDEX idx_event_type (event_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康时间线事件';
+
+-- 家庭挑战表(P2-2 家庭健康互动)
+CREATE TABLE IF NOT EXISTS family_challenge (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    challenge_type VARCHAR(50) NOT NULL COMMENT 'full_checkin/sports/health_week',
+    title VARCHAR(200) NOT NULL COMMENT '挑战标题',
+    description TEXT COMMENT '挑战描述',
+    duration_days INT DEFAULT 7 COMMENT '持续天数',
+    reward_points INT DEFAULT 0 COMMENT '奖励积分',
+    reward_badge VARCHAR(100) COMMENT '奖励徽章',
+    target_mode VARCHAR(20) DEFAULT 'aggregate' COMMENT '目标模式: aggregate=集体总合/all_members=全员个人目标',
+    target_value INT DEFAULT 0 COMMENT '目标值(aggregate=家庭合计值, all_members=每人目标值)',
+    start_date DATE COMMENT '开始日期',
+    end_date DATE COMMENT '结束日期',
+    status VARCHAR(20) DEFAULT 'active' COMMENT 'pending(待同意)/active(进行中)/completed(已完成)/cancelled(已取消)/not_launched(未成功发起)',
+    creator_id BIGINT COMMENT '创建者ID(NULL=系统创建)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    respond_deadline DATETIME NULL COMMENT '回复时效截止时间(超过后未回复的 pending 成员自动置为 rejected)',
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战';
+
+-- 家庭挑战进度表(P2-2 挑战进度)
+CREATE TABLE IF NOT EXISTS family_challenge_progress (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    challenge_id BIGINT NOT NULL COMMENT '挑战ID',
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    progress_value INT DEFAULT 0 COMMENT '进度值',
+    completed TINYINT(1) DEFAULT 0 COMMENT '是否完成',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_challenge_id (challenge_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战进度';
+
+-- 家庭挑战参与成员表(参与机制重构:任意成员发起/可选参与/同意后开始)
+CREATE TABLE IF NOT EXISTS family_challenge_participant (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    challenge_id BIGINT NOT NULL COMMENT '挑战ID',
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    status VARCHAR(20) NOT NULL COMMENT 'pending/agreed/rejected',
+    agreed_at DATETIME NULL COMMENT '同意/拒绝时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_challenge_member (challenge_id, member_id),
+    INDEX idx_member (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战参与成员';
+
+-- 方向性关系分数表(A对B ≠ B对A)
+CREATE TABLE IF NOT EXISTS family_relationship_scores (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    from_member_id BIGINT NULL COMMENT '分数持有方(A对B的行)',
+    to_member_id BIGINT NULL COMMENT '分数对象方',
+    trust_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的信任',
+    intimacy_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的亲近',
+    communication_score DECIMAL(5,2) DEFAULT 0 COMMENT 'A对B的沟通',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_from_to (from_member_id, to_member_id),
+    INDEX idx_family (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='方向性关系分数';
+
+-- 家庭挑战评分配置表(后台可配置)
+CREATE TABLE IF NOT EXISTS family_challenge_score_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    action VARCHAR(20) NOT NULL COMMENT 'initiate/agree/reject',
+    score_type VARCHAR(20) NOT NULL COMMENT 'energy/trust/intimacy/communication',
+    value INT NOT NULL DEFAULT 0 COMMENT '增减值(可正可负,默认±1)',
+    enabled TINYINT DEFAULT 1,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_action_type (action, score_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭挑战评分配置';
+
+-- 健康互动记录表(P2-2 点赞)
+CREATE TABLE IF NOT EXISTS health_interaction (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    from_user_id BIGINT NOT NULL COMMENT '点赞用户ID',
+    to_user_id BIGINT NOT NULL COMMENT '被赞用户ID',
+    action_type VARCHAR(50) NOT NULL COMMENT '行为类型',
+    action_id BIGINT COMMENT '关联行为记录ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_family_action (family_id, action_type, action_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康互动记录(点赞)';
+
+-- 社交圈子表(social circle)
+CREATE TABLE IF NOT EXISTS social_circle (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '圈子名称',
+    type VARCHAR(50) NOT NULL COMMENT '类型: topic/activity/hobby/ability/product/health/provider',
+    match_source VARCHAR(50) COMMENT '匹配源',
+    source_id BIGINT COMMENT '匹配源ID',
+    member_count INT DEFAULT 0 COMMENT '成员数',
+    description VARCHAR(500) COMMENT '圈子描述',
+    cover_image VARCHAR(500) COMMENT '封面图URL',
+    join_condition TINYINT(1) DEFAULT 0 COMMENT '加入条件: 0=公开/1=审核/2=邀请',
+    review_required TINYINT(1) DEFAULT 0 COMMENT '是否需要审核: 0=否/1=是',
+    is_public TINYINT(1) DEFAULT 1 COMMENT '是否公开可见: 0=否/1=是',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_type (type),
+    INDEX idx_is_public (is_public)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='社交圈子';
+
+-- 社交圈子成员表(social circle member)
+CREATE TABLE IF NOT EXISTS social_circle_member (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL COMMENT '圈子ID',
+    member_id BIGINT NULL COMMENT '用户成员ID',
+    member_type VARCHAR(20) NOT NULL COMMENT 'parent/child',
+    status VARCHAR(20) DEFAULT 'approved' COMMENT '申请状态: pending/approved/rejected',
+    role VARCHAR(20) DEFAULT 'member' COMMENT '角色: member/admin',
+    joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_circle_member (circle_id, member_id),
+    INDEX idx_circle (circle_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='社交圈子成员';
+
+-- 社交圈子动态帖子表
+CREATE TABLE IF NOT EXISTS social_circle_post (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL COMMENT '圈子ID',
+    member_id BIGINT NULL COMMENT '发布者成员ID',
+    member_type VARCHAR(20) NOT NULL COMMENT 'parent/child',
+    type VARCHAR(50) NOT NULL COMMENT '帖子类型: checkin/diet/exercise/sleep/water/mood/text',
+    content TEXT COMMENT '文字内容',
+    checkin_id BIGINT COMMENT '关联打卡记录ID',
+    image_urls TEXT COMMENT '图片URL列表JSON',
+    like_count INT DEFAULT 0 COMMENT '点赞数',
+    comment_count INT DEFAULT 0 COMMENT '评论数',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    deleted TINYINT(1) DEFAULT 0,
+    INDEX idx_circle_time (circle_id, created_at),
+    INDEX idx_member_time (member_id, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子动态帖子';
+
+-- 社交圈子点赞表
+CREATE TABLE IF NOT EXISTS social_circle_like (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL,
+    post_id BIGINT NOT NULL COMMENT '被点赞的帖子ID',
+    member_id BIGINT NULL COMMENT '点赞成员ID',
+    member_type VARCHAR(20) NOT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_like (circle_id, post_id, member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子点赞记录';
+
+-- 社交圈子评论表
+CREATE TABLE IF NOT EXISTS social_circle_comment (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL,
+    post_id BIGINT NOT NULL COMMENT '所属帖子ID',
+    parent_id BIGINT DEFAULT NULL COMMENT '回复的评论ID(NULL=一级评论)',
+    member_id BIGINT NULL COMMENT '评论者成员ID',
+    member_type VARCHAR(20) NOT NULL,
+    content TEXT NOT NULL COMMENT '评论内容',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_post_time (post_id, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈子评论记录';
+
+-- 健康圈表(P3 健康社区扩散)
+CREATE TABLE IF NOT EXISTS health_circle (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '圈名称',
+    description VARCHAR(500) COMMENT '圈描述',
+    creator_family_id BIGINT COMMENT '创建家庭ID',
+    invite_code VARCHAR(32) COMMENT '邀请码',
+    member_limit INT DEFAULT 50 COMMENT '成员上限',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/closed',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_invite_code (invite_code),
+    INDEX idx_creator (creator_family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康圈';
+
+-- 健康圈成员表(P3)
+CREATE TABLE IF NOT EXISTS health_circle_member (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL COMMENT '圈ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    role VARCHAR(20) DEFAULT 'member' COMMENT '角色 member/admin',
+    joined_at DATETIME COMMENT '加入时间',
+    left_at DATETIME COMMENT '退出时间',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/inactive',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_circle_id (circle_id),
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康圈成员';
+
+-- 圈挑战表(P3)
+CREATE TABLE IF NOT EXISTS circle_challenge (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    circle_id BIGINT NOT NULL COMMENT '圈ID',
+    challenge_type VARCHAR(50) NOT NULL COMMENT '挑战类型 checkin/step/meditation',
+    title VARCHAR(200) NOT NULL COMMENT '挑战标题',
+    description VARCHAR(500) COMMENT '挑战描述',
+    duration_days INT DEFAULT 7 COMMENT '持续天数',
+    period VARCHAR(20) DEFAULT 'weekly' COMMENT '周期 weekly/monthly',
+    start_date DATETIME COMMENT '开始日期',
+    end_date DATETIME COMMENT '结束日期',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态 active/completed/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_circle_id (circle_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='圈挑战';
+
+-- 推广佣金记录表(P2-PROMO)
+CREATE TABLE IF NOT EXISTS family_commission (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    promoter_family_id BIGINT NOT NULL COMMENT '推广方家庭ID',
+    buyer_family_id BIGINT NOT NULL COMMENT '购买方家庭ID',
+    order_id BIGINT COMMENT '关联订单ID',
+    amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '订单金额',
+    commission_rate DECIMAL(5,2) DEFAULT 10.00 COMMENT '佣金比例%',
+    commission_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '佣金金额',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/paid/cancelled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    paid_at DATETIME COMMENT '支付时间',
+    INDEX idx_promoter (promoter_family_id),
+    INDEX idx_buyer (buyer_family_id),
+    INDEX idx_order (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭推广佣金记录';
+
+-- 推广绑定关系表(P2-PROMO)
+CREATE TABLE IF NOT EXISTS promotion_binding (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    promoter_family_id BIGINT NOT NULL COMMENT '推广方家庭ID',
+    invitee_family_id BIGINT NOT NULL COMMENT '被邀请方家庭ID',
+    invite_code VARCHAR(32) COMMENT '使用的邀请码',
+    binding_type VARCHAR(20) DEFAULT 'direct' COMMENT 'direct/indirect',
+    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/inactive',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    UNIQUE KEY uk_promoter_invitee (promoter_family_id, invitee_family_id),
+    INDEX idx_promoter (promoter_family_id),
+    INDEX idx_invitee (invitee_family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推广绑定关系';
+
+-- 健康维度评分表(7维健康模型)
+CREATE TABLE IF NOT EXISTS health_dimension_scores (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    dimension VARCHAR(20) NOT NULL COMMENT '维度编码: growth/sleep/vision/immunity/nutrition/gut/exercise',
+    score INT NOT NULL COMMENT '评分(0-100)',
+    percentile INT COMMENT '百分位',
+    data_source VARCHAR(20) COMMENT '数据来源: MANUAL/REPORT/PHOTO/VOICE/AVERAGE',
+    tier INT DEFAULT 3 COMMENT '数据可信度层级: 1=最高(报告) 2=中(AI分析) 3=基础(手动)',
+    raw_data TEXT COMMENT '原始数据JSON',
+    assess_date DATE COMMENT '评估日期',
+    expire_date DATE COMMENT '过期日期',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_member_dim (member_id, dimension),
+    INDEX idx_assess_date (assess_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康维度评分';
+
+-- 健康数据源记录表(VOICE/PHOTO等多媒体数据源)
+CREATE TABLE IF NOT EXISTS health_data_sources (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    source_type VARCHAR(20) NOT NULL COMMENT '数据源类型: VOICE/PHOTO/REPORT/MANUAL',
+    dimension_codes VARCHAR(100) COMMENT '关联维度编码(逗号分隔)',
+    parsed_result TEXT COMMENT '解析结果JSON',
+    file_url VARCHAR(500) COMMENT '原始文件URL',
+    confidence DECIMAL(5,4) COMMENT 'AI解析置信度(0-1)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_source (member_id, source_type),
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康数据源记录';
+
+-- 活动评价表
+CREATE TABLE IF NOT EXISTS activity_feedback (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    activity_id BIGINT NOT NULL COMMENT '活动ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '孩子ID',
+    ratings TEXT COMMENT '评分JSON: {"组织形式":5,"会场气氛":4}',
+    comment TEXT COMMENT '文字评价',
+    status TINYINT DEFAULT 0 COMMENT '状态: 0=待审核 1=已发布',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_activity_id (activity_id),
+    INDEX idx_user_id (user_id),
+    INDEX idx_child_id (child_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动评价表';
+
+-- ============================================================
+-- 情绪打卡记录表(迁移117)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS emotion_checkin (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    mood_weather VARCHAR(50) COMMENT '心情晴雨表: sunny/cloudy/rainy/stormy',
+    emotion_tags VARCHAR(200) COMMENT '情绪标签(逗号分隔)',
+    photo_url VARCHAR(500) COMMENT '配图URL',
+    note TEXT COMMENT '文字备注',
+    energy_awarded INT DEFAULT 0 COMMENT '奖励能量值',
+    checkin_date DATE COMMENT '打卡日期',
+    mood_score INT COMMENT '心情分数(1-10)',
+    emotion_type VARCHAR(50) COMMENT '情绪类型',
+    stress_level INT COMMENT '压力等级(1-10)',
+    arousal_level INT COMMENT '激活水平(1-10)',
+    energy_level INT COMMENT '精力水平(1-10)',
+    sleep_hours DECIMAL(4,1) COMMENT '睡眠时长',
+    sleep_quality INT COMMENT '睡眠质量(1-10)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_child_date (child_id, checkin_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='情绪打卡记录';
+
+-- ============================================================
+-- 情绪告警表(迁移118)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS emotion_alert (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    child_id BIGINT NOT NULL COMMENT '家庭成员ID',
+    alert_type VARCHAR(50) NOT NULL COMMENT '告警类型: consecutive_low_mood/crisis_keyword/sharp_decline',
+    severity VARCHAR(20) NOT NULL DEFAULT 'low' COMMENT '严重级别: low/medium/high',
+    title VARCHAR(200) COMMENT '告警标题',
+    message TEXT COMMENT '告警具体描述',
+    related_checkin_id BIGINT COMMENT '关联情绪打卡ID',
+    resolved TINYINT(1) DEFAULT 0 COMMENT '是否已处理',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    resolved_at DATETIME COMMENT '处理时间',
+    INDEX idx_child (child_id),
+    INDEX idx_alert_type (alert_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='情绪告警';
+
+-- ============================================================
+-- 会员等级配置表(迁移119)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS membership_levels (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    level_code VARCHAR(50) NOT NULL COMMENT '等级编码: FREE/PROFESSIONAL/ENTERPRISE',
+    level_name VARCHAR(100) NOT NULL COMMENT '等级名称',
+    level_desc TEXT COMMENT '等级描述',
+    price_monthly INT COMMENT '月费(分)',
+    price_quarterly INT COMMENT '季费(分)',
+    price_yearly INT COMMENT '年费(分)',
+    original_price_monthly INT COMMENT '原价-月费(分)',
+    original_price_quarterly INT COMMENT '原价-季费(分)',
+    original_price_yearly INT COMMENT '原价-年费(分)',
+    features TEXT COMMENT '功能权限列表(JSON)',
+    max_children INT DEFAULT 0 COMMENT '可添加孩子数量',
+    max_tasks_per_day INT DEFAULT 0 COMMENT '每日任务上限',
+    ai_review_enabled TINYINT(1) DEFAULT 0 COMMENT 'AI审核功能',
+    priority_support TINYINT(1) DEFAULT 0 COMMENT '优先客服',
+    teacher_consultation TINYINT(1) DEFAULT 0 COMMENT '老师咨询',
+    level_order INT DEFAULT 1 COMMENT '级别数字(越小级别越低): FREE=1 FAMILY=2 PROVIDER=3 PREMIUM=4 LIFETIME=5',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_level_code (level_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员等级配置';
+
+-- ============================================================
+-- 会员权益配置表(会员等级独立权益管理)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS membership_benefits (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    level_code VARCHAR(50) NOT NULL COMMENT '会员等级: FREE/PROFESSIONAL/ENTERPRISE',
+    benefit_code VARCHAR(50) NOT NULL COMMENT '权益编码: AI_CHAT,FAMILY_MEMBERS等',
+    benefit_name VARCHAR(100) NOT NULL COMMENT '权益名称',
+    benefit_desc VARCHAR(500) COMMENT '权益描述',
+    icon VARCHAR(50) COMMENT '图标emoji',
+    bg_color VARCHAR(20) COMMENT '背景色',
+    sort_order INT DEFAULT 0 COMMENT '排序顺序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_level_benefit (level_code, benefit_code),
+    INDEX idx_level (level_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员权益配置';
+
+-- 会员权益种子数据
+INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES
+('FREE',  'NO_BENEFIT', '暂无会员权益', '开通会员解锁更多权益', '🔒', '#F3F4F6', 0),
+('FREE',  'UPGRADE_HINT', '升级会员', '开通会员享专属权益', '👑', '#FEF3C7', 99),
+('FAMILY', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1),
+('FAMILY', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 10 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2),
+('FAMILY', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案', '📋', '#FEF3C7', 3),
+('FAMILY', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 解读', '📊', '#FCE7F3', 4),
+('FAMILY', 'MANAGER_SERVICE', '专属健康教练服务', '认证健康教练月度随访', '👨‍🏫', '#FFEDD5', 5),
+('FAMILY', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 8-9 折优惠', '🛒', '#E0E7FF', 6),
+('FAMILY', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券享更多优惠', '🎫', '#FEE2E2', 7),
+('PREMIUM', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1),
+('PREMIUM', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 20 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2),
+('PREMIUM', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案 + 定制营养建议', '📋', '#FEF3C7', 3),
+('PREMIUM', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 深度解读', '📊', '#FCE7F3', 4),
+('PREMIUM', 'MANAGER_SERVICE', '专属健康教练服务', '每周 1 次电话随访 + 在线答疑', '👨‍🏫', '#FFEDD5', 5),
+('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 6-8 折优惠', '🛒', '#E0E7FF', 6),
+('PREMIUM', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券 + 会员专属券', '🎫', '#FEE2E2', 7),
+('PREMIUM', 'PRIORITY_SUPPORT', '优先客服', '专属客服通道,快速响应', '🎧', '#EDE9FE', 8),
+('PREMIUM', 'TEACHER_CONSULT', '规划师一对一咨询', '每月 1 次成长规划师专业咨询', '👩‍🏫', '#FBCFE8', 9),
+('PREMIUM', 'GROWTH_COACH', '7X24小时成长教练', 'AI智能教练随时解答家庭教育问题 + 优先响应', '🎯', '#FEF3C7', 10),
+('FAMILY', 'GROWTH_COACH', '7X24小时成长教练', 'AI智能教练随时解答家庭教育问题', '🎯', '#FEF3C7', 10);
+
+-- ============================================================
+-- 家庭会员订阅表(迁移120)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS family_memberships (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    level_code VARCHAR(50) NOT NULL DEFAULT 'FREE' COMMENT '会员等级编码',
+    start_date DATETIME COMMENT '开始时间',
+    end_date DATETIME COMMENT '结束时间',
+    payment_method VARCHAR(50) COMMENT '支付方式: wechat/alipay/card',
+    payment_status VARCHAR(20) DEFAULT 'pending' COMMENT '支付状态: pending/paid/expired/refunded',
+    order_no VARCHAR(100) COMMENT '订单号',
+    transaction_id VARCHAR(200) COMMENT '交易流水号',
+    amount INT COMMENT '支付金额(分)',
+    auto_renew TINYINT(1) DEFAULT 0 COMMENT '自动续费',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_end_date (end_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭会员订阅';
+
+-- ============================================================
+-- 分享事件表(迁移121)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS share_events (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '分享用户ID',
+    target_type VARCHAR(50) NOT NULL COMMENT '分享目标类型: article/growth/product',
+    target_id BIGINT NOT NULL COMMENT '分享目标ID',
+    share_channel VARCHAR(50) COMMENT '分享渠道: wechat/timeline/qq/url',
+    share_title VARCHAR(200) COMMENT '分享标题',
+    share_url VARCHAR(500) COMMENT '分享URL',
+    click_count INT DEFAULT 0 COMMENT '点击次数',
+    read_count INT DEFAULT 0 COMMENT '阅读次数',
+    reward_points INT DEFAULT 0 COMMENT '奖励积分',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired',
+    expires_at DATETIME COMMENT '过期时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user (user_id),
+    INDEX idx_target (target_type, target_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分享事件';
+
+-- ============================================================
+-- 分享阅读记录表(迁移122)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS share_reads (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    share_event_id BIGINT NOT NULL COMMENT '分享事件ID',
+    reader_id BIGINT COMMENT '阅读者用户ID',
+    reader_ip VARCHAR(50) COMMENT '阅读者IP',
+    read_duration INT COMMENT '阅读时长(秒)',
+    read_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '阅读时间',
+    INDEX idx_share_event (share_event_id),
+    INDEX idx_reader (reader_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分享阅读记录';
+
+-- ============================================================
+-- 饮食推荐指数表(迁移123)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS food_recommend_idx (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    food_id BIGINT NOT NULL COMMENT '食材ID',
+    food_name VARCHAR(100) COMMENT '食材名称',
+    index_score INT COMMENT '推荐指数(0-100)',
+    health_report_id BIGINT COMMENT '关联健康报告ID',
+    calculated_at DATETIME COMMENT '计算时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user (user_id),
+    INDEX idx_food (food_id),
+    INDEX idx_report (health_report_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食推荐指数';
+
+-- 报告食材适宜度
+CREATE TABLE IF NOT EXISTS report_food_suitability (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    report_id BIGINT NOT NULL COMMENT '健康报告ID',
+    food_id BIGINT COMMENT '食材ID',
+    food_name VARCHAR(100) NOT NULL COMMENT '食材名称',
+    category VARCHAR(50) COMMENT '分类',
+    score INT NOT NULL COMMENT '适宜度评分',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_report (report_id),
+    INDEX idx_food (food_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告食材适宜度';
+
+-- ============================================================
+-- 家庭共餐选中食材表(持久化共餐场景食材选择)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS diet_family_selected_ingredients (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    food_id BIGINT NOT NULL COMMENT '食材ID',
+    food_name VARCHAR(100) COMMENT '食材名称',
+    score INT COMMENT '推荐分',
+    reason VARCHAR(255) COMMENT '推荐理由',
+    category VARCHAR(50) COMMENT '分类',
+    calories_per_100g INT COMMENT '每100克卡路里',
+    cooking_level TINYINT COMMENT '烹饪难度(1=快手,2=中等,3=耗时)',
+    nutrition_score INT COMMENT '营养密度分(0-100)',
+    balance_reason VARCHAR(255) COMMENT '均衡化推荐理由',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_family_food (family_id, food_id),
+    INDEX idx_family (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭共餐选中食材';
+
+-- ============================================================
+-- 积分兑换商品表(迁移124)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS points_exchange_products (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(200) NOT NULL COMMENT '商品名称',
+    description TEXT COMMENT '商品描述',
+    cover_image VARCHAR(500) COMMENT '封面图片',
+    points_price INT NOT NULL COMMENT '积分价格',
+    stock INT DEFAULT 0 COMMENT '库存',
+    product_type VARCHAR(50) COMMENT '商品类型',
+    category VARCHAR(50) COMMENT '分类',
+    expiry_days INT COMMENT '有效期(天)',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/disabled',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_status (status),
+    INDEX idx_category (category)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分兑换商品';
+
+-- ============================================================
+-- 积分兑换记录表(迁移125)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS points_exchange_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID',
+    child_id BIGINT COMMENT '孩子ID',
+    family_member_id BIGINT COMMENT '家庭成员ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    product_name VARCHAR(200) COMMENT '商品名称快照',
+    points_cost INT NOT NULL COMMENT '消耗积分',
+    quantity INT DEFAULT 1 COMMENT '数量',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed/cancelled',
+    redeem_code VARCHAR(100) COMMENT '兑换码',
+    expired_at DATETIME COMMENT '过期时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user (user_id),
+    INDEX idx_product (product_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分兑换记录';
+
+-- 文章阅读记录表
+CREATE TABLE IF NOT EXISTS article_reading_records (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  article_id BIGINT DEFAULT NULL COMMENT '文章ID',
+  user_id BIGINT COMMENT '用户ID',
+  child_id BIGINT NOT NULL COMMENT '孩子ID',
+  content VARCHAR(500) COMMENT '阅读内容摘要',
+  duration_seconds INT DEFAULT 0 COMMENT '阅读时长(秒)',
+  task_id BIGINT DEFAULT NULL COMMENT '关联的任务ID',
+  reading_completed TINYINT DEFAULT 0 COMMENT '是否已阅读达标(达到最低时长阈值)',
+  read_at DATETIME COMMENT '阅读时间',
+  updated_at DATETIME DEFAULT NULL COMMENT '更新时间',
+  created_at DATETIME,
+  INDEX idx_child_id (child_id),
+  INDEX idx_read_at (read_at),
+  INDEX idx_article_child_date (article_id, child_id, read_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章阅读记录表';
+
+-- 文章答题记录表
+CREATE TABLE IF NOT EXISTS article_quiz_records (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  article_id BIGINT DEFAULT NULL COMMENT '文章ID',
+  user_id BIGINT DEFAULT NULL COMMENT '用户ID',
+  member_id BIGINT DEFAULT NULL COMMENT '家庭成员ID(作答人)',
+  questions TEXT COMMENT '题目快照JSON(含正确答案与维度)',
+  answers TEXT COMMENT '用户作答JSON(含每题所选选项),作答前为NULL',
+  score INT DEFAULT 0 COMMENT '答对题数',
+  total_questions INT DEFAULT 0 COMMENT '题目总数',
+  cf_earned INT DEFAULT 0 COMMENT '发放的CF值',
+  energy_earned INT DEFAULT 0 COMMENT '发放的能量值',
+  created_at DATETIME,
+  updated_at DATETIME,
+  INDEX idx_member (member_id),
+  INDEX idx_article (article_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章答题记录表';
+
+-- ============================================================
+-- 统一报告汇总表(report_summary)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS report_summary (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+  report_type VARCHAR(32) NOT NULL COMMENT '报告类型',
+  source_table VARCHAR(32) NOT NULL COMMENT '来源表',
+  source_id BIGINT NOT NULL COMMENT '来源表记录ID',
+  user_id BIGINT NOT NULL COMMENT '创建者用户ID',
+  family_id BIGINT COMMENT '家庭ID',
+  subject_id BIGINT COMMENT '报告主体ID',
+  subject_name VARCHAR(50) COMMENT '报告主体名称',
+  title VARCHAR(200) COMMENT '报告标题',
+  summary TEXT COMMENT '摘要',
+  file_url VARCHAR(500) COMMENT '文件URL',
+  file_size BIGINT COMMENT '文件大小',
+  file_name VARCHAR(200) COMMENT '文件名',
+  has_file TINYINT DEFAULT 0 COMMENT '是否有文件',
+  overall_score INT COMMENT '综合评分',
+  report_date DATE COMMENT '报告日期',
+  status VARCHAR(20) COMMENT '状态',
+  detail_route VARCHAR(100) COMMENT '详情页路由',
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  INDEX idx_user_id (user_id),
+  INDEX idx_family_id (family_id),
+  INDEX idx_subject_id (subject_id),
+  INDEX idx_report_type (report_type),
+  INDEX idx_report_date (report_date),
+  INDEX idx_status (status),
+  UNIQUE KEY uk_source (source_table, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统一报告汇总表';
+-- report_summary 表已由 DatabaseInitializer 迁移创建
+
+-- category_ppoint: 品类P点比例配置(P点体系)
+CREATE TABLE IF NOT EXISTS category_ppoint (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    category_id BIGINT NOT NULL COMMENT '商品类目ID',
+    ratio INT DEFAULT 0 COMMENT 'P点比例(千分比)',
+    start_date DATETIME COMMENT '生效日期',
+    end_date DATETIME COMMENT '失效日期(null=永不过期)',
+    created_by BIGINT COMMENT '创建人',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_category_id (category_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='品类P点比例配置';
+
+-- commission_dist_log: P点分润记录(P点体系)
+CREATE TABLE IF NOT EXISTS commission_dist_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    buyer_id BIGINT NOT NULL COMMENT '买家用户ID',
+    order_id BIGINT COMMENT '订单ID',
+    product_id BIGINT COMMENT '商品ID',
+    source_ppoint INT DEFAULT 0 COMMENT '本次产生的原始P点数量',
+    level1_user_id BIGINT COMMENT '一级推荐人用户ID',
+    level1_ppoint INT DEFAULT 0 COMMENT '一级推荐人获得P点',
+    level1_wealth_percent INT DEFAULT 0 COMMENT '一级推荐人当时财富等级(profitSharePercent)',
+    level2_user_id BIGINT COMMENT '二级推荐人用户ID',
+    level2_ppoint INT DEFAULT 0 COMMENT '二级推荐人获得P点',
+    level2_wealth_percent INT DEFAULT 0 COMMENT '二级推荐人当时财富等级(profitSharePercent)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed/failed',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_buyer (buyer_id),
+    INDEX idx_order (order_id),
+    INDEX idx_level1 (level1_user_id),
+    INDEX idx_level2 (level2_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='P点分润记录';
+
+-- 会员订单表
+CREATE TABLE IF NOT EXISTS payment_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    level_code VARCHAR(50) DEFAULT 'FREE' COMMENT '会员等级编码',
+    payment_type VARCHAR(20) DEFAULT 'yearly' COMMENT '支付类型: yearly/monthly/trial',
+    period VARCHAR(20) DEFAULT 'yearly' COMMENT '订阅周期: monthly/quarterly/yearly',
+    amount INT COMMENT '支付金额(分)',
+    user_coupon_id BIGINT COMMENT '使用的用户优惠券ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/expired',
+    group_no VARCHAR(64) COMMENT '拆单组号(同一拆单组共享)',
+    period_no INT DEFAULT 1 COMMENT '期数序号(1开始)',
+    pay_method VARCHAR(20) COMMENT '支付方式',
+    transaction_id VARCHAR(200) COMMENT '交易流水号',
+    paid_at DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_order_no (order_no),
+    INDEX idx_family_id (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员支付订单';
+
+-- 能量行为配置表(可配置每种行为的能量发放规则)
+CREATE TABLE IF NOT EXISTS energy_behavior_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    behavior_code VARCHAR(32) NOT NULL COMMENT '行为编码: task_complete/game_finish/...',
+    behavior_name VARCHAR(50) NOT NULL COMMENT '行为名称',
+    description VARCHAR(200) COMMENT '行为描述',
+    default_amount INT DEFAULT 5 COMMENT '默认能量值',
+    amount_source VARCHAR(32) DEFAULT 'fixed' COMMENT '能量值来源: fixed(固定)/points(跟随积分)/order_amount(跟随金额)/config(从配置表读取)',
+    dimension_assign JSON COMMENT '维度分配: [{"dim":"action","ratio":100}]',
+    daily_limit INT DEFAULT 0 COMMENT '每日上限(0=不限制)',
+    cooldown_seconds INT DEFAULT 0 COMMENT '冷却时间(秒,0=无冷却)',
+    expire_days INT DEFAULT 0 COMMENT '过期天数(0=永久)',
+    enabled TINYINT DEFAULT 1 COMMENT '是否启用: 0=禁用 1=启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_behavior_code (behavior_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量行为配置表';
+
+-- 文章评论表
+CREATE TABLE IF NOT EXISTS article_comments (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    article_id BIGINT NOT NULL COMMENT '文章ID',
+    parent_id BIGINT COMMENT '父评论ID',
+    reply_to_id BIGINT COMMENT '引用的评论ID',
+    user_id BIGINT NOT NULL COMMENT '评论用户ID',
+    content TEXT NOT NULL COMMENT '评论内容',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected',
+    audit_by BIGINT COMMENT '审核人ID',
+    audit_at DATETIME COMMENT '审核时间',
+    earned_energy INT DEFAULT 0 COMMENT '获得的能量',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_article (article_id),
+    INDEX idx_article_status (article_id, status),
+    INDEX idx_parent (parent_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章评论';
+
+-- ============================================================
+-- 服务角色申请表(统一角色申请入口)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `service_role_applications` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL COMMENT '申请人ID',
+  `roles` varchar(255) NOT NULL COMMENT '申请角色,JSON数组 e.g. ["planner","nutritionist"]',
+  `id_card` varchar(18) DEFAULT '' COMMENT '身份证号',
+  `id_card_images` text COMMENT '身份证照片,JSON数组[正面URL,反面URL]',
+  `certificate_images` text COMMENT '资质证书照片,JSON数组',
+  `province` varchar(50) DEFAULT '' COMMENT '省',
+  `city` varchar(50) DEFAULT '' COMMENT '市',
+  `district` varchar(50) DEFAULT '' COMMENT '区',
+  `street` varchar(50) DEFAULT '' COMMENT '街道',
+  `address_detail` varchar(200) DEFAULT '' COMMENT '详细地址',
+  `status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0-pending, 1-approved, 2-rejected',
+  `reject_reason` varchar(500) DEFAULT '' COMMENT '驳回原因',
+  `reviewed_by` int(11) DEFAULT NULL COMMENT '审核人ID',
+  `reviewed_at` datetime DEFAULT NULL COMMENT '审核时间',
+  `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_user_id` (`user_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务角色申请表';
+
+-- 用户表新增字段(服务角色申请状态)
+-- ALTER TABLE users ADD COLUMN article_admin_status VARCHAR(20) DEFAULT NULL COMMENT '文章管理员状态: pending/approved/rejected';
+-- ALTER TABLE users ADD COLUMN activity_provider_status VARCHAR(20) DEFAULT NULL COMMENT '活动提供商状态: pending/approved/rejected';
+
+-- ============================================================
+-- 周期性服务续费配置
+-- ============================================================
+CREATE TABLE IF NOT EXISTS periodic_services (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联商品ID',
+    service_name VARCHAR(100) COMMENT '服务名称',
+    cycle_days INT NOT NULL COMMENT '周期天数(如365=年度服务)',
+    remind_before_days INT DEFAULT 30 COMMENT '提前提醒天数',
+    auto_renew TINYINT DEFAULT 0 COMMENT '是否支持自动续费: 0=否 1=是',
+    enabled TINYINT DEFAULT 1 COMMENT '是否启用: 0=禁用 1=启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_product (product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='周期性服务续费配置';
+
+-- ============================================================
+-- 推荐行为记录
+-- ============================================================
+CREATE TABLE IF NOT EXISTS recommendation_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT COMMENT '用户ID',
+    family_id BIGINT COMMENT '家庭ID',
+    child_id BIGINT COMMENT '孩子ID',
+    content_type VARCHAR(20) NOT NULL COMMENT '内容类型: article/task/product/activity',
+    content_id BIGINT NOT NULL COMMENT '内容ID',
+    recommend_score INT COMMENT '推荐分值',
+    action VARCHAR(20) COMMENT '行为: show/click/consume',
+    recommend_type VARCHAR(20) DEFAULT 'tag' COMMENT '推荐方式: profile/dimension/tag',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_user_content (user_id, content_type, content_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐行为记录';
+
+-- products表添加family_scope列(推荐系统-家庭消费去重)
+-- ALTER TABLE products ADD COLUMN family_scope TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员消费过即排除, 0=仅按个人消费排除';
+
+-- activities表添加family_scope列(推荐系统-家庭参与去重)
+-- ALTER TABLE activities ADD COLUMN family_scope TINYINT DEFAULT 1 COMMENT '是否面向家庭: 1=家庭成员参与过即排除, 0=仅按个人参与排除';
+
+-- ============================================================
+-- 活动报名订单表(付费报名:先支付后报名,迁移143)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS activity_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单号(ACT+时间戳+随机)',
+    activity_id BIGINT NOT NULL COMMENT '活动ID',
+    user_id BIGINT NOT NULL COMMENT '下单人userId',
+    total_amount INT NOT NULL COMMENT '实付金额(分)',
+    registrants_json TEXT COMMENT '报名人快照JSON: [{name, phone, childId, type: child/parent, fee}]',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/cancelled',
+    transaction_id VARCHAR(64) COMMENT '微信支付单号',
+    paid_at DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_activity (activity_id),
+    INDEX idx_user (user_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动报名订单表';
+
+-- ============================================================
+-- 活动总结资料表(总结中阶段发布人上传资料,迁移144)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS activity_materials (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    activity_id BIGINT NOT NULL COMMENT '活动ID',
+    title VARCHAR(200) COMMENT '标题',
+    file_url VARCHAR(500) NOT NULL COMMENT '文件地址(图片/PDF/外链URL)',
+    file_type VARCHAR(20) DEFAULT 'image' COMMENT '类型: image/pdf/link',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_activity (activity_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动总结资料表';
+
+-- ============================================================
+-- 小程序虚拟支付道具映射
+-- ============================================================
+CREATE TABLE IF NOT EXISTS virtual_goods_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    goods_type VARCHAR(32) NOT NULL COMMENT 'ASSESSMENT_PACKAGE / MEMBERSHIP / SUBSCRIPTION',
+    biz_key VARCHAR(64) NOT NULL COMMENT '业务SKU标识: 测评套餐ID / 会员等级code / 订阅档位code',
+    product_id VARCHAR(64) NOT NULL COMMENT '微信商户后台道具ID',
+    goods_name VARCHAR(128) NOT NULL COMMENT '道具名称',
+    goods_price INT COMMENT '道具价格(分), 用于拆单判断',
+    status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=停用',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_goods_type_biz (goods_type, biz_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小程序虚拟支付道具映射';
+
+-- ============================================================
+-- 家庭天盘周报导出记录表(迁移144)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS family_fortune_report (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id INT NOT NULL COMMENT '家庭ID',
+    element VARCHAR(32) COMMENT '主导五行元素',
+    lucky_direction VARCHAR(64) COMMENT '本周吉位',
+    pdf_path VARCHAR(255) COMMENT 'PDF周报文件路径',
+    file_size BIGINT COMMENT '文件大小(字节)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭天盘周报导出记录';
+
+-- ============================================================
+-- 五维家庭自检记录表(迁移146,P0-1 五维自检问卷,书稿终章15题)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS five_dimension_self_checks (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '答卷人用户ID',
+    family_member_id BIGINT COMMENT '关联家庭成员ID(可选)',
+    answers_json TEXT COMMENT '用户答案JSON',
+    scores_json VARCHAR(512) COMMENT '各维度得分JSON',
+    total_score INT COMMENT '五维总分(满分45)',
+    advice_json TEXT COMMENT '五行相生寻源建议JSON',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    question_ids_json VARCHAR(255) DEFAULT NULL COMMENT '本次自检使用的题号JSON: [1,5,9,...]',
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维家庭自检记录';
+
+-- 商品赠送会员规则
+CREATE TABLE IF NOT EXISTS product_gift_rules (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联products表',
+    level_code VARCHAR(50) NOT NULL COMMENT '赠送会员等级编码(FAMILY/PREMIUM等)',
+    status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_product_level (product_id, level_code),
+    INDEX idx_product_id (product_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠送会员规则';
+
+-- 商品赠品配置(商品×等级)
+CREATE TABLE IF NOT EXISTS product_gift_configs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '关联products表',
+    level_code VARCHAR(50) NOT NULL COMMENT '会员等级编码(FAMILY/PREMIUM等)',
+    status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_product_level (product_id, level_code),
+    INDEX idx_product_id (product_id),
+    INDEX idx_level_code (level_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品配置(商品×等级)';
+
+-- 商品赠品项
+CREATE TABLE IF NOT EXISTS product_gift_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    config_id BIGINT NOT NULL COMMENT '关联product_gift_configs.id',
+    gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值',
+    gift_product_id BIGINT COMMENT '赠品商品ID(gift_type=product时必填)',
+    gift_product_name VARCHAR(200) COMMENT '赠品商品名快照',
+    gift_product_image VARCHAR(500) COMMENT '赠品商品图快照',
+    cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时必填,单位分)',
+    sort_order INT DEFAULT 0 COMMENT '排序(值越小越靠前)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_config_id (config_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品项';
+
+-- 订单赠品记录
+CREATE TABLE IF NOT EXISTS product_order_gifts (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    order_id BIGINT NOT NULL COMMENT '关联product_orders.id',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号快照',
+    gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值',
+    gift_product_id BIGINT COMMENT '赠品商品ID',
+    gift_product_name VARCHAR(200) COMMENT '赠品商品名快照',
+    gift_product_image VARCHAR(500) COMMENT '赠品商品图快照',
+    cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时)',
+    quantity INT DEFAULT 1 COMMENT '数量(默认1)',
+    fulfilled TINYINT(1) DEFAULT 0 COMMENT '0=已选未发放 1=已发放',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_order_id (order_id),
+    INDEX idx_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单赠品记录';
+
+CREATE TABLE IF NOT EXISTS interaction_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '所属家庭ID',
+    from_member_id BIGINT NULL COMMENT '互动发起方成员ID',
+    to_member_id BIGINT NULL COMMENT '互动接收方成员ID',
+    interaction_type VARCHAR(20) NOT NULL COMMENT '互动类型: dining/outgoing/call/video/chat/play/gift',
+    description VARCHAR(500) COMMENT '互动描述',
+    happened_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '互动发生时间',
+    photo_urls TEXT COMMENT '照片URL列表(逗号分隔)',
+    video_url VARCHAR(500) COMMENT '视频URL',
+    audio_url VARCHAR(500) COMMENT '音频URL',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_ilog_family (family_id),
+    INDEX idx_ilog_to_member (to_member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员互动记录';
+
+CREATE TABLE IF NOT EXISTS relationship_questionnaire_snapshots (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_member_id BIGINT NULL COMMENT '家庭成员ID',
+    respondent_id BIGINT NOT NULL COMMENT '答卷人用户ID',
+    member_name VARCHAR(50) COMMENT '成员名称',
+    relationship_type VARCHAR(20) COMMENT '关系类型: parent/child',
+    ai_generated_json TEXT COMMENT 'AI生成的题目JSON',
+    version VARCHAR(20) COMMENT '版本号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_q_snap_member (family_member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系问卷快照';
+
+CREATE TABLE IF NOT EXISTS relationship_questionnaire_responses (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    snapshot_id BIGINT NOT NULL COMMENT '问卷快照ID',
+    respondent_id BIGINT NOT NULL COMMENT '答卷人用户ID',
+    answers_json TEXT COMMENT '用户回答JSON',
+    trust_score DECIMAL(5,2) DEFAULT 0 COMMENT '信任度得分',
+    intimacy_score DECIMAL(5,2) DEFAULT 0 COMMENT '亲密度得分',
+    communication_score DECIMAL(5,2) DEFAULT 0 COMMENT '沟通质量得分',
+    interaction_bonus DECIMAL(5,2) DEFAULT 0 COMMENT '互动记录加分',
+    total_score DECIMAL(5,2) DEFAULT 0 COMMENT '综合得分',
+    calculated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_q_resp_snap (snapshot_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系问卷回答记录';
+
+-- =============================================
+-- 优惠券发放流水表(会员体系重构: 统一价+优惠券发放/积分兑换)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS coupon_grant_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '收券用户ID',
+    coupon_id BIGINT NOT NULL COMMENT '券模板ID',
+    grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE',
+    period VARCHAR(16) COMMENT '周期标识(YYYY-MM或YYYY-Qn),PERIODIC防重用',
+    quantity INT DEFAULT 1 COMMENT '发放数量',
+    source VARCHAR(64) COMMENT '触发来源(订单号/成员ID等)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_grant (user_id, coupon_id, grant_type, period),
+    INDEX idx_coupon (coupon_id),
+    INDEX idx_user (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='优惠券发放流水表';
+
+-- =============================================
+-- 家庭优惠券表(全链路改为家庭维度:2026-08-31)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS family_coupon (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '所属家庭ID',
+    coupon_id BIGINT NOT NULL COMMENT '券模板ID',
+    status VARCHAR(16) DEFAULT 'AVAILABLE' COMMENT 'AVAILABLE/USED',
+    received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    used_at DATETIME,
+    order_id BIGINT COMMENT '核销时写入的订单号',
+    INDEX idx_family_coupon (family_id, coupon_id),
+    INDEX idx_status (status),
+    INDEX idx_order_id (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券表';
+
+CREATE TABLE IF NOT EXISTS family_coupon_grant_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '受赠家庭ID',
+    coupon_id BIGINT NOT NULL COMMENT '券模板ID',
+    grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE/CF_EXCHANGE',
+    period VARCHAR(16) COMMENT '周期标识(YYYY-MM或YYYY-Qn),PERIODIC防重用',
+    quantity INT DEFAULT 1 COMMENT '发放数量',
+    source VARCHAR(64) COMMENT '触发来源(订单号/成员ID等)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_family_grant (family_id, coupon_id, grant_type, period),
+    INDEX idx_coupon (coupon_id),
+    INDEX idx_family (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭优惠券发放流水表';
+
+-- 家庭加入请求(邀请人审批,旧数据由家庭管理员兜底)
+CREATE TABLE IF NOT EXISTS family_join_requests (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    requester_user_id BIGINT NOT NULL COMMENT '申请加入的用户ID',
+    target_family_id BIGINT NOT NULL COMMENT '目标家庭ID',
+    join_method VARCHAR(20) NOT NULL COMMENT '加入方式: by_code/by_token',
+    invite_ref VARCHAR(50) COMMENT '邀请码或令牌',
+    inviter_user_id BIGINT COMMENT '邀请人用户ID(分享邀请令牌的人)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected/cancelled',
+    approver_user_id BIGINT COMMENT '审批人ID',
+    comment TEXT COMMENT '审批备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_requester (requester_user_id),
+    INDEX idx_target_family (target_family_id),
+    INDEX idx_status (status),
+    INDEX idx_inviter (inviter_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭加入请求(待审批)';
+
+-- 用户同意记录表
+CREATE TABLE IF NOT EXISTS user_consent_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    consent_type VARCHAR(50) NOT NULL COMMENT '同意类型: privacy/agreement/children_data/ai_processing',
+    version VARCHAR(20) NOT NULL COMMENT '协议版本号',
+    status VARCHAR(20) NOT NULL DEFAULT 'agreed' COMMENT 'agreed/withdrawn/archived',
+    ip_address VARCHAR(45) COMMENT '用户IP地址',
+    created_at DATETIME NOT NULL,
+    updated_at DATETIME,
+    INDEX idx_user_consent (user_id, consent_type),
+    INDEX idx_consent_type (consent_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户同意记录';
+
+-- 用户数据授权表
+CREATE TABLE IF NOT EXISTS user_data_permissions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    allow_ai_analysis TINYINT(1) DEFAULT 0 COMMENT '允许AI分析对话数据',
+    allow_personalized_recommend TINYINT(1) DEFAULT 1 COMMENT '允许个性化推荐',
+    allow_share_teacher TINYINT(1) DEFAULT 1 COMMENT '允许向成长规划师共享数据',
+    allow_third_party_share TINYINT(1) DEFAULT 0 COMMENT '允许与第三方共享数据',
+    created_at DATETIME NOT NULL,
+    updated_at DATETIME,
+    INDEX idx_user_perm (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户数据使用授权';
+
+-- ===== 饮食模块 =====
+
+-- 食材库表
+CREATE TABLE IF NOT EXISTS foods (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '食材名称',
+    category VARCHAR(50) COMMENT '分类: 蔬菜/水果/肉禽/水产/蛋奶/豆类/谷物/调味/其他',
+    unit VARCHAR(20) DEFAULT 'g' COMMENT '计量单位',
+    calories DECIMAL(8,2) COMMENT '热量(kcal/100g)',
+    protein DECIMAL(8,2) COMMENT '蛋白质(g/100g)',
+    fat DECIMAL(8,2) COMMENT '脂肪(g/100g)',
+    carbs DECIMAL(8,2) COMMENT '碳水(g/100g)',
+    fiber DECIMAL(8,2) COMMENT '膳食纤维(g/100g)',
+    energy_kj DECIMAL(8,2) COMMENT '能量(kJ/100g)',
+    starch DECIMAL(8,2) COMMENT '淀粉(g/100g)',
+    cholesterol DECIMAL(8,2) COMMENT '胆固醇(mg/100g)',
+    price_level TINYINT COMMENT '价格等级 1-5',
+    nutrition_tags VARCHAR(500) COMMENT '营养标签JSON',
+    suitable_for VARCHAR(200) COMMENT '适宜人群JSON',
+    allergens VARCHAR(200) COMMENT '过敏原JSON',
+    external_source VARCHAR(100) COMMENT '数据来源',
+    external_id VARCHAR(100) COMMENT '外部ID',
+    cover_image VARCHAR(500) COMMENT '食材图片',
+    report_id BIGINT COMMENT '关联报告ID(从PDF解析的食物推荐)',
+    score INT COMMENT '食物评分',
+    status VARCHAR(20) DEFAULT 'active',
+    sort_order INT DEFAULT 0,
+    diet_type VARCHAR(50) COMMENT '饮食类型',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_category (category),
+    INDEX idx_price (price_level)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材库表';
+
+-- 食材营养素表(每食材多营养素)
+CREATE TABLE IF NOT EXISTS food_nutrients (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    food_id BIGINT NOT NULL COMMENT '食材ID',
+    nutrient_code VARCHAR(20) NOT NULL COMMENT '营养素编码',
+    amount_per100g DECIMAL(10,4) NOT NULL COMMENT '每100g含量',
+    unit VARCHAR(20) NOT NULL COMMENT '单位',
+    source VARCHAR(50) DEFAULT 'importer' COMMENT '数据来源',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_food_nutrient (food_id, nutrient_code),
+    INDEX idx_nutrient (nutrient_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材营养素表';
+
+-- 饮食偏好调研表(每成员一份)
+CREATE TABLE IF NOT EXISTS diet_preferences (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_member_id BIGINT NULL COMMENT '绑定家庭成员',
+    allergies JSON COMMENT '过敏原列表',
+    absolute_avoid JSON COMMENT '绝对忌口列表',
+    must_eat JSON COMMENT '必吃食物列表',
+    religious_diet VARCHAR(50) DEFAULT 'none' COMMENT '宗教饮食:none/清真/素食/纯素/印度素',
+    spice_level TINYINT DEFAULT NULL COMMENT '辣度 0-5',
+    flavor_pref JSON COMMENT '口味偏好(酸/甜/咸/鲜/苦)',
+    cuisine_pref JSON COMMENT '菜系偏好(川/粤/淮扬/日料/西餐)',
+    cooking_methods JSON COMMENT '偏好烹饪方式(炒/蒸/煮/烤/凉拌)',
+    health_goals JSON COMMENT '健康目标(减脂/增肌/控糖/肠道调理/护肝)',
+    goal_source VARCHAR(20) DEFAULT NULL COMMENT 'manual/system_recommend',
+    goal_confirmed TINYINT(1) DEFAULT 0 COMMENT '用户已确认',
+    no_preference TINYINT(1) DEFAULT 0 COMMENT '无特殊偏好标记(1=用户明确选择无特殊偏好)',
+    filled_at TIMESTAMP NULL COMMENT '首次填写时间',
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_member (family_member_id),
+    INDEX idx_updated (updated_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食偏好调研表';
+
+-- 共餐配置表(按家庭+日期类型+餐次;config_date非NULL时为当天临时配置,优先级高于默认配置)
+CREATE TABLE IF NOT EXISTS meal_configs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    config_date_type VARCHAR(20) NOT NULL COMMENT '工作日/周末/节假日',
+    config_date DATE DEFAULT NULL COMMENT '当天临时配置日期(NULL=默认工作日/周末配置)',
+    config_date_eff DATE GENERATED ALWAYS AS (COALESCE(config_date, '9999-12-31')) STORED COMMENT '生效日期(NULL映射为9999-12-31,用于唯一约束)',
+    meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner',
+    participant_member_ids JSON NOT NULL COMMENT '参与成员ID列表',
+    notes VARCHAR(255) DEFAULT NULL COMMENT '备注',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_config (family_id, config_date_eff, meal_type),
+    INDEX idx_family (family_id),
+    INDEX idx_config_date (config_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='共餐配置表';
+
+-- 食谱推荐方案表
+CREATE TABLE IF NOT EXISTS diet_recommendations (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    recommendation_date DATE NOT NULL COMMENT '推荐日期',
+    meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner',
+    participant_member_ids JSON COMMENT '参与成员ID列表',
+    menu_json MEDIUMTEXT NOT NULL COMMENT '菜品列表JSON',
+    nutrition_summary JSON COMMENT '营养汇总',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/accepted/adjusted/completed/skipped',
+    version INT DEFAULT 1 COMMENT '版本号(重新生成+1)',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_date (family_id, recommendation_date),
+    INDEX idx_meal (meal_type, recommendation_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱推荐方案表';
+
+-- 饮食记录表
+CREATE TABLE IF NOT EXISTS diet_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    member_id BIGINT NULL COMMENT '吃的成员ID',
+    meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner/snack',
+    record_date DATE NOT NULL COMMENT '记录日期',
+    record_method VARCHAR(20) DEFAULT NULL COMMENT 'photo/manual/from_recommendation',
+    image_url VARCHAR(500) DEFAULT NULL COMMENT '原图OSS路径',
+    ai_recognized_foods JSON DEFAULT NULL COMMENT 'AI识别结果',
+    user_confirmed_foods JSON NOT NULL COMMENT '用户最终确认食材',
+    notes VARCHAR(500) DEFAULT NULL COMMENT '备注',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_date (member_id, record_date),
+    INDEX idx_family_date (family_id, record_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录表';
+
+-- 饮食记录食材明细表
+CREATE TABLE IF NOT EXISTS diet_record_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    record_id BIGINT NOT NULL COMMENT 'diet_records.id',
+    food_name VARCHAR(100) NOT NULL COMMENT '食材名称',
+    food_id BIGINT DEFAULT NULL COMMENT '关联foods表ID',
+    confidence DECIMAL(3,2) DEFAULT NULL COMMENT 'AI置信度0-1',
+    source VARCHAR(20) DEFAULT NULL COMMENT 'ai_recognized/manual/from_recommendation',
+    INDEX idx_record (record_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录食材明细表';
+
+-- 报告指纹检测与解析器调度系统
+
+-- 报告类型注册表
+CREATE TABLE IF NOT EXISTS report_type_registry (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    type_id VARCHAR(64) NOT NULL COMMENT '类型标识,如 dan_a1/gut_beijing',
+    family VARCHAR(32) DEFAULT NULL COMMENT '报告家族: dan/gut_flora/其他',
+    display_name VARCHAR(100) NOT NULL COMMENT '显示名称',
+    description VARCHAR(500) DEFAULT NULL COMMENT '描述',
+    parser_path VARCHAR(255) DEFAULT NULL COMMENT 'Python解析器脚本路径',
+    min_confidence INT DEFAULT 10 COMMENT '识别阈值',
+    is_active TINYINT(1) DEFAULT 1 COMMENT '启用状态',
+    source VARCHAR(20) DEFAULT 'prebuilt' COMMENT '来源: prebuilt/imported/auto_generated',
+    review_status VARCHAR(20) DEFAULT 'approved' COMMENT '审核状态: approved/pending/rejected',
+    version VARCHAR(20) DEFAULT '1.0' COMMENT '版本',
+    created_by BIGINT DEFAULT NULL COMMENT '创建人ID',
+    script_status VARCHAR(20) DEFAULT NULL COMMENT '采集脚本生成状态: generating/ready/failed',
+    expected_indicators TEXT DEFAULT NULL COMMENT '该报告类型应采集的指标项定义(JSON数组),用于数据采集完整性校验',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_type_id (type_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告类型注册表';
+
+-- 报告指纹规则表
+CREATE TABLE IF NOT EXISTS report_fingerprint (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    type_id VARCHAR(64) NOT NULL COMMENT '关联report_type_registry.type_id',
+    pattern VARCHAR(200) NOT NULL COMMENT '文本特征',
+    weight INT DEFAULT 10 COMMENT '权重(正=加分,负=排除扣分)',
+    is_exclusion TINYINT(1) DEFAULT 0 COMMENT '是否为排除规则',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_fp_type (type_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告指纹规则表';
+
+-- 未知报告聚类表
+CREATE TABLE IF NOT EXISTS report_unknown_cluster (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    cluster_key VARCHAR(64) NOT NULL COMMENT '聚类哈希键(同类报告分组)',
+    common_signatures TEXT COMMENT '公共文本特征JSON(用于生成指纹)',
+    llm_template TEXT COMMENT 'LLM解析结果模板JSON',
+    report_count INT DEFAULT 0 COMMENT '累计报告数',
+    status VARCHAR(20) DEFAULT 'collecting' COMMENT '状态: collecting/ready/generated/imported',
+    generated_type_id VARCHAR(64) DEFAULT NULL COMMENT '生成的类型ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_cluster_key (cluster_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='未知报告聚类表';
+
+-- 未知报告上传明细表
+CREATE TABLE IF NOT EXISTS report_unknown_upload (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    cluster_id BIGINT DEFAULT NULL COMMENT '关联report_unknown_cluster.id',
+    file_url VARCHAR(500) DEFAULT NULL COMMENT '文件路径',
+    file_hash VARCHAR(64) DEFAULT NULL COMMENT '文件哈希',
+    llm_result TEXT COMMENT 'LLM解析结果JSON',
+    annotation TEXT COMMENT '管理员标注: 手动指定类型+确认提取结果JSON',
+    uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_unknown_cluster (cluster_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='未知报告上传明细表';
+
+-- ========== 报告展示模板表 ==========
+CREATE TABLE IF NOT EXISTS report_template (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(100) NOT NULL COMMENT '模板名称',
+    report_type VARCHAR(50) NOT NULL COMMENT '报告类型: gut_flora/dan/physical_exam/tongue',
+    config JSON NOT NULL COMMENT '模板配置JSON: fixedBlocks+freeDisplay',
+    is_active TINYINT DEFAULT 1 COMMENT '0=停用 1=启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_report_type (report_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告展示模板';
+
+-- 报告解析器导入记录表
+CREATE TABLE IF NOT EXISTS report_parser_import (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    package_name VARCHAR(100) NOT NULL COMMENT '导入包名称',
+    package_file_url VARCHAR(500) DEFAULT NULL COMMENT '导入包文件路径',
+    fingerprint_json TEXT COMMENT '指纹规则JSON',
+    parser_script TEXT COMMENT '解析器脚本内容',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected',
+    imported_by BIGINT DEFAULT NULL COMMENT '导入人ID',
+    reviewed_by BIGINT DEFAULT NULL COMMENT '审核人ID',
+    review_note VARCHAR(500) DEFAULT NULL COMMENT '审核意见',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告解析器导入记录表';
+
+-- ===== CF值(平台积分)体系 =====
+
+CREATE TABLE IF NOT EXISTS user_platform_balance (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id       BIGINT       NOT NULL COMMENT '用户ID',
+    family_id     BIGINT       COMMENT '归属家庭ID',
+    total_earned  INT DEFAULT 0 COMMENT '累计获得',
+    available     INT DEFAULT 0 COMMENT '可用余额',
+    frozen        INT DEFAULT 0 COMMENT '冻结(提现审核中)',
+    withdrawn     INT DEFAULT 0 COMMENT '已提现',
+    exchanged     INT DEFAULT 0 COMMENT '已兑换',
+    updated_at    DATETIME,
+    created_at    DATETIME,
+    UNIQUE KEY uk_user (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户CF值余额';
+
+CREATE TABLE IF NOT EXISTS platform_balance_log (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id       BIGINT       NOT NULL COMMENT '用户ID',
+    family_id     BIGINT       COMMENT '归属家庭ID',
+    type          VARCHAR(20)  NOT NULL COMMENT 'earn/spend/freeze/unfreeze/withdraw',
+    amount        INT          NOT NULL COMMENT '变动数量',
+    balance_after INT          NOT NULL COMMENT '变动后可用余额',
+    ref_type      VARCHAR(30)  COMMENT 'product_order/activity/withdrawal/migration',
+    ref_id        BIGINT       COMMENT '关联业务ID',
+    remark        VARCHAR(255) COMMENT '备注',
+    created_at    DATETIME,
+    INDEX idx_user_time (user_id, created_at),
+    UNIQUE KEY uk_user_ref (user_id, ref_type, ref_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值流水';
+
+
+
+-- ===== 家庭平台积分池(CF值家庭维度)=====
+
+CREATE TABLE IF NOT EXISTS family_platform_balance (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id     BIGINT       NOT NULL COMMENT '家庭ID',
+    total_earned  INT DEFAULT 0 COMMENT '累计获得',
+    available     INT DEFAULT 0 COMMENT '可用余额',
+    frozen        INT DEFAULT 0 COMMENT '冻结(提现审核中)',
+    withdrawn     INT DEFAULT 0 COMMENT '已提现',
+    exchanged     INT DEFAULT 0 COMMENT '已兑换(抵扣/积分消耗)',
+    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_family (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭平台积分池余额';
+
+CREATE TABLE IF NOT EXISTS family_platform_balance_log (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id     BIGINT       NOT NULL COMMENT '家庭ID',
+    type          VARCHAR(20)  NOT NULL COMMENT 'earn/spend/freeze/unfreeze/withdraw/adjust/migration',
+    amount        INT          NOT NULL COMMENT '变动数量',
+    balance_after INT          NOT NULL COMMENT '变动后可用余额',
+    ref_type      VARCHAR(50)  COMMENT 'product_order/referral_dist/activity/withdrawal/adjust/migration/refund',
+    ref_id        BIGINT       COMMENT '关联业务ID',
+    remark        VARCHAR(255) COMMENT '备注',
+    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_family (family_id),
+    INDEX idx_ref (ref_type, ref_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭平台积分流水';
+
+-- ===== 家庭平台积分兑换记录 =====
+
+CREATE TABLE IF NOT EXISTS family_platform_exchange_record (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id       BIGINT       NOT NULL COMMENT '操作用户ID',
+    family_id     BIGINT       NOT NULL COMMENT '家庭ID',
+    coupon_id     BIGINT       NOT NULL COMMENT '关联优惠券ID',
+    coupon_name   VARCHAR(200) COMMENT '优惠券名称',
+    cf_cost       INT          NOT NULL COMMENT '消耗CF值数量',
+    status        VARCHAR(20)  NOT NULL DEFAULT 'completed' COMMENT 'completed/failed',
+    redeem_code   VARCHAR(32)  COMMENT '兑换码',
+    remark        VARCHAR(255) COMMENT '备注',
+    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_family_time (family_id, created_at),
+    INDEX idx_user (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭CF值兑换记录';
+
+-- ===== 健康方案汇总 =====
+
+CREATE TABLE IF NOT EXISTS health_plans (
+    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id     BIGINT       NOT NULL COMMENT '家庭ID',
+    member_ids    VARCHAR(200) NOT NULL COMMENT '目标成员ID列表(逗号分隔)',
+    dimensions    VARCHAR(100) NOT NULL COMMENT '维度列表(逗号分隔)',
+    goal          TEXT         NOT NULL COMMENT '用户输入的目标',
+    plan_content  TEXT         COMMENT 'LangGraph生成的方案内容',
+    member_name   VARCHAR(50)  COMMENT '成员名称摘要',
+    created_at    DATETIME DEFAULT CURRENT_TIMESTAMP,
+    plan_json     TEXT         COMMENT '结构化方案内容(JSON)',
+    total_days    INT          DEFAULT NULL COMMENT '方案周期天数,驱动服务任务周期边界与截止日期(默认14)',
+    status        VARCHAR(20)  DEFAULT 'draft' COMMENT '方案状态: draft(草稿)/confirmed(已确认)',
+    reviewed_by   BIGINT DEFAULT NULL COMMENT '审核人ID(规划师/营养师)',
+    reviewed_at   DATETIME DEFAULT NULL COMMENT '审核时间',
+    review_comment TEXT DEFAULT NULL COMMENT '审核意见',
+    teacher_id    BIGINT DEFAULT NULL COMMENT '指定规划师ID(用户选择)',
+    updated_at    DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_family (family_id),
+    INDEX idx_created (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康方案汇总记录';
+
+-- 定期调研模板
+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='定期调研模板';
+
+-- 定期调研记录
+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='定期调研记录';
+
+-- 报告通用展示块(迁移211)
+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='报告通用展示块';
+
+-- 营养产品分类(迁移214)
+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='营养产品分类';
+
+-- 营养产品品牌(迁移215)
+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='营养产品品牌';
+
+-- 营养产品主表(迁移216)
+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 TEXT DEFAULT NULL COMMENT '适用人群',
+    ingredients TEXT DEFAULT NULL COMMENT '成份/配料表(从详情页图片提取)',
+    image_url VARCHAR(500) DEFAULT NULL COMMENT '产品图',
+    description TEXT COMMENT '产品描述',
+    status TINYINT DEFAULT 1 COMMENT '状态 0下架/1上架',
+    local_product_id BIGINT DEFAULT NULL COMMENT '本平台商品ID,关联products.id',
+    price DECIMAL(10,2) DEFAULT 0 COMMENT '当前售价',
+    original_price INT DEFAULT 0 COMMENT '原价(整数)',
+    sold_num INT DEFAULT 0 COMMENT '已售数量',
+    source_platform VARCHAR(50) DEFAULT '' COMMENT '来源平台',
+    source_id VARCHAR(100) DEFAULT '' COMMENT '来源平台商品ID',
+    source_url VARCHAR(500) DEFAULT '' COMMENT '来源平台商品链接',
+    product_images TEXT COMMENT '商品图片URL(JSON数组)',
+    spec VARCHAR(200) DEFAULT '' COMMENT '规格',
+    efficacy_tags TEXT COMMENT '成分功效标签JSON数组(如 ["补钙","补锌","益生菌","维生素D"])',
+    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),
+    INDEX idx_source (source_platform, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品';
+
+-- 营养产品年龄分层剂量建议表
+CREATE TABLE IF NOT EXISTS nutrition_age_dosage (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    age_min_months INT NOT NULL COMMENT '产品适用的最小月龄',
+    age_max_months INT NOT NULL COMMENT '产品适用的最大月龄',
+    ingredient_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='营养产品按年龄分组的剂量建议';
+
+-- 营养产品剂量上限(UL)表
+CREATE TABLE IF NOT EXISTS nutrition_ul_limit (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    product_id BIGINT NOT NULL COMMENT '产品ID',
+    ingredient_type VARCHAR(20) NOT NULL COMMENT '成分类型',
+    nutrient_name VARCHAR(100) NOT NULL COMMENT '营养物质名称',
+    age_min_months VARCHAR(20) NOT NULL COMMENT '最小月龄',
+    age_max_months VARCHAR(20) NOT NULL COMMENT '最大月龄',
+    limit_value DECIMAL(10,2) DEFAULT NULL COMMENT '限值',
+    unit VARCHAR(30) DEFAULT NULL COMMENT '价格单位',
+    description VARCHAR(200) 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 (ingredient_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养产品剂量上限';
+
+-- 营养产品成分明细(迁移217)
+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='营养产品成分明细';
+
+-- 营养产品平台在售信息(迁移218)
+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='营养产品平台在售信息';
+
+-- 营养产品评价(迁移219)
+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='营养产品评价';
+
+-- 文件记录表(迁移220:去重 + 维护)
+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 '分类: articles/dan-reports/assessment-reports/health-reports/general',
+    file_type VARCHAR(50) DEFAULT NULL COMMENT '文件类型: image/pdf/video/audio/other',
+    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='文件记录表(去重&维护)';
+
+-- 文件标签字典(迁移222)
+CREATE TABLE IF NOT EXISTS file_tag (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    name VARCHAR(50) NOT NULL COMMENT '标签名称',
+    color VARCHAR(7) DEFAULT '#409EFF' COMMENT '标签颜色(十六进制)',
+    sort_order INT DEFAULT 0 COMMENT '排序号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_name (name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件标签字典';
+
+-- 文件-标签关联(迁移223)
+CREATE TABLE IF NOT EXISTS file_tag_rel (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    file_id BIGINT NOT NULL COMMENT 'file_record.id',
+    tag_id BIGINT NOT NULL COMMENT 'file_tag.id',
+    UNIQUE KEY uk_file_tag (file_id, tag_id),
+    INDEX idx_tag (tag_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件-标签关联';
+
+-- 通关解锁关卡定义
+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='通关解锁关卡定义';
+
+-- 家庭关卡通关进度
+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='家庭关卡通关进度';
+
+-- 菌群知识库文章管理
+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='菌群知识库文章';
+
+-- 后台侧边栏菜单表
+CREATE TABLE IF NOT EXISTS sys_menu (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父菜单ID,0=顶级',
+    title VARCHAR(50) COMMENT '目录标题(有子菜单时用,对应 el-submenu)',
+    label VARCHAR(50) COMMENT '叶子菜单显示名(对应 el-menu-item)',
+    path VARCHAR(200) COMMENT '路由路径(叶子菜单必填,如 /families)',
+    icon VARCHAR(100) COMMENT 'Element UI 图标类名,如 el-icon-s-custom',
+    perm VARCHAR(100) COMMENT '权限标识,如 family:list / task:* / system:config',
+    sort INT DEFAULT 0 COMMENT '排序号,小的在前',
+    visible TINYINT DEFAULT 1 COMMENT '是否显示 1=显示 0=隐藏',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_parent (parent_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后台侧边栏菜单表';
+
+-- 角色-菜单授权表
+CREATE TABLE IF NOT EXISTS sys_role_menu (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    role VARCHAR(50) NOT NULL COMMENT '角色标识: admin/teacher/nutritionist/article_manager/activity_manager/supplier_admin',
+    menu_id BIGINT NOT NULL COMMENT 'sys_menu.id',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_role_menu (role, menu_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色-菜单授权表';
+
+
+
+
+
+-- =============================================
+-- 改版v1: 需求驱动·逐步解锁(问题域/用户意图/站内通知)
+-- =============================================
+
+CREATE TABLE IF NOT EXISTS problem_domains (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  code VARCHAR(50) NOT NULL COMMENT '唯一编码: weight_loss/sleep/constipation',
+  name VARCHAR(50) NOT NULL COMMENT '名称: 减肥/睡眠/便秘',
+  icon VARCHAR(255) DEFAULT '' COMMENT '图标',
+  description VARCHAR(500) DEFAULT '' COMMENT '一句话描述',
+  flow_intro_json TEXT COMMENT '服务流程介绍(7步) JSON',
+  survey_template_id BIGINT DEFAULT NULL COMMENT '专项调研问卷模板ID(SurveyTemplate.id)',
+  content_tags VARCHAR(500) DEFAULT '' COMMENT '内容标签,逗号分隔(过滤文章/知识库)',
+  recommended_product_ids VARCHAR(500) DEFAULT '' COMMENT '推荐产品ID,逗号分隔(Product.id)',
+  task_template_package_ids VARCHAR(500) DEFAULT '' COMMENT '套餐绑定的任务模板包ID(TaskTemplatePackage.id)',
+  reward_cf_value INT DEFAULT 0 COMMENT '全部完成奖励CF值',
+  status TINYINT DEFAULT 1 COMMENT '1启用 0停用',
+  sort_order INT DEFAULT 0,
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  UNIQUE KEY uk_code (code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='问题域定义(改版v1)';
+
+CREATE TABLE IF NOT EXISTS user_intents (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  user_id BIGINT NOT NULL COMMENT '用户ID',
+  intent_type VARCHAR(20) DEFAULT '' COMMENT 'A/B/C 用户类型',
+  problem_domain_code VARCHAR(50) DEFAULT '' COMMENT '问题域编码(仅C类)',
+  journey_stage INT DEFAULT 0 COMMENT '旅程阶段 0-7',
+  stage_updated_at DATETIME DEFAULT NULL COMMENT '最近阶段变化时间',
+  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)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户意图与服务旅程(改版v1)';
+
+CREATE TABLE IF NOT EXISTS user_notifications (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  user_id BIGINT NOT NULL COMMENT '接收用户ID',
+  type VARCHAR(30) DEFAULT 'report_ready' COMMENT 'report_ready/reward_granted/logistics/system/energy_change/points_change/cf_change',
+  title VARCHAR(200) NOT NULL,
+  content VARCHAR(1000) DEFAULT '',
+  ref_type VARCHAR(30) DEFAULT '' COMMENT '关联对象类型: report/package/task',
+  ref_id BIGINT DEFAULT NULL COMMENT '关联对象ID',
+  is_read TINYINT DEFAULT 0,
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  INDEX idx_user_read (user_id, is_read)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内通知(改版v1)';
+
+-- 通知模板(迁移245 W3)
+CREATE TABLE IF NOT EXISTS notification_templates (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  type VARCHAR(30) NOT NULL COMMENT '通知类型: report_ready/reward_granted/system',
+  title VARCHAR(200) NOT NULL COMMENT '默认标题(支持占位符)',
+  content VARCHAR(1000) NOT NULL COMMENT '默认内容模板(支持占位符)',
+  channels VARCHAR(100) DEFAULT 'in_app' COMMENT '推送通道: in_app,sms,wechat,gzh,phone 逗号分隔',
+  status TINYINT DEFAULT 1 COMMENT '1启用 0停用',
+  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  UNIQUE KEY uk_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知模板(改版v1 W3)';
+
+-- 当前状态调研-健康现状档案(迁移239)
+CREATE TABLE IF NOT EXISTS health_status (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '所属账号',
+    member_id BIGINT DEFAULT NULL COMMENT '家庭成员ID(NULL=账号级,非空=成员级归档)',
+    height_cm DECIMAL(5,1) DEFAULT NULL COMMENT '身高(cm)',
+    weight_kg DECIMAL(5,1) DEFAULT NULL COMMENT '体重(kg)',
+    blood_pressure VARCHAR(20) DEFAULT NULL COMMENT '血压,如 120/80',
+    blood_glucose DECIMAL(4,1) DEFAULT NULL COMMENT '空腹血糖(mmol/L)',
+    blood_lipids JSON DEFAULT NULL COMMENT '血脂 [{name,value,unit,note}]',
+    disease_history JSON DEFAULT NULL COMMENT '疾病史 [{name,note}]',
+    medications JSON DEFAULT NULL COMMENT '在服药物/治疗 [{name,note}]',
+    notes VARCHAR(500) DEFAULT NULL COMMENT '其他补充说明',
+    filled_at DATETIME DEFAULT NULL COMMENT '首次填写时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_member (user_id, member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='当前状态调研-健康现状档案';
+
+-- 系统提示词管理表(cfc-web 后台可维护,langgraph 动态加载)
+CREATE TABLE IF NOT EXISTS system_prompts (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    prompt_key VARCHAR(64) NOT NULL COMMENT '提示词键(如 health_coach/analysis/chat)',
+    title VARCHAR(100) NOT NULL COMMENT '提示词标题',
+    content TEXT NOT NULL COMMENT '提示词内容',
+    default_content TEXT COMMENT '出厂默认内容(重置时恢复)',
+    version INT NOT NULL DEFAULT 1 COMMENT '版本号',
+    status TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=禁用',
+    remark VARCHAR(255) COMMENT '备注',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_prompt_key (prompt_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统提示词管理';
+-- 系统提示词版本历史表(版本管理:修改前自动备份,支持回滚)
+CREATE TABLE IF NOT EXISTS system_prompt_versions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    prompt_key VARCHAR(64) NOT NULL COMMENT '提示词键',
+    version INT NOT NULL COMMENT '版本号',
+    title VARCHAR(100) NOT NULL COMMENT '提示词标题',
+    content TEXT NOT NULL COMMENT '提示词内容',
+    operator VARCHAR(100) COMMENT '操作人',
+    remark VARCHAR(500) COMMENT '变更说明',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_prompt_key (prompt_key),
+    INDEX idx_version (prompt_key, version)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统提示词版本历史';
+-- 套餐商品组成明细表(套餐 = 商品 productType=bundle,子商品在 product_bundle_items 中配置)
+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='套餐商品组成明细';
+
+-- 联系人表(cfclub 联系人管理)
+CREATE TABLE IF NOT EXISTS contacts (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    name VARCHAR(100) NOT NULL COMMENT '姓名',
+    phone VARCHAR(20) COMMENT '电话',
+    avatar VARCHAR(500) COMMENT '头像',
+    relationship_type VARCHAR(20) COMMENT '关系类型: family/friend/partner/colleague/other',
+    known_since DATETIME COMMENT '认识时间',
+    bio VARCHAR(500) COMMENT '简介',
+    birthday DATE COMMENT '生日',
+    intimacy_level INT DEFAULT 0 COMMENT '亲密度(0-100)',
+    contact_source VARCHAR(20) DEFAULT 'manual' COMMENT '来源: phone/manual',
+    tags VARCHAR(500) COMMENT '标签(逗号分隔)',
+    last_contact_at DATETIME COMMENT '最近联系时间',
+    contact_count INT DEFAULT 0 COMMENT '联系次数',
+    notes TEXT COMMENT '备注',
+    created_at DATETIME,
+    updated_at DATETIME,
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='联系人表';
+
+-- 联系人帮助记录表(章鱼图数据源)
+CREATE TABLE IF NOT EXISTS contact_help_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '被帮助者(当前用户)',
+    contact_id BIGINT NOT NULL COMMENT '帮助者(联系人)',
+    help_type VARCHAR(32) COMMENT '帮助类型: MONEY/ITEM/EMOTION/RESOURCE/SKILL/TIME',
+    amount DECIMAL(10,2) DEFAULT 0 COMMENT '帮助金额(非金钱类为0)',
+    description VARCHAR(255) COMMENT '帮助内容简述',
+    happened_at DATETIME COMMENT '帮助发生时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_chl_user (user_id),
+    INDEX idx_chl_contact (contact_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='联系人帮助记录(章鱼图数据源)';
+
+-- 珍珠图资源登记表(社会连接资源)
+CREATE TABLE IF NOT EXISTS resource_items (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '资源拥有者(当前用户)',
+    type VARCHAR(16) NOT NULL COMMENT '资源类型: INFO(信息)/PLACE(场所)/SKILL(技能)',
+    connection_type VARCHAR(50) NOT NULL DEFAULT '' COMMENT '连接类型枚举名(ConnectionType)',
+    priority TINYINT DEFAULT 2 COMMENT '优先级: 1=高 2=中 3=低(手动覆盖)',
+    value_score INT DEFAULT 0 COMMENT '价值分 0-100(自动计算,可手动覆盖)',
+    last_interaction_at DATETIME DEFAULT NULL COMMENT '最近互动时间',
+    interaction_count INT DEFAULT 0 COMMENT '互动总次数',
+    name VARCHAR(100) NOT NULL COMMENT '资源名称',
+    description VARCHAR(255) COMMENT '资源描述(可调用方式/说明)',
+    contact_id BIGINT DEFAULT NULL COMMENT '关联联系人ID(可选)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_ri_user (user_id),
+    INDEX idx_ri_connection_type (connection_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图资源登记表(社会连接资源)';
+
+-- 关键人拓展-成效记录
+CREATE TABLE IF NOT EXISTS octopus_effect_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '当前用户(租户键)',
+    member_order_id BIGINT COMMENT '关联 MemberSubscriptionOrder.id(会员订单)',
+    effect_type TINYINT UNSIGNED NOT NULL COMMENT '成效类型 bitmask:1=金额大,2=频次高,4=新成交(支持多选)',
+    effect_amount DECIMAL(10,2) DEFAULT 0 COMMENT '成效金额(元)',
+    effect_desc VARCHAR(500) COMMENT '成效描述',
+    status TINYINT DEFAULT 1 COMMENT '1=有效,0=无效',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    happened_at DATETIME COMMENT '成效发生时间',
+    INDEX idx_oer_user (user_id),
+    INDEX idx_oer_order (member_order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关键人拓展-成效记录';
+
+-- 关键人画像
+CREATE TABLE IF NOT EXISTS octopus_key_person (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '当前用户(租户键)',
+    name VARCHAR(64) NOT NULL COMMENT '关键人姓名',
+    organization VARCHAR(128) COMMENT '单位/公司',
+    department VARCHAR(64) COMMENT '部门',
+    title VARCHAR(64) COMMENT '职务/职位',
+    first_meet_scene VARCHAR(128) COMMENT '初识场合',
+    know_way VARCHAR(255) COMMENT '如何认识(认识路径)',
+    contact_info VARCHAR(255) COMMENT '联系方式(可选)',
+    first_meet_time DATETIME DEFAULT NULL COMMENT '相识具体时间',
+    referrer_id BIGINT DEFAULT NULL COMMENT '介绍人关键人ID(指向同用户另一条关键人)',
+    system_user_id BIGINT DEFAULT NULL COMMENT '匹配到的系统用户ID(如有)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_okp_user (user_id),
+    INDEX idx_okp_org (organization)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关键人画像';
+
+-- 成效-关键人关联
+CREATE TABLE IF NOT EXISTS octopus_effect_key_person (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    effect_id BIGINT NOT NULL COMMENT '关联 octopus_effect_record.id',
+    key_person_id BIGINT NOT NULL COMMENT '关联 octopus_key_person.id',
+    role TINYINT NOT NULL COMMENT '1=引荐,2=决策,3=其他',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_oekp_effect (effect_id),
+    INDEX idx_oekp_person (key_person_id),
+    UNIQUE KEY uk_oekp (effect_id,key_person_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成效-关键人关联';
+
+-- 维度配置表(cfclub 五维能量体系)
+CREATE TABLE IF NOT EXISTS product_dimension_config (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    dimension_code VARCHAR(50) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth',
+    dimension_name VARCHAR(100) NOT NULL COMMENT '维度名称',
+    dimension_type VARCHAR(50) NOT NULL DEFAULT 'five' COMMENT '类型: five(五维)/action(行动)/other',
+    parent_id BIGINT COMMENT '父级ID,无父级为NULL',
+    sort INT DEFAULT 0 COMMENT '排序',
+    enabled TINYINT(1) DEFAULT 1 COMMENT '启用: 0禁用 1启用',
+    dify_dataset_id VARCHAR(100) COMMENT 'Dify Dataset ID,不填则不同步',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度配置表';
+
+-- 知识库表
+CREATE TABLE IF NOT EXISTS dan_knowledge_base (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    title VARCHAR(200) NOT NULL COMMENT '知识标题',
+    content MEDIUMTEXT NOT NULL COMMENT '知识内容',
+    sort INT DEFAULT 0 COMMENT '排序',
+    status TINYINT(1) DEFAULT 1 COMMENT '状态: 0禁用 1启用',
+    remark VARCHAR(500) COMMENT '备注',
+    file_url VARCHAR(500) COMMENT 'fileUrl',
+    file_name VARCHAR(255) COMMENT 'fileName',
+    source_url VARCHAR(500) COMMENT 'sourceUrl',
+    source_type VARCHAR(20) COMMENT 'sourceType',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库表';
+
+-- 标签表
+CREATE TABLE IF NOT EXISTS dan_knowledge_tag (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    tag_name VARCHAR(100) NOT NULL UNIQUE COMMENT '标签名',
+    tag_type VARCHAR(50) DEFAULT 'custom' COMMENT '标签类型: custom/auto',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表';
+
+-- 知识-维度关联表
+CREATE TABLE IF NOT EXISTS dan_knowledge_base_dimension (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    knowledge_id BIGINT NOT NULL COMMENT '知识库ID',
+    dimension_id BIGINT NOT NULL COMMENT '维度配置ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_knowledge_dimension (knowledge_id, dimension_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识-维度关联表';
+
+-- 知识-标签关联表
+CREATE TABLE IF NOT EXISTS dan_knowledge_base_tag (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    knowledge_id BIGINT NOT NULL,
+    tag_id BIGINT NOT NULL,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_knowledge_tag (knowledge_id, tag_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识-标签关联表';
+
+-- 用户画像最新快照表
+CREATE TABLE IF NOT EXISTS profile_snapshot (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    member_id BIGINT NOT NULL UNIQUE COMMENT '家庭成员ID',
+    dimension_scores JSON DEFAULT NULL COMMENT '五维能力评分',
+    body_metrics JSON DEFAULT NULL COMMENT '近30天身体指标',
+    mind_metrics JSON DEFAULT NULL COMMENT '近30天心理指标',
+    wisdom_metrics JSON DEFAULT NULL COMMENT '最近测评成绩',
+    action_metrics JSON DEFAULT NULL COMMENT '近7天行为指标',
+    wealth_metrics JSON DEFAULT NULL COMMENT '近30天财商指标',
+    problem_domains VARCHAR(500) DEFAULT NULL COMMENT '关注的问题域标签',
+    quiz_summary JSON DEFAULT NULL COMMENT '答题认知雷达汇总',
+    computed_at DATETIME DEFAULT NULL COMMENT '最后一次计算时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_member_id (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户画像最新快照';
+
+-- 画像历史表(保留365天)
+CREATE TABLE IF NOT EXISTS profile_history (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    snapshot_date DATE NOT NULL COMMENT '日期',
+    all_metrics JSON NOT NULL COMMENT '当日所有指标完整快照',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_member_date (member_id, snapshot_date),
+    INDEX idx_date (snapshot_date)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户画像历史快照';
+
+-- 健康启航计划购买订单表
+CREATE TABLE IF NOT EXISTS purchase_orders (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    order_no VARCHAR(64) NOT NULL UNIQUE,
+    user_id BIGINT NOT NULL,
+    family_id BIGINT NOT NULL,
+    amount INT NOT NULL,
+    status VARCHAR(20) DEFAULT 'pending',
+    payment_method VARCHAR(20),
+    transaction_id VARCHAR(64),
+    start_date DATETIME NOT NULL,
+    end_date DATETIME NOT NULL,
+    is_early_renewal TINYINT DEFAULT 0,
+    cf_reward_given INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    paid_at DATETIME,
+    INDEX idx_user_id (user_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- 闯关任务进度表
+CREATE TABLE IF NOT EXISTS challenge_progress (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    purchase_id BIGINT NOT NULL,
+    member_id BIGINT NOT NULL,
+    week_num INT NOT NULL,
+    task_type VARCHAR(50) NOT NULL,
+    task_key VARCHAR(100) NOT NULL,
+    task_title VARCHAR(255) NOT NULL,
+    target_value INT DEFAULT 0,
+    current_value INT DEFAULT 0,
+    reward_cf INT DEFAULT 0,
+    status VARCHAR(20) DEFAULT 'pending',
+    completed_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_purchase_id (purchase_id),
+    INDEX idx_member_id (member_id),
+    UNIQUE KEY uk_member_task (member_id, task_key, week_num)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 先天画像体系(迁移270-272)
+-- =============================================
+
+-- 先天画像来源权重配置表
+CREATE TABLE IF NOT EXISTS innate_portrait_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    source_type VARCHAR(20) NOT NULL COMMENT '来源: zodiac/bazi/blood/numsoul',
+    source_name VARCHAR(50) NOT NULL COMMENT '来源名称',
+    weight INT DEFAULT 25 COMMENT '权重(基点,2500=25%)',
+    enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用',
+    ai_enabled TINYINT(1) DEFAULT 1 COMMENT '是否启用AI解读',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_source (source_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='先天画像来源权重配置';
+
+-- 先天画像报告缓存表
+CREATE TABLE IF NOT EXISTS innate_portrait_report (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '成员ID',
+    member_type VARCHAR(10) NOT NULL COMMENT '成员类型: child/parent',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    portrait_json TEXT COMMENT '先天画像数据JSON',
+    ai_reading TEXT COMMENT 'AI解读文案',
+    generated_at DATETIME COMMENT '生成时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_member (member_id, member_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='先天画像报告缓存';
+
+-- 数字能量详细配置表
+CREATE TABLE IF NOT EXISTS numsoul_detail_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    number_type VARCHAR(20) NOT NULL COMMENT '类型: life_path/talent/birthday/destiny',
+    number_value INT NOT NULL COMMENT '数字值 1-9 或主数 11/22/33',
+    title VARCHAR(50) COMMENT '称号',
+    keywords VARCHAR(200) COMMENT '性格关键词',
+    mind_base INT DEFAULT 0 COMMENT '心先天基础分(百分位)',
+    mind_advice TEXT COMMENT '心维度成长建议',
+    color_hex VARCHAR(10) COMMENT '代表色',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_type_num (number_type, number_value)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数字能量详细配置';
+
+-- ============================================================
+-- 五维自检忽略提醒记录表(迁移277)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS self_check_ignores (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    check_id BIGINT COMMENT '忽略的自检记录ID(可空)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '忽略时间',
+    INDEX idx_user_id (user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维自检忽略提醒记录';
+
+-- ============================================================
+-- CF值分佣体系重构(迁移279-282)
+-- ============================================================
+
+-- CF值返佣阶梯配置表(迁移279,替代 promotion_tier_config)
+CREATE TABLE IF NOT EXISTS cf_rate_tier (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    tier_name VARCHAR(50) NOT NULL COMMENT '档位名称',
+    min_team_size INT NOT NULL DEFAULT 0 COMMENT '团队规模下限(含)',
+    rate_percent INT NOT NULL DEFAULT 0 COMMENT '返佣比例(%)',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '排序,越大越高',
+    enabled TINYINT DEFAULT 1 COMMENT '1启用/0停用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_min_size (min_team_size)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值返佣阶梯配置';
+
+-- CF值流转记录表(迁移280)
+CREATE TABLE IF NOT EXISTS cf_transfer_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    from_user_id BIGINT COMMENT '转出用户ID(null=平台)',
+    to_user_id BIGINT COMMENT '转入用户ID',
+    family_id BIGINT COMMENT '所属家庭ID',
+    amount INT NOT NULL COMMENT 'CF值数量',
+    type VARCHAR(16) NOT NULL COMMENT 'transfer/allocate/refund',
+    ref_type VARCHAR(50) COMMENT '关联业务类型',
+    ref_id BIGINT COMMENT '关联业务ID',
+    remark VARCHAR(255),
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_from (from_user_id),
+    INDEX idx_to (to_user_id),
+    INDEX idx_family (family_id),
+    UNIQUE KEY uk_ref_type_id (ref_type, ref_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CF值流转记录';
+
+-- 五维基础能量分表(迁移305)
+CREATE TABLE IF NOT EXISTS family_member_dimension_base_scores (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    member_id BIGINT NULL COMMENT '家庭成员ID',
+    member_type VARCHAR(20) NOT NULL COMMENT '成员类型: child/parent',
+    dimension_code VARCHAR(20) NOT NULL COMMENT '维度: body/mind/wisdom/action/wealth',
+    base_score INT NOT NULL COMMENT '基础能量分(0-100)',
+    last_calculated_at DATETIME NOT NULL COMMENT '最后计算时间',
+    calculated_by VARCHAR(50) DEFAULT 'system' COMMENT '计算来源(system/manual)',
+    UNIQUE KEY uk_member_dimension (member_id, dimension_code),
+    INDEX idx_member (member_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维基础能量分(落库,支持重算)';
+
+-- 珍珠图互动日志表
+CREATE TABLE IF NOT EXISTS pearl_interaction_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '操作人',
+    resource_item_id BIGINT NOT NULL COMMENT '关联资源',
+    interaction_type VARCHAR(20) NOT NULL COMMENT 'PHONE/WECHAT/MEETING/OTHER',
+    content VARCHAR(500) DEFAULT NULL COMMENT '互动内容简记',
+    happened_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '互动发生时间',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_pil_user (user_id),
+    INDEX idx_pil_resource (resource_item_id),
+    INDEX idx_pil_happened (happened_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图互动日志';
+
+-- 珍珠图互动提醒表
+CREATE TABLE IF NOT EXISTS pearl_interaction_reminder (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL,
+    resource_item_id BIGINT NOT NULL,
+    connection_type VARCHAR(50) NOT NULL,
+    suggest_interval_month INT NOT NULL COMMENT '建议互动周期(月)',
+    last_interaction_at DATETIME DEFAULT NULL,
+    days_since_last INT NOT NULL COMMENT '距上次互动天数',
+    status VARCHAR(20) DEFAULT 'PENDING' COMMENT 'PENDING/DONE/DISMISSED',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_pir_user_status (user_id, status),
+    INDEX idx_pir_resource (resource_item_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='珍珠图互动提醒';
+
+-- ============================================================
+-- BASIC 体验会员(迁移360 恢复,对应提交 f86dd164b 的反向还原)
+-- ============================================================
+
+INSERT IGNORE INTO membership_levels
+(level_code, level_name, level_desc, price_monthly, features, max_children, max_tasks_per_day)
+VALUES (
+    'BASIC',
+    '体验会员',
+    '9.9元/月体验版,解锁核心功能,限次使用AI与报告解读',
+    990,
+    '["free_activities","free_courses","discount_purchase","invite_family","mascot_ai_lite","ai_report_reading_lite","assessment_quota_lite","health_report_upload_lite","growth_plan_lite","task_template_lite","referral_commission"]',
+    3,
+    3
+);
+
+INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name, benefit_desc, icon, bg_color, sort_order) VALUES
+('BASIC', 'AI_CHAT_LITE', 'AI健康顾问(限3次/月)', '每月3次AI健康顾问问答', '🤖', '#DBEAFE', 1),
+('BASIC', 'REPORT_READING_LITE', '报告解读(限1次/月)', '每月1次体检/测评报告AI解读', '📊', '#FCE7F3', 4),
+('BASIC', 'GROWTH_PLAN_LITE', '基础成长计划', '创建成长计划但模板有限', '📋', '#FEF3C7', 3),
+('BASIC', 'ASSESSMENT_QUOTA_LITE', '测评预约(限1次)', '每月可预约1次测试', '📅', '#E0E7FF', 8);
+
+-- ============================================================
+
+CREATE TABLE IF NOT EXISTS basic_quota_usage (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '家长用户ID(非family_id)',
+    quota_type VARCHAR(30) NOT NULL COMMENT '配额类型: AI_CHAT/REPORT_READING/ASSESSMENT_QUOTA/FOOD_RECOGNIZE',
+    used_count INT DEFAULT 0 COMMENT '本月已用次数',
+    total_count INT NOT NULL COMMENT '月度总配额',
+    period_start DATE NOT NULL COMMENT '周期开始日期(每月1号)',
+    period_end DATE NOT NULL COMMENT '周期结束日期(月末)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_user_type_period (user_id, quota_type, period_start),
+    INDEX idx_user_period (user_id, period_start)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度配额使用记录';
+
+-- 菌群健康调研项目(单次付费问卷)
+CREATE TABLE IF NOT EXISTS gut_health_projects (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '购买用户ID',
+    family_id BIGINT COMMENT '关联家庭ID',
+    member_id BIGINT COMMENT '测评对象(家庭成员ID)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/paused/completed',
+    questions JSON COMMENT '本次问卷题目+答案JSON',
+    health_recommendations JSON COMMENT 'AI生成的健康建议',
+    risk_scores JSON COMMENT '各维度风险评分(0-100)',
+    microbe_test_recommended TINYINT DEFAULT 0 COMMENT '是否推荐菌群检测',
+    paid_at DATETIME COMMENT '付费时间',
+    completed_at DATETIME COMMENT '完成时间',
+    -- 新增字段:问卷完成后的奖励资格
+    member_discount_qualified TINYINT DEFAULT 0 COMMENT '是否获得会员价购买菌群检测资格',
+    family_membership_qualified TINYINT DEFAULT 0 COMMENT '是否获得一年家庭会员资格',
+    family_membership_activated TINYINT DEFAULT 0 COMMENT '是否已激活家庭会员',
+    -- 新增字段:打卡任务系统
+    cf_refund_amount INT DEFAULT 0 COMMENT 'CF值返还金额(分),19.9元=1990分',
+    cf_refunded TINYINT DEFAULT 0 COMMENT 'CF值是否已返还',
+    completed_tasks_count INT DEFAULT 0 COMMENT '已完成任务数/总任务数',
+    action_tasks JSON COMMENT 'AI生成的一周行动任务列表',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_user_created (user_id, created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菌群健康调研项目';
+
+-- DOA 个人目标-周计划-周复盘(迁移318,家庭打卡个人目标模块)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS doa_stage (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    member_id BIGINT NULL COMMENT '目标归属成员ID(family_members.id)',
+    title VARCHAR(200) COMMENT '阶段主题(可选)',
+    start_date DATE NOT NULL COMMENT '阶段开始日期',
+    end_date DATE NOT NULL COMMENT '阶段结束日期',
+    duration_weeks TINYINT NOT NULL COMMENT '周期周数:2/4/8/12',
+    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/completed/cancelled',
+    created_by BIGINT NOT NULL COMMENT '创建人userId(代管时为代管人)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_ds_family_member (family_id, member_id),
+    INDEX idx_ds_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 阶段';
+
+CREATE TABLE IF NOT EXISTS doa_goal (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    stage_id BIGINT NOT NULL COMMENT '所属阶段ID',
+    domain VARCHAR(20) NOT NULL COMMENT '领域:health/wealth/family/social/help',
+    content VARCHAR(500) NOT NULL COMMENT '量化目标内容',
+    status VARCHAR(20) DEFAULT 'active' COMMENT 'active/achieved/abandoned',
+    sort_order TINYINT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_dg_stage (stage_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 阶段目标';
+
+CREATE TABLE IF NOT EXISTS doa_week (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    stage_id BIGINT NOT NULL COMMENT '所属阶段ID',
+    week_no TINYINT NOT NULL COMMENT '第几周(1起)',
+    week_start DATE NOT NULL COMMENT '本周开始(周一)',
+    week_end DATE NOT NULL COMMENT '本周结束(周日)',
+    plan TEXT COMMENT '周行动宣告计划(A表)',
+    achieved TEXT COMMENT '复盘:本周达成了什么',
+    experience TEXT COMMENT '复盘:有什么体验',
+    worked TEXT COMMENT '复盘:行得通的',
+    improve TEXT COMMENT '复盘:要提升/行不通的',
+    next_challenge TEXT COMMENT '复盘:下周挑战自己什么',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/planned/reviewed',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_dw_stage_week (stage_id, week_no),
+    INDEX idx_dw_stage (stage_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DOA 周记录';
+
+CREATE TABLE IF NOT EXISTS shop_discount_rules (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
+    name VARCHAR(64) NOT NULL COMMENT '规则名称',
+    type VARCHAR(32) NOT NULL COMMENT '类型: PERCENT/FIXED/THRESHOLD',
+    value INT NOT NULL COMMENT '折扣值(PERCENT=百分比数字, FIXED/THRESHOLD=减免金额分)',
+    threshold_amount INT COMMENT '满减门槛(分,仅THRESHOLD)',
+    category_id BIGINT COMMENT '适用类目ID(null=全部)',
+    distribution_system_id BIGINT COMMENT '适用体系ID(null=全部)',
+    product_ids TEXT COMMENT '适用商品ID(逗号分隔,null=全部)',
+    start_time DATETIME COMMENT '开始时间',
+    end_time DATETIME COMMENT '结束时间',
+    max_uses INT DEFAULT 0 COMMENT '最大使用次数(0=不限)',
+    used_count INT DEFAULT 0 COMMENT '已使用次数',
+    enabled TINYINT(1) DEFAULT 1 COMMENT '启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_category (category_id),
+    INDEX idx_enabled (enabled),
+    INDEX idx_distribution (distribution_system_id),
+    INDEX idx_end_time (end_time)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='限时折扣规则表';
+
+-- 首页快捷方式自定义配置(每应用一条记录,locked=置顶)
+CREATE TABLE IF NOT EXISTS user_shortcut_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    app_key VARCHAR(64) NOT NULL COMMENT '应用key',
+    locked TINYINT DEFAULT 0 COMMENT '是否锁定置顶(1=锁定,0=普通)',
+    sort_order INT DEFAULT 0 COMMENT '手动排序序号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    UNIQUE KEY uk_user_app (user_id, app_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页快捷方式自定义配置';
+
+-- 应用使用频度记录
+CREATE TABLE IF NOT EXISTS user_app_usage (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    app_key VARCHAR(64) NOT NULL COMMENT '应用key',
+    enter_count INT DEFAULT 0 COMMENT '累计进入次数',
+    last_enter_at DATETIME COMMENT '最近进入时间',
+    UNIQUE KEY uk_user_app (user_id, app_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='应用使用频度记录';
+-- ===== 任务编排系统(task orchestration)=====
+
+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='任务编排流定义';
+
+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='任务编排流边定义';
+
+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='任务编排流执行记录';
+
+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='任务编排流节点实例';
+-- 报告反馈与AI解答(迁移327)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS report_feedback (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    user_id BIGINT NOT NULL COMMENT '提交人 users.id',
+    member_id BIGINT DEFAULT NULL COMMENT '关联家庭成员 family_members.id',
+    report_id BIGINT DEFAULT NULL COMMENT '健康报告ID',
+    draft_id BIGINT DEFAULT NULL COMMENT '草稿ID',
+    feedback_type VARCHAR(20) DEFAULT 'question' COMMENT 'question=疑问 / suggestion=建议',
+    content VARCHAR(500) NOT NULL COMMENT '反馈内容',
+    ai_answer TEXT COMMENT 'AI 解答',
+    ai_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/answered/failed',
+    conversation_id VARCHAR(64) DEFAULT NULL COMMENT 'AI 会话ID(付费多轮)',
+    created_at DATETIME NOT NULL COMMENT '创建时间',
+    INDEX idx_rf_user (user_id, created_at),
+    INDEX idx_rf_report (report_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告反馈与AI解答';
+
+-- ============================================================
+-- 引导弹窗配置化管理(迁移329-332)
+-- ============================================================
+
+-- 迁移329: 引导弹窗配置表
+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='引导弹窗配置表';
+
+-- 迁移330: 引导弹窗步骤表
+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='引导弹窗步骤表';
+
+-- 迁移331: 引导弹窗触发条件表
+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='引导弹窗触发条件表';
+
+-- 迁移332: 引导弹窗用户消费记录表
+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='引导弹窗用户消费记录表';
+
+-- 迁移340: 首页广告栏配置表
+CREATE TABLE IF NOT EXISTS home_banners (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    title VARCHAR(100) NOT NULL COMMENT '展示标题',
+    banner_type VARCHAR(20) NOT NULL COMMENT '类型: activity/product/link',
+    ref_id BIGINT DEFAULT NULL COMMENT '关联活动/商品ID(banner_type=activity/product时有效)',
+    cover_image VARCHAR(500) DEFAULT NULL COMMENT '封面图URL(可覆盖默认封面)',
+    link_url VARCHAR(500) DEFAULT NULL COMMENT '跳转链接(banner_type=link时有效)',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '排序,值越小越靠前',
+    enabled TINYINT NOT NULL DEFAULT 1 COMMENT '启用状态:1=启用 0=禁用',
+    start_time DATETIME DEFAULT NULL COMMENT '投放开始时间(NULL=不限)',
+    end_time DATETIME DEFAULT NULL COMMENT '投放结束时间(NULL=不限)',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页广告栏配置表';
+
+-- 迁移344: 创建 supply_customer_relation 表(供应商团队客户归属记录)
+CREATE TABLE IF NOT EXISTS supply_customer_relation (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    system_id BIGINT NOT NULL COMMENT '供应商体系ID(supply_system.id)',
+    parent_user_id BIGINT NOT NULL COMMENT '上级供应商用户ID(supply_hierarchy.parent_id)',
+    family_id BIGINT NOT NULL COMMENT '客户家庭ID(family_members.family_id)',
+    child_user_id BIGINT DEFAULT NULL COMMENT '下单用户ID(users.id),可为空',
+    relation_type VARCHAR(32) NOT NULL DEFAULT 'family' COMMENT '归属类型: family=家庭归属 order=订单关联',
+    source VARCHAR(32) NOT NULL DEFAULT 'manual' COMMENT '来源: manual=人工绑定 order=下单自动 referral=推荐码',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_system_family (system_id, family_id),
+    INDEX idx_parent_user (parent_user_id),
+    INDEX idx_family_id (family_id),
+    UNIQUE KEY uk_system_family (system_id, family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商团队客户归属关系表';
+
+-- 迁移345: 商品维度关联表(product_dimension_mapping)
+CREATE TABLE IF NOT EXISTS product_dimension_mapping (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID(products.id)',
+    dimension_code VARCHAR(32) NOT NULL COMMENT '维度编码: body/wisdom/mind/action/wealth',
+    match_score INT DEFAULT 100 COMMENT '匹配度 0-100',
+    match_reason VARCHAR(200) COMMENT '匹配原因说明',
+    tags VARCHAR(500) COMMENT '推荐标签JSON',
+    enabled TINYINT DEFAULT 1 COMMENT '启用状态 1=启用 0=禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_product_id (product_id),
+    INDEX idx_dimension (dimension_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品维度关联表';
+
+-- 迁移346: 推荐曝光日志表(product_recommendation_log)
+CREATE TABLE IF NOT EXISTS product_recommendation_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT DEFAULT NULL COMMENT '用户ID(users.id)',
+    family_id BIGINT DEFAULT NULL COMMENT '家庭ID(family_members.family_id)',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    scene VARCHAR(32) NOT NULL COMMENT '场景: dimension_page/ai_chat/report_upload/repurchase',
+    reason VARCHAR(200) COMMENT '推荐理由',
+    match_score INT DEFAULT NULL COMMENT '匹配分',
+    was_clicked TINYINT DEFAULT 0 COMMENT '是否点击 1=是 0=否',
+    was_purchased TINYINT DEFAULT 0 COMMENT '是否购买 1=是 0=否',
+    clicked_at DATETIME DEFAULT NULL COMMENT '点击时间',
+    purchased_at DATETIME DEFAULT NULL COMMENT '购买时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    ab_test_group VARCHAR(32) DEFAULT NULL COMMENT 'AB测试分组(A/B/C/控制组)',
+    recommendation_type VARCHAR(32) DEFAULT NULL COMMENT '推荐来源类型:dimension/co_purchase/repurchase/profile_gap/manual',
+    INDEX idx_user_scene (user_id, scene),
+    INDEX idx_product_purchased (product_id, was_purchased)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='推荐曝光日志表';
+
+-- 迁移347a: 复购提醒记录表(repurchase_reminder_record)
+CREATE TABLE IF NOT EXISTS repurchase_reminder_record (
+>>>>>>> 6be3f0cb7b10c6fe0aa222c2c00bc1ed63e84c70
     id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
     user_id BIGINT NOT NULL COMMENT '用户ID(users.id)',
     family_id BIGINT NOT NULL COMMENT '家庭ID',

+ 1 - 0
cfc-frontend/pages/activity/activity-detail/activity-detail.vue

@@ -358,6 +358,7 @@ export default {
   methods: {
     // ===== 工具方法 =====
     getImageUrl: function(path) {
+ cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
       if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
       var s = String(path).trim()
       if (!s || s === 'null' || s === '/null') return ''

+ 1 - 0
cfc-frontend/pages/activity/index.vue

@@ -376,6 +376,7 @@ export default {
   },
   methods: {
     getImageUrl: function(path) {
+ cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
       if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
       var s = String(path).trim()
       if (!s || s === 'null' || s === '/null') return ''

+ 63 - 2
cfc-frontend/pages/article-center/article-detail.vue

@@ -41,6 +41,28 @@
         <view class="divider"></view>
         <view class="detail-body"><mp-html :content="article.content" :domain="config.API_BASE_URL" :tag-style="contentTagStyle" /></view>
 
+        <!-- 关联商品(文章标签匹配推荐) -->
+        <view class="related-section" v-if="relatedProducts.length > 0">
+          <view class="related-header">
+            <text class="related-title">相关商品</text>
+            <text class="related-sub">与本文内容相关</text>
+          </view>
+          <scroll-view scroll-x class="related-scroll" :show-scrollbar="false">
+            <view class="related-list">
+              <view class="related-item" v-for="p in relatedProducts" :key="p.id" @click="goProduct(p)">
+                <image class="related-img" :src="getImageUrl(p.coverImage)" mode="aspectFill" />
+                <view class="related-info">
+                  <text class="related-name">{{ p.name }}</text>
+                  <view class="related-price-row">
+                    <text class="related-price">¥{{ p.memberPrice }}</text>
+                    <text class="related-match">匹配{{ p.tagMatchCount }}标签</text>
+                  </view>
+                </view>
+              </view>
+            </view>
+          </scroll-view>
+        </view>
+
         <!-- 评论区(已屏蔽) -->
         <view style="height: 200rpx;"></view>
       </scroll-view>
@@ -87,7 +109,7 @@
 </template>
 
 <script>
-import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz } from '@/utils/api.js'
+import { getArticleDetail, reportReadingTime, getShareQrCode, completeArticleRead, generateQuiz, submitQuiz, getArticleRelatedProducts } from '@/utils/api.js'
 import shareMixin from '../../components/share-mixin.js'
 import ContentSharePoster from '@/components/ContentSharePoster.vue'
 import MpHtml from '@/components/mp-html/mp-html.vue'
@@ -99,6 +121,7 @@ export default {
   data() {
     return {
       articleId: '', article: null, loading: true, error: false, errorMsg: '',
+      relatedProducts: [],
       readingSeconds: 0, lastReportedSeconds: 0, readingCompleted: false,
       isTimerRunning: false, timerHandle: null, syncHandle: null,
       currentChildId: null, isLogin: false,
@@ -155,15 +178,38 @@ export default {
           this.article.content = this.resolveContentUrls(this.article.content)
           this.setShareInfo('推荐阅读: ' + (res.data.title || ''), '/pages/article-center/article-detail?id=' + id)
           this.startReadingTimer()
+          this.loadRelatedProducts(id)
         } else { this.error = true; this.errorMsg = '文章不存在或无权限查看' }
       } catch (e) { this.error = true; this.errorMsg = '加载失败' }
       finally { this.loading = false }
     },
     formatDate(d) { return d ? d.slice(0, 10) : '' },
+    loadRelatedProducts: function(id) {
+      var self = this
+      getArticleRelatedProducts({ articleId: id, size: 6 }).then(function(res) {
+        if (res && res.code === 200 && res.data) {
+          var list = res.data
+          var i
+          for (i = 0; i < list.length; i++) {
+            list[i].coverImage = self.getImageUrl(list[i].coverImage)
+          }
+          self.relatedProducts = list
+        }
+      }).catch(function() {})
+    },
+    goProduct: function(p) {
+      if (p && p.id) {
+        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + p.id })
+      }
+    },
     getImageUrl: function(path) {
       if (!path) return ''
       if (path.indexOf('http://') === 0 || path.indexOf('https://') === 0) return path
-      return config.API_BASE_URL + path
+      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+      var s = String(path).trim()
+      if (!s || s === 'null' || s === '/null') return ''
+      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
     },
     resolveContentUrls: function(html) {
       if (!html) return html
@@ -351,6 +397,21 @@ export default {
 .divider { height: 1rpx; background: #eee; margin: 24rpx 30rpx; }
 .detail-body { padding: 0 30rpx; font-size: 32rpx; color: #444; line-height: 1.8; }
 
+/* 关联商品 */
+.related-section { padding: 30rpx 0 20rpx; }
+.related-header { display: flex; align-items: baseline; padding: 0 30rpx; margin-bottom: 20rpx; }
+.related-title { font-size: 30rpx; font-weight: 700; color: #333; }
+.related-sub { font-size: 22rpx; color: #999; margin-left: 16rpx; }
+.related-scroll { white-space: nowrap; padding-left: 30rpx; }
+.related-list { display: inline-flex; }
+.related-item { width: 240rpx; margin-right: 20rpx; background: #fff; border-radius: 16rpx; overflow: hidden; box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06); display: inline-block; vertical-align: top; }
+.related-img { width: 240rpx; height: 240rpx; display: block; background: #f5f5f5; }
+.related-info { padding: 14rpx 16rpx 18rpx; }
+.related-name { font-size: 24rpx; color: #333; line-height: 1.4; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; white-space: normal; min-height: 68rpx; }
+.related-price-row { display: flex; align-items: center; justify-content: space-between; margin-top: 10rpx; }
+.related-price { font-size: 26rpx; font-weight: 700; color: #F97316; }
+.related-match { font-size: 18rpx; color: #999; }
+
 .detail-footer { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 30rpx; display: flex; align-items: center; gap: 16rpx; box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.06); z-index: 10; }
 .share-btn { height: 72rpx; padding: 0 30rpx; background: linear-gradient(135deg, #F97316, #FB923C); border-radius: 36rpx; display: flex; align-items: center; justify-content: center; border: none; }
 .share-btn::after { border: none; }

+ 5 - 2
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue

@@ -348,9 +348,12 @@ export default {
       })
     },
     getImageUrl: function(path) {
-      if (!path) return '/static/default-product.png'
+      if (!path || path === 'null' || path === 'undefined' || path === '/null') return '/static/default-product.png'
       if (path.startsWith('http') || path.startsWith('/static/')) return path
-      return config.API_BASE_URL + path
+      var s = String(path).trim()
+      if (!s || s === 'null' || s === 'undefined' || s === '/null') return ''
+      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
     },
     onSwiperImageError: function(idx) {
       // 单张图片加载失败时,替换为占位图

+ 4 - 1
cfc-frontend/pages/growth/diet-checkin.vue

@@ -169,6 +169,7 @@ export default {
   data() {
     return {
       memberId: null,
+      taskId: null,
       submitting: false,
       pendingSubmit: false,
       isRecording: false,
@@ -206,6 +207,7 @@ export default {
     }
   },
     onLoad: function(options) {
+    this.taskId = options.taskId || null
     this.todayText = this.getToday()
     this.setShareInfo('我在用浠艾福记录孩子的每日饮食,一起来打卡吧', '/pages/growth/diet-checkin')
     var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
@@ -449,6 +451,7 @@ export default {
           method: 'POST',
           data: {
             memberId: self.memberId,
+            taskId: self.taskId || undefined,
             mealType: self.form.mealType,
             foodItems: self.form.foodItems || '',
             remark: self.form.remark || '',
@@ -462,7 +465,7 @@ export default {
 success: function(res) {
               self.submitting = false
               if (res.data && res.data.code === 200) {
-                uni.showToast({ title: '打卡成功', icon: 'success' })
+                uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
                 setTimeout(function() {
                   uni.navigateBack()
                 }, 800)

+ 5 - 2
cfc-frontend/pages/growth/exercise-checkin.vue

@@ -161,6 +161,7 @@ export default {
   data() {
     return {
       memberId: null,
+      taskId: null,
       isSelfCheckin: false,
       submitting: false,
       pendingSubmit: false,
@@ -206,7 +207,8 @@ export default {
       return '录音 ' + this.recordSeconds + '秒'
     }
   },
-  onLoad: function() {
+  onLoad: function(options) {
+    this.taskId = options.taskId || null
     this.todayText = this.getToday()
     var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
     if (role !== 'child') {
@@ -445,6 +447,7 @@ export default {
           method: 'POST',
           data: {
             memberId: self.memberId,
+            taskId: self.taskId || undefined,
             exerciseType: self.form.exerciseType,
             durationMinutes: self.form.durationMinutes ? parseInt(self.form.durationMinutes) : null,
             intensity: self.form.intensity,
@@ -458,7 +461,7 @@ export default {
           header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
           success: function(res) {
             if (res.data && res.data.code === 200) {
-              uni.showToast({ title: '打卡成功', icon: 'success' })
+              uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
               self.syncedStepText = ''
               setTimeout(function() {
                 uni.navigateBack()

+ 5 - 2
cfc-frontend/pages/growth/sleep-checkin.vue

@@ -96,6 +96,7 @@ export default {
   data() {
     return {
       memberId: null,
+      taskId: null,
       submitting: false,
       history: [],
       todayText: '',
@@ -107,7 +108,8 @@ export default {
       }
     }
   },
-  onLoad: function() {
+  onLoad: function(options) {
+    this.taskId = options.taskId || null
     this.todayText = this.getToday()
     var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
     if (role !== 'child') {
@@ -165,6 +167,7 @@ export default {
           method: 'POST',
           data: {
             memberId: self.memberId,
+            taskId: self.taskId || undefined,
             sleepTime: self.form.sleepTime,
             wakeTime: self.form.wakeTime,
             durationMinutes: duration,
@@ -175,7 +178,7 @@ export default {
           success: function(res) {
               self.submitting = false
               if (res.data && res.data.code === 200) {
-                uni.showToast({ title: '打卡成功', icon: 'success' })
+                uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
                 setTimeout(function() {
                   uni.navigateBack()
                 }, 800)

+ 7 - 12
cfc-frontend/pages/growth/upload.vue

@@ -273,18 +273,13 @@ export default {
     formatPrice: function(cents) {
       if (cents === null || cents === undefined) return '¥0'
       return '¥' + (cents / 100).toFixed(2)
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/guide/activities/detail.vue

@@ -318,18 +318,13 @@ export default {
       var h = ('0' + d.getHours()).slice(-2)
       var mi = ('0' + d.getMinutes()).slice(-2)
       return y + '-' + m + '-' + day + ' ' + h + ':' + mi
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 11 - 16
cfc-frontend/pages/guide/activities/index.vue

@@ -82,7 +82,7 @@
 </template>
 
 <script>
-import { getActivityList, publishActivity, cancelActivity, deleteActivity } from '../../../utils/api.js'
+import { getGuideActivityList, publishActivity, cancelActivity, deleteActivity } from '../../../utils/api.js'
 import config from '@/config.js'
 import { parseDate } from '../../../utils/format.js'
 
@@ -108,10 +108,10 @@ export default {
     loadActivities() {
       var self = this
       self.loading = true
-      getActivityList(self.currentFilter).then(function(res) {
+      getGuideActivityList(self.currentFilter).then(function(res) {
         self.loading = false
-        if (res.code === 200) {
-          self.activities = res.data || []
+        if (res && res.code === 200) {
+          self.activities = (res.data && Array.isArray(res.data)) ? res.data : []
         }
       }).catch(function() {
         self.loading = false
@@ -207,18 +207,13 @@ export default {
       var h = ('0' + d.getHours()).slice(-2)
       var mi = ('0' + d.getMinutes()).slice(-2)
       return y + '-' + m + '-' + day + ' ' + h + ':' + mi
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/guide/packages/share.vue

@@ -154,18 +154,13 @@ export default {
     },
     closePoster() {
       this.$refs.posterPopup.close()
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/meal/recipe-detail.vue

@@ -147,18 +147,13 @@ export default {
       } catch (e) {
         this.stepList = str.split(/[;;\n]/).filter(function(s) { return s.trim() })
       }
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/mind-detail/articles.vue

@@ -254,18 +254,13 @@ onShow: function() {
         .replace(/&quot;/gi, '"')
         .replace(/\s+/g, ' ')
         .trim()
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 21 - 0
cfc-frontend/pages/mind-detail/index.vue

@@ -149,6 +149,27 @@
           </view>
         </view>
 
+
+        <!-- ===== DAN 报告(登录后) ===== -->
+        <view class="section" v-if="isLoggedIn">
+          <view class="section-header">
+            <text class="section-title">📊 DAN 报告</text>
+            <text class="section-more" @click="goDanUpload('mind')">上传 +</text>
+          </view>
+          <view class="dan-card" v-for="report in danReports" :key="report.id" @click="viewDanReport(report)">
+            <view class="dan-card-header">
+              <text class="dan-report-date">{{ formatDate(report.assessmentDate) }}</text>
+              <text :class="['dan-status', report.draftStatus === 'confirmed' ? 'confirmed' : 'pending']">
+                {{ report.draftStatus === 'confirmed' ? '已确认' : '待确认' }}
+              </text>
+            </view>
+            <text class="dan-report-desc">DAN A2 心维度测评报告</text>
+          </view>
+          <view class="dan-empty" v-if="danReports.length === 0">
+            <text class="dan-empty-text">暂无 DAN 报告,点击上方「上传 +」上传测评报告</text>
+          </view>
+        </view>
+
         <!-- 肠脑轴情绪卡片 -->
         <view class="section" v-if="gutIndicators && gutIndicators.length > 0">
           <view class="section-header">

+ 2 - 2
cfc-frontend/pages/profile-extra/family-members.vue

@@ -326,8 +326,8 @@ export default {
       this.showMemberDetailModal = true
     },
     canEditMember(member) {
-      if (!this.isAdmin) return false
-      // 家长账号(users 表成员)与手写家庭成员均可编辑资料(后端 updateMember/updateParentMember 分别处理)
+      // 家长账号(users 表成员)与手写家庭成员均可编辑资料
+      // 后端 updateMember 会自行验证是否关联登录账号/手机,若不可编辑会返回错误
       return member.source === 'family_member' || member.source === 'parent'
     },
     onAddMember() {

+ 7 - 12
cfc-frontend/pages/promotion/material.vue

@@ -220,18 +220,13 @@ export default {
           uni.showToast({ title: '下载失败', icon: 'none' })
         }
         })
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/shop/checkout/checkout.vue

@@ -1115,18 +1115,13 @@ showOnlineSlotPicker: function() {
     selectServicePerson: function(person) {
       this.selectedServicePerson = person
       this.showServicePersonModal = false
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 7 - 12
cfc-frontend/pages/shop/order-detail/order-detail.vue

@@ -290,18 +290,13 @@ export default {
     formatPriceWithSymbol(price) {
       if (!price && price !== 0) return '-'
       return '¥' + (price / 100).toFixed(2)
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     },
     // 履约状态标签
     fulfillStatusLabel: function(status) {

+ 7 - 12
cfc-frontend/pages/shop/order-list/order-list.vue

@@ -356,18 +356,13 @@ export default {
     },
     onImgError(e, item) {
       item.coverImage = ''
-    },getImageUrl: function(path) {
-
-      if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
-
-      var s = String(path).trim()
-
-      if (!s || s === 'null' || s === '/null') return ''
-
-      if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
-
-      return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
-
+        getImageUrl: function(path) {
+          if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
+          var s = String(path).trim()
+          if (!s || s === 'null' || s === '/null') return ''
+          if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
+          return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
+        } cff388913 (fix(image): 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀)
     }
   }
 }

+ 9 - 0
cfc-frontend/pages/tasks/tasks.vue

@@ -420,6 +420,9 @@ export default {
       if (at === 'article') return '去阅读'
       if (at === 'activity') return '去报名'
       if (at === 'cart' || at === 'order') return '去购物'
+      if (at === 'diet') return '去打卡'
+      if (at === 'sleep') return '去打卡'
+      if (at === 'exercise') return '去打卡'
       return '完成'
     },
     /** 统一任务动作入口:开始/联动跳转/填写/直接完成 */
@@ -477,6 +480,12 @@ export default {
         uni.navigateTo({ url: '/pages/activity/list?taskId=' + taskId })
       } else if (at === 'cart' || at === 'order') {
         uni.navigateTo({ url: '/pages/shop/cart?taskId=' + taskId })
+      } else if (at === 'diet') {
+        uni.navigateTo({ url: '/pages/growth/diet-checkin?taskId=' + taskId })
+      } else if (at === 'sleep') {
+        uni.navigateTo({ url: '/pages/growth/sleep-checkin?taskId=' + taskId })
+      } else if (at === 'exercise') {
+        uni.navigateTo({ url: '/pages/growth/exercise-checkin?taskId=' + taskId })
       }
     },
     closeInputModal() {

+ 1 - 0
cfc-frontend/utils/api.js

@@ -1954,6 +1954,7 @@ export const getUnlockStatus = () => {
 export const getArticleCategories = () => request('/api/articles/categories', 'POST')
 export const getArticleList = (data) => request('/api/articles/list', 'POST', data)
 export const getArticleDetail = (data) => request('/api/articles/detail', 'POST', data)
+export const getArticleRelatedProducts = (data) => request('/api/articles/related-products', 'POST', data)
 export const getFeaturedArticles = (data) => request('/api/articles/featured', 'POST', data)
 export const recordArticleRead = (data) => request('/api/articles/record-read', 'POST', data)
 export const reportReadingTime = (data) => request('/api/articles/report-reading-time', 'POST', data)

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-64b64dd06ff2080639da9fcb3377cf134771ffa4
+25dd65064f3f52ea59c44fe515da2dff41070c4d

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1601",
+  "version": "1.0.1609",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1601",
+      "version": "1.0.1609",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1602",
+  "version": "1.0.1610",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 116 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,122 @@
 
 ---
 
+## v1.0.1610 (2026-10-14)
+
+### Bug 修复
+- 超级管理员(admin)放开代传报告权限(isProxyUploader 加 admin 白名单)
+
+
+## v1.0.1609 (2026-10-14)
+
+### 文档
+- 补充设计文档完成逻辑分支矩阵(按taskId/任务类型/补打三维度穷举8分支)
+- 营养产品标签方案(2026-10-12-nutrition-product-tagging.md)
+
+### Bug 修复
+- 消除 AI 建议与 structuredAnalysis 解析 WARN 日志
+- 家庭成员可编辑判断放宽(family-members.vue)
+- 修复活动管理页空白——错误使用 getActivityList 而非 getGuideActivityList
+
+### 其他
+-    加 @JsonIgnoreProperties(ignoreUnknown=true) 兜底未知字段,避免 AI 输出扩展
+-    导致 Jackson 反序列化整体失败(FiveDimensionSelfCheckService.parseAdvices)。
+- 2. EnergyService/EmiReportService.parseStructuredAnalysis:structured_analysis
+-    实际存储的是 DAN 解析 items 平铺数组 [{code,name,value,category},...]
+-    (DanReportUploadService.confirmUpload 写入),并非嵌套对象 {extra:{...}}。
+-    原 JSON.parseObject(Map.class) 遇 [ 开头抛 fastjson illegal input WARN。
+-    现对 [ 开头(平铺数组)静默返回 null,调用方据此降级(自驱力/自我概念回 0,
+-    与"初期回 0 后续由自检问卷填充"语义一致);嵌套对象形态仍按原逻辑解析。
+- 
+
+### 新功能
+- 文章详情页关联商品推荐(article-detail.vue + api.js)
+- 文章关联商品标签匹配推荐(ArticleController/Product/ProductRecommendationService + schema products.tags)
+- 营养产品功效标签与年龄/剂量UL校验(实体/迁移/服务/接口 + schema 营养部分 + DatabaseInitializer)
+
+
+## v1.0.1608 (2026-10-14)
+
+### Bug 修复
+- 解析完成后清理 OSS 下载中转缓存(.jdcloud-cache 用后即删)
+- 采集链路三重加固——防止非菌群报告/类型不匹配报告静默入库
+
+### 其他
+- - 仅删除 .jdcloud-cache 缓存文件(getFilePath 下载产物),本地磁盘直存原文件不动
+- - 解析成功/失败/异常所有路径均清理;重试时自动重新下载
+- 
+- 采集校验 matched 1/23 pass=false 仍照常入库,前端显示"内容未采集完整"。
+- 加固(ReportCollectService.collect 健康管道):
+- ① 采集指标校验 pass=false → 阻止自动入库,标记失败由管理端人工处理/重传
+- ② gut_flora 家族类型解析结果必须含菌群实质数据(gutFlora/indicators 非空),
+-    只解析出 overview 的报告判定为内容不符
+- ③ 指纹识别类型与用户所选报告类型不一致(非 DAN)→ 阻止入库并明确提示
+- runCollectValidation 三个重载改为返回 boolean(跳过校验返回 true)。
+- DAN 管道分流不受影响(指纹命中 dan_* 仍走 collectDanReport)。
+- 
+
+
+## v1.0.1607 (2026-10-13)
+
+### 重构
+- 全项目时间显示统一走全局 formatTime/formatDate(移除 52 文件本地格式化方法,内联 substring/toLocaleString 改全局,formatDateTime 并入 formatTime)
+
+
+## v1.0.1606 (2026-10-13)
+
+### 文档
+- API_REFERENCE.md 三个打卡接口补 taskId 参数说明
+- 打卡接入任务完成链路状态更新为全栈完成
+
+### 新功能
+- 三个Controller补isDailyProgress二次分流、前端三个打卡页+tasks.vue透传taskId
+- 每日自动生成饮食/睡眠/运动打卡进度任务
+- 报告管理模块重组(合并健康报告/代传独立/详情合并/删除报告审核)
+
+### 其他
+- - cron 主流程接入,每日为所有能登录用户幂等生成 3 条 is_daily_progress 任务
+- - 新增实现计划文档 docs/superpowers/plans/2026-10-13-health-checkin-task-linkage-plan.md
+- 
+- - JS 方法 loadDanReports/goDanUpload/viewDanReport 与 CSS 一直在,仅模板缺失导致采集内容不显示
+- - 与 wisdom-detail L167-184 同构
+- 
+
+### Bug 修复
+- 恢复心智维度页 DAN 报告模板块(72f6e0693 误删,参照 wisdom-detail)
+
+
+## v1.0.1605 (2026-10-13)
+
+### 新功能
+- 三个健康打卡Controller挂钩任务回写(一次性完成+每日进度累计)
+
+
+## v1.0.1604 (2026-10-13)
+
+### 文档
+- actionType 注释补充 diet/sleep/exercise 取值
+
+### Bug 修复
+- 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀
+
+### 其他
+- 
+
+
+## v1.0.1603 (2026-10-13)
+
+### Bug 修复
+- 已撤回文章支持重新编辑流程(状态机+前台按钮)
+
+### 其他
+- - AdminArticleController.update(): 放开 withdrawn 编辑限制(原仅 draft/published)
+- - ArticleManage.vue: 已撤回行下拉菜单新增「重新编辑」操作
+- - ArticleEdit.vue: 编辑页标题模板扩展匹配 withdrawn(显示「保存到草稿箱重新编辑」)
+- - getUpdatedSince(): status 缺省时默认 published,堵住 RAG 增量接口内容泄漏
+- - generateQuiz(): 增加 status=published 检查,禁止对撤回文章生成 AI 题目
+- 
+
+
 ## v1.0.1602 (2026-10-13)
 
 ### Bug 修复

+ 117 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1602
+> 当前版本: v1.0.1610
 
 ## 历史版本
 
@@ -8,6 +8,122 @@
 
 ---
 
+## v1.0.1610 (2026-10-14)
+
+### Bug 修复
+- 超级管理员(admin)放开代传报告权限(isProxyUploader 加 admin 白名单)
+
+
+## v1.0.1609 (2026-10-14)
+
+### 文档
+- 补充设计文档完成逻辑分支矩阵(按taskId/任务类型/补打三维度穷举8分支)
+- 营养产品标签方案(2026-10-12-nutrition-product-tagging.md)
+
+### Bug 修复
+- 消除 AI 建议与 structuredAnalysis 解析 WARN 日志
+- 家庭成员可编辑判断放宽(family-members.vue)
+- 修复活动管理页空白——错误使用 getActivityList 而非 getGuideActivityList
+
+### 其他
+-    加 @JsonIgnoreProperties(ignoreUnknown=true) 兜底未知字段,避免 AI 输出扩展
+-    导致 Jackson 反序列化整体失败(FiveDimensionSelfCheckService.parseAdvices)。
+- 2. EnergyService/EmiReportService.parseStructuredAnalysis:structured_analysis
+-    实际存储的是 DAN 解析 items 平铺数组 [{code,name,value,category},...]
+-    (DanReportUploadService.confirmUpload 写入),并非嵌套对象 {extra:{...}}。
+-    原 JSON.parseObject(Map.class) 遇 [ 开头抛 fastjson illegal input WARN。
+-    现对 [ 开头(平铺数组)静默返回 null,调用方据此降级(自驱力/自我概念回 0,
+-    与"初期回 0 后续由自检问卷填充"语义一致);嵌套对象形态仍按原逻辑解析。
+- 
+
+### 新功能
+- 文章详情页关联商品推荐(article-detail.vue + api.js)
+- 文章关联商品标签匹配推荐(ArticleController/Product/ProductRecommendationService + schema products.tags)
+- 营养产品功效标签与年龄/剂量UL校验(实体/迁移/服务/接口 + schema 营养部分 + DatabaseInitializer)
+
+
+## v1.0.1608 (2026-10-14)
+
+### Bug 修复
+- 解析完成后清理 OSS 下载中转缓存(.jdcloud-cache 用后即删)
+- 采集链路三重加固——防止非菌群报告/类型不匹配报告静默入库
+
+### 其他
+- - 仅删除 .jdcloud-cache 缓存文件(getFilePath 下载产物),本地磁盘直存原文件不动
+- - 解析成功/失败/异常所有路径均清理;重试时自动重新下载
+- 
+- 采集校验 matched 1/23 pass=false 仍照常入库,前端显示"内容未采集完整"。
+- 加固(ReportCollectService.collect 健康管道):
+- ① 采集指标校验 pass=false → 阻止自动入库,标记失败由管理端人工处理/重传
+- ② gut_flora 家族类型解析结果必须含菌群实质数据(gutFlora/indicators 非空),
+-    只解析出 overview 的报告判定为内容不符
+- ③ 指纹识别类型与用户所选报告类型不一致(非 DAN)→ 阻止入库并明确提示
+- runCollectValidation 三个重载改为返回 boolean(跳过校验返回 true)。
+- DAN 管道分流不受影响(指纹命中 dan_* 仍走 collectDanReport)。
+- 
+
+
+## v1.0.1607 (2026-10-13)
+
+### 重构
+- 全项目时间显示统一走全局 formatTime/formatDate(移除 52 文件本地格式化方法,内联 substring/toLocaleString 改全局,formatDateTime 并入 formatTime)
+
+
+## v1.0.1606 (2026-10-13)
+
+### 文档
+- API_REFERENCE.md 三个打卡接口补 taskId 参数说明
+- 打卡接入任务完成链路状态更新为全栈完成
+
+### 新功能
+- 三个Controller补isDailyProgress二次分流、前端三个打卡页+tasks.vue透传taskId
+- 每日自动生成饮食/睡眠/运动打卡进度任务
+- 报告管理模块重组(合并健康报告/代传独立/详情合并/删除报告审核)
+
+### 其他
+- - cron 主流程接入,每日为所有能登录用户幂等生成 3 条 is_daily_progress 任务
+- - 新增实现计划文档 docs/superpowers/plans/2026-10-13-health-checkin-task-linkage-plan.md
+- 
+- - JS 方法 loadDanReports/goDanUpload/viewDanReport 与 CSS 一直在,仅模板缺失导致采集内容不显示
+- - 与 wisdom-detail L167-184 同构
+- 
+
+### Bug 修复
+- 恢复心智维度页 DAN 报告模板块(72f6e0693 误删,参照 wisdom-detail)
+
+
+## v1.0.1605 (2026-10-13)
+
+### 新功能
+- 三个健康打卡Controller挂钩任务回写(一次性完成+每日进度累计)
+
+
+## v1.0.1604 (2026-10-13)
+
+### 文档
+- actionType 注释补充 diet/sleep/exercise 取值
+
+### Bug 修复
+- 云文件绝对 URL 不再重复叠加 API_BASE_URL 前缀
+
+### 其他
+- 
+
+
+## v1.0.1603 (2026-10-13)
+
+### Bug 修复
+- 已撤回文章支持重新编辑流程(状态机+前台按钮)
+
+### 其他
+- - AdminArticleController.update(): 放开 withdrawn 编辑限制(原仅 draft/published)
+- - ArticleManage.vue: 已撤回行下拉菜单新增「重新编辑」操作
+- - ArticleEdit.vue: 编辑页标题模板扩展匹配 withdrawn(显示「保存到草稿箱重新编辑」)
+- - getUpdatedSince(): status 缺省时默认 published,堵住 RAG 增量接口内容泄漏
+- - generateQuiz(): 增加 status=published 检查,禁止对撤回文章生成 AI 题目
+- 
+
+
 ## v1.0.1602 (2026-10-13)
 
 ### Bug 修复

+ 8 - 0
cfc-web/src/api/admin.js

@@ -2078,6 +2078,14 @@ export function listHealthReports(params) {
   })
 }
 
+export function adminListHealthReports(params) {
+  return request({
+    url: '/api/health/report/admin-list',
+    method: 'post',
+    data: params
+  })
+}
+
 export function getHealthReportDetail(reportId) {
   return request({
     url: '/api/health/report/detail',

+ 3 - 1
cfc-web/src/main.js

@@ -16,7 +16,7 @@ import {
 import 'element-ui/lib/theme-chalk/index.css'
 import './styles/admin-tokens.css'
 import './assets/css/sort.css'
-import { formatPrice, formatPriceWithSymbol, formatRate, formatScore } from './utils/format'
+import { formatPrice, formatPriceWithSymbol, formatRate, formatScore, formatDateTime, formatDate } from './utils/format'
 
 Vue.use(Button)
 Vue.use(Table)
@@ -84,6 +84,8 @@ Vue.prototype.formatPrice = formatPrice
 Vue.prototype.formatPriceWithSymbol = formatPriceWithSymbol
 Vue.prototype.formatRate = formatRate
 Vue.prototype.formatScore = formatScore
+Vue.prototype.formatTime = formatDateTime
+Vue.prototype.formatDate = formatDate
 
 // Fix Element UI 2.15.x el-submenu removeTabindex null error
 // https://github.com/ElemeFE/element/issues/21965

+ 11 - 17
cfc-web/src/router/index.js

@@ -605,19 +605,6 @@ const routes = [
         component: () => import('@/views/teacher/GrowthPlans.vue'),
         meta: { title: '成长计划', perm: 'growth:plans' }
       },
-      {
-        path: 'health-reports',
-        name: 'HealthReports',
-        component: () => import('@/views/admin/UnifiedHealthReports.vue'),
-        meta: { title: '健康报告', perm: 'health:reports' }
-      },
-      {
-        path: 'health-reports/:id/edit',
-        name: 'HealthReportEdit',
-        component: () => import('@/views/admin/HealthReportEdit.vue'),
-        meta: { title: '编辑报告', perm: 'health:reports' },
-        props: true
-      },
       {
         path: 'health-indicators',
         name: 'HealthIndicators',
@@ -649,10 +636,17 @@ const routes = [
         meta: { title: '报告管理', perm: 'health:reports' }
       },
       {
-        path: 'report-audit',
-        name: 'HealthReportAudit',
-        component: () => import('@/views/health/HealthReportAudit.vue'),
-        meta: { title: '报告审核', perm: 'report:audit' }
+        path: 'report-management/:id',
+        name: 'HealthReportEdit',
+        component: () => import('@/views/admin/HealthReportEdit.vue'),
+        meta: { title: '报告详情/编辑', perm: 'health:reports' },
+        props: true
+      },
+      {
+        path: 'report-proxy-upload',
+        name: 'ProxyReportUpload',
+        component: () => import('@/views/admin/ProxyReportUpload.vue'),
+        meta: { title: '代传报告', perm: 'health:reports' }
       },
       {
         path: 'report-parse-status',

+ 24 - 0
cfc-web/src/utils/format.js

@@ -39,3 +39,27 @@ export function formatScore (percentile) {
   if (percentile === null || percentile === undefined) return '0.00'
   return (Number(percentile) / 100).toFixed(2)
 }
+
+
+/**
+ * Format ISO datetime string to Chinese style: 'yyyy-MM-dd HH:mm:ss' -> 'yyyy年MM月dd日 HH:mm:ss'
+ * Handles: null/undefined -> '-', already-formatted strings -> idempotent passthrough
+ */
+export function formatDateTime (t) {
+  if (t === null || t === undefined || t === '') return '-'
+  const s = String(t).replace('T', ' ').trim()
+  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})(.*)$/)
+  if (!m) return s
+  const timePart = m[4] ? ' ' + m[4].trim().substring(0, 8) : ''
+  return m[1] + '年' + m[2] + '月' + m[3] + '日' + timePart
+}
+
+/**
+ * Format ISO datetime string to Chinese date only: 'yyyy-MM-dd HH:mm:ss' -> 'yyyy年MM月dd日'
+ */
+export function formatDate (t) {
+  if (t === null || t === undefined || t === '') return '-'
+  const dt = formatDateTime(t)
+  if (dt === '-') return '-'
+  return dt.indexOf(' ') >= 0 ? dt.substring(0, dt.indexOf(' ')) : dt
+}

+ 1 - 1
cfc-web/src/utils/permissions.js

@@ -154,7 +154,7 @@ export const RESIDENT_ROLES = ['butler', 'knowledge_admin', 'teacher', 'nutritio
 const ROLE_HOME_PAGES = {
   butler: '/my-families',
   teacher: '/teacher-dashboard',
-  nutritionist: '/health-reports',
+  nutritionist: '/report-management',
   supplier: '/product-manage',
   activity_manager: '/activities',
   knowledge_admin: '/article-manage',

+ 1 - 5
cfc-web/src/views/Children.vue

@@ -331,11 +331,7 @@ export default {
         }
       })
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 2 - 8
cfc-web/src/views/Families.vue

@@ -536,14 +536,8 @@ export default {
         this.couponSubmitting = false
       }
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    },
-    formatTime(t) {
-      return t ? t.replace('T', ' ').substring(0, 19) : '-'
-    },
+
+
     handleViewCoupons(row) {
       this.currentRow = row
       this.couponPagination = { current: 1, size: 20, total: 0 }

+ 1 - 4
cfc-web/src/views/GuideFamilyTask.vue

@@ -355,10 +355,7 @@ taskLoading: false,
       const map = { pending: '待完成', pending_review: '待审核', completed: '已完成', overdue: '已超时', cancelled: '已取消' }
       return map[status] || status
     },
-    formatDate(date) {
-      if (!date) return '-'
-      return new Date(date).toLocaleString()
-    },
+
     handleReview(task, approved) {
       this.currentTask = task
       this.reviewAction = approved

+ 1 - 4
cfc-web/src/views/GuidePackages.vue

@@ -314,10 +314,7 @@ export default {
       const map = { draft: '草稿', pending: '待审核', approved: '已发布', rejected: '已拒绝', offline: '已下架' }
       return map[status] || status
     },
-    formatDate(date) {
-      if (!date) return '-'
-      return new Date(date).toLocaleString()
-    },
+
     handleCreate() {
       this.form = {
         id: null,

+ 2 - 3
cfc-web/src/views/Layout.vue

@@ -242,12 +242,11 @@ export default {
           { path: '/activity-registration-review', label: '活动报名审核', icon: 'el-icon-document-checked', perm: 'activity:registration' },
         ]},
 
-        // ===== 7. 健康饮食 (health:*,报告管理/报告审核/应季食材挂载,顺序对齐方案最终菜单树) =====
+        // ===== 7. 健康饮食 (health:*,报告管理/代传报告挂载,顺序对齐方案最终菜单树) =====
         { title: '健康饮食', icon: 'el-icon-first-aid-kit', perm: 'health:*',
           children: [
-            { path: '/health-reports', label: '健康报告', icon: 'el-icon-document', perm: 'health:reports' },
             { path: '/report-management', label: '报告管理', icon: 'el-icon-document', perm: 'health:reports' },
-            { path: '/report-audit', label: '报告审核', icon: 'el-icon-document-checked', perm: 'report:audit' },
+            { path: '/report-proxy-upload', label: '代传报告', icon: 'el-icon-upload', perm: 'health:reports' },
             { path: '/report-parse-status', label: '解析进度', icon: 'el-icon-data-line', perm: 'health:reports' },
             { path: '/health-indicators', label: '健康指标', icon: 'el-icon-data-line', perm: 'health:indicators' },
             { path: '/health-checkins', label: '健康打卡', icon: 'el-icon-s-order', perm: 'health:checkins' },

+ 1 - 4
cfc-web/src/views/OperationLogs.vue

@@ -224,10 +224,7 @@ export default {
       }
       return tags[type] || ''
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      return new Date(dateStr).toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 5
cfc-web/src/views/Points.vue

@@ -212,11 +212,7 @@ export default {
       }
       return texts[type] || type
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 5
cfc-web/src/views/Rewards.vue

@@ -449,11 +449,7 @@ export default {
         }
       }).catch(() => {})
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 5
cfc-web/src/views/TaskTemplates.vue

@@ -545,11 +545,7 @@ export default {
       }
       return texts[difficulty] || difficulty
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 5
cfc-web/src/views/Tasks.vue

@@ -384,11 +384,7 @@ export default {
         this.$message.error('操作失败')
       }
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/TeacherConsult.vue

@@ -183,10 +183,7 @@ export default {
         this.replyLoading = false
       }
     },
-    formatDate(date) {
-      if (!date) return '-'
-      return new Date(date).toLocaleString()
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/Users.vue

@@ -575,10 +575,7 @@ const res = await getUserList(params)
     getRoleText(role) {
       return getRoleLabel(role)
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      return String(dateStr).replace('T', ' ').substring(0, 19)
-    },
+
     memberLevelLabel(level) {
       const map = { FREE: '免费会员', BASIC: '体验会员', FAMILY: '家庭会员', PROVIDER: '标准会员', PREMIUM: '高级家庭会员', LIFETIME: '终身会员' }
       return map[level] || '免费会员'

+ 1 - 5
cfc-web/src/views/Wishes.vue

@@ -577,11 +577,7 @@ export default {
       }
       return types[status] || 'info'
     },
-    formatDate(dateStr) {
-      if (!dateStr) return '-'
-      const date = new Date(dateStr)
-      return date.toLocaleString('zh-CN')
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/Activities.vue

@@ -375,10 +375,7 @@ export default {
         this.regLoading = false
       }
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     async handleRegApprove(row) {
       try {
         const res = await approveRegistration({ registrationId: row.id })

+ 1 - 4
cfc-web/src/views/admin/ActivityRegistrationReview.vue

@@ -112,10 +112,7 @@ export default {
     this.loadActivities()
   },
   methods: {
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     async loadActivities() {
       try {
         const res = await getActivityList({ page: 1, size: 100 })

+ 1 - 1
cfc-web/src/views/admin/ArticleEdit.vue

@@ -10,7 +10,7 @@
             <el-button type="success" @click="handleSubmitReview" :loading="reviewLoading" v-if="isEdit && articleStatus === 'draft' && !articleWasPublished">提交审核</el-button>
             <el-button type="success" @click="handleSave(true)" :loading="publishing" v-if="!isEdit || articleStatus === 'draft'">直接发布</el-button>
           </template>
-          <template v-if="articleStatus === 'rejected'">
+          <template v-if="articleStatus === 'rejected' || articleStatus === 'withdrawn'">
             <el-button type="primary" @click="handleReDraft" :loading="reDraftLoading">保存到草稿箱重新编辑</el-button>
           </template>
           <template v-if="readonly && articleStatus !== 'rejected'">

+ 3 - 5
cfc-web/src/views/admin/ArticleManage.vue

@@ -224,7 +224,7 @@
                 </el-dropdown-menu>
               </el-dropdown>
             </template>
-            <!-- 已撤回:查看 + 重新发布 -->
+            <!-- 已撤回:查看 + 重新发布 + 重新编辑 -->
             <template v-else-if="row.status === 'withdrawn'">
               <el-button size="mini" type="primary" @click="$router.push('/article-edit?id=' + row.id + '&readonly=1')">查看</el-button>
               <el-button size="mini" @click="openReadRecords(row)">阅读记录</el-button>
@@ -234,6 +234,7 @@
                 </el-button>
                 <el-dropdown-menu slot="dropdown">
                   <el-dropdown-item command="rePublish" icon="el-icon-s-promotion">重新发布</el-dropdown-item>
+                  <el-dropdown-item command="reDraft" icon="el-icon-edit-outline">重新编辑</el-dropdown-item>
                   <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
                 </el-dropdown-menu>
               </el-dropdown>
@@ -607,10 +608,7 @@ export default {
         this.$message.error(e.message || '复制失败')
       }
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     formatDuration(seconds) {
       if (!seconds) return '0秒'
       var min = Math.floor(seconds / 60)

+ 1 - 4
cfc-web/src/views/admin/AssessmentOrders.vue

@@ -240,10 +240,7 @@ export default {
       var map = { 0: '待确认', 1: '已确认', 2: '已完成', 3: '已取消', 4: '未到' }
       return map[status] !== undefined ? map[status] : (status !== null && status !== undefined ? status : '-')
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/ButlerAssignments.vue

@@ -83,10 +83,7 @@ export default {
     loadList() {
       this.loadData()
     },
-    formatTime(iso) {
-      if (!iso) return '-'
-      return iso.substring(0, 19).replace('T', ' ')
-    },
+
     loadData() {
       this.loading = true
       var params = { pageNum: this.pageNum, pageSize: this.pageSize }

+ 1 - 4
cfc-web/src/views/admin/DanExecution.vue

@@ -260,10 +260,7 @@ export default {
       this.page = val
       this.loadData()
     },
-    formatTime(time) {
-      if (!time) return '-'
-      return new Date(time).toLocaleString('zh-CN')
-    },
+
     executionStatusType(status) {
       if (status === 'completed') return 'success'
       if (status === 'pending') return 'warning'

+ 1 - 4
cfc-web/src/views/admin/DanSettlement.vue

@@ -311,10 +311,7 @@ export default {
       this.page = val
       this.loadData()
     },
-    formatTime(time) {
-      if (!time) return '-'
-      return new Date(time).toLocaleString('zh-CN')
-    },
+
     statusType(status) {
       if (status === 'settled') return 'success'
       if (status === 'pending') return 'warning'

+ 1 - 4
cfc-web/src/views/admin/Dashboard.vue

@@ -249,10 +249,7 @@ export default {
       const map = { pending: '\u5f85\u5ba1\u6838', approved: '\u5df2\u901a\u8fc7', completed: '\u5df2\u5b8c\u6210', rejected: '\u5df2\u62d2\u7edd' }
       return map[status] || status
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/DiscountRuleManagement.vue

@@ -157,10 +157,7 @@ export default {
       const map = { PERCENT: 'warning', FIXED: 'success', THRESHOLD: '' }
       return map[val] || ''
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return String(t).replace('T', ' ').substring(0, 19)
-    },
+
     getEmptyForm() {
       return {
         id: null,

+ 1 - 4
cfc-web/src/views/admin/EmotionAlertManage.vue

@@ -187,10 +187,7 @@ export default {
       var map = { high: '高', medium: '中', low: '低' }
       return map[severity] || severity
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/FamilyEarnings.vue

@@ -157,10 +157,7 @@ export default {
       if (isNaN(num)) return '0.00'
       return (num / 100).toFixed(2)
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : String(t)
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/FamilyEarningsWithdraw.vue

@@ -115,10 +115,7 @@ export default {
       if (isNaN(num)) return '0.00'
       return Math.abs(num / 100).toFixed(2)
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : String(t)
-    }
+
   }
 }
 </script>

+ 2 - 10
cfc-web/src/views/admin/HealthCheckinManage.vue

@@ -81,16 +81,8 @@ export default {
     this.loadList()
   },
   methods: {
-    formatDate(d) {
-      if (!d) return '-'
-      var date = new Date(d)
-      return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0')
-    },
-    formatTime(d) {
-      if (!d) return '-'
-      var date = new Date(d)
-      return date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0') + ' ' + date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0')
-    },
+
+
     moodLabel(m) {
       var map = { happy: '开心', normal: '平静', sad: '低落', angry: '烦躁', sick: '不适' }
       return map[m] || m || '-'

+ 1 - 4
cfc-web/src/views/admin/HealthPlanManage.vue

@@ -203,10 +203,7 @@ export default {
         this.tasksLoading = false
       }
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace('T', ' ').substring(0, 19)
-    },
+
     statusType(s) {
       const map = { draft: 'info', pending_review: 'warning', published: 'success', rejected: 'danger' }
       return map[s] || 'info'

+ 1 - 1
cfc-web/src/views/admin/HealthReportEdit.vue

@@ -424,7 +424,7 @@ export default {
       })
     },
     goBack() {
-      this.$router.push({ name: 'HealthReports' })
+      this.$router.push({ path: '/report-management' })
     }
   }
 }

+ 1 - 3
cfc-web/src/views/admin/HealthReportReassign.vue

@@ -93,9 +93,7 @@ export default {
     this.loadFamilies()
   },
   methods: {
-    formatTime(t) {
-      return t ? String(t).replace('T', ' ').substring(0, 19) : '-'
-    },
+
     getMemberName(memberId) {
       if (!memberId) return '-'
       var m = this.memberList.find(function(x) { return x.id === memberId })

+ 2 - 20
cfc-web/src/views/admin/HomeBannerManage.vue

@@ -609,26 +609,8 @@ export default {
       }
       this.loadList()
     },
-    formatDate(ts) {
-      if (!ts) return '-'
-      var d = new Date(ts)
-      if (isNaN(d.getTime())) return '-'
-      var y = d.getFullYear()
-      var m = String(d.getMonth() + 1).padStart(2, '0')
-      var day = String(d.getDate()).padStart(2, '0')
-      var h = String(d.getHours()).padStart(2, '0')
-      var min = String(d.getMinutes()).padStart(2, '0')
-      var s = String(d.getSeconds()).padStart(2, '0')
-      return y + '-' + m + '-' + day + ' ' + h + ':' + min + ':' + s
-    },
-    formatTime(t) {
-      if (!t) return '-'
-      var ts = t
-      if (typeof t === 'string' && t.indexOf('T') > -1) {
-        ts = new Date(t).getTime()
-      }
-      return this.formatDate(ts)
-    },
+
+
     // 选择小程序页面
     selectMiniPage() {
       this.$message.info('小程序页面选择功能待后端开发')

+ 1 - 3
cfc-web/src/views/admin/IndicatorCorrelations.vue

@@ -253,9 +253,7 @@ export default {
     defName(id) {
       return (this.defMap[id] || '#' + id)
     },
-    formatTime(t) {
-      return t ? t.replace('T', ' ').substring(0, 19) : '-'
-    }
+
   }
 }
 </script>

+ 1 - 3
cfc-web/src/views/admin/IndicatorMappings.vue

@@ -227,9 +227,7 @@ export default {
     defName(id) {
       return (this.defMap[id] || '#' + id)
     },
-    formatTime(t) {
-      return t ? t.replace('T', ' ').substring(0, 19) : '-'
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/MembershipCenter.vue

@@ -574,10 +574,7 @@ export default {
       if (price === null || price === undefined) return '—'
       return (price / 100).toFixed(2)
     },
-    formatTime(t) {
-      if (!t) return '—'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     levelTagType(code) {
       return LEVEL_TAG_TYPE[code] || 'primary'
     },

+ 1 - 5
cfc-web/src/views/admin/NoticeManage.vue

@@ -168,11 +168,7 @@ export default {
       var map = { platform: 'warning', activity: 'primary', promotion: 'danger' }
       return map[type] || ''
     },
-    formatTime(time) {
-      if (!time) return '-'
-      var d = new Date(time)
-      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0') + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0')
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/OrderDetail.vue

@@ -243,10 +243,7 @@ export default {
       if (cents == null) return '-'
       return '¥' + (cents / 100).toFixed(2)
     },
-    formatTime(time) {
-      if (!time) return '-'
-      return new Date(time).toLocaleString()
-    }
+
   }
 }
 </script>

+ 1 - 4
cfc-web/src/views/admin/OrderManage.vue

@@ -405,10 +405,7 @@ export default {
         return snapshot
       }
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     handleActionCmd(row, cmd) {
       switch (cmd) {
         case 'ship': this.showShipDialog(row); break;

+ 1 - 7
cfc-web/src/views/admin/PendingRefund.vue

@@ -237,13 +237,7 @@ async loadList() {
         }
       }
     },
-    formatTime(time) {
-      if (!time) return '-'
-      const d = new Date(time)
-      const pad = n => n < 10 ? '0' + n : n
-      return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) +
-        ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
-    },
+
     formatPriceWithSymbol(amount) {
       if (amount == null) return '-'
       return '¥' + (amount / 100).toFixed(2)

+ 1 - 4
cfc-web/src/views/admin/PointsLog.vue

@@ -167,10 +167,7 @@ export default {
     typeLabel(type) {
       return (TYPE_MAP[type] && TYPE_MAP[type].label) || type || '—'
     },
-    formatTime(t) {
-      if (!t) return '-'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     async loadData() {
       this.loading = true
       try {

+ 1 - 4
cfc-web/src/views/admin/ProductManage.vue

@@ -409,10 +409,7 @@ export default {
       const map = { FREE: '免费版', BASIC: '体验版', FAMILY: '家庭版', PROVIDER: '服务商版', PREMIUM: '高级版', LIFETIME: '终身版' }
       return map[code] || code
     },
-    formatTime(t) {
-      if (!t) return '—'
-      return t.replace ? t.replace('T', ' ').substring(0, 19) : t
-    },
+
     formatPriceWithSymbol(price) {
       if (!price) return '免费'
       const v = (price / 100).toFixed(2)

+ 2 - 7
cfc-web/src/views/admin/ProfileManagement.vue

@@ -37,7 +37,7 @@
         </el-table-column>
         <el-table-column prop="computed_at" label="更新时间" width="160">
           <template slot-scope="{ row }">
-            {{ row.computed_at ? formatDateTime(row.computed_at) : '-' }}
+            {{ row.computed_at ? formatTime(row.computed_at) : '-' }}
           </template>
         </el-table-column>
         <el-table-column label="操作" width="120" fixed="right">
@@ -179,12 +179,7 @@ export default {
       var m = { sleep: '睡眠', attention: '注意力', emotion: '情绪' }
       return m[d] || d
     },
-    formatDateTime(dateStr) {
-      if (!dateStr) return '-'
-      var dt = dateStr.toString()
-      if (dt.length >= 19) return dt.substring(0, 19).replace('T', ' ')
-      return dt
-    }
+
   }
 }
 </script>

Some files were not shown because too many files changed in this diff