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:
14
.env.example
14
.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
|
||||
# 请在首次登录后及时修改密码
|
||||
|
||||
15
README.md
15
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 食物识别(可选)
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
# ===== 运动记录 =====
|
||||
|
||||
|
||||
@@ -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="本地优先的综合健康管理应用",
|
||||
|
||||
Reference in New Issue
Block a user