Files
EasyScreen/README.md
T
Tatta 4e2775c8c3 init: EasyScreen 电子菜单系统
- server: FastAPI 后端(多屏管理、素材上传、节目编排、客户端注册/配置/心跳/崩溃上报、管理台托管)
- android: Java 客户端(minSdk 23,全屏图片/视频轮播、远程配置、开机自启、崩溃上报)
- web: React + Vite + antd 管理台(屏幕/素材/节目管理)
- 屏幕设备 ID 关联机制、gunicorn 生产部署脚本
2026-08-12 20:57:55 +08:00

214 lines
10 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.
# EasyScreen 电子菜单系统
基于 **Android 6.0 电子显示屏** 的电子菜单(广告屏)解决方案。支持多块屏幕独立配置、远程下发图片/视频节目、开机自启全屏播放。
系统分三端:
| 端 | 技术栈 | 说明 |
|---|---|---|
| `android/` | 原生 JavaminSdk 23,兼容 Android 6.0+) | 运行在电子显示屏上,全屏轮播图片/视频 |
| `server/` | Python FastAPI + SQLAlchemy | 配置管理、素材存储、客户端接口、管理台托管 |
| `web/` | React + Vite + antd | 管理台:多屏管理、素材上传、节目编排 |
```
┌─────────────┐ 开机/重开自动拉取配置 ┌──────────────┐
│ Android 显示屏 │ ─── /api/client/config ───▶│ │
│ (ExoPlayer │ ─── /media/xxx 拉素材 ─────▶│ FastAPI │
│ + Glide) │ ◀── /api/client/register ─── │ 后端服务 │
└─────────────┘ 心跳 /heartbeat │ + MySQL/SQLite
崩溃上报 /crash │ │
┌─────────────┐ 管理台(浏览器) │ │
│ Web 管理台 │ ─── /api/... ──────────────▶│ │
│ (React) │ └──────────────┘
└─────────────┘
```
---
## 核心概念:屏幕 ID 关联
**每块显示屏由唯一「设备 ID」标识**(如 `SF-01`),这是后台屏幕与 App 关联的钥匙:
1. 后台「屏幕管理」新增屏幕时**推荐填写设备 ID**(如 `SF-01`),屏幕列表会显示所有屏幕的设备 ID(可一键复制)
2. App 首次启动进入配置界面,填写**服务器地址 + 屏幕 ID**(与后台完全一致,含大小写)
3. App 保存时自动校验:屏幕 ID 不存在则提示错误,不会进入播放
4. App 用该 ID 注册并拉取这块屏幕绑定的节目 → **多块屏各配各的 ID,各播各的节目**
> 屏幕 ID 留空时,App 用设备硬件 ID(ANDROID_ID)自动注册新屏幕,后台会自动出现该设备,改名后绑定节目即可。
---
## 目录结构
```
EasyScreen/
├── server/ # FastAPI 后端
│ ├── app/
│ │ ├── main.py # 入口(API + /media + 管理台托管)
│ │ ├── config.py # settings.json 读取(多数据源切换)
│ │ ├── database.py # SQLAlchemy 引擎/会话
│ │ ├── models.py # 数据表:screens/assets/playlists/items/bindings
│ │ ├── auth.py # JWT 登录鉴权
│ │ └── routers/ # admin_auth/screens/assets/playlists/client
│ ├── settings.json # 数据库与服务器配置
│ ├── uploads/ # 上传的素材文件(/media 访问)
│ ├── logs/crashes/ # App 崩溃上报日志(自动创建)
│ ├── scripts/ # start_prod.sh/.bat、stop_prod.sh
│ ├── requirements.txt
│ └── run_local.py # 本地开发启动(uvicorn --reload
├── android/ # Android 客户端工程(Android Studio 打开)
│ └── app/src/main/java/com/easyscreen/player/
│ ├── App.java # 全局崩溃捕获(本地记录 + 上报后端)
│ ├── MainActivity.java # 全屏播放调度(图片/视频轮播、心跳、定期重载)
│ ├── SetupActivity.java # 配置界面(服务器地址 + 屏幕 ID + 记住配置 + 校验)
│ ├── receiver/BootReceiver.java # 开机自启
│ ├── api/ # OkHttp + Gson 网络层(回调自动切主线程)
│ └── util/ # Prefs / DeviceId / CrashHandler
└── web/ # React 管理台
└── src/
├── pages/ # Login/Screens/Assets/Playlists
├── components/Layout.jsx
└── api/client.js # axios 封装(token 持久化)
```
---
## 快速开始
### 1. 后端
```bash
cd server
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt # Windows
.venv\Scripts\python run_local.py # 启动,端口 5889
```
首次启动自动建表。默认使用 SQLite(`easyscreen.db`)零配置运行;切换 MySQL 见下方配置说明。
### 2. 管理台
生产模式(后端已托管管理台,构建一次即可):
```bash
cd web
npm install
npm run build # 生成 web/distFastAPI 启动时自动托管
```
开发模式(热更新):
```bash
npm run dev # http://localhost:5173 (已代理 /api 和 /media 到 5889
```
默认账号 `admin / admin123`(在 `server/settings.json``admin` 段修改)。
### 3. Android 客户端
用 Android Studio 打开 `android/` 目录,等 Gradle 同步完成后构建 APK 安装到显示屏设备(或直接使用 `app/build/outputs/apk/debug/app-debug.apk`)。
**首次配置**:打开 App 进入配置界面——
- **服务器地址**:如 `http://192.168.1.100:5889`(保存时自动测试连通性)
- **屏幕 ID(可选)**:后台「屏幕管理」中的设备 ID;保存时自动校验是否存在
- **记住本次配置**(默认勾选):下次打开免输入,直接进入播放
**USB 调试小技巧**:手机 USB 连电脑时执行 `adb reverse tcp:5889 tcp:5889`App 地址填 `http://127.0.0.1:5889` 即可直连电脑后端,无需局域网 IP。
---
## 使用流程
1. **登录管理台** → 上传素材(图片/视频)到「素材管理」
2. 「节目编排」创建节目:左侧素材库**分页网格**可视化点选素材(图片缩略图/视频图标),右侧设置每项展示时长(0=自动:图片默认 10 秒,视频按自身时长)与播放顺序
3. 「屏幕管理」将节目**绑定**到目标屏幕(每块屏可绑定不同节目)
4. 屏幕设备:开机自动注册 → 拉配置 → 循环播放;每 30 秒心跳上报,后台实时显示在线状态;后台修改节目后 App 10 分钟内自动生效(重启 App 立即生效)
---
## 配置说明(server/settings.json
| 字段 | 说明 |
|---|---|
| `active_db` | 当前生效数据源:`dev_sqlite`(默认,无需 MySQL/ `dev` / `production` |
| `databases.*` | 各数据源连接信息;MySQL 填 host/port/user/password/database_name,切换时改 `active_db` 即可,代码零改动 |
| `server.base_url` | **必须修改**:部署机器的局域网 IP + 端口(Android 设备通过它访问素材) |
| `server.max_upload_mb` | 单文件上传大小上限(默认 500MB) |
| `server.allowed_types` | 允许上传的 MIME 类型 |
| `admin.username/password` | 管理台登录账号密码(**上线前务必修改**) |
| `admin.token_secret` | JWT 签名密钥(**上线前务必改为随机字符串**) |
---
## API 一览
在线文档:启动后访问 `http://localhost:5889/docs`Swagger UI)。
**管理端**(需 `Authorization: Bearer <token>`
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/auth/login` | 登录,返回 token |
| GET/POST | `/api/screens` | 屏幕列表 / 新增(推荐填写 device_id 用于 App 关联) |
| PUT/DELETE | `/api/screens/{id}` | 编辑 / 删除屏幕 |
| POST | `/api/screens/{id}/bind` | 绑定节目(替换当前生效节目) |
| DELETE | `/api/screens/{id}/bind` | 解除绑定 |
| GET/POST | `/api/assets` | 素材列表 / 上传(multipart |
| DELETE | `/api/assets/{id}` | 删除素材(被节目引用时返回 409) |
| GET/POST | `/api/playlists` | 节目列表 / 新建 |
| PUT/DELETE | `/api/playlists/{id}` | 编辑(全量替换节目项)/ 删除 |
**客户端**(无需鉴权,以 `device_id` 标识)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/client/register` | 设备注册(幂等,按 device_id 复用或创建屏幕) |
| GET | `/api/client/check?device_id=xxx` | 校验屏幕是否已存在(App 配置时提示用) |
| GET | `/api/client/config?device_id=xxx` | 拉取当前生效播放配置(含 version 版本号) |
| POST | `/api/client/heartbeat` | 心跳上报(30s 间隔,超 90s 视为离线) |
| POST | `/api/client/crash` | 崩溃上报(写入 server/logs/crashes/ |
| GET | `/media/{file}` | 素材文件访问 |
---
## 生产部署
### 构建管理台并单端口托管
```bash
cd web && npm run build # 生成 web/dist
```
后端启动时若检测到 `web/dist` 存在,会自动托管管理台(SPA 路由回退到 index.html),生产环境只需暴露一个端口。
### 启动后端
```bash
cd server
scripts/start_prod.sh # Linuxgunicorn + 2 workers + 日志(logs/
scripts/start_prod.bat # Windowsgunicorn 前台运行
```
### MySQL 切换
`settings.json` 中创建数据库并填入连接信息,将 `active_db` 改为 `dev``production`
```sql
CREATE DATABASE easyscreen DEFAULT CHARACTER SET utf8mb4;
CREATE USER 'easyscreen'@'%' IDENTIFIED BY '你的密码';
GRANT ALL PRIVILEGES ON easyscreen.* TO 'easyscreen'@'%';
```
---
## 常见问题
- **App 打开闪退**:先看后端 `server/logs/crashes/` 是否有崩溃日志(App 会自动上报),或 `adb logcat``AndroidRuntime` 段;常见为网络回调线程问题与 Glide/ExoPlayer 生命周期问题,最新版本已修复。
- **App 提示"屏幕 ID 不存在"**:确认后台「屏幕管理」已创建该屏幕,且设备 ID 与 App 填写内容**完全一致**(含大小写)。
- **管理台打不开**:确认 `web/` 下已执行 `npm run build`(或使用 `npm run dev` 开发模式)。
- **客户端提示"连接服务器失败"**:检查 `settings.json``base_url` 是否为显示屏可达的局域网地址、防火墙是否放行端口。
- **上传被拒**:检查文件类型是否在 `allowed_types` 内(视频建议 MP4/H.264 编码,老设备解码能力有限)。
- **Android 6.0 播放卡顿**:优先使用 H.264 编码、分辨率不超过屏幕尺寸的 MP4;图片避免超大 PNG。
- **删除素材失败 409**:素材正被节目引用,先在「节目编排」中移除。