init: EasyScreen 电子菜单系统
- server: FastAPI 后端(多屏管理、素材上传、节目编排、客户端注册/配置/心跳/崩溃上报、管理台托管) - android: Java 客户端(minSdk 23,全屏图片/视频轮播、远程配置、开机自启、崩溃上报) - web: React + Vite + antd 管理台(屏幕/素材/节目管理) - 屏幕设备 ID 关联机制、gunicorn 生产部署脚本
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# EasyScreen 电子菜单系统
|
||||
|
||||
基于 **Android 6.0 电子显示屏** 的电子菜单(广告屏)解决方案。支持多块屏幕独立配置、远程下发图片/视频节目、开机自启全屏播放。
|
||||
|
||||
系统分三端:
|
||||
|
||||
| 端 | 技术栈 | 说明 |
|
||||
|---|---|---|
|
||||
| `android/` | 原生 Java(minSdk 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/dist,FastAPI 启动时自动托管
|
||||
```
|
||||
|
||||
开发模式(热更新):
|
||||
|
||||
```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 # Linux:gunicorn + 2 workers + 日志(logs/)
|
||||
scripts/start_prod.bat # Windows:gunicorn 前台运行
|
||||
```
|
||||
|
||||
### 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**:素材正被节目引用,先在「节目编排」中移除。
|
||||
Reference in New Issue
Block a user