1. 项目概述一个现代健身教练平台的诞生如果你和我一样既是一个健身爱好者又对技术充满热情那么你肯定也经历过这样的困境市面上那些健身App要么功能臃肿、广告满天飞要么就是闭源、收费昂贵数据还锁死在他们的服务器里。几年前一个名为 workout.lol 的开源项目曾让我眼前一亮它简洁、现代完全符合我对一个私人健身工具的所有想象。然而这个项目最终因为商业合作和视频版权成本等问题被出售随后便陷入了停滞社区的热情也随之冷却。作为一个深度参与过原项目的贡献者我亲眼目睹了这一切也感受到了社区成员的失落。正是这份不甘心驱使我决定从头开始打造一个全新的、更强大的、真正属于社区的现代健身教练平台——这就是workout.cool。workout.cool 不仅仅是一个健身计划生成器或记录工具它是一个综合性的健身教练平台。它的核心是围绕一个全面的动作数据库构建的这个数据库包含了详尽的动作说明、执行要点甚至视频演示。基于这个数据库平台可以为你智能生成个性化的训练计划让你能够系统地追踪每一次训练的组数、次数、重量和身体感受从而清晰地看到自己的进步轨迹。无论你是刚刚踏入健身房的新手还是寻求突破瓶颈的资深训练者这个工具都能为你提供结构化的指导让你的训练不再盲目。2. 项目架构与核心技术选型解析2.1 为什么选择 Next.js 与 App Router在技术栈的选型上我毫不犹豫地选择了Next.js并且采用了其最新的App Router架构。这背后有几个核心考量。首先Next.js 提供了开箱即用的服务端渲染SSR和静态生成SSG能力这对于一个内容驱动型应用至关重要。动作库的页面、训练计划详情页这些内容非常适合预渲染能极大提升首次加载速度和搜索引擎优化效果。其次App Router 基于 React Server Components允许我们更自然地在服务器端处理数据获取和业务逻辑。例如获取用户训练记录、查询动作详情这些操作可以直接在服务器组件中完成无需先加载一个空壳页面再通过客户端useEffect去获取数据这带来了更快的初始渲染和更精简的客户端代码。注意从 Pages Router 迁移到 App Router 需要思维上的转变。最大的挑战在于理解 Server Components 和 Client Components 的边界。我的经验是默认将所有组件视为 Server Component只有在明确需要交互性如useState,useEffect,onClick或浏览器 API如localStorage时才在文件顶部添加“use client”指令。这能最大化利用服务端渲染的优势。2.2 采用 Feature-Sliced Design (FSD) 进行代码组织随着功能增多代码库很容易变得混乱不堪。为了避免重蹈许多项目“面条式代码”的覆辙我决定采用Feature-Sliced Design架构模式。FSD 的核心思想是按业务功能Feature来切片和组织代码而不是按技术角色如 components, hooks, utils。在 workout.cool 的src/目录下你可以看到清晰的分层shared/: 存放真正跨领域共享的代码如 UI 组件库基于 shadcn/ui、工具函数、配置文件。这里的代码应该对业务一无所知。entities/: 定义核心业务实体如User用户、Exercise动作、WorkoutPlan训练计划。这里主要是 TypeScript 类型定义、Prisma 模型以及一些与实体相关的纯函数。features/: 这是 FSD 的核心。每个独立的业务功能都是一个文件夹例如auth认证、exercise-management动作管理、workout-planning计划制定。每个 feature 内部是自包含的拥有自己的ui/组件、model/状态、hooks、lib/业务逻辑、api/服务端动作。widgets/: 组合多个 features 形成的复杂 UI 模块例如侧边栏导航、页面头部它们本身不直接对应某个业务实体。processes/: 描述跨越多个 features 的复杂用户流程例如“用户从注册到完成第一个训练计划”这个流程。app/: Next.js 的页面路由层负责将上述所有模块组合成具体的页面。这种架构的优势在于极强的可维护性和可预测性。当需要修改“动作管理”功能时你几乎只需要在features/exercise-management目录下工作不会意外影响到用户认证模块。新成员加入项目也能通过目录结构快速理解业务边界。2.3 后端与数据层Prisma PostgreSQL 的组合拳对于数据层我选择了Prisma作为 ORM搭配PostgreSQL数据库。Prisma 以其类型安全的数据库查询和直观的数据模型定义而著称。在prisma/schema.prisma文件中你可以像这样定义一个Exercise动作模型model Exercise { id Int id default(autoincrement()) name String // 动作名称如“杠铃深蹲” nameEn String? map(“name_en”) // 英文名称 slug String unique // URL 友好标识 description String? // 详细描述HTML格式 primaryMuscle MuscleGroup? relation(fields: [primaryMuscleId], references: [id]) primaryMuscleId Int? equipment Equipment[] relation(“ExerciseEquipment”) // 所需器械 videos Video[] // 关联的视频 createdAt DateTime default(now()) updatedAt DateTime updatedAt map(“exercises”) }这种定义方式不仅生成了数据库表还自动为 TypeScript 生成了完全类型化的PrismaClient。这意味着你在编写查询时如prisma.exercise.findMany({ where: { primaryMuscle: { name: “Chest” } } })能获得完美的代码自动补全和类型检查几乎杜绝了因字段名拼写错误导致的运行时bug。PostgreSQL 作为关系型数据库其稳定性和丰富的功能如 JSONB 类型存储训练记录中的灵活数据、全文搜索用于动作库检索是项目的坚实后盾。通过 Docker 进行容器化部署也保证了开发、测试、生产环境的高度一致性。2.4 前端 UI 与样式Radix UI 与 shadcn/ui 的化学反应为了构建一个既美观又具备高可访问性的用户界面我选择了Radix UI作为底层原始组件库并在此基础上采用了shadcn/ui的方案。Radix UI 提供了完全无样式、功能完备的 UI 原语如 Dialog, Dropdown Menu, Tabs它们解决了复杂的交互逻辑和可访问性问题。而 shadcn/ui 本质上是一套基于 Tailwind CSS 的、可自由定制的组件样式模板。这样做的好处是极致的控制权。不同于直接引入一个完整的组件库如 MUIshadcn/ui 允许你将组件代码直接复制到你的项目中成为你代码库的一部分。你可以随意修改这些组件的任何样式或行为而无需担心版本升级带来的破坏性变更也无需处理繁琐的theme provider覆盖。结合Tailwind CSS的原子化工具类UI 的开发变得快速且一致。例如一个训练计划卡片组件可以快速搭建并确保与整个应用的设计系统保持一致。3. 核心功能实现与实操要点3.1 构建可扩展的动作数据库动作数据库是 workout.cool 的基石。设计时我重点考虑了可扩展性和多语言支持。一个动作Exercise不仅仅有名称和描述它关联着目标肌群MuscleGroup、所需器械Equipment、动作类型如力量、有氧、难度级别以及最重要的——教学视频Video。数据库关系设计如下Exercise 与 MuscleGroup: 多对多关系。一个深蹲主要刺激股四头肌但也涉及臀大肌和核心肌群。Exercise 与 Equipment: 多对多关系。一个动作可能需要杠铃、哑铃或徒手完成。Exercise 与 Video: 一对多关系。一个动作可以有多个角度的演示视频。为了实现多语言我为name、description等字段设计了对应的nameEn、descriptionEn字段。在前端可以根据用户的语言偏好动态切换。视频资源则通过full_video_url链接到外部平台如 YouTube并缓存其缩略图full_video_image_url这样既避免了天价的视频存储与流量成本又保证了内容的丰富性。实操心得CSV 数据导入策略项目提供了pnpm run import:exercises-full脚本来从 CSV 文件批量导入动作数据。这里有个关键细节CSV 文件需要处理“一对多”关系。如上文示例同一条动作相同id在 CSV 中会以多行存在通过attribute_name和attribute_value来标记不同的属性如TYPE: STRENGTH,PRIMARY_MUSCLE: QUADRICEPS。在导入脚本中需要先解析 CSV将同一id的数据行聚合再创建包含所有关联数据的完整 Exercise 记录。这比维护多个关联 CSV 文件要方便得多。3.2 智能化训练计划生成引擎生成训练计划是平台的核心智能所在。我的设计思路是基于规则与偏好结合而非复杂的黑盒AI模型以保证计划的透明度和可解释性。用户画像收集首先系统会通过问卷收集用户信息训练目标增肌、减脂、提升力量、训练经验新手、中级、高级、每周可用训练天数、偏好训练部位、可用器械等。动作池筛选根据用户画像从动作数据库中筛选出符合条件的动作。例如一个目标是“增肌”且只有哑铃的用户系统会优先筛选出哑铃相关的复合动作如哑铃卧推、哑铃划船和孤立动作。计划模板匹配系统内置了几种经典的训练分割模板如“全身训练”、“上下肢分化”、“推/拉/腿分化”。根据用户每周训练天数自动匹配最合适的模板。动作编排与容量设定将筛选出的动作填充到模板的每一天中。遵循“先复合后孤立”、“上下肢/推拉平衡”的原则。并为每个动作设定初始的组数、次数建议。例如新手增肌计划可能推荐每个复合动作做3-4组每组8-12次。渐进超负荷逻辑计划不是静态的。系统会追踪用户每次训练的实际完成情况如最后一组是否轻松完成。基于此在下一周或下一循环中会自动建议微调重量或增加次数实现渐进超负荷这是持续进步的关键。这个引擎的逻辑主要封装在features/workout-planning/lib/generator.ts中它纯粹是服务端的业务逻辑通过 API 路由或 Server Action 提供给前端调用。3.3 训练记录与进度追踪记录功能的设计追求快速与无感。在训练界面用户面对的是一个针对当日计划的、极简的记录表。// 一个简化版的训练记录数据结构 interface WorkoutSet { id: string; exerciseId: number; targetReps: number; // 计划目标次数 actualReps: number; // 实际完成次数 weight: number; // 使用的重量kg/lb rpe: number; // 主观用力程度1-10分 notes?: string; // 备注如“感觉不稳定” } interface WorkoutSession { id: string; planId: string; date: Date; sets: WorkoutSet[]; overallFeeling: ‘great’ | ‘good’ | ‘average’ | ‘poor’; }用户只需点击“”增加一组输入重量和次数即可。系统会自动保存草稿防止意外退出丢失数据。RPE自感用力度的录入是一个专业功能帮助高级训练者更精细地调控强度。所有记录最终关联到用户和具体的训练计划用于生成长期的力量曲线图、容量趋势图等可视化报告让进步一目了然。3.4 用户认证与数据安全我选择了Better Auth作为认证解决方案。它是一个开源、可自托管的现代认证库完美适配 Next.js App Router支持多种登录方式邮箱/密码、OAuth等。相比于自己从头实现认证逻辑使用 Better Auth 能避免很多安全陷阱如密码哈希、会话管理、CSRF 防护。关键配置在于将会话管理、数据库适配与 Prisma 集成。所有用户相关的数据训练计划、记录都通过userId进行严格隔离确保用户只能访问自己的数据。API 路由和 Server Actions 中都会验证会话这是数据安全的第一道防线。4. 本地开发与部署实操指南4.1 两种本地开发环境搭建项目支持 Docker 和非 Docker 两种开发方式推荐使用Docker以获得一致的环境。 Docker 开发流程推荐确保本地已安装 Docker 和 Docker Compose。克隆项目后复制环境变量模板cp .env.example .env。你只需要关注少数几个变量如本地端口数据库配置在 Docker Compose 文件中已预设好。运行make dev命令。这个 Makefile 指令会依次执行启动 PostgreSQL 和 Redis如果用到容器。运行数据库迁移prisma migrate dev在数据库中创建所有表。可选地运行种子脚本导入示例动作数据。启动 Next.js 开发服务器并开启热重载。打开浏览器访问http://localhost:3000你将看到一个完整的、带示例数据的 workout.cool 应用。避坑提示如果遇到数据库连接错误请检查.env文件中的DATABASE_URL。在 Docker 环境下主机名应为postgres服务名而不是localhost。例如DATABASE_URL“postgresql://postgres:yourpasswordpostgres:5432/workout_cool?schemapublic”。 非 Docker 手动安装如果你偏好原生环境则需要安装 Node.js (v18)、pnpm 和 PostgreSQL。在 PostgreSQL 中手动创建数据库createdb workout_cool。同样复制.env文件并将DATABASE_URL指向你的本地 PostgreSQL 实例。运行pnpm install安装依赖然后执行npx prisma migrate dev和pnpm dev。两种方式都能顺利运行但 Docker 方式免去了手动安装和配置数据库的麻烦尤其适合团队协作确保所有人的环境完全一致。4.2 生产环境部署详解对于生产部署安全性和性能是首要考虑因素。使用 Docker Compose 部署最简单这是自托管最推荐的方式。项目根目录下的docker-compose.prod.yml文件定义了生产服务栈version: ‘3.8’ services: postgres: image: postgres:16-alpine environment: POSTGRES_DB: workout_cool POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: { ... } app: build: . ports: - “${APP_PORT}:3000” environment: DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}postgres:5432/workout_cool NODE_ENV: production depends_on: postgres: condition: service_healthy部署步骤在服务器上安装 Docker 和 Docker Compose。将项目代码克隆至服务器。创建.env.production文件填写强密码的数据库凭证、密钥等。运行docker-compose -f docker-compose.prod.yml up -d所有服务将后台运行。使用 Nginx 或 Caddy 作为反向代理配置域名、SSL 证书并将流量转发到容器的 3000 端口。手动部署到 VPS如果你熟悉 Node.js 生产环境也可以手动部署在服务器上安装 Node.js, pnpm, PostgreSQL。构建应用pnpm run build这会生成一个优化的.next输出目录。运行数据库迁移NODE_ENVproduction npx prisma migrate deploy。使用 PM2 等进程管理器启动应用pm2 start pnpm –name “workout-cool” – start。同样配置反向代理和 SSL。部署到 Vercel针对前端需分离后端workout.cool 是全栈应用直接部署到 Vercel 需要处理数据库连接可能被墙或延迟高。一个更优的架构是将前端Next.js部署在 Vercel将 PostgreSQL 数据库部署在可公开访问的云服务如 AWS RDS、Supabase 或 Neon并确保 Vercel 的环境变量正确配置了远程数据库连接字符串。这种模式利用了 Vercel 的全球 CDN 和自动 HTTPS同时保证了数据库的稳定可控。4.3 数据迁移与备份策略对于用户来说数据是无价的。在自托管时必须建立定期备份机制。Docker Compose 配置中已经将 PostgreSQL 数据卷挂载到postgres_data。最简单的备份方式是定期执行pg_dump命令# 在宿主机上执行将数据库备份到文件 docker exec workout-cool-postgres-1 pg_dump -U postgres workout_cool backup_$(date %Y%m%d).sql可以将此命令加入服务器的 crontab实现每日自动备份并将备份文件同步到远程存储如 AWS S3、Backblaze B2。同时务必在.env.production中使用强密码并考虑将数据库服务置于内部网络仅允许应用容器访问以增强安全性。5. 常见问题排查与社区贡献5.1 开发与部署中的常见问题在开发和部署 workout.cool 的过程中我遇到并解决了不少典型问题这里记录下排查思路1. 数据库连接失败Docker 环境症状应用启动时报错PrismaClientInitializationError提示无法连接到数据库。排查首先运行docker ps确认 PostgreSQL 容器正在运行。检查应用容器的DATABASE_URL环境变量。在 Docker Compose 网络内主机名应为服务名postgres端口为内部端口5432。进入 PostgreSQL 容器 (docker exec -it container_id psql -U postgres)手动检查workout_cool数据库和表是否存在。解决确保docker-compose.yml中应用服务depends_on数据库服务并配置了condition: service_healthy等待数据库就绪后再启动应用。2. Prisma 迁移冲突症状多人协作开发时同时修改了schema.prisma并生成迁移导致合并冲突。排查查看prisma/migrations目录下的迁移文件历史找到冲突的迁移记录。解决沟通协调按顺序应用迁移。如果是在开发分支可以重置数据库 (npx prisma migrate reset) 并重新应用所有迁移。切勿在生产环境使用reset。最佳实践是每次修改 schema 前先拉取最新代码生成迁移后立即提交。3. 导入 CSV 数据时外键约束错误症状运行pnpm run import:exercises-full时报错提示Foreign key constraint failed。排查CSV 数据中引用了不存在的关联记录 ID。例如一个动作的primaryMuscleId指向了一个数据库中不存在的肌群 ID。解决确保 CSV 导入遵循正确的顺序。通常需要先导入基础实体如MuscleGroup,Equipment再导入关联它们的Exercise。检查提供的 CSV 示例文件理解其数据结构和依赖关系。4. 生产环境静态资源加载 404症状部署后Logo、用户上传的图片等静态资源无法加载。排查Next.js 在构建时public目录下的文件会被复制到输出目录。如果使用 Docker 构建需要确保COPY public ./public指令正确执行。如果使用对象存储检查配置的存储桶策略和 CDN 域名。解决对于 Docker 部署确保 Dockerfile 中包含了COPY public ./public。对于 Vercel 等平台通常会自动处理。自定义服务器部署时需要配置静态文件中间件。5.2 如何为 workout.cool 贡献代码workout.cool 是一个真正的社区项目我非常欢迎并依赖社区的贡献。以下是清晰的贡献流程发现或提出议题在 GitHub Issues 中查看是否有你想解决的问题。如果没有可以新建一个 Issue清晰描述问题或功能建议。声明工作在 Issue 下留言说明你打算解决它避免重复劳动。Fork 与分支Fork 主仓库到你的账户并基于main分支创建一个描述性的分支如feat/add-dark-mode或fix/typo-in-exercise-description。开发与测试在本地进行修改。遵循项目的代码风格Prettier ESLint 已配置。为你的更改添加或更新测试。确保pnpm run build和pnpm run lint通过。提交与推送使用清晰的提交信息例如feat: add support for RPE input in workout log。将分支推送到你的 Fork。发起 Pull Request (PR)在你的 Fork 页面发起 PR 到主仓库的main分支。在 PR 描述中引用相关的 Issue如Closes #123并详细说明你的更改内容、测试方法以及任何需要审查者注意的事项。代码审查与合并我会尽快 Review 你的 PR。可能会提出一些修改建议。经过讨论和必要的修改后PR 将被合并。给贡献者的建议从小处着手。修复一个错别字、完善一个文档句子都是极好的贡献。在修改功能前尤其是涉及 FSD 架构的部分先花点时间理解现有代码的组织方式。如果你不确定如何实现某个功能可以在 Issue 或 Discord 社区中先发起讨论。5.3 项目未来可能的演进方向作为一个开源项目workout.cool 的未来由社区共同塑造。目前我脑海中已有一些演进思路也期待大家的想法移动端 PWA利用 Next.js 的响应式设计和 PWA 能力让应用可以安装到手机主屏幕提供接近原生 App 的离线训练记录体验。高级分析功能集成更复杂的图表库提供肌肉疲劳度分析、训练量Volume随时间变化趋势、基于 RPE 的自适应计划微调建议。社区分享与模板允许用户将自己的训练计划发布为模板供其他用户一键套用形成高质量的 UGC 计划库。API 开放提供一套完整的 RESTful 或 GraphQL API让开发者可以构建第三方客户端、集成智能手表数据或者进行更深入的量化分析。更多训练模式支持目前以力量训练为主未来可以扩展支持耐力训练跑步、骑行、HIIT 计时、瑜伽课程等模块。重启 workout.lol 的精神并构建 workout.cool对我来说是一次充满挑战但也极其充实的旅程。它验证了现代 Web 技术栈Next.js 14, Prisma, Tailwind在构建复杂全栈应用时的强大与高效也让我深刻体会到清晰的架构FSD对于长期维护的重要性。最让我欣慰的是通过开源这个工具不再是一个人的项目而是汇聚了许多同样热爱健身与技术的开发者智慧的作品。每一次 Issue 的提交、每一次 PR 的合并都让这个平台变得更健壮、更实用。如果你也在寻找一个能完全掌控数据、高度可定制的健身管理工具不妨尝试部署属于你自己的 workout.cool或者加入我们一起把它变得更好。毕竟最好的健身伙伴可能就是你自己亲手参与打造的那一个。