Files

FITCOACH 后端NestJS

健身动作数据代理与推荐服务。所有动作数据统一存储在后端,向前端小程序提供 REST 接口。

技术栈

  • NestJS 10 + Express
  • 内存数据集(构建时由 exercises-dataset 导入),查询/筛选/推荐均在内存完成,毫秒级响应
  • 可选静态媒体服务(/media,指向 backend/media/

环境变量(.env,参考 .env.example

变量 默认值 说明
PORT 3000 服务端口
API_BASE_URL http://localhost:3000 小程序访问基址(生成媒体 URL 用)
MEDIA_BASE_URL https://cdn.jsdelivr.net/gh/hasaneyldrm/exercises-dataset@main 媒体资源基址CDN 直链,免本地下载)
CORS_ENABLED true 是否允许跨域Web 调试用)
WX_APPID 微信小程序 AppID不填则登录走开发降级模式(任意 code 生成稳定虚拟 openid
WX_SECRET 微信小程序 AppSecret配合 WX_APPID 调用 code2session 换取真实 openid
JWT_SECRET fitcoach-dev-secret token 签名密钥,生产务必修改为强随机值

安装与运行

npm install
npm run build        # 触发 prebuild: 自动下载 exercises.json 到 src/exercises/data/
npm run start:dev    # 开发watch
npm run start:prod   # 生产node dist/main.js

脚本

  • npm run sync:media — 将数据集的图片/动画同步到 backend/media/(供 /media 提供)。可选环境变量 MEDIA_CONCURRENCY(并发)、MEDIA_ONLY=gif|image
  • node scripts/analyze.mjs — 扫描数据集,输出各维度去重值与缺失的中文标签(用于补全 labels.ts)。

数据导入说明

数据集 exercises.json1,324 条)通过 scripts/ensure-data.mjs 在构建时自动从 GitHub 下载; 也可手动把 exercises-dataset/data/exercises.json 复制到 src/exercises/data/exercises.json。 服务启动时由 ExercisesService.onModuleInit() 读取并 normalize() 为带中文字段的结构化对象。

接口示例

# 列表 + 筛选(按器械 dumbbell分页
curl "http://localhost:3000/api/exercises?equipment=dumbbell&pageSize=5"

# 动作详情
curl "http://localhost:3000/api/exercises/0001"

# 目标肌群智能推荐(肱二头肌,限定哑铃)
curl "http://localhost:3000/api/recommend?target=biceps&equipment=dumbbell&limit=10"

# 分类元数据
curl "http://localhost:3000/api/categories/targets"
curl "http://localhost:3000/api/categories/equipment"

# 训练组合
curl "http://localhost:3000/api/collections"
curl "http://localhost:3000/api/collections/legs/exercises?pageSize=10"

# 搜索
curl "http://localhost:3000/api/search?q=abs"

# ===== 用户体系(登录 / 收藏 / 计划)=====
# 登录(开发模式:任意 code 均可,后端自动建号)
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login -H 'content-type: application/json' \
  -d '{"code":"dev_123","userInfo":{"nickname":"阿强","avatar":"🦊"}}' | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>console.log(JSON.parse(d).token))")

# 个人资料
curl http://localhost:3000/api/auth/me -H "Authorization: Bearer $TOKEN"
curl -X PATCH http://localhost:3000/api/users/me -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"nickname":"阿强Pro"}'

# 收藏动作
curl -X POST http://localhost:3000/api/favorites/exercises -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"target":"0001"}'
curl http://localhost:3000/api/favorites/exercises -H "Authorization: Bearer $TOKEN"
curl http://localhost:3000/api/favorites/exercises/0001/status -H "Authorization: Bearer $TOKEN"

# 我的计划
curl -X POST http://localhost:3000/api/plans -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"name":"胸肌日","exerciseIds":["0001","0002"]}'
curl http://localhost:3000/api/plans -H "Authorization: Bearer $TOKEN"
# 从官方计划一键导入为我的计划
curl -X POST http://localhost:3000/api/plans/import -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"slug":"home"}'

# 官方计划(按水平分级)
curl http://localhost:3000/api/plans/official -H "Authorization: Bearer $TOKEN"
curl "http://localhost:3000/api/plans/official/home/exercises?pageSize=10" -H "Authorization: Bearer $TOKEN"

推荐算法

对每条动作计算相关性评分:

  • 目标肌群完全匹配 target+100理由「主要训练 XX」
  • 出现在协同肌群 secondaryMuscles+40理由「协同训练 XX」
  • 指定器械匹配:+30若为自重可替代+10 仅返回评分 > 0 的动作,按评分降序、名称升序返回。

用户体系 / 收藏 / 计划v2 新增)

独立的 auth / users / favorites / plans 四个模块,构成完整用户闭环:

  • 登录POST /api/auth/login 接收 wx.logincode,生产环境调用微信 code2session 换取真实 openid未配置 WX_APPID/WX_SECRET 时自动降级,用任意 code 生成稳定虚拟 openid,方便本地联调。返回自签 tokenHMAC-SHA25630 天有效)与用户资料。
  • 鉴权:受保护接口需 Authorization: Bearer <token>;统一 AuthGuard 校验,非法/过期返回 401。
  • 资料GET /api/auth/mePATCH /api/users/me(昵称/头像 emoji
  • 收藏:动作收藏(/api/favorites/exercises+ 官方计划收藏(/api/favorites/plans),均带状态查询接口。
  • 我的计划:完整 CRUD/api/plans+ 向计划增删动作;POST /api/plans/import 可把官方计划一键复制为个人计划。
  • 官方计划:在原有 8 个训练组合基础上新增「按水平分级」的官方推荐计划(新手七天 / 新手燃脂 / 进阶增肌 / 进阶力量 / 高级竞技 / 舒展放松 / 女子塑形等),通过 GET /api/plans/official新手入门 / 进阶提升 / 高级挑战 / 全阶段适用 分组下发。

数据持久化

用户、收藏、计划数据落地在 backend/data-store/*.jsonusers.json / favorites-exercises.json / favorites-plans.json / plans.json),由 src/common/store.ts 的单例 JsonStore 在进程内加载、每次写入整体落盘。无需数据库即可持久化;首次启动自动建目录与空文件。

该目录为运行时数据,建议加入 .gitignore(已在仓库根 .gitignore 忽略 data-store/)。

目录

src/exercises/
├── exercises.module.ts
├── exercises.service.ts      # 数据加载、筛选、分类、推荐、组合
├── exercises.controller.ts   # /api 路由
├── exercises.interface.ts    # 类型定义
├── dto/                      # QueryExercisesDto / RecommendQueryDtoclass-validator 校验)
├── utils/normalize.ts        # 原始 → 结构化(类型推导、媒体 URL、中文标签
└── data/
    ├── exercises.json        # 数据集(构建时生成)
    ├── labels.ts             # 中英文标签映射
    └── collections.ts        # 智能训练组合 / 官方计划定义(含 level 分级)
src/common/
├── store.ts                  # 文件持久化单例 JsonStoredata-store/
├── token.ts                  # token 签名 / 校验
├── auth.guard.ts             # Bearer 鉴权守卫
└── current-user.decorator.ts # 注入当前 openid
src/auth/   src/users/   src/favorites/   src/plans/   # 用户体系四模块