From 4c22a137cf60026f7fd117fb6a6e2ed91469eb8f Mon Sep 17 00:00:00 2001 From: lzh Date: Fri, 23 Jan 2026 21:16:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=AD=98=E5=82=A8=E5=9C=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=EF=BC=8C=E7=94=9F=E4=BA=A7=E7=8E=AF=E5=A2=83=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=AE=BF=E4=B8=BB=E6=9C=BA=E7=9B=AE=E5=BD=95=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 管理员账号在数据库初始化时创建,不再从环境变量读取 - 默认账号: admin / admin123 - 首次启动时自动创建,请在登录后修改密码 - 移除 ADMIN_USERNAME 和 ADMIN_PASSWORD 环境变量 - 生产环境 MySQL 数据直接存储在宿主机 /opt/vitals/mysql_data - 便于备份和恢复 - 更直观的数据管理 - 更新部署指南,添加 MySQL 数据目录创建和备份说明 - 更新 .env.example 和 README.md 反映新的配置方式 Co-Authored-By: Claude Opus 4.5 --- .env.example | 14 +++--- README.md | 15 +++--- docker-compose.prod.yml | 7 +++ docker-compose.yml | 5 +- docs/deployment-guide.md | 95 ++++++++++++++++++++++++------------- src/vitals/core/database.py | 13 ++++- src/vitals/web/app.py | 40 +--------------- 7 files changed, 98 insertions(+), 91 deletions(-) diff --git a/.env.example b/.env.example index 7c2bf3c..55d20b4 100644 --- a/.env.example +++ b/.env.example @@ -25,12 +25,6 @@ MYSQL_PASSWORD=your_password_here MYSQL_DATABASE=vitals MYSQL_ROOT_PASSWORD=rootpassword -# ============================================ -# 管理员账户(首次启动时自动创建) -# ============================================ -ADMIN_USERNAME=admin -ADMIN_PASSWORD=changeme123 - # ============================================ # 安全配置 # ============================================ @@ -44,3 +38,11 @@ JWT_SECRET=your_random_secret_key_here DASHSCOPE_API_KEY=your_dashscope_api_key_here # DeepSeek # DEEPSEEK_API_KEY=your_deepseek_api_key_here + +# ============================================ +# 默认管理员账户 +# ============================================ +# 首次启动时自动创建在数据库中 +# 用户名: admin +# 密码: admin123 +# 请在首次登录后及时修改密码 diff --git a/README.md b/README.md index d93a03e..6cf2207 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,14 @@ # 1. 复制环境变量模板 cp .env.example .env -# 2. 编辑 .env 文件,设置管理员密码和 JWT 密钥 +# 2. 编辑 .env 文件,设置 JWT 密钥 vim .env # 3. 启动服务 docker-compose up -d -# 4. 访问 http://localhost:8080 +# 4. 访问 http://localhost:8888 +# 默认管理员账号: admin / admin123 ``` ### 本地开发 @@ -71,9 +72,7 @@ export MYSQL_USER=vitals export MYSQL_PASSWORD=your_password export MYSQL_DATABASE=vitals -# 设置应用环境变量 -export ADMIN_USERNAME=admin -export ADMIN_PASSWORD=your_password +# 设置 JWT 密钥 export JWT_SECRET=$(openssl rand -hex 32) # 启动服务 @@ -82,6 +81,8 @@ uvicorn vitals.web.app:app --host 0.0.0.0 --port 8080 访问 `http://localhost:8080` 即可使用 Web 界面。 +> **注意**: 默认管理员账号为 `admin` / `admin123`,请在首次登录后及时修改密码。 + ### 页面导航 - `/` - 首页(今日概览) - `/login` - 登录页面 @@ -286,12 +287,12 @@ pip install -e . | `MYSQL_PASSWORD` | 是 | MySQL 密码 | | `MYSQL_DATABASE` | 是 | MySQL 数据库名(默认 `vitals`) | | `MYSQL_ROOT_PASSWORD` | 是 | MySQL root 密码(Docker 部署时需要) | -| `ADMIN_USERNAME` | 是 | 管理员用户名(首次启动时创建) | -| `ADMIN_PASSWORD` | 是 | 管理员密码 | | `JWT_SECRET` | 是 | JWT 签名密钥(建议用 `openssl rand -hex 32` 生成) | | `DASHSCOPE_API_KEY` | 否 | 阿里云通义千问 API Key,用于 AI 食物识别 | | `DEEPSEEK_API_KEY` | 否 | DeepSeek API Key,用于 AI 食物识别 | +**默认管理员账号**: `admin` / `admin123`(请在首次登录后修改) + ## 技术栈 - **后端**: Python, FastAPI, MySQL 8.0 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f03d2ff..7054226 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -2,6 +2,13 @@ # 使用方式: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d services: + mysql: + # 生产环境不暴露 MySQL 端口,仅容器内部访问 + ports: [] + # 使用宿主机目录存储数据,便于备份 + volumes: + - /opt/vitals/mysql_data:/var/lib/mysql + vitals: env_file: - .env.production diff --git a/docker-compose.yml b/docker-compose.yml index bcc2141..2858d21 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: volumes: - mysql_data:/var/lib/mysql ports: - - "3306:3306" + - "3399:3306" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s @@ -38,9 +38,6 @@ services: - MYSQL_USER=${MYSQL_USER:-vitals} - MYSQL_PASSWORD=${MYSQL_PASSWORD:-vitalspassword} - MYSQL_DATABASE=${MYSQL_DATABASE:-vitals} - # 管理员账户(首次启动时创建) - - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} - - ADMIN_PASSWORD=${ADMIN_PASSWORD:-changeme123} # JWT 密钥(请使用随机字符串) - JWT_SECRET=${JWT_SECRET:-vitals-secret-key-change-in-production} # AI 食物识别(可选) diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md index b86ba24..150fd0d 100644 --- a/docs/deployment-guide.md +++ b/docs/deployment-guide.md @@ -170,11 +170,7 @@ vim .env 编辑 `.env` 文件内容: ```bash -# 管理员账户 -ADMIN_USERNAME=admin -ADMIN_PASSWORD=YourStrongPassword123! - -# JWT 密钥(粘贴上面生成的随机字符串) +# JWT 密钥��粘贴上面生成的随机字符串) JWT_SECRET=粘贴上面生成的64位随机字符串 # AI 食物识别(可选) @@ -182,10 +178,9 @@ DASHSCOPE_API_KEY= DEEPSEEK_API_KEY= ``` -**密码要求:** -- 至少 12 位 -- 包含大小写字母和数字 -- 建议包含特殊字符 +**默认管理员账号**: `admin` / `admin123` + +> 部署后请立即登录并修改默认密码! --- @@ -198,16 +193,21 @@ cd /opt/vitals mkdir -p /opt/vitals/data chmod 755 /opt/vitals/data -# 2. 构建并启动 -docker compose up -d --build +# 2. 创建 MySQL 数据目录(宿主机存储) +mkdir -p /opt/vitals/mysql_data +# MySQL 容器需要写权限,设置为 777 或使用特定用户 +chmod 777 /opt/vitals/mysql_data -# 3. 查看运行状态 +# 3. 构建并启动 +docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build + +# 4. 查看运行状态 docker compose ps -# 4. 查看日志 +# 5. 查看日志 docker compose logs -f -# 5. 测试是否正常运行 +# 6. 测试是否正常运行 curl http://localhost:8080/api/today ``` @@ -355,12 +355,17 @@ BACKUP_DIR="/opt/vitals/backups" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR -cp /opt/vitals/data/vitals.db $BACKUP_DIR/vitals_$DATE.db + +# 方式1: 使用 mysqldump 导出 SQL(推荐) +docker compose exec -T mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD:-rootpassword} vitals > $BACKUP_DIR/vitals_$DATE.sql + +# 方式2: 直接复制 mysql_data 目录(完整备份) +# tar -czf $BACKUP_DIR/mysql_data_$DATE.tar.gz -C /opt/vitals mysql_data # 保留最近 7 天的备份 -find $BACKUP_DIR -name "vitals_*.db" -mtime +7 -delete +find $BACKUP_DIR -name "vitals_*.sql" -mtime +7 -delete -echo "$(date): Backup completed - vitals_$DATE.db" +echo "$(date): Backup completed - vitals_$DATE.sql" ``` ### 10.2 设置定时任务 @@ -376,14 +381,29 @@ crontab -e 添加一行: ``` -0 3 * * * /opt/vitals/backup.sh >> /var/log/vitals-backup.log 2>&1 +0 3 * * * cd /opt/vitals && ./backup.sh >> /var/log/vitals-backup.log 2>&1 ``` ### 10.3 验证备份 ```bash # 手动执行一次 -/opt/vitals/backup.sh +cd /opt/vitals && ./backup.sh + +# 查看备份文件 +ls -la /opt/vitals/backups/ +``` + +### 10.4 恢复备份 + +```bash +# 从 SQL 文件恢复 +docker compose exec -T mysql mysql -u root -p${MYSQL_ROOT_PASSWORD:-rootpassword} vitals < /opt/vitals/backups/vitals_20260123_120000.sql + +# 或从 tar.gz 恢复(完整恢复) +# tar -xzf /opt/vitals/backups/mysql_data_20260123_120000.tar.gz -C /opt/vitals +# docker compose restart +``` # 查看备份文件 ls -la /opt/vitals/backups/ @@ -472,12 +492,13 @@ ss -tlnp | grep 8080 | 检查项 | 命令/操作 | 预期结果 | |--------|-----------|----------| | 服务运行 | `docker compose ps` | 状态为 running | +| MySQL 数据目录 | `ls -la /opt/vitals/mysql_data/` | 有数据文件 | | API 响应 | `curl localhost:8080/api/today` | 返回 JSON | | HTTP 访问 | 浏览器打开 `http://IP` | 显示首页 | | HTTPS 访问 | 浏览器打开 `https://域名` | 显示首页,有锁图标 | | 登录功能 | 访问 `/login` 用 admin 登录 | 登录成功 | | 管理后台 | 访问 `/admin` | 显示管理面板 | -| 自动备份 | `ls /opt/vitals/backups/` | 有备份文件 | +| 备份脚本 | `ls -la /opt/vitals/backup.sh` | 文件存在且可执行 | --- @@ -485,7 +506,7 @@ ss -tlnp | grep 8080 | 项目 | 要求 | |------|------| -| 管理员密码 | 至少 12 位,包含大小写字母、数字、特殊字符 | +| 管理员密码 | 部署后立即修改默认密码 (admin/admin123) | | JWT 密钥 | 使用 `openssl rand -hex 32` 生成的随机字符串 | | HTTPS | 生产环境必须启用 | | 防火墙 | 只开放必要端口 (22, 80, 443) | @@ -543,28 +564,34 @@ docker compose logs ### Q: 忘记管理员密码 +管理员密码存储在 MySQL 数据库中,可以通过以下方式重置: + ```bash -cd /opt/vitals -# 修改 .env 中的 ADMIN_PASSWORD -vim .env -# 重启服务 -docker compose restart +# 进入 MySQL 容器 +docker compose exec mysql mysql -u root -p + +# 选择数据库 +use vitals; + +# 生成新的密码哈希(Python) +# python -c "import bcrypt; print(bcrypt.hashpw(b'newpassword', bcrypt.gensalt()).decode())" + +# 更新管理员密码(将下方哈希值替换为生成的) +UPDATE users SET password_hash = '$2b$12$...' WHERE name = 'admin'; ``` ### Q: 数据库损坏 从备份恢复: ```bash -# 停止服务 -docker compose down +# 从 SQL 备份恢复 +docker compose exec -T mysql mysql -u root -p${MYSQL_ROOT_PASSWORD:-rootpassword} vitals < /opt/vitals/backups/vitals_20260123_120000.sql -# 恢复备份 -cp /opt/vitals/backups/vitals_最新日期.db /opt/vitals/data/vitals.db - -# 启动服务 -docker compose up -d +# 或直接复制数据目录恢复(需要重启) +# tar -xzf /opt/vitals/backups/mysql_data_20260123_120000.tar.gz -C /opt/vitals +# docker compose restart ``` --- -*最后更新: 2026-01-22* +*最后更新: 2026-01-23* diff --git a/src/vitals/core/database.py b/src/vitals/core/database.py index a9ff735..03ca896 100644 --- a/src/vitals/core/database.py +++ b/src/vitals/core/database.py @@ -59,7 +59,7 @@ def get_connection(): def init_db(): - """初始化数据库表""" + """初始化数据库表和默认管理员用户""" with get_connection() as (conn, cursor): # 运动记录表 cursor.execute(""" @@ -182,6 +182,17 @@ def init_db(): ) """) + # 创建默认管理员用户(仅在用户表为空时) + cursor.execute("SELECT COUNT(*) as count FROM users") + if cursor.fetchone()["count"] == 0: + from .auth import hash_password + admin_password_hash = hash_password("admin123") + cursor.execute(""" + INSERT INTO users (name, created_at, is_active, password_hash, is_admin, is_disabled) + VALUES (%s, %s, 1, %s, 1, 0) + """, ("admin", datetime.now().isoformat(), admin_password_hash)) + print("[Vitals] 默认管理员用户已创建 - 用户名: admin, 密码: admin123") + # ===== 运动记录 ===== diff --git a/src/vitals/web/app.py b/src/vitals/web/app.py index eac07c2..9fd759f 100644 --- a/src/vitals/web/app.py +++ b/src/vitals/web/app.py @@ -15,51 +15,13 @@ from uuid import uuid4 from ..core import database as db from ..core.models import Exercise, Meal, Sleep, UserConfig, Weight, User, Reading, Invite -from ..core.auth import hash_password, verify_password, create_token, decode_token, generate_invite_code, get_token_expire_seconds +from ..core.auth import verify_password, create_token, decode_token, generate_invite_code, get_token_expire_seconds # 初始化数据库 db.init_db() db.migrate_auth_fields() # 迁移认证字段 -def ensure_admin_user(): - """确保管理员账户存在,密码与环境变量同步""" - admin_username = os.environ.get("ADMIN_USERNAME") - admin_password = os.environ.get("ADMIN_PASSWORD") - - if not admin_username or not admin_password: - return # 未配置环境变量,跳过 - - # 检查是否已存在该用户名 - existing = db.get_user_by_name(admin_username) - new_password_hash = hash_password(admin_password) - - if existing: - # 用户已存在,同步密码(环境变量优先) - if not existing.is_admin: - existing.is_admin = True - if existing.password_hash != new_password_hash: - existing.password_hash = new_password_hash - db.update_user(existing) - print(f"[Vitals] 管理员密码已从环境变量同步") - return - - # 创建管理员账户 - admin_user = User( - name=admin_username, - password_hash=new_password_hash, - is_admin=True, - is_active=True, - is_disabled=False, - ) - db.add_user(admin_user) - print(f"[Vitals] 管理员账户 '{admin_username}' 已创建") - - -# 启动时创建管理员账户 -ensure_admin_user() - - app = FastAPI( title="Vitals 健康管理", description="本地优先的综合健康管理应用",