Tennis Training Hub
网球训练管理与分析应用,提供训练计划、姿势分析、实时摄像头分析、在线视频录制、成就系统、管理员工作台与视频库管理。当前版本在媒体服务之外新增数据库驱动的后台任务系统,用于承接训练计划生成、动作纠正、多模态分析、录制归档与每日 NTRP 刷新这类高延迟任务。
Architecture
client/: React 19 + TypeScript + Tailwind CSS 4 + shadcn/uiserver/: Express + tRPC + Drizzle + MySQL/TiDB,负责业务 API、登录、训练数据与视频库元数据media/: Go 媒体服务,负责录制会话、分段上传、WebRTC 信令、关键片段标记与 FFmpeg 归档server/worker.ts: Node 后台 worker,负责执行重任务队列docker-compose.yml: 单机部署编排deploy/nginx.te.hao.work.conf:te.hao.work的宿主机 nginx 入口配置
Realtime Analysis
实时分析页现在采用“识别 + 录制 + 落库”一体化流程:
- 浏览器端基于 MediaPipe Pose 自动识别
forehand / backhand / serve / volley / overhead / slice / lob / unknown - 最近 6 帧动作结果会做时序加权稳定化,降低正手/反手/未知动作间的瞬时抖动
- 连续同类动作会自动合并为片段,最长单段不超过 10 秒
- 停止分析后会自动保存动作区间、评分维度、反馈摘要和可选本地录制视频
- 实时分析结果会自动回写训练记录、日训练聚合、成就进度与 NTRP 评分链路
- 移动端支持竖屏最大化预览,主要操作按钮固定在侧边
Video Library And PC Editing
- 视频库支持直接打开
PC 轻剪辑工作台 - 轻剪辑支持播放器预览、手动入点/出点、从当前播放位置快速设点
- 分析关键时刻会自动生成建议片段;即使视频 metadata 尚未返回,也会按分析帧数估算时间轴
- 剪辑草稿保存在浏览器本地,可导出 JSON 供后续后台剪辑任务或人工复核使用
Online Recording
在线录制模块采用双链路设计:
- 浏览器端
MediaRecorder本地压缩并每 60 秒自动分段上传 - 浏览器端
RTCPeerConnection同步建立 WebRTC 低延迟推流链路 - 客户端运动检测自动写入关键片段 marker,也支持手动标记
- 摄像头中断后自动重连,保留既有分段与会话
- Go 媒体 worker 将分段合并归档,并产出 WebM 回放;FFmpeg 可用时额外生成 MP4
- Node app worker 轮询媒体归档状态,归档完成后自动登记到视频库并向任务中心反馈结果
- 服务端媒体会话校验兼容
/media/sessions/...路径,避免录制结束时因路径不一致导致 404
Background Tasks
统一后台任务覆盖以下路径:
training_plan_generatetraining_plan_adjustanalysis_correctionspose_correction_multimodalmedia_finalizentrp_refresh_userntrp_refresh_all
前端提供全局任务中心,页面本地也会显示任务提交、执行中、完成或失败状态。训练页、分析页和录制页都可以在用户离开页面后继续完成后台任务。
另外提供独立日志页 /logs,用于查看后台任务历史、失败原因与通知记录;管理员工作台 /admin 可集中查看用户、后台任务、实时分析会话、应用设置和审计日志。
Multimodal LLM
- 文本类任务使用
LLM_API_URL/LLM_API_KEY/LLM_MODEL - 图片类任务可单独指定
LLM_VISION_API_URL/LLM_VISION_API_KEY/LLM_VISION_MODEL - 所有图片输入都要求可从公网访问,因此本地相对路径会通过
APP_PUBLIC_BASE_URL规范化为绝对 URL - 若视觉模型链路返回非标准 JSON 或缺失数组字段,服务端会先做结构兼容和字段补全,再尝试生成视觉报告
- 若视觉模型链路不可用,系统会自动回退到结构化指标驱动的文本纠正,避免任务直接失败
- 系统内置“视觉标准图库”页面
/vision-lab,可把公网网球参考图入库并保存每次识别结果 ADMIN_USERNAMES可指定哪些用户名账号拥有 admin 视角,例如H1- 用户名登录支持直接进入系统;仅首次创建新用户时需要填写
REGISTRATION_INVITE_CODE - 新用户首次登录时只需提交一次用户名;若用户名不存在才需要额外填写邀请码
vision-lab支持对历史fallback/failed记录重新排队,便于修复上游返回不稳定导致的旧数据
Quick Start
Local development
pnpm install
cp .env.example .env
set -a && source .env && set +a && pnpm exec drizzle-kit migrate
pnpm dev
本地开发时:
- Node 应用默认运行在
http://localhost:3000 - 若设置
MEDIA_SERVICE_URL=http://127.0.0.1:8081,Express 会把/media代理到 Go 服务 - Go 媒体服务可单独启动:
cd media
go mod tidy
go run .
Checks
pnpm check
pnpm test
pnpm test:go
pnpm build
pnpm test:e2e
pnpm verify
cd media
go build ./...
首次运行浏览器测试前执行:
pnpm exec playwright install chromium
若本地数据库是空库或刚新增了 schema,先执行:
set -a && source .env && set +a && pnpm exec drizzle-kit migrate
Production Deployment
单机部署推荐:
- 宿主机 nginx 处理
80/443和 TLS docker compose up -d --build启动app + app-worker + media + media-worker + db- nginx 将
/转发到宿主机127.0.0.1:3002 -> app:3000,/media/转发到127.0.0.1:8081 -> media:8081 - 如需绕过 nginx 直连调试,也可通过公网 4 位端口访问主站:
http://te.hao.work:8302/
详细步骤见:
docs/deploy.mddocs/media-architecture.mddocs/frontend-recording.mddocs/runtime-operations.md
2026-03-15 已在真实环境执行一次重建与 smoke test:
docker compose up -d --build migrate app app-worker- Playwright 复测
https://te.hao.work/login、/checkin、/videos、/recorder、/live-camera、/admin - 复测后关键链路全部通过,确认线上已切换到最新前端与业务版本
Documentation Index
docs/FEATURES.md: 当前功能特性与能力边界docs/testing.md: 自动测试分层与运行方式docs/verified-features.md: 已验证通过的项目清单docs/developer-workflow.md: 阶段可中断的开发与本地提交流程docs/deploy.md: 部署指南docs/media-architecture.md: 媒体服务架构docs/frontend-recording.md: 前端录制与移动端适配说明docs/runtime-operations.md: 运行时任务稳定性、日志清理、重启与 smoke 流程
Environment
关键环境变量见 .env.example,重点包括:
DATABASE_URLJWT_SECRETADMIN_USERNAMESREGISTRATION_INVITE_CODEMYSQL_DATABASEMYSQL_USERMYSQL_PASSWORDMYSQL_ROOT_PASSWORDLLM_API_URLLLM_API_KEYLLM_MODELLLM_VISION_API_URLLLM_VISION_API_KEYLLM_VISION_MODELAPP_PUBLIC_BASE_URLLOCAL_STORAGE_DIRMEDIA_SERVICE_URLVITE_MEDIA_BASE_URL
LLM 烟雾测试:
pnpm test:llm
pnpm test:llm -- "你好,做个自我介绍"
Notes
- 浏览器兼容目标以 Chrome 为主
- 录制文件优先产出 WebM,MP4 为服务端可选归档产物
- 存储策略当前为本地卷优先,适合单机 Compose 部署
- 浏览器测试会启动真实 Node 服务,因此要求本地测试库已完成 Drizzle 迁移
描述
语言
TypeScript
94.3%
Go
2.9%
JavaScript
2%
CSS
0.6%