init: EasyScreen 电子菜单系统

- server: FastAPI 后端(多屏管理、素材上传、节目编排、客户端注册/配置/心跳/崩溃上报、管理台托管)
- android: Java 客户端(minSdk 23,全屏图片/视频轮播、远程配置、开机自启、崩溃上报)
- web: React + Vite + antd 管理台(屏幕/素材/节目管理)
- 屏幕设备 ID 关联机制、gunicorn 生产部署脚本
This commit is contained in:
Tatta
2026-08-12 20:57:55 +08:00
commit 4e2775c8c3
58 changed files with 6451 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
"""EasyScreen 后端入口。"""
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from .config import get_server_config
from .database import Base, _engine
from .routers import admin_assets, admin_auth, admin_playlists, admin_screens, client
app = FastAPI(title="EasyScreen 电子菜单后台", version="1.0.0")
# 开发期允许前端跨域(Vite dev server);生产建议同域部署或收紧来源
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 自动建表
Base.metadata.create_all(bind=_engine)
# 媒体文件访问(客户端播放地址 /media/{file}
_upload_dir = os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(__file__)), get_server_config().get("upload_dir", "./uploads"))
)
os.makedirs(_upload_dir, exist_ok=True)
app.mount("/media", StaticFiles(directory=_upload_dir), name="media")
app.include_router(admin_auth.router)
app.include_router(admin_screens.router)
app.include_router(admin_assets.router)
app.include_router(admin_playlists.router)
app.include_router(client.router)
@app.get("/api/health")
def health():
return {"status": "ok"}
# ---------------- 管理台静态托管(生产单端口部署) ----------------
# 若存在 web/distnpm run build 产物),则由本服务直接托管管理台。
# 注意:/api 与 /media 路由已在上方注册,此处 catch-all 仅兜底前端路由。
_WEB_DIST = os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "web", "dist")
)
@app.get("/{full_path:path}", include_in_schema=False)
def serve_web(full_path: str):
if not os.path.isdir(_WEB_DIST):
return {"message": "EasyScreen API 运行中。管理台未构建:请在 web/ 目录执行 npm run build"}
# 优先返回真实文件(JS/CSS/图片等),其余路径回退到 index.html(SPA 路由)
file_path = os.path.join(_WEB_DIST, full_path)
if full_path and os.path.isfile(file_path):
return FileResponse(file_path)
return FileResponse(os.path.join(_WEB_DIST, "index.html"))