基于Strava API与Serverless架构构建个人跑步数据分析与自动化报告系统
1. 项目概述:一个跑者的自动化数据中枢
如果你和我一样,是个喜欢跑步、又爱折腾数据的跑者,那你肯定对Strava不陌生。每次跑完步,看着APP里那些距离、配速、爬升的数据,总感觉它们背后藏着更多故事——比如这次跑步的节奏控制得怎么样?和上周比是进步了还是退步了?这些数据能不能更直观地分享给一起跑的朋友们?这个名为“strava-run-log”的项目,就是我为了解决这些问题而搭建的一个私人自动化数据中枢。它的核心目标很简单:自动抓取我在Strava上的跑步记录,进行深度分析,然后把一份清晰、有趣、包含行动建议的报告,自动推送到我的Discord频道,甚至还能帮我生成适合发在社交媒体上的文案草稿。
整个系统就像一个不知疲倦的跑步助理。我不再需要手动导出数据、打开表格软件做分析,或者绞尽脑汁想今天的朋友圈文案。从跑步结束、数据同步到Strava的那一刻起,后续的所有流程——数据获取、解析、分析、报告生成和分发——全部自动完成。这不仅仅是省时间,更重要的是建立了一个持续、客观的反馈循环。我能立刻看到每次训练的效果,了解自己的状态趋势,这对于科学训练和保持动力至关重要。无论你是刚开始用数据指导跑步的新手,还是已经有一定跑量、希望提升训练效率的严肃跑者,这个项目都能帮你把散落的数据点,串联成有价值的洞察。
2. 核心架构与设计思路拆解
2.1 为什么选择“事件驱动+Serverless”架构?
这个项目的核心逻辑是“事件响应”:当Strava上有新的跑步活动时,触发后续一系列处理流程。为了实现这一点,我放弃了传统的定时轮询(比如每隔一小时去检查一次有没有新跑步),而是采用了Strava提供的Webhook(网络钩子)机制。Webhook允许Strava在特定事件(如新的跑步活动被创建)发生时,主动向一个我指定的URL发送一个HTTP POST请求。这带来了几个关键优势:首先是实时性,跑步一结束,报告几乎立刻就能生成;其次是高效性,只有在真正有数据更新时才消耗计算资源,避免了无意义的空转查询。
为了承载这个Webhook端点,并运行后续的数据处理逻辑,我选择了Vercel这样的Serverless(无服务器)平台。Serverless架构完美匹配了本项目“低频触发、瞬时计算”的特点。我的代码被拆分成一个个独立的函数(例如,处理Webhook的函数、生成报告的函数),只有当事件发生时,对应的函数才会被唤醒执行,执行完毕即释放资源。这意味着我几乎不需要关心服务器的维护、扩容等问题,而且成本极低,在个人使用量级下甚至可以完全免费。整个数据流可以概括为:Strava事件 -> Vercel Webhook函数触发 -> 获取活动详情 -> 分析数据 -> 格式化消息 -> 调用Discord Webhook发送。这个链条清晰、解耦,每一环都可以独立开发和调试。
2.2 数据流与模块职责划分
为了让整个系统清晰可维护,我将功能划分成了几个逻辑模块:
-
认证与数据获取模块 :这是与Strava API交互的基石。它负责处理OAuth 2.0授权流程,管理访问令牌的获取、刷新与存储。核心任务是向Strava请求跑步活动的列表和详细数据,包括分段配速、心率、步频等原始数据。考虑到令牌会过期,这个模块还必须内置自动刷新令牌的逻辑,确保长期运行的稳定性。
-
数据分析与报告生成引擎 :这是项目的“大脑”。它接收原始的活动数据,并进行多维度加工:
- 基础摘要 :计算总距离、运动时间、平均配速、爬升高度等。
- 节奏分析 :这是我最看重的部分。通过分析每公里或每英里的分段配速,判断本次跑步是“负分割”(后程加速)、“正分割”(后程降速)还是“匀速”。这直接反映了体能分配策略的好坏。
- 训练建议生成 :基于本次跑步的表现(如平均心率、配速稳定性、疲劳感)和近期训练负荷,给出简单的后续训练建议,例如“建议明天进行轻松跑恢复”或“可以尝试一次间歇跑”。
- 社交文案生成 :根据分析结果,用不同的语气(如Threads的轻松闲聊风、X的简洁精炼风)自动生成包含关键数据和“梗”的文案草稿。
-
消息分发与集成模块 :这是项目的“嘴巴”和“手”。主要利用Discord的Incoming Webhook功能,将生成好的格式化报告(Markdown格式,包含表格和表情符号)发送到指定的Discord频道。我专门建立了一个名为“run-log”的频道,所有报告都按时间顺序排列,形成了一个可搜索的训练日志。未来扩展也可以轻松接入Slack、Telegram等。
-
实时教练与健康报告端点 :这是项目的进阶功能,旨在突破Strava数据更新的延迟。通过一个自定义的API端点,我可以从Apple Watch或其他健康应用实时推送跑步中的关键指标(当前配速、即时心率、已跑距离)。系统会根据预设的个人目标(目标配速、最大心率)进行判断,并在需要时通过Discord发送实时语音提示般的指导信息,如“当前配速过快,建议放缓至5‘30’/km”。此外,每周报告端点会统计过去7天的总运动时间,并对照世界卫生组织(WHO)推荐的中高强度运动量(每周150-300分钟)给出达标情况反馈。
设计心得 :将“数据获取”、“分析逻辑”和“消息推送”分离是关键。这样,当我想要更换推送平台(比如从Discord换成飞书),或者增加新的分析维度(比如增加跑步功率分析)时,只需要修改其中一个模块,而不会牵一发而动全身。这种松耦合的设计大大提升了项目的可扩展性和可维护性。
3. 从零开始的详细搭建指南
3.1 Strava API应用创建与初始配置
一切始于Strava API。你需要创建一个“应用”来获得访问数据的合法身份。首先,用你的Strava账号登录后,访问 https://www.strava.com/settings/api 。点击“Create Your App”按钮。这里有几个字段需要仔细填写:
- 应用名称 :可以起一个像“My Run Logger”这样的名字。
- 类别 :选择“Data Analysis & Research”或类似选项。
- 网站 :可以填写你的个人博客或GitHub主页,如果没有,填写
http://localhost也可。 - 授权回调域名 :这是 至关重要的一步 。在开发阶段,我们使用本地测试,所以这里填写
localhost。请注意,Strava要求必须是完整的域名格式,但localhost是特例。如果你未来打算部署到线上(如your-app.vercel.app),则需要先在这里添加该域名,否则OAuth回调会失败。
创建成功后,你会获得两串密钥: Client ID (一串数字)和 Client Secret (一串加密字符串)。请立即将它们妥善保存, Client Secret 尤其敏感,相当于你应用的密码,绝不能泄露。我建议在项目根目录下创建一个名为 .secrets 的文件夹(注意前面的点),并在其中创建一个 strava.env 文件来存储它们。同时,务必在项目的 .gitignore 文件中添加 .secrets/ 这一行,确保这些敏感信息不会被意外提交到公开的代码仓库。
你的 .secrets/strava.env 文件初始内容如下:
STRAVA_CLIENT_ID=你的ClientID数字
STRAVA_CLIENT_SECRET=你的ClientSecret长字符串
STRAVA_ACCESS_TOKEN=
STRAVA_REFRESH_TOKEN=
STRAVA_TOKEN_EXPIRES_AT=
STRAVA_ATHLETE_ID=
后面三个字段目前留空,我们下一步来填充它们。为了安全,建议在终端执行 chmod 600 .secrets/strava.env ,将文件权限设置为仅所有者可读可写。
3.2 OAuth授权流程详解与令牌获取
Strava API使用OAuth 2.0协议进行授权。简单来说,就是需要你(用户)明确授权我这个“应用”访问你的数据。这个过程只需要手动做一次。
首先,我们需要构造一个授权URL。将下面的 YOUR_CLIENT_ID 替换成你实际的Client ID数字:
https://www.strava.com/oauth/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost/exchange_token&approval_prompt=force&scope=read,activity:read_all
redirect_uri:必须和你在创建应用时填写的回调域名匹配,这里我们用了http://localhost/exchange_token。实际上,/exchange_token这个路径可以是任意值,只要前后一致即可。approval_prompt=force:确保每次都会弹出授权页面,避免使用缓存的授权。scope=read,activity:read_all:这是请求的权限范围。read是基础读取权限,activity:read_all是读取 所有 活动(包括私密活动)的权限,这对完整分析是必需的。
在浏览器中打开这个URL,你会看到Strava的授权页面,询问你是否允许“My Run Logger”访问你的基本信息和活动数据。点击“授权”后,页面会跳转到类似 http://localhost/exchange_token?code=1a2b3c4d5e6f7g8h&scope=read,activity:read_all 的地址。注意URL中的 code=1a2b3c4d5e6f7g8h (实际code是一串不同的字符),这个 code 就是我们需要的关键临时凭证,它有效期很短。
接下来,我们用这个 code 去交换长期的令牌。打开终端,使用 curl 命令(确保你已加载了 .secrets/strava.env 中的变量,或直接替换下面的值):
curl -X POST https://www.strava.com/oauth/token \
-d client_id=你的CLIENT_ID \
-d client_secret=你的CLIENT_SECRET \
-d code=上一步获取的CODE \
-d grant_type=authorization_code
如果一切顺利,Strava会返回一个JSON响应,其中包含 access_token 、 refresh_token 、 expires_at (令牌过期的时间戳,单位秒)以及 athlete.id 。请立即将这些值更新到你的 strava.env 文件中:
STRAVA_ACCESS_TOKEN=eyJh...(很长的一串)
STRAVA_REFRESH_TOKEN=abcdef...(另一串)
STRAVA_TOKEN_EXPIRES_AT=1744567890
STRAVA_ATHLETE_ID=1234567
至此,最复杂的授权环节就完成了。 access_token 用于调用绝大多数API,但它有过期时间(通常为6小时)。 refresh_token 则可以在 access_token 过期后,用来获取一组新的令牌,而且 refresh_token 本身有效期很长(默认为“长期有效”,但建议定期使用以保持其活性)。
3.3 核心API调用与数据解析实战
拿到令牌后,我们就可以和Strava API对话了。最常用的两个接口是获取活动列表和活动详情。
获取最近一次活动 :用于测试和常规的“最新跑步报告”功能。
curl -G “https://www.strava.com/api/v3/athlete/activities” \
-H “Authorization: Bearer $STRAVA_ACCESS_TOKEN” \
-d “per_page=1” \
-d “page=1”
-G表示使用GET请求,并将-d参数以查询字符串形式附加。per_page=1表示只获取一条记录。- 返回的是一个活动对象的数组,里面包含了活动的ID、名称、距离、运动时间、平均速度等概要信息。但注意, 分段数据(splits)不包含在这个列表接口中 。
获取活动详情(包含分段数据) :这是进行分析的基石。你需要从上一步的响应中拿到活动的 id 。
curl -H “Authorization: Bearer $STRAVA_ACCESS_TOKEN” \
“https://www.strava.com/api/v3/activities/{activity_id}”
这个接口返回的信息非常丰富。对于我们分析至关重要的字段包括:
distance: 总距离(米)。moving_time: 运动时间(秒),排除暂停时间。total_elevation_gain: 总爬升(米)。average_speed: 平均速度(米/秒),需要转换为更常用的“每公里配速”。has_heartrate: 布尔值,表示是否有心率数据。average_heartrate: 平均心率(如有)。average_cadence: 平均步频(步/分钟,如有)。splits_metric: 核心字段 。这是一个数组,包含了每公里的分段数据,如split的序号、distance、moving_time、elevation_difference等。通过分析这个数组里每个分段的速度变化,就能判断出本次跑步的节奏策略。
实操技巧 :在代码中处理这些数据时,一定要做好错误处理和空值判断。例如,不是每次跑步都戴心率设备,所以
average_heartrate可能为null。你的代码应该能优雅地处理这种情况,比如在报告中显示“心率数据缺失”,而不是直接报错崩溃。另外,Strava API有调用频率限制(默认每15分钟100次,每天1000次),对于个人项目通常足够,但编写代码时也应避免不必要的重复调用。
4. 数据分析引擎的构建与报告生成
4.1 从原始数据到跑步摘要
拿到活动的详细数据后,第一步是将其转化为人类可读的摘要。这里涉及一些简单的计算和单位转换。
核心计算示例(以Python伪代码为例) :
# 假设 activity 是从API获取的活动详情字典
distance_km = activity[‘distance’] / 1000.0 # 米转公里
moving_time_min = activity[‘moving_time’] / 60.0 # 秒转分钟
# 计算平均配速(分钟/公里)
average_speed_mps = activity[‘average_speed’] # 米/秒
if average_speed_mps > 0:
pace_sec_per_km = 1000 / average_speed_mps # 跑一公里需要的秒数
pace_min_per_km = pace_sec_per_km / 60
# 格式化为 “5:30” 这样的形式
pace_str = f“{int(pace_min_per_km)}:{int(pace_sec_per_km % 60):02d}”
else:
pace_str = “N/A”
# 处理可能为空的心率和步频
hr = activity.get(‘average_heartrate’, ‘N/A’)
cadence = activity.get(‘average_cadence’, ‘N/A’)
这样,我们就得到了一次跑步最基础的几个指标: 距离 、 运动时间 、 平均配速 和 爬升 。心率和步频如果有数据则显示,没有则用“N/A”或“设备数据缺失”友好提示。
4.2 分段配速分析与节奏判断
这是分析部分的精华。 splits_metric 数组给了我们逐公里的时间。计算每个分段的配速后,我们可以进行模式识别。
分析逻辑 :
- 提取分段数据 :遍历
splits_metric,计算每一公里的配速(秒/公里)。 - 计算趋势 :比较后半程平均配速与前半程平均配速。
- 如果后半程平均配速 快于 前半程,则为 负分割 (Negative Split) ,通常意味着体力分配出色,后程加速能力强。
- 如果后半程平均配速 慢于 前半程,则为 正分割 (Positive Split) ,可能意味着起步过快,后程乏力。
- 如果两者相差无几,则为 匀速跑 (Even Pace) ,是控制力强的表现。
- 稳定性评估 :计算所有分段配速的标准差。标准差越小,说明配速越稳定。
- 生成解读 :结合趋势和稳定性,生成一句简单的分析。例如:“本次跑步呈现明显的负分割(后5公里平均配速4‘55’‘ vs 前5公里5’05‘’),且配速稳定(标准差仅3秒),状态出色!”
注意事项 :Strava提供的
max_speed字段需要谨慎对待。GPS信号在城市高楼间或隧道中可能发生“漂移”,导致瞬间速度异常增高。因此,这个值更适合作为参考,而不应作为关键指标。在报告中,可以标注“最高速度(可能受GPS信号影响)”。
4.3 社交文案的自动化生成策略
生成SNS文案的目标是: 有趣、有信息量、符合平台调性 。我的策略是基于分析结果,使用模板填充关键数据点,并加入一些随机的、符合语境的“梗”或表情符号。
文案模板示例 :
- Threads风格(偏轻松、叙事) :
“今早的10公里打卡完成!🧐 数据上来说有点意思:平均配速5‘20’‘,但仔细看分段,居然跑了个负分割(后程比前程快!),看来昨晚睡得好确实有用。总爬升150米,算是小有起伏。心率带忘了充电…下次一定!#跑步 #数据控 #Strava”
- X风格(偏简洁、精炼) :
“10K | 53‘20’‘ | 配速5‘20’‘/km | 爬升150m。节奏控制完美,负分割达成。数据不会说谎。 #跑步 #Strava”
实现方法 :可以预先定义几个不同的文案模板,每个模板中有一些占位符,如 {distance} , {pace} , {analysis_comment} , {hashtags} 。然后根据本次跑步的数据和分析结果,选择合适的模板,填充占位符,并从一个预定义的“词库”中随机选取一个与本次跑步表现(如“状态好”、“匀速”、“爬升大”)匹配的短句或表情组合进去,增加文案的随机性和趣味性。
5. 与Discord和Vercel的集成部署
5.1 配置Discord Webhook实现自动推送
Discord的Incoming Webhook功能是实现自动通知的绝佳工具。首先,在你的Discord服务器中,进入“服务器设置” -> “集成” -> “Webhook”,创建一个新的Webhook。选择你想要发送消息的频道(比如我创建的 #run-log 频道),然后点击“复制Webhook URL”。这个URL长这样: https://discord.com/api/webhooks/一串数字/一串密钥 。
在你的项目代码中,发送消息就变得非常简单,只需要向这个URL发送一个携带特定JSON数据的POST请求。以下是一个Python示例:
import requests
import json
def send_to_discord(summary, analysis, sns_draft):
webhook_url = “YOUR_DISCORD_WEBHOOK_URL”
# 构建Discord支持的Embed消息格式,可以使消息更美观
data = {
“embeds”: [{
“title”: “🏃 跑步日志更新”,
“description”: summary,
“color”: 0x00ff00, # 绿色
“fields”: [
{“name”: “📊 深度分析”, “value”: analysis, “inline”: False},
{“name”: “💬 SNS文案草稿”, “value”: sns_draft, “inline”: False}
],
“footer”: {“text”: “自动生成于 strava-run-log”}
}]
}
headers = {‘Content-Type’: ‘application/json’}
response = requests.post(webhook_url, data=json.dumps(data), headers=headers)
return response.status_code == 204
这样,每次生成的报告就会以一条格式精美的Embed消息形式出现在你的Discord频道里,非常适合作为日志回顾。
5.2 使用Vercel部署并设置Strava Webhook
为了让整个流程完全自动化,我们需要一个公开的、始终在线的URL来接收Strava的Webhook通知。Vercel的Serverless Functions非常适合这个任务。
部署步骤 :
- 将你的代码推送到GitHub等代码仓库。
- 在Vercel官网导入你的项目仓库。
- 在Vercel项目的“Settings” -> “Environment Variables”中,添加所有必要的环境变量:
STRAVA_CLIENT_ID,STRAVA_CLIENT_SECRET,STRAVA_ACCESS_TOKEN,STRAVA_REFRESH_TOKEN,STRAVA_TOKEN_EXPIRES_AT,STRAVA_VERIFY_TOKEN(一个你自己设定的随机字符串,用于验证),以及上一步复制的DISCORD_WEBHOOK_URL。 - 部署后,你会获得一个类似
https://your-project.vercel.app的域名。
创建Webhook端点 :在你的项目中创建一个API路由文件,例如 /api/strava/webhook.js (Node.js) 或 /api/strava/webhook.py (Python)。这个端点需要处理两种请求:
- GET请求(验证) :Strava在创建订阅时会发送一个GET请求,包含
hub.challenge参数。你的端点必须原样返回这个挑战码。# Python (Flask-like) 示例 @app.route(‘/api/strava/webhook’, methods=[‘GET’]) def verify_webhook(): mode = request.args.get(‘hub.mode’) token = request.args.get(‘hub.verify_token’) challenge = request.args.get(‘hub.challenge’) if mode == ‘subscribe’ and token == os.environ[‘STRAVA_VERIFY_TOKEN’]: return jsonify({‘hub.challenge’: challenge}) return ‘Verification failed’, 403 - POST请求(事件处理) :当有新活动时,Strava会发送POST请求。你需要验证请求来源(通过验证token),然后解析其中的
object_id(即活动ID),最后触发你的数据处理和推送流程。@app.route(‘/api/strava/webhook’, methods=[‘POST’]) def handle_webhook(): # 1. 可选的签名验证(更安全) # 2. 解析JSON,确认 event_type 是 ‘create’ 且 object_type 是 ‘activity’ data = request.json if data[‘object_type’] == ‘activity’ and data[‘aspect_type’] == ‘create’: activity_id = data[‘object_id’] # 异步触发你的处理函数,避免超时 process_new_activity.delay(activity_id) return ‘EVENT_RECEIVED’, 200 return ‘Ignored event’, 200
注册Webhook订阅 :最后一步是告诉Strava你的Webhook地址。你需要调用Strava的订阅API。可以写一个简单的脚本 register_subscription.sh :
#!/bin/bash
curl -X POST https://www.strava.com/api/v3/push_subscriptions \
-F client_id=$STRAVA_CLIENT_ID \
-F client_secret=$STRAVA_CLIENT_SECRET \
-F callback_url=$WEBHOOK_CALLBACK_URL \
-F verify_token=$STRAVA_VERIFY_TOKEN
执行这个脚本(确保环境变量已设置),如果成功,Strava就会开始向你的Vercel应用发送事件通知了。现在,你可以去跑个步试试,结束后几分钟内,应该就能在Discord频道里看到自动生成的报告了!
6. 进阶功能:实时教练与健康周报
6.1 构建实时跑步数据接收端点
Strava的数据同步有延迟,有时长达几分钟。为了获得真正的实时反馈,我增加了一个独立的API端点 /api/live/metrics 。这个端点的设计初衷是接收来自Apple Watch、Garmin手表或其他跑步APP(通过如“健康”数据桥接工具)实时推送的跑步关键指标。
端点设计 :它接受一个JSON格式的POST请求,包含以下字段:
session_id: 本次跑步会话的唯一标识,用于区分不同次跑步。pace_sec: 当前实时配速(秒/公里)。hr: 当前实时心率。distance_km: 已跑距离(公里)。elapsed_sec: 已运动时间(秒)。force: 布尔值,为true时强制发送一次建议,用于测试。
核心逻辑 :这个端点内部维护着一个简单的状态机,针对每个 session_id 记录最近一次收到数据的时间、心率状态等。它会根据预设的个性化目标(如目标配速 COACH_TARGET_PACE_SEC 、最大安全心率 COACH_MAX_HR )进行判断:
- 配速过快/过慢提醒 :如果当前配速持续快于或慢于目标配速一定阈值(例如10秒)超过30秒,则生成提示。
- 心率过高预警 :如果当前心率持续超过最大安全心率超过
COACH_HR_SUSTAINED_SEC(例如120秒),则发出预警,建议减速。 - 冷却提醒 :在跑步开始后的一段时间(
COACH_COOLDOWN_SEC,例如90秒)内,如果配速过快,会提示“起步稍快,注意热身”。 - 反馈频率控制 :通过
COACHING_FREQUENCY_SEC环境变量控制,避免在短时间内发送过多消息打扰跑者。
个性化配置 :为了适配不同跑者(例如我自己和我父母),我通过环境变量 COACH_USER_PROFILES_JSON 来存储多套配置。这是一个JSON字符串,包含了不同 user_id 对应的目标参数。这样,当实时数据推送时带上 user_id 字段,系统就能调用对应的配置进行判断,实现个性化的实时指导。
6.2 实现WHO标准周度健康报告
世界卫生组织建议成年人每周至少进行150-300分钟的中等强度有氧运动。这个端点 /api/strava/weekly-report 就是为了量化我每周的跑步是否达标。
实现原理 :
- 获取数据 :调用Strava的
athlete/activities接口,获取最近7天的所有跑步活动。 - 计算有效时间 :遍历这些活动,累加每项活动的
moving_time(运动时间)。这里有一个关键点:Strava的活动有“运动类型”,我们需要筛选出“Run”类型。另外,是否所有跑步都算“中等强度”?一个简单的启发式规则是:将平均心率高于静息心率一定比例(例如,达到最大心率储备的60%-70%)的跑步计入。在初始版本中,为了简化,我暂时将所有跑步时间都计入,未来可以加入心率区间过滤。 - 生成报告 :计算总分钟数,并与150和300分钟两个阈值比较。生成类似这样的报告:“过去一周(MM-DD至MM-DD),您共跑步X次,总运动时间YYY分钟。达到WHO推荐的中等强度运动量下限(150分钟)!继续加油,向300分钟的目标迈进吧!”
- 推送报告 :端点提供一个查询参数
?send=true。当调用这个参数时,报告不仅会返回给调用者,还会通过之前配置好的Discord Webhook发送到频道中,非常适合设置一个每周日的定时任务(例如,用Vercel的Cron Jobs)来自动推送周报。
这两个进阶功能将项目从一个被动的数据记录器,转变为一个主动的、个性化的跑步伙伴和健康顾问,极大地提升了其实用性和趣味性。
7. 常见问题排查与优化心得
7.1 授权与令牌相关错误
-
问题:
invalid_client或client_id invalid- 排查 :首先检查
STRAVA_CLIENT_ID和STRAVA_CLIENT_SECRET是否与API应用设置中的完全一致,且没有多余的空格。Client ID是纯数字,Client Secret是字符串。确保你在请求中使用的redirect_uri与创建应用时填写的完全匹配(包括http还是https,末尾是否有斜杠)。 - 解决 :最稳妥的方式是重新从Strava API设置页面复制这两个值,并更新你的环境变量文件。
- 排查 :首先检查
-
问题:
activity:read_permission missing- 排查 :在获取活动详情时返回403错误,提示权限不足。
- 解决 :这说明OAuth授权时申请的
scope不包含activity:read_all。你需要让用户重新授权。在构造授权URL时,务必包含&scope=read,activity:read_all和&approval_prompt=force参数,以强制弹出授权页面并申请完整权限。用户授权后,使用新的code换取令牌。
-
问题:令牌过期导致
401 Unauthorized- 排查 :这是最常见的问题。
access_token有效期很短(约6小时)。你的代码必须能够自动处理过期情况。 - 解决 :在每次调用API前,检查
STRAVA_TOKEN_EXPIRES_AT的时间戳是否已过期(或即将过期,例如5分钟内)。如果过期,立即使用refresh_token调用令牌刷新接口(grant_type=refresh_token)获取新的access_token、refresh_token和expires_at,并更新你的存储(环境变量或数据库)。 重要 :刷新后,旧的refresh_token会失效,必须使用返回的新refresh_token替换旧的。
- 排查 :这是最常见的问题。
7.2 Webhook与部署相关问题
-
问题:Strava Webhook订阅失败或收不到事件
- 排查1(验证失败) :运行注册订阅脚本时,确保
callback_url是HTTPS且可公开访问(Vercel部署的域名),并且verify_token与你代码中验证逻辑使用的STRAVA_VERIFY_TOKEN完全一致。Strava在创建订阅时会立即发送一个GET请求进行验证,你的端点必须正确响应hub.challenge。 - 排查2(事件未触发) :确认活动是“跑步”类型,并且是 新创建 的。编辑已有活动不会触发
create事件。检查Vercel函数的日志,看是否有POST请求到达以及处理过程中是否有错误。 - 解决 :使用
curl或 Postman 手动向你的Webhook端点发送一个模拟的POST请求,检查后端逻辑是否能正确运行并调用Discord。确保所有环境变量在Vercel中已正确设置。
- 排查1(验证失败) :运行注册订阅脚本时,确保
-
问题:Discord未收到消息
- 排查 :首先检查Discord Webhook URL是否正确,且没有失效(可以在Discord设置中重新复制)。查看Vercel函数日志,确认消息发送函数的HTTP请求是否成功(状态码应为204)。如果失败,检查发送的消息体格式是否符合Discord API要求,特别是Embeds格式。
- 解决 :简化测试,先尝试发送一条纯文本消息,确保Webhook基础功能正常,再逐步测试复杂的Embed格式。
7.3 性能与数据准确性优化
-
心得:异步处理与错误重试
- Webhook端点必须在2秒内响应Strava,否则Strava会视为失败并可能重试。因此, 绝对不能在Webhook处理函数内同步执行获取活动详情、分析、推送等所有操作 。正确的做法是,Webhook端点只负责验证和接收事件,然后立即返回成功。将活动ID放入一个任务队列(如Redis,或Vercel上可以使用Serverless Functions的异步调用特性),由另一个后台函数异步处理耗时的任务。同时,对于可能失败的步骤(如调用Strava API、发送Discord消息),要加入指数退避的重试机制。
-
心得:数据缓存与API限流
- Strava API有严格的限流。避免不必要的重复调用。例如,在生成周报时,获取最近7天活动列表后,如果还需要每个活动的详情,不要立即为每个活动ID单独调用详情接口,这会导致请求数暴增。应该先批量获取活动概要(列表接口一次最多可返回200条),然后只对确实需要的活动(比如跑步)再去获取详情。可以考虑将一些不常变的数据(如运动员基本信息)进行短期缓存。
-
心得:个性化与扩展性
- 环境变量
COACH_USER_PROFILES_JSON的引入,使得系统可以轻松服务于多个用户。未来,可以考虑建立一个简单的数据库(如Vercel Postgres)来存储用户配置和跑步历史,实现更复杂的趋势分析和长期数据追踪。对于社交文案,可以引入简单的机器学习模型(如基于少量样本的文本生成),让文案更加多样化和个性化,而不仅仅是模板替换。
- 环境变量
更多推荐
所有评论(0)