
“AI 软件开发实战教程”系列第 4 篇:在确定工程架构和开始写业务代码之前,用真实请求、真实接收者和可控故障验证关键外部服务,避免把“应该可用”误写成“已经可用”。
上一篇完成正式产品规划后,“邻行”已经明确了第一个受控试用版本要做什么。
其中一个核心能力是主动提醒:车主和乘客不需要一直刷新页面,新候选出现或到了匹配截止时间时,系统应该提醒他们回来查看。
首版计划借助“喵提醒”把消息发送到微信。用户关注服务号、创建提醒单并取得一个喵码,邻行使用这个喵码触发提醒。
我以前在其他项目中使用过这项服务,也确认它能够正常发送消息。
如果只看这一点,似乎可以直接进入架构设计和开发。
但产品规划提出的问题并不只是“能不能发出一条消息”:
当前接口是否仍然可用; 两个不同微信用户是否都能收到; 接口返回什么才算成功; 无效喵码会怎样返回; 重复请求会不会产生重复提醒; 请求超时以后能不能自动重试; 第三方服务停止时,站内业务是否还能继续; 提醒正文会不会把用户的出行信息交给第三方。
这些问题不应该留到业务代码写完以后才回答。
所以,这一节点没有开始搭建应用,而是先完成 Gate A:喵提醒接口验证。
1. “以前用过”是一条证据,但不是当前结论
已有经验当然有价值。
它说明这个服务不是完全未知的候选方案,也说明最基本的发送能力曾经成立。我们可以在此基础上缩小调查范围,不必从“这个产品是否真实存在”开始。
但外部服务会变化,项目场景也不同。
另一个项目能够发送一条运维通知,不代表邻行已经解决了双用户提醒、重复发送、隐私和降级问题。
这里需要区分三句话:
我以前使用过这个服务 ≠ 当前接口契约没有变化 ≠ 当前产品的提醒流程已经通过历史经验可以成为当前验证的输入,不能成为跳过验证的理由。
2. 先把外部依赖变成一道门
“之后测试一下接口”很容易被后续任务挤掉。
为了让验证真正影响项目进度,产品规划把喵提醒设置成一道明确的技术门禁。
通过以前,需要验证:
两个不同微信接收者的真实送达; 接口地址、参数、响应和基本内容限制; 无效或失效喵码; 官方频率建议; 超时、服务端错误和网络结果不确定; 第三方是否提供幂等,也就是同一个业务事件重复请求时能否避免重复执行; 额度、使用条款、隐私和停服风险; 服务不可用时的站内状态和替代方案。
如果没有通过,产品仍然允许用户发布和查看信息,但只能使用站内提醒,也不能启动用于验证“主动提醒价值”的正式试用。
这样,技术试验就不再是一份可有可无的调查,而是一个会改变产品承诺和开发顺序的节点。
3. 先定义怎样测试,避免测试本身制造问题
第三方接口验证并不等于随意发送大量请求。
这次测试先确定了几个边界:
只使用专门创建的临时测试喵码; 测试正文不包含姓名、微信号或真实行程; 文档和仓库不记录喵码; 每次请求遵守官方建议的至少 10 秒间隔; 不通过连续请求主动触发 IP 封禁; 不用超长正文试探破坏性上限; 超时和 HTTP 500 使用本地桩服务模拟,不向第三方制造故障流量。
“能测什么”和“怎样安全地测”应该同时设计。
如果为了验证限流而让当前出口地址被封,或者把真实联系方式写进测试消息,测试本身就会成为新的风险。
4. 官方文档是起点,不是最终事实
本次主要核对了以下公开资料:
资料:喵提醒开发文档 地址:https://www.showdoc.com.cn/miaotixing
资料:常见开发语言调用 API 示例 地址:https://www.showdoc.com.cn/miaotixing/11558945608826863
资料:用户配置建议 地址:https://www.showdoc.com.cn/miaotixing/9165297677700051
资料:喵提醒官网 地址:https://miaotixing.com/
喵提醒的公开资料能够确认一些基本信息:
用户创建提醒单后取得喵码; 开发者通过 GET /trigger请求触发提醒;id参数传递喵码, text参数传递动态正文;字符串使用 UTF-8,正文应当进行网址参数编码; 官方示例建议提醒间隔至少 10 秒; 服务支持 HTTP 和 HTTPS; 普通公众号提醒适合简短正文。
这些资料足够我们开始设计探针,但还不能决定实现细节。
公开代码示例提到读取 JSON 中的 code 和 msg,官网也表示支持 JSON 或 JSONP。实际请求却显示,默认响应是纯文本。
这正是为什么接口文档之后还要有真实验证。
5. 第一个意外:HTTP 200 不代表业务成功
第一次使用明显不符合格式的随机测试值请求 HTTPS 接口,得到:
HTTP/1.1 200 OKContent-Type: text/plain; charset=UTF-8发送失败:参数格式有误随后使用外形符合规则、但实际不存在的随机测试值请求,得到:
HTTP/1.1 200 OKContent-Type: text/plain; charset=UTF-8发送失败:找不到该提醒单两个请求的 HTTP 状态都是 200。
HTTP 200 只能说明服务器正常返回了一个响应,不能说明提醒业务成功。
如果代码只写成这样:
只要 HTTP 状态是 200 → 标记提醒发送成功那么无效喵码也会被记录成成功。
邻行最终确定的判断方式是:
HTTP 状态和响应正文都符合预期 → 第三方已接受正文明确表示参数或提醒单错误 → 第三方明确失败超时、断线或无法识别的正文 → 结果不确定这里使用“第三方已接受”,而不是“用户已经收到”。
接口返回“完成”,只能证明第三方接受了请求。真实送达仍然需要接收端证据。
6. 第二个意外:HTTP 不会自动升级成 HTTPS
官方较早的代码示例主要使用 HTTP 地址。
实际读取 HTTP 入口响应头时,没有发现它自动跳转到 HTTPS,仍然直接返回 HTTP 200。
这意味着不能照抄旧示例,也不能期待服务端替调用方升级连接。
喵码相当于触发提醒的凭据。如果通过普通 HTTP 传输,查询参数中的喵码和提醒正文都可能暴露在未加密链路中。
因此,邻行把下面这条写成强制约束:
服务端只能请求 HTTPS 入口,浏览器前端不能自行拼接触发地址。
技术文档中的示例代码用于解释调用方式,不一定满足当前项目的安全要求。
7. 接口返回成功以后,还要让真实接收者确认
最初使用两个测试喵码发送短中文提醒,两个接口响应都是:
HTTP/1.1 200 OKContent-Type: text/plain; charset=UTF-8完成接收端确认两条提醒都已收到,正文显示完整。
但继续核对以后发现,这两个喵码属于同一个微信账号。
这可以证明:
有效喵码能够通过接口校验; 第三方接受后,提醒能够送达到这个微信账号; 短中文正文可以完整显示。
它不能证明司机和乘客两个不同用户都能收到。
于是又让另一个微信账号注册喵提醒,并分别执行双边测试:
乘客侧:发现车主司机侧:发现乘客两个接口请求都返回“完成”,司机和乘客随后分别确认收到对应消息,正文完整。
到这里,双用户真实送达才算通过。
这个过程提醒我们:
测试数据有两条,不等于测试对象有两个。
验收条件写的是“两个不同接收者”,证据就必须真的来自两个接收者。
8. 相同请求会产生第二条提醒
接下来,把乘客侧完全相同的请求再次发送一次。
接口仍然返回“完成”,没有提供请求编号、幂等标识或“已经处理”的提示。乘客微信也确实收到了第二条相同提醒。
这说明喵提醒不会替邻行处理重复请求。
因此,产品规划中的“一个候选只创建一个业务事件”还需要在工程实现中落实为:
每个候选事件拥有稳定的业务标识; 同一个业务事件只能创建一次发送任务; 司机和乘客的发送结果分别记录; 用户重复点击或后台任务重复执行不能创建新通知; 不能依赖第三方替系统去重。
“接口可以重复调用”是一种能力,“业务应该重复执行”是另一回事。
第三方不提供幂等时,业务系统必须自己承担幂等责任。
9. 超时以后,最危险的动作是立刻重试
如果请求明确返回参数错误,系统知道它失败了。
超时不一样。
超时可能发生在服务端收到请求以前,也可能发生在服务端已经处理、但响应没有回到客户端以后。
为了验证这个边界,本地启动了一个一次性故障桩:它完整接收请求,但故意不返回响应。
结果是:
本地桩已经看到完整请求; 客户端等待 1 秒后超时; 客户端没有收到任何响应正文。
从调用方看,这次请求“失败”了;从服务端看,请求已经到达,它完全可能已经发送提醒。
如果这时立刻自动重试,而第三方又不提供幂等,用户就可能收到两条消息。
因此,邻行最终采用三类渠道状态:
站内提醒已创建 → 待发送 → 第三方已接受 → 第三方明确失败 → 结果不确定其中“结果不确定”不能自动转换成“再次发送”。
用户仍然可以在站内看到提醒,系统也可以提示外部提醒状态不确定,但不能为了追求表面成功率制造重复轰炸。
10. 服务端错误不能回滚业务事实
HTTP 500 表示第三方服务端发生错误。
这次没有尝试让真实第三方发生故障,而是用本地桩返回一个可控响应:
HTTP/1.0 500 Internal Server ErrorContent-Type: text/plainservice failed这个场景被归类为“第三方明确失败”。
但失败的只是外部发送渠道,不是候选本身。
正确顺序应该是:
候选成立 → 创建站内提醒 → 分别尝试司机和乘客的外部提醒 → 独立记录每一方的渠道结果不能写成:
先调用第三方提醒 → 成功后才保存候选否则第三方短暂故障可能让已经成立的候选直接消失。
外部渠道负责传播业务事实,不应该决定业务事实是否存在。
11. 隐私问题不只在数据库里
即使邻行没有把微信号放进提醒正文,时间和路线组合仍然可能形成出行规律。
最初测试为了确认中文、时间和路线能否完整显示,使用了不对应真实行程的示例内容。
正式产品并不需要把这些信息交给第三方。
提醒的目的只是让用户回来查看,正文可以缩小成:
发现新的同路信息,请返回邻行查看司机或乘客身份、日期、时间、路线和联系方式,都只在用户登录邻行后显示。
同时,喵码按照敏感凭据处理:
只由后端读取; 不出现在浏览器地址、页面源码和分享预览中; 不写入普通应用日志; 不写进错误提示; 不提交到代码仓库; 账户删除时按照产品规则及时删除。
减少第三方接触的数据,通常比在隐私说明中增加更多文字更有效。
12. 公开条款不完整时,不要假装风险不存在
公开资料能够说明基本调用方式、免费服务号提醒和频率建议,但没有找到足够明确的:
数据怎样处理和保留; 严格调用额度; 服务可用性保证; 接口变化通知; 停服后的迁移安排。
这不自动意味着服务不能使用,也不能被写成“没有风险”。
最后由产品负责人作出一个有边界的决定:接受该风险用于小范围受控试用,但必须满足以下条件:
外部正文只发送最小信息; 页面明确说明提醒依赖第三方,可能延迟或失败; 站内提醒始终保留; 第三方不可用时仍允许发布、查找和联系方式交换; 提醒渠道在架构上可以替换; 正式试用期间统计接受、明确失败和结果不确定的数量,但不记录敏感正文。
技术验证不能替产品负责人接受风险,也不能替法律和隐私审查。
它能做的是把风险变得具体,让决定不再建立在模糊感觉上。
13. 为什么 Gate A 可以通过
完成真实请求、接收确认和故障模拟后,Gate A 的结论是:
PASS / ACCEPTED FOR CONTROLLED PILOT通过依据包括:
两个不同微信接收者都实际收到提醒,正文完整; 成功、参数格式错误和喵码不存在已经区分; HTTP 和 HTTPS 行为已经确认; 相同请求会重复送达,幂等责任已经明确; 超时、HTTP 500 和结果不确定的状态已经定义; 频率建议、最小正文、敏感凭据保护和站内降级成为强制约束; 产品负责人明确接受剩余的第三方风险。
这里的通过不表示:
第三方保证每次送达; 喵提醒是永久且不可替换的渠道; 普通社区用户已经接受注册和填写喵码; 产品可以跳过微信内置浏览器验证; 产品可以立即开始正式试用。
一个 Gate 通过,只代表这一个节点的退出条件满足。
14. 节点收尾怎样防止结论散落在聊天中
验证完成以后,又按照节点收尾模板做了一次独立验收。
这次收尾完成了四件事:
保存完整验证记录,包括官方资料、实际响应、接收确认、故障模拟和强制约束; 更新正式产品规划,把 Gate A 从“待验证”改为“已通过”; 更新 README,让下一次会话知道当前应该进入 Gate B; 生成 Gate A 检查点,明确节点是 GO,产品规划阶段仍然是CONDITIONAL GO。
历史检查点中“Gate A 尚未验证”的内容没有被修改,因为它准确记录了当时的状态。
新的检查点负责说明:哪些旧结论已经被新证据取代。
这样既保留真实过程,也避免后续 AI 把历史状态误认为当前事实。
15. 一套可以复用的外部服务验证方法
如果你的产品依赖短信、邮件、支付、地图、对象存储、AI 模型或其他第三方接口,也可以使用类似顺序:
从产品规划提取外部依赖承诺 → 写清通过、失败和降级条件 → 阅读当前官方文档 → 使用专用测试账号和最小测试数据 → 验证正常响应和真实最终结果 → 验证无效凭据和业务错误 → 检查协议、返回类型和文档差异 → 验证重复请求与幂等行为 → 用本地桩模拟超时、断线和服务端错误 → 核对额度、隐私、条款和停服风险 → 由负责人接受、拒绝或缩小风险 → 同步事实源并生成检查点验证时至少要分开三层成功:
很多外部服务故障,正是因为系统把第一层或第二层直接当成第三层。
16. AI 在这个节点适合做什么
AI 很适合帮助:
从产品规划中提取验证清单; 对照官方资料寻找缺失契约; 设计不包含个人信息的测试正文; 生成无效参数和边界测试; 记录响应头、正文类别和时间; 设计超时与 HTTP 500 的本地故障桩; 对照退出条件整理通过和未通过证据; 检查文档、README 和检查点是否互相矛盾; 扫描仓库中是否意外留下测试凭据。
AI 不应该替用户完成:
伪造第二个真实接收者; 把接口返回“完成”当作微信送达证据; 未经确认重复发送大量提醒; 为了测试限流主动触发第三方封禁; 自行接受隐私和停服风险; 把测试喵码写入文档或提交历史。
技术验证仍然需要真实设备、真实接收者和负责人决定。
AI 的价值是帮助我们更系统地获得和保存证据,而不是让缺失的证据看起来已经存在。
17. 当前真实进度
到第四篇结束时,仓库中的真实状态是:
Office Hours 产品讨论已经完成; 正式产品规划内容审阅已经完成; Gate A 喵提醒接口验证已经完成并通过; 司机和乘客两个不同微信账号都完成真实接收确认; 重复发送、超时和 HTTP 500 的处理边界已经明确; Gate A 验证记录、产品规划、README 和节点检查点已经同步并提交; 产品规划在 Gate A 收尾时仍然是 REVIEWED / CONDITIONAL GO;当时计划下一步制作 Gate B 前置验证页面; Gate C 受控试用准备尚未完成; 工程架构、页面设计、开发计划和应用代码仍未开始。
后续调整:第四篇完成后,产品负责人补充了另一个现有项目在微信内置浏览器正常使用的经验。项目据此不再制作一次性 Gate B 页面,产品规划可以先完成最终批准并进入工程架构。邻行成品的邀请链接、登录保持、复制微信号、返回微信和分享预览隐私仍将在首条可运行闭环完成后,使用至少一台 iOS 和一台 Android 真机验收。
这次调整不是把 Gate B 写成已经通过,而是把它从“实现前可行性验证”改成“正式试用前成品验收”。
18. 最后
外部服务最容易制造一种错觉:文档存在、接口能访问,所以依赖已经确定。
真正可靠的结论需要继续追问:
成功状态是否真的代表业务成功; 最终用户是否真实收到结果; 相同请求是否会重复执行; 超时以后系统知道多少、不知道多少; 外部故障会不会破坏内部业务事实; 发送给第三方的数据能否再少一点; 服务变化或停止以后,产品还能不能继续。
AI 可以很快写出一段调用第三方接口的代码。
但在写这段代码以前,先验证它应该怎样成功、怎样失败、失败以后怎样降级,往往能避免更多返工。
下一篇将记录产品规划怎样完成最终批准,并开始把产品规则转化为工程架构和页面体验约束。
19. 附录:相关工具与仓库
19.1 gstack
仓库:garrytan/gstack 地址:https://github.com/garrytan/gstack
前一阶段使用了其中的 office-hours 完成产品讨论。后续成品验收将使用浏览器相关能力辅助检查,但微信内置浏览器和真机结果仍需真实设备确认。
19.2 dev-harness
仓库:Dev-Wiki/dev-harness 地址:https://github.com/Dev-Wiki/dev-harness
本节点使用其 Git 工作流安全提交 Gate A 验证记录、产品规划状态、README 和节点检查点。正式开发计划与任务看板仍要等产品规划最终批准和架构完成后生成。
19.3 UI UX Pro Max
仓库:nextlevelbuilder/ui-ux-pro-max-skill 地址:https://github.com/nextlevelbuilder/ui-ux-pro-max-skill
本节点尚未进入正式页面设计。产品规划最终批准并确定工程架构后,再使用它设计移动端页面、表单、提醒状态和隐私提示。
夜雨聆风