New Database
This commit is contained in:
@@ -11,12 +11,14 @@
|
||||
- **控制端** (`/control`) — 完整配置面板
|
||||
- **Root 管理员** (`id=0`) — 首个自动创建的账户,拥有全部功能与用户管理权限
|
||||
|
||||
数据库使用 **PostgreSQL**(Docker 内置),生产以 **Gunicorn** 多 worker 运行。
|
||||
|
||||
---
|
||||
|
||||
## 功能
|
||||
|
||||
- **多用户**:Root 管理员可注册/编辑/删除用户;所有用户数据完全隔离,普通用户无管理权限
|
||||
- **两种注册方式**:管理员代注册(正式账户,可代配 TOTP/Passkey);用户自助申请(管理员确认后生成临时账户)
|
||||
- **账户创建**:仅管理员可创建 —— 管理员代注册(正式账户,可代配 TOTP/Passkey)或创建临时账户(用户自助填写登录方式)
|
||||
- **临时账户**:服务器持有临时 TOTP 密钥,管理员仅可见实时验证码;用户登录后被强制引导配置自己的登录方式,配置完成即正式生效
|
||||
- **日历**:支持订阅 iCalendar (.ics) 链接及本地文件上传,自动同步日程
|
||||
- **天气**:基于和风天气 (QWeather) API,含逐小时/分钟级预报
|
||||
@@ -24,6 +26,7 @@
|
||||
- **番茄钟**:定时提醒,自动继续,断网重连自动恢复
|
||||
- **倒计时**:可自定义时长,到时提醒
|
||||
- **自定义展示页**:支持多组自定义 HTML 模板,一键切换
|
||||
- **数据库管理(仅 Root)**:在 `/control → 设置 → 数据库管理` 导入旧版 SQLite 数据库文件或本系统导出的备份(自动识别格式)、导出现有数据库备份
|
||||
|
||||
---
|
||||
|
||||
@@ -41,7 +44,7 @@
|
||||
**登录流程**:
|
||||
|
||||
```
|
||||
首次启动(无数据库)→ 自动创建 Root 管理员 (id=0),控制台打印临时密码
|
||||
首次启动(空数据库)→ 自动创建 Root 管理员 (id=0),控制台打印临时密码
|
||||
↓
|
||||
访问 /control → 输入 6 位数字(自动识别 TOTP / 临时账户 TOTP / Root 临时密码)
|
||||
↓
|
||||
@@ -58,16 +61,16 @@ Root 临时密码 → 强制跳转 /mfa-setup 配置 MFA
|
||||
|
||||
**用户管理(仅 Root)**:
|
||||
- 管理员代注册:填写信息 → 正式账户,可随后代配置 TOTP(生成密钥+二维码)或 Passkey(注册仪式在当前浏览器完成)
|
||||
- 用户自助申请:用户在 `/register-request` 填写信息 → 管理员确认 → 生成临时账户(服务器持有临时 TOTP 密钥,管理员仅看到实时验证码)
|
||||
- 注册/编辑备注/删除用户均需**二次验证管理员身份**(Passkey 优先,无 Passkey 时用 TOTP)
|
||||
- 创建临时账户:生成临时 TOTP(密钥仅存服务器),管理员仅看到实时验证码
|
||||
- 注册/编辑备注/删除用户均需**二次验证管理员身份**(TOTP 优先,无 TOTP 时用 Passkey)
|
||||
- 删除用户会级联删除其全部数据并使其会话立即失效
|
||||
- 用户 ID 对用户透明(仅在备注默认为 ID 时可见一次),普通用户只能看到自己的数据
|
||||
|
||||
**安全防护**:
|
||||
- 登录频率限制(渐进式封禁:5次→5分钟、10次→15分钟、15次→24小时)
|
||||
- CSRF 防护(Origin/Referer 校验)
|
||||
- 敏感操作二次验证(用户管理、MFA 配置需重新确认身份)
|
||||
- **双 Token 认证**:短期 access token(`Authorization: Bearer`,15 分钟)+ 长期 refresh token(httpOnly cookie,30 天滑动续期、每次刷新轮换)
|
||||
- 敏感操作二次验证(用户管理、MFA 配置、数据库管理需重新确认身份)
|
||||
- **双 Token 认证**:短期 access token(`Authorization: Bearer`,15 分钟)+ 长期 refresh token(httpOnly cookie,30 天滑动续期、每次刷新轮换、并发重放安全)
|
||||
- Access token 快照会话版本:删除用户 / 踢设备 / 踢所有用户后**立即**使全部 access token 失效
|
||||
- 显示端 SSE 通过一次性短时 ticket 鉴权(EventSource 无法带自定义 header)
|
||||
- 内容安全策略(CSP)限制脚本/样式来源
|
||||
@@ -78,21 +81,30 @@ Root 临时密码 → 强制跳转 /mfa-setup 配置 MFA
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Python 3.11+
|
||||
- PostgreSQL 14+(Docker 部署时无需单独安装,镜像内置)
|
||||
|
||||
### 开发环境
|
||||
|
||||
```bash
|
||||
# 1. 安装依赖
|
||||
# 1. 准备 PostgreSQL(本机或 Docker)
|
||||
# docker run -d --name pg -e POSTGRES_USER=iot -e POSTGRES_PASSWORD=iot \
|
||||
# -e POSTGRES_DB=iot_dboard -p 5432:5432 postgres:16-alpine
|
||||
|
||||
# 2. 安装依赖
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 2. 直接运行(调试模式)
|
||||
python start.py
|
||||
# 3. 运行(开发模式)
|
||||
DATABASE_URL="postgresql://iot:iot@localhost:5432/iot_dboard" python run.py
|
||||
|
||||
# 3. 访问
|
||||
# 显示端: http://localhost:5000
|
||||
# 控制端: http://localhost:5000/control
|
||||
# 4. 访问
|
||||
# 显示端: http://localhost:5001
|
||||
# 控制端: http://localhost:5001/control
|
||||
```
|
||||
|
||||
### 生产部署 (推荐 Nginx + Certbot)
|
||||
### 生产部署 (推荐 Nginx + Gunicorn)
|
||||
|
||||
```
|
||||
客户端 (HTTPS)
|
||||
@@ -102,10 +114,10 @@ Nginx (SSL termination + Certbot)
|
||||
│ proxy_pass http://127.0.0.1:5000
|
||||
│
|
||||
▼ 5000 (HTTP)
|
||||
Flask (127.0.0.1:5000)
|
||||
Gunicorn (2 workers × 2 threads, start:app)
|
||||
│
|
||||
▼
|
||||
data.db (SQLite)
|
||||
PostgreSQL (Docker 内置, 端口 5432)
|
||||
```
|
||||
|
||||
#### 1. Nginx 配置
|
||||
@@ -125,30 +137,22 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 300s; # SSE 长连接
|
||||
}
|
||||
|
||||
# 可选:限制上传大小
|
||||
client_max_body_size 5M;
|
||||
}
|
||||
|
||||
# HTTP → HTTPS 强制跳转
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
client_max_body_size 200M; # 数据库导入需要较大体积
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 启动 Flask
|
||||
#### 2. 启动 Gunicorn
|
||||
|
||||
```bash
|
||||
# 生产模式启动
|
||||
FLASK_HTTPS=yes python start.py
|
||||
|
||||
# 可选:固定 SECRET_KEY(保持跨重启会话有效)
|
||||
export FLASK_SECRET_KEY="$(openssl rand -hex 32)"
|
||||
DATABASE_URL="postgresql://iot:iot@127.0.0.1:5432/iot_dboard" \
|
||||
FLASK_HTTPS=yes gunicorn --workers 2 --threads 2 --bind 127.0.0.1:5000 start:app
|
||||
```
|
||||
|
||||
后台调度器(闹钟/提醒/天气/番茄钟)通过 **PostgreSQL advisory lock** 保证只在其中一个 worker 内运行;若持有者异常退出,其他 worker 的 watchdog 会自动接管。
|
||||
|
||||
#### 3. Certbot 申请证书
|
||||
|
||||
```bash
|
||||
@@ -157,14 +161,12 @@ sudo certbot --nginx -d your-domain.com
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Docker 部署
|
||||
|
||||
### 快速启动
|
||||
|
||||
```bash
|
||||
# 构建并启动(后台运行,首次会自动从 Gitee clone 代码)
|
||||
# 构建并启动(自动创建 PostgreSQL 与 Web 服务)
|
||||
docker compose up -d
|
||||
|
||||
# 查看启动日志(获取临时密码)
|
||||
@@ -195,36 +197,36 @@ docker compose up -d
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- FLASK_BIND=0.0.0.0
|
||||
- DATABASE_URL=postgresql://iot:${POSTGRES_PASSWORD:-iot}@db:5432/iot_dboard
|
||||
- FLASK_SECRET_KEY=your-secret-here # 可选:固定密钥
|
||||
- FLASK_HTTPS=yes # 如果前面有 HTTPS 反代
|
||||
- GUNICORN_WORKERS=2 # 默认 2(对应 1 物理核心 = 2 逻辑核心)
|
||||
- GUNICORN_THREADS=2
|
||||
```
|
||||
|
||||
### 资源限制
|
||||
|
||||
`docker-compose.yml` 中已按 **1 物理核心(2 逻辑核心)+ 2G 内存** 配置:
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "2"
|
||||
memory: 2G
|
||||
```
|
||||
|
||||
### 持久化数据
|
||||
|
||||
- **数据库**:`./data/` 目录挂载到 `/app/data/`,数据库文件 `data.db` 自动在此创建
|
||||
- **项目代码**:命名卷 `app_code` 挂载到 `/app`,首次自动初始化为镜像内克隆的代码,之后 `git pull` 的修改永久保留数据库文件 `data.db` 会自动在该目录下创建,重启/升级后数据保留。
|
||||
|
||||
```bash
|
||||
# 数据文件位置
|
||||
ls -la data/
|
||||
# data.db ← 数据库
|
||||
```
|
||||
- **PostgreSQL 数据**:命名卷 `pgdata`(`docker compose down` 不丢失)
|
||||
- **旧数据库导入**:将旧 SQLite `.db` 文件放入 `./data/` 目录后,在 `/control → 设置 → 数据库管理` 上传导入(也支持直接选择文件上传)
|
||||
- **项目代码**:命名卷 `app_code` 挂载到 `/app`,首次自动初始化为镜像内代码,之后 `git pull` 的修改永久保留
|
||||
|
||||
### 搭配 Nginx 反代(生产)
|
||||
|
||||
使用宿主机上的 Nginx(或额外 Nginx 容器)做 SSL 终结。配置与前文 Nginx 部分相同,只需将 `proxy_pass` 指向 Docker 宿主机 IP:5000:
|
||||
使用宿主机上的 Nginx(或额外 Nginx 容器)做 SSL 终结。配置与前文 Nginx 部分相同,只需将 `proxy_pass` 指向 Docker 宿主机 IP:5000。
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_pass http://宿主机IP:5000;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ 注意:容器内 Flask 绑定 `0.0.0.0:5000`,宿主机可直接访问 `localhost:5000`。如需限制仅 Nginx 访问,在 `docker-compose.yml` 中将 `ports` 改为 `127.0.0.1:5000:5000`。
|
||||
> ⚠️ 注意:容器内 Gunicorn 绑定 `0.0.0.0:5000`,宿主机可直接访问 `localhost:5000`。如需限制仅 Nginx 访问,在 `docker-compose.yml` 中将 `ports` 改为 `127.0.0.1:5000:5000`。
|
||||
|
||||
---
|
||||
|
||||
@@ -232,27 +234,59 @@ location / {
|
||||
|
||||
| 变量 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `DATABASE_URL` | `postgresql://iot:iot@db:5432/iot_dboard` | PostgreSQL 连接串(Docker 内指向 db 服务) |
|
||||
| `FLASK_HTTPS` | 空 | 设为 `yes`/`true` 启用 refresh token cookie 的 Secure 属性(Nginx 反代时启用) |
|
||||
| `FLASK_SECRET_KEY` | 自动随机生成 | 保留兼容;认证已由 access/refresh 双 token 承载,不再依赖 Flask session |
|
||||
| `FLASK_BIND` | `127.0.0.1` | 监听地址,Docker 部署需设为 `0.0.0.0` |
|
||||
| `FLASK_DB_PATH` | `./data.db` | 数据库文件路径,Docker 挂载数据卷时需同步修改 |
|
||||
| `FLASK_DEBUG` | 空 | 设为 `true` 开启 Flask 调试模式(**生产环境禁用**) |
|
||||
| `FLASK_DISABLE_SCHEDULER` | 空 | 设为 `true` 禁用后台调度器(调试用) |
|
||||
| `GUNICORN_WORKERS` | `2` | Gunicorn worker 数(对应逻辑核心数) |
|
||||
| `GUNICORN_THREADS` | `2` | 每个 worker 的线程数 |
|
||||
|
||||
---
|
||||
|
||||
## 数据库管理(仅 Root)
|
||||
|
||||
> 位置:`/control → 设置 → 数据库管理`(仅 Root 用户可见)
|
||||
|
||||
- **导出现有数据库**:下载自包含的 JSON 备份(含 schema 版本与全部数据,不含会话令牌;**含登录凭据,请妥善保管**)
|
||||
- **导入旧版 SQLite 数据库**:上传旧版单用户 `.db` 文件,自动迁移(旧数据归属 Root、缺失字段用 DEFAULT 填充、旧版弃用表自动丢弃、旧版 PIN 不会迁移)
|
||||
- **导入备份文件**:上传本系统导出的 JSON 备份(与 SQLite 文件自动识别)
|
||||
|
||||
导入为**单事务原子操作**:任何一行失败都会整体回滚,当前数据不受影响;导入成功会清空会话(所有设备需重新登录)。
|
||||
|
||||
---
|
||||
|
||||
## 数据库迁移(版本化,升级无需重写数据)
|
||||
|
||||
数据库内置 `meta` 表记录 **schema 版本**(程序内 `DB_VERSION`,当前为 **2**)。每次启动时比对:
|
||||
|
||||
| 情况 | 行为 |
|
||||
|------|------|
|
||||
| 数据库版本 == 程序版本 | 直接使用,跳过升级 |
|
||||
| 无版本字段或 数据库版本 < 程序版本 | 创建缺失的表/列,缺失字段一律用 SCHEMA DEFAULT 填充,并更新版本号 |
|
||||
| 数据库版本 > 程序版本 | **报错并终止启动**(数据库比程序新,需先升级程序) |
|
||||
|
||||
从旧版 SQLite 数据库迁移到 PostgreSQL **不依赖自动迁移**:由 Root 在 `/control → 设置 → 数据库管理` 手动导入旧 `.db` 文件完成。
|
||||
|
||||
运行测试:`python3 tests/test_multi_user.py`(需本地 PostgreSQL,可用环境变量 `TEST_PG_HOST/TEST_PG_USER/TEST_PG_PASS/TEST_PG_DB` 覆盖)。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
├── run.py # 入口(端口 5001,仅开发)
|
||||
├── start.py # 入口(端口 5000,推荐生产使用)
|
||||
├── start.py # 生产入口(Gunicorn WSGI: start:app,含调度器单例)
|
||||
├── run.py # 开发入口(Flask dev server,端口 5001)
|
||||
├── Dockerfile # 镜像构建(Gunicorn)
|
||||
├── docker-compose.yml # PostgreSQL + Web 服务编排
|
||||
├── requirements.txt
|
||||
├── backend/
|
||||
│ ├── app.py # Flask 应用工厂 + 安全配置
|
||||
│ ├── auth.py # 双 Token 认证(TOTP/Passkey/恢复码/临时密码)
|
||||
│ ├── webauthn_mgr.py # WebAuthn (Passkey) 注册与认证
|
||||
│ ├── jwt_util.py # Ed25519 JWT 签名(和风天气)
|
||||
│ ├── models.py # SQLite 模型、schema 版本迁移、token 管理
|
||||
│ ├── pg_compat.py # sqlite3 兼容的 PostgreSQL 访问层(方言翻译)
|
||||
│ ├── models.py # 数据模型、schema 版本迁移、token 管理
|
||||
│ ├── db_admin.py # 数据库导入/导出(SQLite/JSON、原子恢复)
|
||||
│ ├── routes.py # 全部 API 路由 + CSRF 防护 + SSE ticket
|
||||
│ ├── scheduler.py # 后台调度器(advisory lock 单例 + watchdog)
|
||||
│ └── *_mgr.py # 各功能模块管理器
|
||||
└── frontend/
|
||||
├── templates/ # HTML 模板
|
||||
@@ -260,29 +294,18 @@ location / {
|
||||
├── css/ # 样式
|
||||
├── js/ # JavaScript(纯原生,无框架)
|
||||
│ ├── token.js # 双 Token 客户端(access 存储/刷新/401 重试/SSE ticket)
|
||||
│ ├── control.js # 控制端逻辑
|
||||
│ └── display.js # 显示端逻辑(账户时区渲染)
|
||||
│ ├── control.js # 控制端逻辑(含数据库管理)
|
||||
│ └── display.js # 显示端逻辑(账户时区渲染、本地到期兜底)
|
||||
└── ringtones/ # 自定义铃声
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 数据库迁移(版本化,升级无需重写数据)
|
||||
|
||||
数据库内置 `meta` 表记录 **schema 版本**(程序内 `DB_VERSION`,当前为 1)。每次启动时比对:
|
||||
|
||||
| 情况 | 行为 |
|
||||
|------|------|
|
||||
| 数据库版本 == 程序版本 | 直接使用,跳过重建 |
|
||||
| 无版本字段(旧版数据库)或 数据库版本 < 程序版本 | 重建数据库并迁移(旧版单用户数据自动归属 Root (id=0),新增字段一律用 DEFAULT 填充,`camera_config` 等已删除的表自动丢弃) |
|
||||
| 数据库版本 > 程序版本 | **报错并终止启动**(数据库比程序新,需先升级程序) |
|
||||
|
||||
旧版 4 位 PIN 不会被迁移。运行测试:`python3 tests/test_multi_user.py`。
|
||||
|
||||
## 注意
|
||||
|
||||
- **Passkey (WebAuthn) 需要 HTTPS**(localhost 除外)
|
||||
- 旧版 4 位 PIN 已弃用,数据库迁移时旧 PIN 不会被迁移
|
||||
- 旧版 4 位 PIN 已弃用,SQLite 导入时旧 PIN 不会被迁移
|
||||
- 多 Gunicorn worker 下,显示端闹钟/番茄钟/倒计时提醒除 SSE 即时推送外,还有**本地到期检测兜底**,保证到点必定提醒
|
||||
|
||||
### License
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user