From 7ab87e8f15ff3b4ead59d0badac765ebb513eb48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=9D=B0=20Jie=20Zhao=20=EF=BC=88=E9=9B=84?= =?UTF-8?q?=E7=8B=AE=E6=B1=BD=E8=BD=A6=E7=A7=91=E6=8A=80=EF=BC=89?= <00061074@chery.local> Date: Mon, 3 Nov 2025 12:29:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B3=A8=E5=86=8C=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 + Dockerfile.low-mem | 63 +++++++++++++ README_DOCKER.md | 68 +++++++++++++- README_GIT_PUSH.md | 182 +++++++++++++++++++++++++++++++++++++ core/base.py | 173 +++++++++++++++++++++++++---------- docker-compose.low-mem.yml | 41 +++++++++ git_push.bat | 86 ++++++++++++++++++ git_push.py | 138 ++++++++++++++++++++++++++++ git_push.sh | 84 +++++++++++++++++ web_app.py | 75 +++++++++------ 10 files changed, 829 insertions(+), 83 deletions(-) create mode 100644 Dockerfile.low-mem create mode 100644 README_GIT_PUSH.md create mode 100644 docker-compose.low-mem.yml create mode 100644 git_push.bat create mode 100644 git_push.py create mode 100644 git_push.sh diff --git a/Dockerfile b/Dockerfile index 64eb5cf..dd77b6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,4 +44,6 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python -c "import requests; requests.get('http://localhost:5000/health')" || exit 1 # 使用gunicorn启动应用(生产环境) +# 注意:默认配置使用2个worker,适合1GB+内存的机器 +# 如果机器内存较小(512MB-1GB),建议修改为 "--workers", "1" CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "web_app:app"] diff --git a/Dockerfile.low-mem b/Dockerfile.low-mem new file mode 100644 index 0000000..e5dd9ec --- /dev/null +++ b/Dockerfile.low-mem @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# 小内存优化版 Dockerfile(适用于 512MB-1GB 内存的 Linux 机器) +FROM python:3-slim + +# 设置工作目录 +WORKDIR /app + +# 设置环境变量(优化内存使用) +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + FLASK_APP=web_app.py \ + FLASK_ENV=production \ + PYTHONHASHSEED=0 \ + MALLOC_ARENA_MAX=2 + +# 安装系统依赖(最小化安装) +RUN apt-get update && apt-get install -y --no-install-recommends \ + tesseract-ocr \ + tesseract-ocr-chi-sim \ + tesseract-ocr-eng \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +# 复制requirements文件 +COPY requirements.txt . + +# 创建最小化的 requirements.txt(移除GUI相关依赖) +RUN echo "scikit-learn>=1.3.0" > requirements-minimal.txt && \ + echo "pandas>=2.0.0" >> requirements-minimal.txt && \ + echo "numpy>=1.24.0" >> requirements-minimal.txt && \ + echo "joblib>=1.3.0" >> requirements-minimal.txt && \ + echo "requests>=2.31.0" >> requirements-minimal.txt && \ + echo "python-dotenv>=1.0.0" >> requirements-minimal.txt && \ + echo "python-dateutil>=2.8.0" >> requirements-minimal.txt && \ + echo "Pillow>=10.0.0" >> requirements-minimal.txt && \ + echo "pytesseract>=0.3.10" >> requirements-minimal.txt && \ + echo "opencv-python-headless>=4.8.0" >> requirements-minimal.txt && \ + echo "Flask>=3.0.0" >> requirements-minimal.txt && \ + echo "Werkzeug>=3.0.0" >> requirements-minimal.txt && \ + echo "gunicorn" >> requirements-minimal.txt + +# 安装Python依赖(使用最小化依赖,opencv使用headless版本) +RUN pip install --no-cache-dir -r requirements-minimal.txt + +# 复制应用代码 +COPY . . + +# 创建必要的目录 +RUN mkdir -p templates static/css static/js logs data models + +# 设置权限 +RUN chmod +x start_web.py + +# 暴露端口 +EXPOSE 5000 + +# 健康检查(不使用 requests,直接用 Python) +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health').read()" || exit 1 + +# 使用gunicorn启动应用(小内存配置:单worker,减少内存占用) +CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "1", "--threads", "2", "--timeout", "120", "--worker-class", "sync", "--max-requests", "1000", "--max-requests-jitter", "100", "--preload", "--access-logfile", "-", "--error-logfile", "-", "web_app:app"] + diff --git a/README_DOCKER.md b/README_DOCKER.md index 0a97952..4df1d63 100644 --- a/README_DOCKER.md +++ b/README_DOCKER.md @@ -186,12 +186,72 @@ server { } ``` +## 💾 小内存机器配置(512MB-1GB) + +如果你的 Linux 机器内存较小,可以使用专门优化的小内存版本: + +```bash +# 使用小内存优化的 Dockerfile 和配置 +docker-compose -f docker-compose.low-mem.yml up -d + +# 查看日志 +docker-compose -f docker-compose.low-mem.yml logs -f + +# 停止容器 +docker-compose -f docker-compose.low-mem.yml down +``` + +### 小内存优化措施 + +1. **单 Worker 配置**:Gunicorn 使用 1 个 worker + 2 个线程,减少内存占用 +2. **最小化依赖**:移除 GUI 相关依赖(CustomTkinter、Kivy等) +3. **OpenCV Headless**:使用 `opencv-python-headless`,不包含 GUI 组件 +4. **内存限制**:容器内存限制为 512MB(最大),保留 256MB +5. **环境变量优化**:设置 `MALLOC_ARENA_MAX=2` 和 `PYTHONHASHSEED=0` 减少内存碎片 + +### 内存需求对比 + +| 配置 | 内存需求 | 适用场景 | +|------|---------|---------| +| 标准版(Dockerfile) | 1GB+ | 服务器、开发环境 | +| 小内存版(Dockerfile.low-mem) | 512MB-1GB | 树莓派、小型 VPS、低配服务器 | + +### 手动调整内存限制 + +如果使用标准版,可以通过修改 `docker-compose.yml` 来限制内存: + +```yaml +services: + web: + deploy: + resources: + limits: + memory: 512M # 限制最大内存 + reservations: + memory: 256M # 保留内存 +``` + ## 📝 注意事项 -1. **OCR功能**:Docker镜像已包含Tesseract OCR支持 -2. **内存限制**:建议至少分配1GB内存给容器 -3. **文件权限**:确保挂载的目录有适当的读写权限 -4. **时区设置**:如需修改时区,在Dockerfile中添加: +1. **OCR功能**: + - Docker镜像已包含 Tesseract OCR 支持(轻量级) + - **未包含** PaddleOCR 和 EasyOCR(内存占用大,需要手动安装) + - 默认只使用 Tesseract OCR,内存占用很小 + +2. **内存限制**: + - 标准版:建议至少分配 **1GB 内存**给容器 + - 小内存版:可以在 **512MB 内存**的机器上运行 + +3. **Worker 数量调整**: + - 标准版默认 2 个 worker(适合 1GB+ 内存) + - 小内存机器可以在 `Dockerfile` 中修改为 1 个 worker: + ```dockerfile + CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "1", ...] + ``` + +4. **文件权限**:确保挂载的目录有适当的读写权限 + +5. **时区设置**:如需修改时区,在Dockerfile中添加: ```dockerfile ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/README_GIT_PUSH.md b/README_GIT_PUSH.md new file mode 100644 index 0000000..b8fd907 --- /dev/null +++ b/README_GIT_PUSH.md @@ -0,0 +1,182 @@ +# Git 自动上传脚本使用指南 + +本目录提供了三个版本的 Git 自动上传脚本,适用于不同操作系统。 + +## 📋 脚本说明 + +### 1. `git_push.sh` - Linux/Mac Bash 脚本 +适用于 Linux 和 MacOS 系统。 + +**使用方法:** +```bash +# 给脚本添加执行权限(首次使用) +chmod +x git_push.sh + +# 基本使用(交互式输入提交信息) +./git_push.sh + +# 直接指定提交信息 +./git_push.sh "feat: 添加新功能" +``` + +### 2. `git_push.bat` - Windows 批处理脚本 +适用于 Windows 系统。 + +**使用方法:** +```cmd +# 基本使用(交互式输入提交信息) +git_push.bat + +# 直接指定提交信息 +git_push.bat "feat: 添加新功能" +``` + +### 3. `git_push.py` - Python 跨平台脚本(推荐) +适用于所有操作系统(Windows、Linux、Mac),需要 Python 3.6+。 + +**使用方法:** +```bash +# 基本使用(交互式输入提交信息) +python git_push.py + +# 直接指定提交信息 +python git_push.py "feat: 添加新功能" + +# 多个单词的提交信息 +python git_push.py "feat: 添加新功能和修复bug" +``` + +## 🚀 功能特性 + +所有脚本都包含以下功能: + +1. **自动检测 Git 仓库**:检查当前目录是否为 Git 仓库 +2. **显示更改状态**:显示将要提交的文件 +3. **交互式确认**:在提交前确认操作 +4. **智能提交信息**: + - 可以直接通过命令行参数指定 + - 也可以交互式输入 + - 默认提供带时间戳的提交信息 +5. **自动添加文件**:自动添加所有更改的文件 +6. **错误处理**:友好的错误提示和处理 +7. **颜色输出**:支持彩色输出(Python 版本) + +## 📝 使用示例 + +### 示例 1:基本使用 +```bash +# Linux/Mac +./git_push.sh + +# Windows +git_push.bat + +# Python(跨平台) +python git_push.py +``` +脚本会: +1. 显示当前更改的文件 +2. 询问是否继续 +3. 请求输入提交信息(或使用默认) +4. 自动添加、提交并推送 + +### 示例 2:指定提交信息 +```bash +# Linux/Mac +./git_push.sh "fix: 修复用户注册bug" + +# Windows +git_push.bat "fix: 修复用户注册bug" + +# Python +python git_push.py "fix: 修复用户注册bug" +``` + +### 示例 3:提交多个文件的更改 +```bash +# 脚本会自动检测所有更改的文件 +./git_push.sh "feat: 添加Docker支持和优化内存配置" +``` + +## ⚙️ 配置说明 + +### 默认分支 +脚本会自动检测当前分支并使用该分支进行推送。 + +### 远程仓库 +确保已配置远程仓库: +```bash +# 查看远程仓库 +git remote -v + +# 如果未配置,添加远程仓库 +git remote add origin +``` + +### 身份验证 +如果推送时遇到身份验证问题: + +1. **HTTPS 方式**: + - 需要输入用户名和密码(或访问令牌) + - 可以配置凭证助手避免每次输入: + ```bash + git config --global credential.helper store + ``` + +2. **SSH 方式**: + - 配置 SSH 密钥更安全 + - 将远程 URL 改为 SSH 格式: + ```bash + git remote set-url origin git@github.com:username/repo.git + ``` + +## 🔧 故障排除 + +### 问题 1:权限不足(Linux/Mac) +```bash +# 解决方案:添加执行权限 +chmod +x git_push.sh +``` + +### 问题 2:Python 脚本无法运行 +```bash +# 检查 Python 版本 +python --version # 需要 Python 3.6+ + +# 如果系统中有多个 Python 版本 +python3 git_push.py +``` + +### 问题 3:推送失败(认证问题) +- 检查远程仓库地址是否正确 +- 确认已配置 SSH 密钥或访问令牌 +- 可以手动执行 `git push origin ` 测试 + +### 问题 4:没有需要提交的更改 +- 脚本会检测是否有未提交的更改 +- 如果没有更改,可以选择拉取远程更新 + +## 💡 建议 + +1. **推荐使用 Python 版本**(`git_push.py`): + - 跨平台兼容性最好 + - 错误处理更完善 + - 支持彩色输出 + +2. **提交信息规范**: + - 使用规范的提交信息格式,例如: + - `feat: 添加新功能` + - `fix: 修复bug` + - `docs: 更新文档` + - `chore: 更新配置` + - `refactor: 代码重构` + +3. **定期提交**: + - 建议频繁提交小的更改 + - 避免一次性提交大量更改 + +## 📚 相关文档 + +- [Git 官方文档](https://git-scm.com/doc) +- [提交信息规范](https://www.conventionalcommits.org/) + diff --git a/core/base.py b/core/base.py index bc8aa66..5b523a2 100644 --- a/core/base.py +++ b/core/base.py @@ -135,6 +135,50 @@ class DataManager: ) ''') + # 餐食记录表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS meal_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT, + date TEXT, + meal_type TEXT, + foods TEXT, + quantities TEXT, + calories REAL, + satisfaction_score INTEGER, + food_items TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (user_id) + ) + ''') + + # 反馈记录表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS feedback_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT, + date TEXT, + recommended_foods TEXT, + user_choice TEXT, + feedback_type TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (user_id) + ) + ''') + + # 问卷记录表 + cursor.execute(''' + CREATE TABLE IF NOT EXISTS questionnaire_records ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT, + questionnaire_type TEXT, + answers TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users (user_id), + UNIQUE(user_id, questionnaire_type) + ) + ''') + # 分析结果表 cursor.execute(''' CREATE TABLE IF NOT EXISTS analysis_results ( @@ -195,66 +239,95 @@ class DataManager: return None # 解析用户基本信息 - data_dict = json.loads(result[0]) + try: + data_dict = json.loads(result[0]) + except json.JSONDecodeError as e: + logger.error(f"解析用户数据JSON失败: user_id={user_id}, error={e}") + conn.close() + return None - # 获取餐食记录 - cursor.execute(''' - SELECT date, meal_type, foods, quantities, calories, satisfaction_score, food_items - FROM meal_records - WHERE user_id = ? - ORDER BY date DESC - ''', (user_id,)) - - meal_rows = cursor.fetchall() + # 获取餐食记录(如果表存在) meals = [] - for row in meal_rows: - meal = { - 'date': row[0], - 'meal_type': row[1], - 'foods': json.loads(row[2]) if row[2] else [], - 'quantities': json.loads(row[3]) if row[3] else [], - 'calories': row[4], - 'satisfaction_score': row[5], - 'food_items': json.loads(row[6]) if row[6] else [] - } - meals.append(meal) + try: + cursor.execute(''' + SELECT date, meal_type, foods, quantities, calories, satisfaction_score, food_items + FROM meal_records + WHERE user_id = ? + ORDER BY date DESC + ''', (user_id,)) + + meal_rows = cursor.fetchall() + for row in meal_rows: + try: + meal = { + 'date': row[0], + 'meal_type': row[1], + 'foods': json.loads(row[2]) if row[2] else [], + 'quantities': json.loads(row[3]) if row[3] else [], + 'calories': row[4], + 'satisfaction_score': row[5], + 'food_items': json.loads(row[6]) if row[6] else [] + } + meals.append(meal) + except (json.JSONDecodeError, IndexError) as e: + logger.warning(f"解析餐食记录失败: {e}") + continue + except sqlite3.OperationalError as e: + # 表不存在,跳过 + logger.warning(f"meal_records表不存在,跳过: {e}") - # 获取反馈记录 - cursor.execute(''' - SELECT date, recommended_foods, user_choice, feedback_type - FROM feedback_records - WHERE user_id = ? - ORDER BY date DESC - ''', (user_id,)) - - feedback_rows = cursor.fetchall() + # 获取反馈记录(如果表存在) feedback = [] - for row in feedback_rows: - fb = { - 'date': row[0], - 'recommended_foods': json.loads(row[1]) if row[1] else [], - 'user_choice': row[2], - 'feedback_type': row[3] - } - feedback.append(fb) + try: + cursor.execute(''' + SELECT date, recommended_foods, user_choice, feedback_type + FROM feedback_records + WHERE user_id = ? + ORDER BY date DESC + ''', (user_id,)) + + feedback_rows = cursor.fetchall() + for row in feedback_rows: + try: + fb = { + 'date': row[0], + 'recommended_foods': json.loads(row[1]) if row[1] else [], + 'user_choice': row[2], + 'feedback_type': row[3] + } + feedback.append(fb) + except (json.JSONDecodeError, IndexError) as e: + logger.warning(f"解析反馈记录失败: {e}") + continue + except sqlite3.OperationalError as e: + # 表不存在,跳过 + logger.warning(f"feedback_records表不存在,跳过: {e}") - # 获取问卷数据 - cursor.execute(''' - SELECT questionnaire_type, answers - FROM questionnaire_records - WHERE user_id = ? - ''', (user_id,)) - - questionnaire_rows = cursor.fetchall() + # 获取问卷数据(如果表存在) preferences = {} - for row in questionnaire_rows: - preferences[row[0]] = json.loads(row[1]) if row[1] else {} + try: + cursor.execute(''' + SELECT questionnaire_type, answers + FROM questionnaire_records + WHERE user_id = ? + ''', (user_id,)) + + questionnaire_rows = cursor.fetchall() + for row in questionnaire_rows: + try: + preferences[row[0]] = json.loads(row[1]) if row[1] else {} + except (json.JSONDecodeError, IndexError) as e: + logger.warning(f"解析问卷记录失败: {e}") + continue + except sqlite3.OperationalError as e: + # 表不存在,跳过 + logger.warning(f"questionnaire_records表不存在,跳过: {e}") conn.close() # 构建完整的用户数据 user_data = UserData( - user_id=data_dict['user_id'], + user_id=data_dict.get('user_id', user_id), profile=data_dict.get('profile', {}), meals=meals, feedback=feedback, @@ -266,7 +339,7 @@ class DataManager: return user_data except Exception as e: - logger.error(f"获取用户数据失败: {e}") + logger.error(f"获取用户数据失败: user_id={user_id}, error={e}", exc_info=True) return None def save_analysis_result(self, result: AnalysisResult) -> bool: diff --git a/docker-compose.low-mem.yml b/docker-compose.low-mem.yml new file mode 100644 index 0000000..3989017 --- /dev/null +++ b/docker-compose.low-mem.yml @@ -0,0 +1,41 @@ +version: '3.8' + +services: + web: + build: + context: . + dockerfile: Dockerfile.low-mem + container_name: diet_recommendation_app_low_mem + ports: + - "5000:5000" + environment: + - FLASK_ENV=production + - PYTHONUNBUFFERED=1 + - PYTHONHASHSEED=0 + - MALLOC_ARENA_MAX=2 + volumes: + # 持久化数据目录 + - ./data:/app/data + - ./logs:/app/logs + - ./models:/app/models + restart: unless-stopped + # 内存限制(小内存机器配置) + deploy: + resources: + limits: + memory: 512M + reservations: + memory: 256M + networks: + - app-network + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/health').read()"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + +networks: + app-network: + driver: bridge + diff --git a/git_push.bat b/git_push.bat new file mode 100644 index 0000000..e51690c --- /dev/null +++ b/git_push.bat @@ -0,0 +1,86 @@ +@echo off +REM -*- coding: utf-8 -*- +REM Git 自动上传脚本(Windows) + +chcp 65001 >nul +setlocal enabledelayedexpansion + +echo === Git 自动上传脚本 === + +REM 检查是否在 git 仓库中 +if not exist .git ( + echo 错误: 当前目录不是 git 仓库 + exit /b 1 +) + +REM 获取当前分支 +for /f "tokens=2" %%i in ('git branch --show-current 2^>nul') do set CURRENT_BRANCH=%%i +if "!CURRENT_BRANCH!"=="" ( + for /f "delims=" %%i in ('git rev-parse --abbrev-ref HEAD 2^>nul') do set CURRENT_BRANCH=%%i +) +echo 当前分支: !CURRENT_BRANCH! + +REM 检查是否有未提交的更改 +git status --porcelain >nul 2>&1 +if errorlevel 1 ( + echo 没有需要提交的更改 + set /p CONTINUE="是否继续检查远程更新? (y/n) " + if /i "!CONTINUE!"=="y" ( + echo 拉取远程更新... + git pull origin !CURRENT_BRANCH! 2>nul || echo 拉取失败,可能没有远程分支 + ) + exit /b 0 +) + +REM 显示当前状态 +echo 当前更改状态: +git status --short + +REM 询问是否继续 +set /p CONTINUE="是否继续提交并推送? (y/n) " +if /i not "!CONTINUE!"=="y" ( + echo 操作已取消 + exit /b 0 +) + +REM 获取提交信息 +if "%1"=="" ( + set /p COMMIT_MSG="请输入提交信息(或直接按回车使用默认信息): " + if "!COMMIT_MSG!"=="" ( + for /f "tokens=1-3 delims=/- " %%a in ('date /t') do set DATE_STR=%%a-%%b-%%c + for /f "tokens=1-2 delims=: " %%a in ('time /t') do set TIME_STR=%%a:%%b + set COMMIT_MSG=chore: update code !DATE_STR! !TIME_STR! + ) +) else ( + set COMMIT_MSG=%1 +) + +REM 添加所有更改 +echo 添加文件到暂存区... +git add -A +if errorlevel 1 ( + echo 添加文件失败 + exit /b 1 +) + +REM 提交更改 +echo 提交更改... +git commit -m "!COMMIT_MSG!" +if errorlevel 1 ( + echo 提交失败 + exit /b 1 +) +echo 提交成功! + +REM 推送到远程 +echo 推送到远程仓库... +git push origin !CURRENT_BRANCH! +if errorlevel 1 ( + echo 推送失败,可能需要设置远程仓库或认证 + echo 提示: 可以手动执行 'git push origin !CURRENT_BRANCH!' + exit /b 1 +) + +echo ✓ 推送成功! +echo === 完成 === + diff --git a/git_push.py b/git_push.py new file mode 100644 index 0000000..237899c --- /dev/null +++ b/git_push.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Git 自动上传脚本(跨平台) +支持 Windows、Linux、Mac +""" + +import os +import sys +import subprocess +import platform +from datetime import datetime + +# 颜色输出(Windows 10+ 支持 ANSI) +def print_color(text, color="white"): + colors = { + "red": "\033[0;31m", + "green": "\033[0;32m", + "yellow": "\033[1;33m", + "blue": "\033[0;34m", + "reset": "\033[0m" + } + if platform.system() == "Windows": + # Windows 10+ 支持 ANSI,需要启用 + try: + import ctypes + kernel32 = ctypes.windll.kernel32 + kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) + except: + pass + + color_code = colors.get(color.lower(), colors["reset"]) + reset = colors["reset"] + print(f"{color_code}{text}{reset}") + +def run_command(cmd, check=True): + """执行命令并返回结果""" + try: + result = subprocess.run( + cmd, + shell=True, + check=check, + capture_output=True, + text=True, + encoding='utf-8' + ) + return result.returncode == 0, result.stdout.strip(), result.stderr.strip() + except subprocess.CalledProcessError as e: + return False, e.stdout.strip() if hasattr(e, 'stdout') else "", e.stderr.strip() if hasattr(e, 'stderr') else "" + +def main(): + print_color("=== Git 自动上传脚本 ===", "green") + + # 检查是否在 git 仓库中 + if not os.path.exists(".git"): + print_color("错误: 当前目录不是 git 仓库", "red") + sys.exit(1) + + # 获取当前分支 + success, branch, _ = run_command("git branch --show-current", check=False) + if not success: + success, branch, _ = run_command("git rev-parse --abbrev-ref HEAD", check=False) + + if not branch: + print_color("错误: 无法获取当前分支", "red") + sys.exit(1) + + print_color(f"当前分支: {branch}", "yellow") + + # 检查是否有未提交的更改 + success, status, _ = run_command("git status --porcelain", check=False) + if not status: + print_color("没有需要提交的更改", "yellow") + response = input("是否继续检查远程更新? (y/n) ").strip().lower() + if response == 'y': + print_color("拉取远程更新...", "green") + run_command(f"git pull origin {branch}", check=False) + sys.exit(0) + + # 显示当前状态 + print_color("当前更改状态:", "yellow") + success, status_output, _ = run_command("git status --short", check=False) + print(status_output) + + # 询问是否继续 + response = input("是否继续提交并推送? (y/n) ").strip().lower() + if response != 'y': + print_color("操作已取消", "yellow") + sys.exit(0) + + # 获取提交信息 + if len(sys.argv) > 1: + commit_msg = " ".join(sys.argv[1:]) + else: + default_msg = f"chore: update code {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + user_msg = input(f"请输入提交信息(或直接按回车使用默认信息): ").strip() + commit_msg = user_msg if user_msg else default_msg + + # 添加所有更改 + print_color("添加文件到暂存区...", "green") + success, _, error = run_command("git add -A") + if not success: + print_color(f"添加文件失败: {error}", "red") + sys.exit(1) + + # 提交更改 + print_color("提交更改...", "green") + success, _, error = run_command(f'git commit -m "{commit_msg}"') + if not success: + if "nothing to commit" in error.lower(): + print_color("没有需要提交的更改", "yellow") + sys.exit(0) + print_color(f"提交失败: {error}", "red") + sys.exit(1) + print_color("提交成功!", "green") + + # 推送到远程 + print_color("推送到远程仓库...", "green") + success, _, error = run_command(f"git push origin {branch}") + if not success: + print_color(f"推送失败: {error}", "red") + print_color(f"提示: 可以手动执行 'git push origin {branch}'", "yellow") + print_color("可能需要设置远程仓库地址或进行身份验证", "yellow") + sys.exit(1) + + print_color("✓ 推送成功!", "green") + print_color("=== 完成 ===", "green") + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print_color("\n操作已取消", "yellow") + sys.exit(0) + except Exception as e: + print_color(f"发生错误: {e}", "red") + sys.exit(1) + diff --git a/git_push.sh b/git_push.sh new file mode 100644 index 0000000..4a50404 --- /dev/null +++ b/git_push.sh @@ -0,0 +1,84 @@ +#!/bin/bash +# -*- coding: utf-8 -*- +# Git 自动上传脚本(Linux/Mac) + +set -e # 遇到错误立即退出 + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${GREEN}=== Git 自动上传脚本 ===${NC}" + +# 检查是否在 git 仓库中 +if [ ! -d .git ]; then + echo -e "${RED}错误: 当前目录不是 git 仓库${NC}" + exit 1 +fi + +# 获取当前分支 +CURRENT_BRANCH=$(git branch --show-current) +echo -e "${YELLOW}当前分支: ${CURRENT_BRANCH}${NC}" + +# 检查是否有未提交的更改 +if [ -z "$(git status --porcelain)" ]; then + echo -e "${YELLOW}没有需要提交的更改${NC}" + read -p "是否继续检查远程更新? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo -e "${GREEN}拉取远程更新...${NC}" + git pull origin "$CURRENT_BRANCH" || echo -e "${YELLOW}拉取失败,可能没有远程分支${NC}" + fi + exit 0 +fi + +# 显示当前状态 +echo -e "${YELLOW}当前更改状态:${NC}" +git status --short + +# 询问是否继续 +read -p "是否继续提交并推送? (y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}操作已取消${NC}" + exit 0 +fi + +# 获取提交信息 +if [ -z "$1" ]; then + echo -e "${YELLOW}请输入提交信息(或直接按回车使用默认信息):${NC}" + read -r COMMIT_MSG + if [ -z "$COMMIT_MSG" ]; then + COMMIT_MSG="chore: update code $(date '+%Y-%m-%d %H:%M:%S')" + fi +else + COMMIT_MSG="$1" +fi + +# 添加所有更改 +echo -e "${GREEN}添加文件到暂存区...${NC}" +git add -A + +# 提交更改 +echo -e "${GREEN}提交更改...${NC}" +if git commit -m "$COMMIT_MSG"; then + echo -e "${GREEN}提交成功!${NC}" +else + echo -e "${RED}提交失败${NC}" + exit 1 +fi + +# 推送到远程 +echo -e "${GREEN}推送到远程仓库...${NC}" +if git push origin "$CURRENT_BRANCH"; then + echo -e "${GREEN}✓ 推送成功!${NC}" +else + echo -e "${RED}推送失败,可能需要设置远程仓库或认证${NC}" + echo -e "${YELLOW}提示: 可以手动执行 'git push origin $CURRENT_BRANCH'${NC}" + exit 1 +fi + +echo -e "${GREEN}=== 完成 ===${NC}" + diff --git a/web_app.py b/web_app.py index 15b8ab6..ba66b5a 100644 --- a/web_app.py +++ b/web_app.py @@ -411,40 +411,57 @@ def user_register(): # 如果用户不存在,创建新用户 if not user_data: - initial_data = { - 'profile': { - 'name': name, - 'age': 25, - 'gender': '未知', - 'height': 170, - 'weight': 60, - 'activity_level': 'moderate' - }, - 'preferences': {} + from core.base import UserData + # 直接创建UserData对象并保存 + initial_profile = { + 'name': name, + 'age': 25, + 'gender': '未知', + 'height': 170, + 'weight': 60, + 'activity_level': 'moderate' } - if core.create_user(user_id, initial_data): - user_data = core.get_user_data(user_id) - else: + user_data = UserData( + user_id=user_id, + profile=initial_profile, + preferences={} + ) + + # 保存用户数据 + if not core.data_manager.save_user_data(user_data): + logger.error(f"保存用户数据失败: user_id={user_id}") return jsonify({ 'success': False, - 'message': '创建用户失败' + 'message': '创建用户失败:数据保存失败' + }), 500 + + # 验证保存是否成功(重新获取一次) + saved_user_data = core.get_user_data(user_id) + if not saved_user_data: + logger.error(f"用户数据保存后无法获取: user_id={user_id}") + return jsonify({ + 'success': False, + 'message': '创建用户失败:数据验证失败' + }), 500 + user_data = saved_user_data + else: + # 用户已存在,更新姓名 + user_data.profile['name'] = name + if not core.data_manager.save_user_data(user_data): + logger.error(f"更新用户数据失败: user_id={user_id}") + return jsonify({ + 'success': False, + 'message': '更新用户信息失败' }), 500 - # 更新用户基本信息 - if user_data: - user_data.profile['name'] = name - core.data_manager.save_user_data(user_data) - session['user_id'] = user_id - return jsonify({ - 'success': True, - 'user_id': user_id, - 'name': name - }) - else: - return jsonify({ - 'success': False, - 'message': '用户数据获取失败' - }), 500 + # 设置会话 + session['user_id'] = user_id + + return jsonify({ + 'success': True, + 'user_id': user_id, + 'name': user_data.profile.get('name', name) + }) except Exception as e: logger.error(f"用户注册失败: {e}", exc_info=True)