|
@@ -41,6 +41,9 @@ public class DatabaseInitializer implements CommandLineRunner {
|
|
|
// 4. 初始化默认数据
|
|
// 4. 初始化默认数据
|
|
|
initializeDefaultData();
|
|
initializeDefaultData();
|
|
|
|
|
|
|
|
|
|
+ // 5. 初始化食品相关表
|
|
|
|
|
+ initFoodTables();
|
|
|
|
|
+
|
|
|
log.info("数据库初始化完成!");
|
|
log.info("数据库初始化完成!");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2932,6 +2935,126 @@ try {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ 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)', "
|
|
|
|
|
+ + "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 '食材图片', "
|
|
|
|
|
+ + "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)"
|
|
|
|
|
+ + ");"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ 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 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) {
|
|
|
|
|
+ if (e.getMessage() != null && e.getMessage().contains("Duplicate column")) {
|
|
|
|
|
+ log.info("列 {}.{} 已存在,跳过", table, column);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.warn("添加列 {}.{} 失败: {}", table, column, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private void insertProductSeed(String name, String desc, String type,
|
|
private void insertProductSeed(String name, String desc, String type,
|
|
|
Integer price,
|
|
Integer price,
|
|
|
String domain, String memberEligible) {
|
|
String domain, String memberEligible) {
|