refactor: 管理员账号存储在数据库,生产环境使用宿主机目录存储数据

- 管理员账号在数据库初始化时创建,不再从环境变量读取
  - 默认账号: admin / admin123
  - 首次启动时自动创建,请在登录后修改密码
- 移除 ADMIN_USERNAME 和 ADMIN_PASSWORD 环境变量
- 生产环境 MySQL 数据直接存储在宿主机 /opt/vitals/mysql_data
  - 便于备份和恢复
  - 更直观的数据管理
- 更新部署指南,添加 MySQL 数据目录创建和备份说明
- 更新 .env.example 和 README.md 反映新的配置方式

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-01-23 21:16:32 +08:00
parent 18e0668941
commit 4c22a137cf
7 changed files with 98 additions and 91 deletions

View File

@@ -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
# 请在首次登录后及时修改密码

View File

@@ -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

View File

@@ -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

View File

@@ -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 食物识别(可选)

View File

@@ -170,11 +170,7 @@ vim .env
编辑 `.env` 文件内容:
```bash
# 管理员账户
ADMIN_USERNAME=admin
ADMIN_PASSWORD=YourStrongPassword123!
# JWT 密钥(粘贴上面生成的随机字符串)
# JWT 密钥<E5AF86><E992A5>粘贴上面生成的随机字符串
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*

View File

@@ -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")
# ===== 运动记录 =====

View File

@@ -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="本地优先的综合健康管理应用",