NutriAI - 智能个性化膳食推荐系统
本系统的核心是的三元驱动模式。基于详细的用户画像,提供高度个性化的推荐。结合用户的时间、地点、季节等场景因素。所有推荐均基于权威营养学指南和食品数据库。通过用户反馈持续优化推荐结果,形成闭环。这是推荐系统的基石。我们需要为每个群体定义核心特征和营养需求。这是一个典型的微服务架构,可以保证系统的可扩展性和稳定性。React Native / Flutter(跨端App),Vue.js(管理后台)Py



一、 系统核心设计理念
本系统的核心是 “用户画像 × 营养学模型 × 智能算法” 的三元驱动模式。
-
精准化: 基于详细的用户画像,提供高度个性化的推荐。
-
场景化: 结合用户的时间、地点、季节等场景因素。
-
科学化: 所有推荐均基于权威营养学指南和食品数据库。
-
互动化: 通过用户反馈持续优化推荐结果,形成闭环。
二、 用户群体分析与核心营养需求(用户画像基础)
这是推荐系统的基石。我们需要为每个群体定义核心特征和营养需求。
| 受众群体 | 核心特征与需求 | 重点关注营养素/食材 | 推荐场景举例 |
|---|---|---|---|
| 儿童 | 生长发育、挑食、食品安全 | 钙、铁、锌、DHA、蛋白质、维生素 | 促进骨骼发育的早餐、增强免疫力的加餐 |
| 青少年 | 学业繁重、快速生长、运动量大 | 高蛋白、复合碳水、钙、B族维生素 | 备考营养餐、运动后恢复餐、防痘食谱 |
| 学生群体 | 预算有限、时间紧张、食堂为主 | 经济、便捷、高能量、营养均衡 | 宿舍快手菜、食堂选餐指南、熬夜宵夜 |
| 上班族 | 久坐、压力大、外卖依赖、亚健康 | 高纤维、护眼、抗疲劳、低脂低卡 | 办公室健康零食、快手午餐便当、护肝食谱 |
| 孕妇 | 特殊生理阶段、对食物敏感 | 叶酸、铁、钙、蛋白质、DHA | 缓解孕吐食谱、孕期各阶段营养补充、控糖餐 |
| 老年人 | 代谢慢、慢性病、咀嚼消化能力弱 | 高钙、优质蛋白、膳食纤维、低钠低脂 | 三高(高血压/高血脂/高血糖)调理餐、易消化食谱 |
| 马拉松运动员 | 极限能量消耗、肌肉修复 | 极高碳水、电解质、蛋白质、抗氧化 | 赛前糖原负荷餐、赛中能量补给、赛后恢复餐 |
| 过敏群体 | 对特定食物(如坚果、海鲜、乳糖)过敏 | 严格规避过敏原,同时保证营养替代 | 无麸质食谱、乳糖不耐受替代品、素食者补铁方案 |
三、 系统架构与技术栈
这是一个典型的微服务架构,可以保证系统的可扩展性和稳定性。
+-------------------+ +-----------------------+
| 前端 Client | -> | API Gateway |
+-------------------+ +----------+------------+
|
v
+-------------------------------------------------------------------------+
| 后端 Backend (Microservices) |
| +-------------+ +-------------+ +--------------+ +-----------------+ |
| | 用户服务 | | 推荐服务 | | 内容服务 | | 数据与分析服务 | |
| | User Service| | Rec Service | | Content Service| | Analytics Service| |
| +-------------+ +-------------+ +--------------+ +-----------------+ |
+-------------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------------+
| 数据层 Data Layer |
| +--------------+ +----------------+ +----------------+ +-----------+ |
| | 用户画像DB | | 食谱&营养DB | | 日志与行为DB | | 模型DB | |
| | (MySQL/PostgreSQL) | (MongoDB/ES) | (ClickHouse) | (Redis) | |
| +--------------+ +----------------+ +----------------+ +-----------+ |
+-------------------------------------------------------------------------+
技术栈选型:
-
前端: React Native / Flutter(跨端App),Vue.js(管理后台)
-
后端: Python(Django/FastAPI)或 Java(Spring Boot)。Python在数据科学和AI集成上更有优势,是本项目的首选。
-
数据库:
-
MySQL/PostgreSQL: 存储用户基本信息、关系型数据。
-
MongoDB/Elasticsearch: 存储非结构化的食谱、文章内容,便于复杂查询。
-
Redis: 缓存热点数据、用户会话、推荐结果。
-
ClickHouse: 存储用户行为日志,用于大数据分析。
-
-
算法与AI:
-
协同过滤: “喜欢A食谱的人也喜欢B食谱”。
-
内容基于推荐: 根据食谱的标签(高蛋白、低卡)与用户画像匹配。
-
知识图谱: 构建“用户-症状-营养素-食材-食谱”之间的关系网络,实现更深度的推理推荐。
-
-
部署与运维: Docker, Kubernetes, Nginx, CI/CD(Jenkins/GitLab CI)
四、 核心功能模块设计
1. 用户画像系统
-
注册问卷: 用户首次使用时,通过精心设计的问卷收集信息:
-
基础信息:年龄、性别、身高、体重、目标(增肌/减脂/维持健康)。
-
所属群体:从预设的七大群体中选择。
-
健康状况:慢性病(三高)、过敏原、饮食习惯(素食/清真)。
-
生活方式:活动水平、睡眠时间、压力水平、烹饪条件。
-
-
动态画像: 通过用户日常的浏览、收藏、评分、分享行为,持续更新其偏好。
2. 内容(食谱)管理系统
-
结构化食谱数据: 每个食谱包含:标题、图片、食材清单、步骤、烹饪时间、难度。
-
营养标签: 自动计算或人工标注每个食谱的:卡路里、蛋白质、脂肪、碳水化合物、钠、钙等含量,并打上“高蛋白”、“低GI”、“补铁”等标签。
-
关联知识: 关联相关的健康知识文章,如“为什么孕妇需要补充叶酸?”
3. 智能推荐引擎(核心)
推荐策略是混合的,根据不同场景触发不同的算法。
-
策略一:基于规则的冷启动
-
当新用户注册或系统对新用户了解不多时,直接根据其选择的“受众群体”和“健康目标”,从内容库中推送符合该群体营养需求的标准化食谱。
-
-
策略二:协同过滤
-
Item-CF(物品协同): “购买了鸡胸肉的用户,也经常购买西兰花和糙米”,推荐相关食材或食谱。 -
User-CF(用户协同): “和您画像相似的另一个孕妇用户,喜欢这个缓解孕吐的柠檬姜茶食谱”。
-
-
策略三:内容基于推荐
-
将用户画像(需要补钙)与食谱标签(高钙)进行匹配。这是实现个性化和安全性(如规避过敏原)的关键。
-
-
策略四:基于知识图谱的推荐
-
构建一个图谱:
用户 -> 有“失眠”症状 -> 需要“镁”和“色氨酸” -> 富含于“香蕉”、“牛奶” -> 关联食谱“香蕉牛奶燕麦粥”。 -
这种方法能提供可解释的推荐,例如:“我们为您推荐香蕉牛奶燕麦粥,因为它富含镁和色氨酸,有助于改善您的睡眠问题。”
-
-
推荐结果融合与排序
-
将以上多种策略产生的推荐结果进行加权融合。
-
引入多样性机制,避免推荐内容过于单一。
-
考虑时效性(推荐时令食材)和流行度。
-
4. 个性化推送与交互系统
-
推送时机: 早餐前推送早餐建议,周末推送需要长时间准备的食谱。
-
推送渠道: App内消息、短信、电子邮件、微信小程序。
-
反馈闭环: 提供“喜欢”、“不感兴趣”、“收藏”、“分享”等按钮。用户的负面反馈(“不感兴趣”)对优化算法至关重要。
五、 针对不同群体的推荐策略示例
-
孕妇:
-
触发词: “孕早期”、“孕吐”
-
推荐逻辑: 内容基于推荐 -> 搜索标签为“缓解孕吐”、“易消化”的食谱,如姜糖、苏打饼干。同时,推送知识文章《孕早期营养注意事项》。
-
-
马拉松运动员:
-
触发词: “赛前3天”
-
推荐逻辑: 规则推荐 -> 调用“赛前糖原负荷”规则,推送高碳水化合物、低脂的食谱,如意大利面、米饭、香蕉。
-
-
过敏群体:
-
触发词: 用户画像中标记“花生过敏”
-
推荐逻辑: 强规则过滤 -> 在所有推荐流程的第一步,严格过滤掉所有含有花生或可能交叉污染的食谱。这是最高优先级的规则。
-
-
上班族:
-
触发词: “工作日午餐”、“15分钟”
-
推荐逻辑: 内容基于 + 协同过滤 -> 推荐标签为“快手”、“便当”、“护眼”的食谱,同时参考其他上班族的高收藏食谱。
-
系统架构与技术栈
# docker-compose.yml
version: '3.8'
services:
# 后端服务
api-gateway:
build: ./services/gateway
ports:
- "8000:8000"
environment:
- NODE_ENV=production
depends_on:
- user-service
- recommendation-service
- content-service
user-service:
build: ./services/user
environment:
- DATABASE_URL=postgresql://user:pass@postgres:5432/nutriai
- REDIS_URL=redis://redis:6379
recommendation-service:
build: ./services/recommendation
environment:
- ML_MODEL_PATH=/models/nutrition_model.h5
content-service:
build: ./services/content
environment:
- MONGODB_URI=mongodb://mongo:27017/nutriai
# 数据库
postgres:
image: postgres:14
environment:
- POSTGRES_DB=nutriai
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
mongo:
image: mongo:6
volumes:
- mongo_data:/data/db
# 前端
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- api-gateway
volumes:
postgres_data:
redis_data:
mongo_data:
数据库模型设计
-- services/user/schema.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 用户画像表
CREATE TABLE user_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
age INTEGER NOT NULL,
gender VARCHAR(10) CHECK (gender IN ('male', 'female', 'other')),
height_cm DECIMAL(5,2),
weight_kg DECIMAL(5,2),
target_weight_kg DECIMAL(5,2),
activity_level VARCHAR(20) CHECK (activity_level IN ('sedentary', 'light', 'moderate', 'active', 'very_active')),
dietary_restrictions TEXT[], -- 过敏原和饮食限制
health_conditions TEXT[], -- 健康状况
user_group VARCHAR(50) NOT NULL CHECK (
user_group IN ('child', 'teenager', 'student', 'office_worker',
'pregnant', 'elderly', 'athlete', 'allergy')
),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 用户目标表
CREATE TABLE user_goals (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
goal_type VARCHAR(50) NOT NULL,
target_value DECIMAL(10,2),
current_value DECIMAL(10,2),
deadline_date DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
核心后端服务
API Gateway (Node.js + Express)
// services/gateway/src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
// 安全中间件
app.use(helmet());
app.use(cors());
app.use(express.json());
// 限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 限制每个IP每15分钟100次请求
});
app.use(limiter);
// 服务路由
const services = {
'/users': 'http://user-service:3001',
'/recommendations': 'http://recommendation-service:3002',
'/content': 'http://content-service:3003'
};
// 设置代理中间件
Object.keys(services).forEach(route => {
app.use(route, createProxyMiddleware({
target: services[route],
changeOrigin: true,
pathRewrite: {
[`^${route}`]: ''
},
onError: (err, req, res) => {
console.error(`Proxy error for ${route}:`, err);
res.status(503).json({
error: 'Service temporarily unavailable',
message: `The ${route.slice(1)} service is currently unavailable`
});
}
}));
});
// 健康检查端点
app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error('Global error handler:', err);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'production'
? 'Something went wrong'
: err.message
});
});
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});
用户服务 (Python + FastAPI)
# services/user/app/main.py
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List, Optional
import uuid
from datetime import datetime
from .database import get_db, engine
from . import models, schemas, crud, auth
models.Base.metadata.create_all(bind=engine)
app = FastAPI(title="User Service", version="1.0.0")
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
return crud.create_user(db=db, user=user)
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: uuid.UUID, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post("/users/{user_id}/profile", response_model=schemas.UserProfile)
def create_user_profile(
user_id: uuid.UUID,
profile: schemas.UserProfileCreate,
db: Session = Depends(get_db)
):
return crud.create_user_profile(db=db, user_id=user_id, profile=profile)
@app.get("/users/{user_id}/recommendation-profile")
def get_recommendation_profile(user_id: uuid.UUID, db: Session = Depends(get_db)):
"""为推荐系统提供用户画像数据"""
user_profile = crud.get_user_profile(db, user_id=user_id)
if not user_profile:
raise HTTPException(status_code=404, detail="User profile not found")
return {
"user_id": user_id,
"user_group": user_profile.user_group,
"dietary_restrictions": user_profile.dietary_restrictions or [],
"health_conditions": user_profile.health_conditions or [],
"age": user_profile.age,
"gender": user_profile.gender,
"bmi": calculate_bmi(user_profile.height_cm, user_profile.weight_kg),
"activity_level": user_profile.activity_level
}
def calculate_bmi(height_cm: float, weight_kg: float) -> Optional[float]:
if height_cm and weight_kg:
height_m = height_cm / 100
return round(weight_kg / (height_m ** 2), 1)
return None
# services/user/app/schemas.py
from pydantic import BaseModel, EmailStr, validator
from typing import Optional, List
import uuid
from datetime import datetime
class UserBase(BaseModel):
email: EmailStr
phone: Optional[str] = None
class UserCreate(UserBase):
pass
class User(UserBase):
id: uuid.UUID
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
class UserProfileBase(BaseModel):
age: int
gender: str
height_cm: Optional[float] = None
weight_kg: Optional[float] = None
target_weight_kg: Optional[float] = None
activity_level: str
dietary_restrictions: Optional[List[str]] = None
health_conditions: Optional[List[str]] = None
user_group: str
@validator('user_group')
def validate_user_group(cls, v):
allowed_groups = ['child', 'teenager', 'student', 'office_worker',
'pregnant', 'elderly', 'athlete', 'allergy']
if v not in allowed_groups:
raise ValueError(f'User group must be one of {allowed_groups}')
return v
class UserProfileCreate(UserProfileBase):
pass
class UserProfile(UserProfileBase):
id: uuid.UUID
user_id: uuid.UUID
created_at: datetime
updated_at: datetime
class Config:
orm_mode = True
推荐服务 (Python + ML)
# services/recommendation/app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Dict, Any
import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import redis
import json
import logging
app = FastAPI(title="Recommendation Service", version="1.0.0")
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Redis连接
redis_client = redis.Redis(host='redis', port=6379, decode_responses=True)
class RecommendationRequest(BaseModel):
user_id: str
user_profile: Dict[str, Any]
limit: int = 10
context: Optional[Dict[str, Any]] = None
class Recipe(BaseModel):
id: str
title: str
ingredients: List[str]
nutrition_facts: Dict[str, float]
tags: List[str]
cooking_time: int
difficulty: str
user_group: List[str]
class RecommendationEngine:
def __init__(self):
self.vectorizer = TfidfVectorizer(stop_words='english')
self.recipes_df = self._load_recipes()
self._build_similarity_matrix()
def _load_recipes(self) -> pd.DataFrame:
"""从数据库加载食谱数据"""
# 这里应该是从内容服务获取数据
# 为演示目的,我们创建一些示例数据
recipes_data = [
{
"id": "1",
"title": "高蛋白鸡胸肉沙拉",
"ingredients": ["鸡胸肉", "生菜", "番茄", "橄榄油", "柠檬汁"],
"nutrition_facts": {"calories": 350, "protein": 35, "carbs": 12, "fat": 18},
"tags": ["高蛋白", "低卡", "健身", "午餐"],
"cooking_time": 15,
"difficulty": "简单",
"user_group": ["athlete", "office_worker"]
},
{
"id": "2",
"title": "孕妇营养早餐",
"ingredients": ["燕麦", "牛奶", "香蕉", "坚果", "蜂蜜"],
"nutrition_facts": {"calories": 420, "protein": 18, "carbs": 65, "fat": 12},
"tags": ["孕妇", "早餐", "高钙", "高纤维"],
"cooking_time": 10,
"difficulty": "简单",
"user_group": ["pregnant"]
}
]
return pd.DataFrame(recipes_data)
def _build_similarity_matrix(self):
"""构建食谱相似度矩阵"""
recipe_texts = [
' '.join(recipe['tags'] + recipe['ingredients'])
for _, recipe in self.recipes_df.iterrows()
]
self.tfidf_matrix = self.vectorizer.fit_transform(recipe_texts)
self.similarity_matrix = cosine_similarity(self.tfidf_matrix, self.tfidf_matrix)
def get_recommendations(self, request: RecommendationRequest) -> List[Recipe]:
"""获取个性化推荐"""
user_group = request.user_profile['user_group']
dietary_restrictions = request.user_profile.get('dietary_restrictions', [])
# 过滤符合用户群体的食谱
filtered_recipes = self.recipes_df[
self.recipes_df['user_group'].apply(
lambda x: user_group in x
)
]
# 应用饮食限制
if dietary_restrictions:
filtered_recipes = filtered_recipes[
~filtered_recipes['ingredients'].apply(
lambda ingredients: any(
restriction in ' '.join(ingredients)
for restriction in dietary_restrictions
)
)
]
# 基于内容的推荐
recommendations = self._content_based_filtering(
filtered_recipes, request.user_profile
)
# 应用业务规则
recommendations = self._apply_business_rules(
recommendations, request.user_profile
)
return recommendations[:request.limit]
def _content_based_filtering(self, recipes: pd.DataFrame, user_profile: Dict) -> List[Recipe]:
"""基于内容的过滤算法"""
scored_recipes = []
for _, recipe in recipes.iterrows():
score = 0
# 根据用户群体匹配
if user_profile['user_group'] in recipe['user_group']:
score += 3
# 根据营养需求评分
score += self._calculate_nutrition_score(recipe, user_profile)
# 根据烹饪时间偏好调整分数
if user_profile.get('activity_level') in ['sedentary', 'light']:
if recipe['cooking_time'] <= 20:
score += 2
scored_recipes.append((recipe, score))
# 按分数排序
scored_recipes.sort(key=lambda x: x[1], reverse=True)
return [recipe for recipe, _ in scored_recipes]
def _calculate_nutrition_score(self, recipe: pd.Series, user_profile: Dict) -> float:
"""计算营养匹配分数"""
score = 0
nutrition = recipe['nutrition_facts']
user_group = user_profile['user_group']
# 根据不同用户群体的营养需求评分
nutrition_rules = {
'athlete': {
'protein': (30, 50, 2), # 最小, 最大, 权重
'carbs': (40, 70, 1.5)
},
'pregnant': {
'protein': (20, 35, 1.5),
'calories': (300, 500, 1)
},
'elderly': {
'protein': (15, 30, 2),
'calories': (200, 400, 1.5)
}
}
if user_group in nutrition_rules:
rules = nutrition_rules[user_group]
for nutrient, (min_val, max_val, weight) in rules.items():
if nutrient in nutrition:
value = nutrition[nutrient]
if min_val <= value <= max_val:
score += weight
return score
def _apply_business_rules(self, recipes: List, user_profile: Dict) -> List:
"""应用业务规则"""
filtered_recipes = []
for recipe in recipes:
# 排除含有过敏原的食谱
if self._contains_allergens(recipe, user_profile.get('dietary_restrictions', [])):
continue
# 根据健康状况过滤
if not self._is_healthy_for_conditions(recipe, user_profile.get('health_conditions', [])):
continue
filtered_recipes.append(recipe)
return filtered_recipes
def _contains_allergens(self, recipe: pd.Series, allergens: List[str]) -> bool:
"""检查是否含有过敏原"""
ingredients_text = ' '.join(recipe['ingredients']).lower()
return any(allergen.lower() in ingredients_text for allergen in allergens)
def _is_healthy_for_conditions(self, recipe: pd.Series, conditions: List[str]) -> bool:
"""根据健康状况检查食谱适宜性"""
nutrition = recipe['nutrition_facts']
health_rules = {
'diabetes': nutrition.get('carbs', 0) < 60,
'hypertension': nutrition.get('sodium', 0) < 500,
'obesity': nutrition.get('calories', 0) < 400
}
for condition in conditions:
if condition in health_rules and not health_rules[condition]:
return False
return True
# 初始化推荐引擎
recommendation_engine = RecommendationEngine()
@app.post("/recommendations/")
async def get_recommendations(request: RecommendationRequest):
"""获取个性化膳食推荐"""
try:
# 检查缓存
cache_key = f"recommendations:{request.user_id}"
cached_result = redis_client.get(cache_key)
if cached_result:
logger.info(f"Returning cached recommendations for user {request.user_id}")
return json.loads(cached_result)
# 生成推荐
recommendations = recommendation_engine.get_recommendations(request)
# 缓存结果(5分钟)
redis_client.setex(
cache_key,
300, # 5分钟
json.dumps([rec.to_dict() for rec in recommendations])
)
logger.info(f"Generated {len(recommendations)} recommendations for user {request.user_id}")
return recommendations
except Exception as e:
logger.error(f"Error generating recommendations: {str(e)}")
raise HTTPException(status_code=500, detail="Error generating recommendations")
@app.get("/health")
async def health_check():
return {"status": "healthy", "service": "recommendation"}
前端React组件
// frontend/src/components/UserProfileForm.jsx
import React, { useState } from 'react';
import {
Box,
Container,
Typography,
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
Button,
Chip,
Grid,
Card,
CardContent
} from '@mui/material';
const USER_GROUPS = {
child: { name: '儿童', minAge: 3, maxAge: 12 },
teenager: { name: '青少年', minAge: 13, maxAge: 19 },
student: { name: '学生', minAge: 18, maxAge: 25 },
office_worker: { name: '上班族', minAge: 22, maxAge: 60 },
pregnant: { name: '孕妇', minAge: 20, maxAge: 45 },
elderly: { name: '老年人', minAge: 60, maxAge: 100 },
athlete: { name: '运动员', minAge: 18, maxAge: 50 },
allergy: { name: '过敏人群', minAge: 1, maxAge: 100 }
};
const ALLERGENS = [
'牛奶', '鸡蛋', '花生', '坚果', '大豆', '小麦', '鱼类', '贝类',
'芝麻', '芹菜', '芥末', '二氧化硫', '羽扇豆', '软体动物'
];
const HEALTH_CONDITIONS = [
'糖尿病', '高血压', '高血脂', '心脏病', '肾病', '肝病',
'肠胃敏感', '骨质疏松', '贫血', '甲状腺问题'
];
const UserProfileForm = () => {
const [formData, setFormData] = useState({
age: '',
gender: '',
height: '',
weight: '',
targetWeight: '',
activityLevel: '',
userGroup: '',
dietaryRestrictions: [],
healthConditions: []
});
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/users/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (response.ok) {
alert('个人信息保存成功!');
// 跳转到推荐页面
} else {
alert('保存失败,请重试');
}
} catch (error) {
console.error('Error saving profile:', error);
alert('网络错误,请重试');
}
};
const handleChipSelect = (field, value) => {
setFormData(prev => ({
...prev,
[field]: prev[field].includes(value)
? prev[field].filter(item => item !== value)
: [...prev[field], value]
}));
};
return (
<Container maxWidth="md" sx={{ py: 4 }}>
<Card>
<CardContent>
<Typography variant="h4" component="h1" gutterBottom align="center">
个性化营养配置
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<TextField
required
fullWidth
label="年龄"
type="number"
value={formData.age}
onChange={(e) => setFormData({...formData, age: e.target.value})}
/>
</Grid>
<Grid item xs={12} sm={6}>
<FormControl fullWidth required>
<InputLabel>性别</InputLabel>
<Select
value={formData.gender}
label="性别"
onChange={(e) => setFormData({...formData, gender: e.target.value})}
>
<MenuItem value="male">男</MenuItem>
<MenuItem value="female">女</MenuItem>
<MenuItem value="other">其他</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth required>
<InputLabel>用户群体</InputLabel>
<Select
value={formData.userGroup}
label="用户群体"
onChange={(e) => setFormData({...formData, userGroup: e.target.value})}
>
{Object.entries(USER_GROUPS).map(([key, group]) => (
<MenuItem key={key} value={key}>
{group.name}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
过敏原限制
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{ALLERGENS.map(allergen => (
<Chip
key={allergen}
label={allergen}
clickable
color={formData.dietaryRestrictions.includes(allergen) ?
'primary' : 'default'}
onClick={() => handleChipSelect('dietaryRestrictions', allergen)}
/>
))}
</Box>
</Grid>
<Grid item xs={12}>
<Typography variant="h6" gutterBottom>
健康状况
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{HEALTH_CONDITIONS.map(condition => (
<Chip
key={condition}
label={condition}
clickable
color={formData.healthConditions.includes(condition) ?
'primary' : 'default'}
onClick={() => handleChipSelect('healthConditions', condition)}
/>
))}
</Box>
</Grid>
<Grid item xs={12}>
<Button
type="submit"
fullWidth
variant="contained"
size="large"
sx={{ mt: 2 }}
>
开始个性化推荐
</Button>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
</Container>
);
};
export default UserProfileForm;
机器学习模型服务
# services/recommendation/app/ml_models.py
import tensorflow as tf
import numpy as np
from typing import List, Dict
import joblib
class NutritionRecommendationModel:
def __init__(self):
self.model = self._build_model()
self.scaler = joblib.load('/models/scaler.pkl')
self.label_encoders = joblib.load('/models/label_encoders.pkl')
def _build_model(self):
"""构建深度学习推荐模型"""
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu', input_shape=(20,)),
tf.keras.layers.Dropout(0.3),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(32, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy']
)
return model
def preprocess_features(self, user_features: Dict, recipe_features: Dict) -> np.ndarray:
"""预处理特征"""
# 编码分类特征
encoded_features = []
# 用户特征
encoded_features.extend([
user_features['age'] / 100, # 归一化
1 if user_features['gender'] == 'male' else 0,
self.label_encoders['activity_level'].transform([user_features['activity_level']])[0],
self.label_encoders['user_group'].transform([user_features['user_group']])[0]
])
# 食谱特征
encoded_features.extend([
recipe_features['calories'] / 1000,
recipe_features['protein'] / 100,
recipe_features['carbs'] / 100,
recipe_features['fat'] / 100,
recipe_features['cooking_time'] / 60
])
return np.array([encoded_features])
def predict_preference(self, user_features: Dict, recipe_features: Dict) -> float:
"""预测用户对食谱的偏好程度"""
features = self.preprocess_features(user_features, recipe_features)
prediction = self.model.predict(features, verbose=0)
return float(prediction[0][0])
# 高级推荐策略
class AdvancedRecommendationStrategy:
def __init__(self):
self.nutrition_model = NutritionRecommendationModel()
self.knowledge_graph = self._build_knowledge_graph()
def _build_knowledge_graph(self):
"""构建营养学知识图谱"""
return {
'nutrient_deficiencies': {
'fatigue': ['iron', 'b12', 'vitamin_d'],
'weak_bones': ['calcium', 'vitamin_d'],
'poor_vision': ['vitamin_a', 'zinc'],
'immune_weakness': ['vitamin_c', 'zinc', 'vitamin_d']
},
'food_sources': {
'iron': ['spinach', 'red_meat', 'lentils', 'tofu'],
'calcium': ['milk', 'cheese', 'yogurt', 'tofu', 'broccoli'],
'vitamin_c': ['oranges', 'strawberries', 'bell_peppers', 'broccoli'],
'protein': ['chicken', 'fish', 'eggs', 'tofu', 'lentils']
}
}
def get_knowledge_based_recommendations(self, user_profile: Dict) -> List[Dict]:
"""基于知识图谱的推荐"""
recommendations = []
# 分析用户可能的营养需求
inferred_needs = self._infer_nutritional_needs(user_profile)
for nutrient, priority in inferred_needs.items():
if priority > 0.7: # 高优先级需求
food_sources = self.knowledge_graph['food_sources'].get(nutrient, [])
recommendations.extend(
self._find_recipes_with_ingredients(food_sources, user_profile)
)
return recommendations
def _infer_nutritional_needs(self, user_profile: Dict) -> Dict[str, float]:
"""推断用户营养需求"""
needs = {}
user_group = user_profile['user_group']
health_conditions = user_profile.get('health_conditions', [])
# 基于用户群体的基础需求
group_needs = {
'child': {'protein': 0.8, 'calcium': 0.9, 'iron': 0.7},
'teenager': {'protein': 0.9, 'calcium': 0.8, 'iron': 0.8},
'pregnant': {'protein': 0.9, 'iron': 0.9, 'calcium': 0.8, 'folic_acid': 1.0},
'elderly': {'protein': 0.8, 'calcium': 0.9, 'vitamin_d': 0.9},
'athlete': {'protein': 1.0, 'carbs': 0.9, 'electrolytes': 0.8}
}
needs.update(group_needs.get(user_group, {}))
# 基于健康状况调整
condition_adjustments = {
'anemia': {'iron': 1.0, 'vitamin_c': 0.8},
'osteoporosis': {'calcium': 1.0, 'vitamin_d': 0.9},
'diabetes': {'fiber': 0.9, 'complex_carbs': 0.8}
}
for condition in health_conditions:
if condition in condition_adjustments:
needs.update(condition_adjustments[condition])
return needs
部署配置和监控
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommendation-service
labels:
app: recommendation-service
spec:
replicas: 3
selector:
matchLabels:
app: recommendation-service
template:
metadata:
labels:
app: recommendation-service
spec:
containers:
- name: recommendation-service
image: nutriai/recommendation-service:latest
ports:
- containerPort: 3002
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: database-url
- name: REDIS_URL
value: "redis://redis-service:6379"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3002
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3002
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: recommendation-service
spec:
selector:
app: recommendation-service
ports:
- port: 3002
targetPort: 3002
type: ClusterIP
-
微服务架构 - 可扩展、可维护
-
机器学习推荐 - 个性化智能推荐
-
实时数据处理 - Redis缓存和消息队列
-
健康监控 - 完善的健康检查机制
-
安全防护 - 输入验证、速率限制
-
前端界面 - 现代化的用户界面
-
容器化部署 - Docker和Kubernetes支持
更多推荐



所有评论(0)