273 lines
8.9 KiB
Markdown
273 lines
8.9 KiB
Markdown
# Dashboard - 多人副屏系统
|
||
|
||
[](https://opensource.org/licenses/MIT)
|
||
|
||
---
|
||
|
||
## 项目简介
|
||
|
||
多用户副屏系统,支持日历、天气、闹钟、番茄钟、倒计时等功能。每个用户拥有完全隔离的数据与独立的显示端/控制端:
|
||
- **显示端** (`/`) — 大屏/触屏设备展示信息
|
||
- **控制端** (`/control`) — 完整配置面板
|
||
- **Root 管理员** (`id=0`) — 首个自动创建的账户,拥有全部功能与用户管理权限
|
||
|
||
---
|
||
|
||
## 功能
|
||
|
||
- **多用户**:Root 管理员可注册/编辑/删除用户;所有用户数据完全隔离,普通用户无管理权限
|
||
- **两种注册方式**:管理员代注册(正式账户,可代配 TOTP/Passkey);用户自助申请(管理员确认后生成临时账户)
|
||
- **临时账户**:服务器持有临时 TOTP 密钥,管理员仅可见实时验证码;用户登录后被强制引导配置自己的登录方式,配置完成即正式生效
|
||
- **日历**:支持订阅 iCalendar (.ics) 链接及本地文件上传,自动同步日程
|
||
- **天气**:基于和风天气 (QWeather) API,含逐小时/分钟级预报
|
||
- **闹钟**:多组闹钟设定,自动弹出提醒,支持自定义铃声
|
||
- **番茄钟**:定时提醒,自动继续,断网重连自动恢复
|
||
- **倒计时**:可自定义时长,到时提醒
|
||
- **自定义展示页**:支持多组自定义 HTML 模板,一键切换
|
||
|
||
---
|
||
|
||
## 🔐 安全特性
|
||
|
||
支持**双因素认证 (MFA)**,适合公网部署:
|
||
|
||
| 方式 | 说明 |
|
||
|------|------|
|
||
| **Passkey (WebAuthn)** | 指纹、面容、安全密钥等设备内置生物/硬件认证 |
|
||
| **TOTP** | Google Authenticator / Microsoft Authenticator 等标准应用 |
|
||
| **一次性恢复码** | 8 个恢复码,每个仅可使用一次 |
|
||
| **临时密码** | 首次启动为 Root 自动生成 6 位随机密码,显示在控制台 |
|
||
|
||
**登录流程**:
|
||
|
||
```
|
||
首次启动(无数据库)→ 自动创建 Root 管理员 (id=0),控制台打印临时密码
|
||
↓
|
||
访问 /control → 输入 6 位数字(自动识别 TOTP / 临时账户 TOTP / Root 临时密码)
|
||
↓
|
||
Root 临时密码 → 强制跳转 /mfa-setup 配置 MFA
|
||
正式用户 TOTP/Passkey → 直接进入控制面板
|
||
临时账户 TOTP → 强制跳转 /mfa-setup 配置登录方式,配置完成后临时 TOTP 立即失效
|
||
```
|
||
|
||
**用户管理(仅 Root)**:
|
||
- 管理员代注册:填写信息 → 正式账户,可随后代配置 TOTP(生成密钥+二维码)或 Passkey(注册仪式在当前浏览器完成)
|
||
- 用户自助申请:用户在 `/register-request` 填写信息 → 管理员确认 → 生成临时账户(服务器持有临时 TOTP 密钥,管理员仅看到实时验证码)
|
||
- 注册/编辑备注/删除用户均需**二次验证管理员身份**(Passkey 优先,无 Passkey 时用 TOTP)
|
||
- 删除用户会级联删除其全部数据并使其会话立即失效
|
||
- 用户 ID 对用户透明(仅在备注默认为 ID 时可见一次),普通用户只能看到自己的数据
|
||
|
||
**安全防护**:
|
||
- 登录频率限制(渐进式封禁:5次→5分钟、10次→15分钟、15次→24小时)
|
||
- CSRF 防护(Origin/Referer 校验)
|
||
- 敏感操作二次验证(用户管理、MFA 配置需重新确认身份)
|
||
- 会话 Cookie HttpOnly + SameSite=Lax + Secure(HTTPS 下)
|
||
- 会话版本按用户存储:删除用户 / "踢掉所有显示端" 立即失效
|
||
- 内容安全策略(CSP)限制脚本/样式来源
|
||
- 铃声上传仅允许音频格式+MIME 校验
|
||
- 第三方日历数据 HTML 转义防止 XSS
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
### 开发环境
|
||
|
||
```bash
|
||
# 1. 安装依赖
|
||
pip install -r requirements.txt
|
||
|
||
# 2. 直接运行(调试模式)
|
||
python start.py
|
||
|
||
# 3. 访问
|
||
# 显示端: http://localhost:5000
|
||
# 控制端: http://localhost:5000/control
|
||
```
|
||
|
||
### 生产部署 (推荐 Nginx + Certbot)
|
||
|
||
```
|
||
客户端 (HTTPS)
|
||
│
|
||
▼ 443
|
||
Nginx (SSL termination + Certbot)
|
||
│ proxy_pass http://127.0.0.1:5000
|
||
│
|
||
▼ 5000 (HTTP)
|
||
Flask (127.0.0.1:5000)
|
||
│
|
||
▼
|
||
data.db (SQLite)
|
||
```
|
||
|
||
#### 1. Nginx 配置
|
||
|
||
```nginx
|
||
server {
|
||
listen 443 ssl;
|
||
server_name your-domain.com;
|
||
|
||
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||
|
||
location / {
|
||
proxy_pass http://127.0.0.1:5000;
|
||
proxy_set_header X-Forwarded-For $remote_addr;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
}
|
||
|
||
# 可选:限制上传大小
|
||
client_max_body_size 5M;
|
||
}
|
||
|
||
# HTTP → HTTPS 强制跳转
|
||
server {
|
||
listen 80;
|
||
server_name your-domain.com;
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
```
|
||
|
||
#### 2. 启动 Flask
|
||
|
||
```bash
|
||
# 生产模式启动
|
||
FLASK_HTTPS=yes python start.py
|
||
|
||
# 可选:固定 SECRET_KEY(保持跨重启会话有效)
|
||
export FLASK_SECRET_KEY="$(openssl rand -hex 32)"
|
||
```
|
||
|
||
#### 3. Certbot 申请证书
|
||
|
||
```bash
|
||
sudo certbot --nginx -d your-domain.com
|
||
```
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## Docker 部署
|
||
|
||
### 快速启动
|
||
|
||
```bash
|
||
# 构建并启动(后台运行,首次会自动从 Gitee clone 代码)
|
||
docker compose up -d
|
||
|
||
# 查看启动日志(获取临时密码)
|
||
docker compose logs app
|
||
|
||
# 停止
|
||
docker compose down
|
||
```
|
||
|
||
访问 `http://你的IP:5000`,日志中会打印临时密码。
|
||
|
||
### 更新代码
|
||
|
||
```bash
|
||
# 方法一:进入容器内 git pull(推荐)
|
||
docker compose exec app git pull
|
||
docker compose restart app
|
||
|
||
# 方法二:重建镜像(较慢)
|
||
docker compose down
|
||
docker compose build --no-cache
|
||
docker compose up -d
|
||
```
|
||
|
||
### 环境变量配置
|
||
|
||
编辑 `docker-compose.yml` 的 `environment` 段:
|
||
|
||
```yaml
|
||
environment:
|
||
- FLASK_BIND=0.0.0.0
|
||
- FLASK_SECRET_KEY=your-secret-here # 可选:固定密钥
|
||
- FLASK_HTTPS=yes # 如果前面有 HTTPS 反代
|
||
```
|
||
|
||
### 持久化数据
|
||
|
||
- **数据库**:`./data/` 目录挂载到 `/app/data/`,数据库文件 `data.db` 自动在此创建
|
||
- **项目代码**:命名卷 `app_code` 挂载到 `/app`,首次自动初始化为镜像内克隆的代码,之后 `git pull` 的修改永久保留数据库文件 `data.db` 会自动在该目录下创建,重启/升级后数据保留。
|
||
|
||
```bash
|
||
# 数据文件位置
|
||
ls -la data/
|
||
# data.db ← 数据库
|
||
```
|
||
|
||
### 搭配 Nginx 反代(生产)
|
||
|
||
使用宿主机上的 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`。
|
||
|
||
---
|
||
|
||
## 环境变量
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `FLASK_HTTPS` | 空 | 设为 `yes`/`true` 启用 Secure Cookie(Nginx 反代时启用) |
|
||
| `FLASK_SECRET_KEY` | 自动随机生成 | 固定密钥使会话跨重启保持有效 |
|
||
| `FLASK_BIND` | `127.0.0.1` | 监听地址,Docker 部署需设为 `0.0.0.0` |
|
||
| `FLASK_DB_PATH` | `./data.db` | 数据库文件路径,Docker 挂载数据卷时需同步修改 |
|
||
| `FLASK_DEBUG` | 空 | 设为 `true` 开启 Flask 调试模式(**生产环境禁用**) |
|
||
|
||
---
|
||
|
||
## 文件结构
|
||
|
||
```
|
||
├── run.py # 入口(端口 5001,仅开发)
|
||
├── start.py # 入口(端口 5000,推荐生产使用)
|
||
├── requirements.txt
|
||
├── backend/
|
||
│ ├── app.py # Flask 应用工厂 + 安全配置
|
||
│ ├── auth.py # MFA 认证(TOTP/Passkey/恢复码/临时密码)
|
||
│ ├── webauthn_mgr.py # WebAuthn (Passkey) 注册与认证
|
||
│ ├── jwt_util.py # Ed25519 JWT 签名(和风天气)
|
||
│ ├── models.py # SQLite 数据库模型与迁移
|
||
│ ├── routes.py # 全部 API 路由 + CSRF 防护
|
||
│ └── *_mgr.py # 各功能模块管理器
|
||
└── frontend/
|
||
├── templates/ # HTML 模板
|
||
└── static/
|
||
├── css/ # 样式
|
||
├── js/ # JavaScript(纯原生,无框架)
|
||
└── ringtones/ # 自定义铃声
|
||
```
|
||
|
||
---
|
||
|
||
### 数据库迁移(升级无需重写数据)
|
||
|
||
启动时会自动检测并迁移数据库:旧版单用户数据库的数据自动归属 Root (id=0);
|
||
新增的字段一律带 DEFAULT 并在迁移时自动填充;`camera_config` 等已删除的表自动丢弃。
|
||
旧版 4 位 PIN 不会被迁移。运行测试:`python3 tests/test_multi_user.py`。
|
||
|
||
## 注意
|
||
|
||
- **Passkey (WebAuthn) 需要 HTTPS**(localhost 除外)
|
||
- 旧版 4 位 PIN 已弃用,数据库迁移时旧 PIN 不会被迁移
|
||
|
||
### License
|
||
MIT
|