Files
DDUp/docs/plans/2026-01-20-public-deployment-design.md
2026-01-22 12:57:26 +08:00

363 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 公网部署设计文档
**日期:** 2026-01-20
**状态:** 已确认,待实现
---
## 概述
为 Vitals 健康管理应用添加公网访问能力,包括用户认证、邀请码注册、管理后台,并通过 Docker 容器部署到云服务器。
---
## 需求总结
- **访问方式**:公网访问,通过 IP 地址(无域名)
- **认证方式**用户名密码登录JWT Token 认证
- **注册方式**:邀请码注册,邀请码由管理员生成
- **权限模型**:普通用户 + 管理员
- **部署方式**Docker 容器
---
## 角色权限
| 功能 | 普通用户 | 管理员 |
|------|---------|--------|
| 查看/编辑自己的数据 | ✅ | ✅ |
| 查看/编辑所有用户数据 | ❌ | ✅ |
| 管理用户(禁用/删除) | ❌ | ✅ |
| 生成邀请码 | ❌ | ✅ |
---
## 数据模型
### 用户表改动 (users)
新增字段:
| 字段 | 类型 | 说明 |
|------|------|------|
| password_hash | TEXT | 密码哈希bcrypt |
| email | TEXT | 邮箱(可选) |
| is_admin | BOOLEAN | 是否管理员,默认 false |
| is_disabled | BOOLEAN | 是否禁用,默认 false |
### 新增邀请码表 (invites)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | INTEGER | 主键,自增 |
| code | TEXT | 邀请码8位随机字符串 |
| created_by | INTEGER | 创建者 user_id |
| used_by | INTEGER | 使用者 user_idnull 表示未使用) |
| created_at | DATETIME | 创建时间 |
| expires_at | DATETIME | 过期时间(可选) |
---
## 认证方案
### JWT Token
- 登录成功后返回 token存储在浏览器 localStorage
- 每次请求在 Header 中携带 `Authorization: Bearer <token>`
- Token 有效期 7 天
- Token 内容:`{ user_id, username, is_admin, exp }`
### 密码存储
- 使用 bcrypt 哈希
- 不存储明文密码
---
## 页面设计
### 登录页 `/login`
```
┌─────────────────────────────────────┐
│ Vitals 登录 │
├─────────────────────────────────────┤
│ 用户名 │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ 密码 │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ [ 登录 ] │
│ │
│ 没有账号?立即注册 │
└─────────────────────────────────────┘
```
### 注册页 `/register`
```
┌─────────────────────────────────────┐
│ Vitals 注册 │
├─────────────────────────────────────┤
│ 邀请码 * │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ 用户名 * │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ 密码 * │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ 确认密码 * │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ [ 注册 ] │
│ │
│ 已有账号?立即登录 │
└─────────────────────────────────────┘
```
### 管理后台 `/admin`(仅管理员可见)
```
┌─────────────────────────────────────┐
│ 导航栏(首页/运动/.../管理) │
├─────────────────────────────────────┤
│ 管理后台 │
├─────────────────────────────────────┤
│ 用户管理 │
│ ┌─────────────────────────────┐ │
│ │ 用户名 │ 状态 │ 创建时间 │ 操作│ │
│ │ rocky │ 正常 │ 01-20 │禁用│ │
│ │ test │ 禁用 │ 01-19 │启用│ │
│ └─────────────────────────────┘ │
├─────────────────────────────────────┤
│ 邀请码管理 [ 生成邀请码 ] │
│ ┌─────────────────────────────┐ │
│ │ 邀请码 │ 状态 │ 使用者 │ │
│ │ ABC12345 │ 已用 │ rocky │ │
│ │ XYZ98765 │ 未用 │ - │ │
│ └─────────────────────────────┘ │
├─────────────────────────────────────┤
│ 数据浏览 │
│ 用户筛选:[ 全部用户 ▼ ] │
│ 数据类型:[ 全部类型 ▼ ] │
│ ┌─────────────────────────────┐ │
│ │ 数据列表... │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
---
## API 设计
### 新增认证端点
| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/auth/login` | POST | 登录,返回 JWT |
| `/api/auth/register` | POST | 注册(需邀请码) |
| `/api/auth/me` | GET | 获取当前用户信息 |
### 新增管理端点
| 端点 | 方法 | 说明 |
|------|------|------|
| `/api/admin/users` | GET | 用户列表 |
| `/api/admin/users/{id}` | DELETE | 删除用户 |
| `/api/admin/users/{id}/disable` | POST | 禁用用户 |
| `/api/admin/users/{id}/enable` | POST | 启用用户 |
| `/api/admin/invites` | GET | 邀请码列表 |
| `/api/admin/invites` | POST | 生成邀请码 |
| `/api/admin/data` | GET | 查看所有数据(支持筛选) |
### 现有 API 改动
- 所有 `/api/*` 端点添加 JWT 认证中间件
- 未登录返回 `401 Unauthorized`
- 数据自动按当前用户 `user_id` 过滤
- 排除列表:`/api/auth/login``/api/auth/register`
---
## 访问流程
```
用户访问任意页面
检查 localStorage 是否有 Token
无 Token → 跳转 /login
有 Token → 验证 Token 有效性
Token 无效/过期 → 跳转 /login
Token 有效 → 正常显示页面
```
---
## Docker 部署
### 文件结构
```
vitals/
├── Dockerfile
├── docker-compose.yml
├── .env.example
└── data/
└── vitals.db
```
### Dockerfile
```dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY pyproject.toml .
COPY src/ src/
RUN pip install --no-cache-dir -e .
EXPOSE 8080
CMD ["python", "-m", "vitals.web.app", "--host", "0.0.0.0"]
```
### docker-compose.yml
```yaml
version: '3.8'
services:
vitals:
build: .
ports:
- "8080:8080"
volumes:
- ./data:/app/data
environment:
- ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
- JWT_SECRET=${JWT_SECRET}
restart: unless-stopped
```
### .env.example
```env
# 管理员账户(首次启动时创建)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change_me_please
# JWT 密钥(请使用随机字符串)
JWT_SECRET=your_random_secret_key_here
# AI 食物识别(可选)
DASHSCOPE_API_KEY=your_key_here
```
### 部署步骤
```bash
# 1. 上传代码到服务器
scp -r vitals/ user@server:/path/to/
# 2. SSH 到服务器
ssh user@server
# 3. 进入项目目录
cd /path/to/vitals
# 4. 复制并编辑环境变量
cp .env.example .env
vim .env
# 5. 启动容器
docker-compose up -d
# 6. 查看日志
docker-compose logs -f
```
访问:`http://服务器IP:8080`
---
## 实现步骤
1. **数据库改动**
- users 表添加 `password_hash``is_admin``is_disabled` 字段
- 新增 `invites`
2. **认证模块**
- 实现 JWT 生成/验证PyJWT
- 实现密码哈希bcrypt
- 添加认证中间件
3. **认证 API**
- 实现 `/api/auth/login`
- 实现 `/api/auth/register`
- 实现 `/api/auth/me`
4. **管理 API**
- 实现 `/api/admin/*` 端点
- 添加管理员权限检查
5. **前端页面**
- 登录页 `/login`
- 注册页 `/register`
- 管理后台 `/admin`
- 修改现有页面添加 Token 检查
6. **Docker 配置**
- 创建 Dockerfile
- 创建 docker-compose.yml
- 创建 .env.example
7. **首次启动逻辑**
- 检查环境变量创建管理员账户
8. **测试**
- 本地 Docker 测试
- 部署到服务器验证
---
## 技术选型
| 组件 | 选择 |
|------|------|
| 认证 | JWT (PyJWT) |
| 密码哈希 | bcrypt |
| 容器 | Docker + docker-compose |
| Web 服务器 | Uvicorn内置 |
---
## 安全考虑
- 密码使用 bcrypt 哈希存储
- JWT 设置合理过期时间7 天)
- 邀请码一次性使用
- 管理员操作需二次确认
- 建议后续添加 HTTPS通过 nginx 反向代理 + Let's Encrypt