从“步数排行榜”到“AI 健身管家”——Workout Challenge 如何用 Django + React 把同事圈卷成伦敦程序员的团建神器

关键词:Docker 化部署、Strava 自动同步、Celery 定时任务、隐私优先、AI 营养贴士、暗黑模式、响应式邮件


开场白:当 Slack 通知响起,“@all,九月步数挑战开始!”

如果你也在伦敦科技公司待过,一定熟悉这种场景——

  • iPhone 用户把 Apple Watch 环合上截图甩群;
  • Android 同事反手甩一张 Google Fit 柱状图;
  • 骑折叠车通勤的设计师把 Garmin 码表数据导成 CSV,还要手动换算公里数;
  • HR 小姐姐把 Excel 贴在 Notion,结果有人把步数填到“卡路里”列……

痛点总结:设备割裂、数据格式乱、隐私心里打鼓、统计表格劝退。

Workout Challenge 的出现,就是要把这场“跨物种”健身社交,变成“一次 Docker compose up”就能 hosting 的私域竞赛。下面带你拆它的技术骨骼。


一、整体架构:把“前端仪表盘-后端 API-定时任务”装进一只容器

┌---------------------------┐
│ Nginx (80)                │
│  ├─ /  → React 静态资源    │
│  └─ /api → Gunicorn(Django)│
└------------┬--------------┘
             │Celery Worker
             │Celery Beat
             │Redis
             ▼
┌---------------------------┐
│ Postgres / SQLite          │
└---------------------------┘
  • 前端:React + MUI,暗黑模式用 prefers-color-scheme 自动切换,图表用 Chart.js,把“环闭合”做成圆环进度条,UI 一秒“果味”。
  • 后端:Django + Django-REST-framework,JWT 登录,权限到“竞赛”级,保证 A 公司的排行榜不会把 B 公司数据泄露。
  • 任务队列:Celery + Redis,每天 04:00 向 Strava 拉数据,自动生成周一排行榜、周四个人报告。
  • 邮件模板:MJML 写的响应式 HTML,在 Outlook/Gmail/Apple Mail 都能“颜值在线”。

二、30 秒本地体验(真·可跑通)

# 1) 有 Docker 就行
docker run -p 80:80 vanalmsick/workout_challenge
# 浏览器打开 localhost,注册→建赛→手动加数据,即刻出榜。

# 2) 想源码级 Hack
git clone <repo>
cd workout_challenge
# 前端
cd src-frontend && npm i && npm start
# 后端
cd src-backend && python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# 环境变量示例
export DEBUG=true STRAVA_CLIENT_ID=xxxxx STRAVA_CLIENT_SECRET=xxxxx
python manage.py migrate
python manage.py runserver
# 定时任务
redis-server &  # 另窗
celery -A workout_challenge worker -l INFO
celery -A workout_challenge beat -l INFO

三、生产部署:docker-compose.yml 的“每一行”都藏着血泪教训

下面这份是作者在伦敦某金融公司内网落地的“最终版”,把调试端口也留好,方便你排雷:

version: '3.9'
services:
  workoutchallenge:
    image: vanalmsick/workout_challenge
    ports:
      - "80:80"
      - "5555:5555"   # Celery Flower,仅限内网
      - "9001:9001"   # Supervisord
      - "8000:8000"   # Django Admin
    volumes:
      - /usr/pi/workout_challenge/django:/data   # 持久化上传头像、SQLite(可选)
    environment:
      - POSTGRES_HOST=workoutchallenge-database
      - POSTGRES_DB=workoutchallenge
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=use_a_vault_pls
      - MAIN_HOST=https://fit.yourcompany.com
      - SECRET_KEY=<32-random-chars>
      - STRAVA_CLIENT_ID=12345
      - STRAVA_CLIENT_SECRET=<from-strava>
      - EMAIL_HOST=smtp.gmail.com
      - EMAIL_PORT=465
      - EMAIL_HOST_USER=fit@yourcompany.com
      - EMAIL_HOST_PASSWORD=<app-password>
      - OPENAI_API_KEY=<optional-for-ai-tips>
    restart: unless-stopped
    depends_on:
      database:
        condition: service_healthy

  database:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=use_a_vault_pls
    volumes:
      - /usr/pi/workout_challenge/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]

踩坑提示:

  • MAIN_HOST 一定带协议 https://,否则 Strava OAuth 回调被挡。
  • 用 Gmail SMTP 记得开“两步验证→应用专用密码”,公司 Exchange 同理。
  • 如果跑在树莓派,arm64 镜像已就绪,镜像体积 ≈ 300 MB,已压到极限。

四、Strava 同步的“魔法 15 分钟”

  1. 登录 Strava → Settings → My API Application → 创建得到 CLIENT_ID & CLIENT_SECRET
  2. 用户首次点击“Link Strava”→ OAuth 回调 → 后台拿 refresh_token → 存库。
  3. Celery Beat 每天 04:00 触发 sync_strava 任务:

    • 批量调用 /athlete/activities,分页拉到昨日数据;
    • 按“竞赛规则”过滤(例如仅 Ride/Run,最短 10 min);
    • 写库同时触发“积分重算”→ 排行榜实时更新。

速率限制:普通应用 100 req/15 min、1000/天;申请 Strava Developer Program 可升到 300/3000,记得在 README 里附申请链接,帮读者省邮件往返。


五、AI 营养小贴士:把 OpenAI 当成“健身邮件彩蛋”

在周一排行榜邮件末尾,加一段由 gpt-3.5-turbo 写的“本周饮食/恢复建议”:

prompt = f"""
You are a witty UK nutritionist. Based on the stats below, give 2 fun tips
(50 words max each, emoji allowed, no medical claims).
Total workouts: {count}, avg kcal: {avg_kcal}, longest distance: {max_km} km.
"""

用户反馈:“为了看 AI 段子,居然主动把 workout 记满!”——技术价值 = 提升数据完整性。


六、暗黑模式与响应式邮件:把“像素眼”程序员也留住

  • 前端用 useMediaQuery('(prefers-color-scheme: dark)') 实时切换主题。
  • 邮件用 MJML 写一次,自动输出 Outlook + Gmail + 手机端兼容 HTML;暗黑模式通过 @media (prefers-color-scheme: dark) 内嵌 CSS 完成。

实测:iOS 邮件夜间模式打开,背景自动 #1c1c1e,文字 #f2f2f7,排行榜彩色条不变形,程序员群直呼“舒适”。


七、FAQ:提前回答同事/老板的 6 个灵魂拷问

Q1:步数会不会被公司后台偷窥?
A: 数据存在你们自己的 Postgres,源码可审,Django 权限到“竞赛”级,DBA 只能看到哈希 ID。

Q2:没 Strava 的同事怎么办?
A: 支持手动填“时长/距离/卡路里/次数”,还能上传 GPX,后台用 gpxpy 解析,一样算分。

Q3:如果某人谎报?
A: 规则引擎支持“每日/单次上限”,比如跑步最多 30 km 计分;异常数据自动标红,管理员一键“质询”邮件。

Q4:我们想加“平板支撑分钟”新指标?
A: 在 CompetitionGoal 模型里加一行 metric='plank_min',前端自动识别,不用改表结构。

Q5:会不会给 Strava 刷爆限额?
A: 同步任务用 Celery 限流 + 令牌桶,开发者计划 3000/天 足够 500 人使用。

Q6:能集成企业微信/Slack 推送吗?
A: 只要新增 slack_webhook 字段,Celery 任务里 POST 一份 JSON,十分钟搞定。


收束:当技术把“健身”做成数据产品

Workout Challenge 的精巧在于:没有炫技的“AI 大模型”,却把 Docker、Celery、OAuth、MJML 这些老朋友拧成一股绳,解决了一个真实的小痛点——“跨设备步数排行榜”。

如果你正愁“团建怎么玩”“内网能不能跑”“数据会不会泄露”,docker run 一行命令,就能让全公司秒变“环闭合”战场。剩下的,只是你和同事谁先把积分刷到 100% 的故事。


下一步行动

  1. Star 项目,把镜像拉到本地跑一次 → 评估 UI 是否合老板口味;
  2. docker-compose.yml 把调试端口留好,内部 PoC 一天搞定;
  3. 申请 Strava Developer Program,把 1000 次/天升到 3000,给全公司发邀请链接;
  4. 打开 src-backend/ai_tips.py,把 prompt 改成你们行业梗,让 AI 邮件彩蛋成为周一最期待的段子。

愿你在下一次“步数挑战”里,既保住隐私,也保住第一名。

“Closing the rings, without closing your data.”