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

10 KiB
Raw Blame History

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. 后端

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. 管理台

生产模式(后端已托管管理台,构建一次即可):

cd web
npm install
npm run build        # 生成 web/distFastAPI 启动时自动托管

开发模式(热更新):

npm run dev          # http://localhost:5173 (已代理 /api 和 /media 到 5889

默认账号 admin / admin123(在 server/settings.jsonadmin 段修改)。

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:5889App 地址填 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/docsSwagger 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} 素材文件访问

生产部署

构建管理台并单端口托管

cd web && npm run build        # 生成 web/dist

后端启动时若检测到 web/dist 存在,会自动托管管理台(SPA 路由回退到 index.html),生产环境只需暴露一个端口。

启动后端

cd server
scripts/start_prod.sh          # Linuxgunicorn + 2 workers + 日志(logs/
scripts/start_prod.bat         # Windowsgunicorn 前台运行

MySQL 切换

settings.json 中创建数据库并填入连接信息,将 active_db 改为 devproduction

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 logcatAndroidRuntime 段;常见为网络回调线程问题与 Glide/ExoPlayer 生命周期问题,最新版本已修复。
  • App 提示"屏幕 ID 不存在":确认后台「屏幕管理」已创建该屏幕,且设备 ID 与 App 填写内容完全一致(含大小写)。
  • 管理台打不开:确认 web/ 下已执行 npm run build(或使用 npm run dev 开发模式)。
  • 客户端提示"连接服务器失败":检查 settings.jsonbase_url 是否为显示屏可达的局域网地址、防火墙是否放行端口。
  • 上传被拒:检查文件类型是否在 allowed_types 内(视频建议 MP4/H.264 编码,老设备解码能力有限)。
  • Android 6.0 播放卡顿:优先使用 H.264 编码、分辨率不超过屏幕尺寸的 MP4;图片避免超大 PNG。
  • 删除素材失败 409:素材正被节目引用,先在「节目编排」中移除。