首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用 WorkBuddy 搭一个能上线的 App:六个阶段、十六个坑、一份可复用的经验

用 WorkBuddy 搭一个能上线的 App:六个阶段、十六个坑、一份可复用的经验

原创
作者头像
用户12783788
发布于 2026-09-23 22:23:01
发布于 2026-09-23 22:23:01
1741
举报

先说结论

用 WorkBuddy 这类 AI 搭子做应用,"写代码"是最不值钱的环节。真正决定成败的是三件事:把场景想清楚、把验证做到真机、把发布闭成闭环。代码只是这三件事的副产品。

这篇文章不是教程,是复盘。素材来自一个真实项目——高中智能错题本 App「错题智库」从 v1.0 迭代到 v1.13.5(versionCode 28)的全过程,外加成长中心自动化、Supabase 边缘函数部署、学生端 UI 评审几个真实场景。16 条踩坑记录原样保留,因为坑比经验更值得记住——经验告诉你"什么能做",坑告诉你"什么会炸"。

全流程地图

① 场景选择

定输入输出、圈能力边界

把"AI 能做的"当成"我该做的"

先摸边界,再定范围

② 文档准备

写需求 / 架构 / 验收标准

需求只活在脑子里

写成陌生人也看得懂的施工图

③ 开发

先骨架后血肉

平台差异没提前吃掉

Web 先跑通,再套原生壳

④ 反复调试

定位真根因

把现象当根因、在错的环境复现

让真机自己记录现场

⑤ 上线发布

版本 / 缓存 / 调用闭环

发布成功 ≠ 用户拿到

版本号 + cache-busting + 指纹校验

⑥ 环境与协作

依赖、版本、节奏

环境差异 + 协作错位

固定版本、先评审后执行

下面按这六步展开。第 ④ 步是重灾区,全文最长的篇幅留给了它。

一、场景选择:AI 能做很多,你该做很少

结论:选场景的第一原则是"先摸边界,再圈范围"。

AI 搭子最大的迷惑性在于——你说什么它都能做。于是很容易滑向功能膨胀,最后做出一个"什么都沾一点、什么都不闭环"的东西。这不是能力问题,是自律问题。

真实教训来自「成长中心自动化」。最初的设想很朴素:让脚本把成长中心的任务全领一遍。真跑起来才发现,能力边界极其清楚:

可自动化

盲盒、签到、兑换

能

不可自动化

发文、互动、真实浏览类任务

不能(要求真实外部行为,代刷既不合规也不可行)

如果这个边界是在"开发到一半"才发现的,那就是纯粹的浪费。 好在它是在"先探一遍"的时候发现的——探一遍的成本,远低于做一半再推翻。

所以选场景时,先回答三个问题:

谁用? 用户画像决定取舍。错题本的用户是高中生,所以"按住说话"这种输入方式必须在真机上跑通——它不是锦上添花,是刚需。

输入输出是什么? 能画出一条完整链路的需求才叫需求。错题本的链路是:拍照/识题 → OCR 与 AI 解题 → 错题入库 → 生成复习计划。闭环清晰,每一步都有明确产物。

怎么证明它成了? 验收标准要前置。比如成长中心自动化的验证标准是"真开盲盒才算跑通",而不是"连接测试通过"。

一个判断小技巧:如果你写不出"怎么验证它是对的",那这个功能现在还不该进排期。 它不是需求,是愿望。

二、文档准备:把需求写成"陌生人也能看懂的施工图"

结论:AI 读的是文档,不是你的脑子。

很多人抱怨"AI 不懂我",翻译过来通常是"你没写下来"。文档准备这一步的产出质量,直接决定后面三个阶段要返工几次。

本项目能一路迭代到 v1.13.5,靠的是一份约 71 KB、7 章、16 条踩坑的交接文档《工作交接-错题智库 v1.13.5》。它做对了三件事:

写清 What:功能清单、页面结构、数据结构、接口约定,一项不落。

写清 How to verify:每个能力都写"怎么算成功"。测语音功能,写的是"真机能出文字",不是"按钮颜色正常"。

写清 Constraints:平台只支持 Android、localStorage 单机持久化、模型锁死某个版本……边界写死,AI 才不会替你"发挥"。

除此之外,还有一类东西常被忽略,但必须写进文档——协作协议。比如"变更前先给方案、确认后再落地"、"部署类任务要一次给到最终 URL"。这些不是需求,是你和搭子之间的工作约定。写进去,后面能省掉大量来回确认。

通用经验补充:给 AI 写需求,最有效的结构是「功能清单 + 数据结构 + 验收标准 + 已知约束」四段式。缺任何一段,都会在后期以"返工"的形式补回来,而且是带利息的。

还有个小技巧:文档里最好带一页速览表。七章文档没人想看第二遍,但那张表会被反复引用。信息密度高,比篇幅长重要得多。

三、开发:先跑通骨架,再长出原生能力

结论:开发阶段最贵的不是写功能,是吃掉平台差异。

错题智库的架构很典型,也很值得抄:

代码语言:bash
复制
浏览器 / Android WebView
 └─ index.html(单页应用,hash 路由)
     ├─ js/data.js      演示数据与数据模型
     ├─ js/utils.js     存储层 / SM-2 间隔重复 / 掌握度模型 / LaTeX 混排
     ├─ js/charts.js    零依赖 SVG 图表
     ├─ js/ocr.js       OCR 封装(Tesseract.js v5 + PDF.js)
     ├─ js/ai.js        AI 总控:LLM / TTS / ASR / 苏格拉底引导(79 KB)
     ├─ js/app.js       路由 / 交互 / 按住说话状态机(186 KB)
     └─ js/pdf-export.js + 七处导出
Android 桥
 ├─ MainActivity.java        权限申请、JS 桥注册
 ├─ FileChooserClient.java   文件选择 + WebView 权限桥
 ├─ AsrBridge.java           原生语音识别
 ├─ TtsBridge.java           原生 TTS
 ├─ PickerBridge.java        图库/相机/文档原生直达
 ├─ DownloadBridge.java      下载
 └─ AssetWebViewClient.java  资源加载

"Web 优先,原生补差"这条路线有个巨大好处:功能能在浏览器里先跑通、先验证,等真的需要系统能力了,再一层层接原生——而不是一上来就陷进 Android 工程的泥潭里。

但代价也很明确:平台差异会在这一步集中爆发。

content:// 地址 fetch 失败

系统选择器拿到的文件,网页读不了

跨源 / 协议限制

原生做同源代理

密码框自动填充"只画界面不给值"

界面显示有值,input.value 却是空

浏览器自动填充只渲染、不写值

三级回落:input.value → 已存配置 → 默认值

默认值与 input.value 分裂

用户没改动,默认值被当成空值存进去,静默抹掉原配置

默认值靠 value 属性回填,保存却只读 input.value

试听/保存双处拦截,界面有值 ≠ input.value 有值

豆包通道 CORS

请求被浏览器拦下

非 OpenAI 协议

Key 走 URL 参数绕 CORS

lambda 编不过

-source 8 撞上 javac 20

Java 版本兼容

改用匿名 Runnable,不用 lambda

overlayfs 副本写不进去

七副本内容不一致

overlayfs 必须先删后写

先 rm -f 再 dd,MD5 唯一性校验

这些坑有个共同点:它们都不是"逻辑错",而是"环境错"。 逻辑错,AI 自己往往能发现;环境错,只有把代码放到真实平台上跑才会暴露。

所以开发阶段的正确姿势是:越早把代码放到最终运行环境里跑,越早发现问题。 别等到"功能全做完了再上真机",那时候每修一个环境坑,都要连带重测一堆功能。

四、反复调试:一个"卡红"按钮,修了 4 个版本

这一节写长一点,因为它是整个项目里最贵的学费。

4.1 现象

错题本里有个"按住说话"的语音作答按钮。真机上,按住之后按钮永久变红,松手也不提交。用户一测就卡,开发者一测就"没问题"。

从 v1.13.2 到 v1.13.5,连续四个版本都在修同一个"卡红",每次都觉得找到根因了,用户一测——还是卡。

4.2 四轮拉锯

v1.13.2

第一次卡红

partial 中间结果提前把状态切成"识别中",松手没触发提交

全程保持红、松手必提交、引擎 4s 静默自动收尾;步骤按钮独立

v1.13.3

第二次卡红

引擎自行静默结束,stop() 变成空操作

stop() 明确返回布尔并立即复原;新增 9s 页面看门狗

v1.13.4

仍不对,且明确要求"要真能出文字"

降级链路顺序 / 复原延迟

降级链改云端优先;删掉 9s 看门狗改"松手立即复原"(实测 321ms);给"无系统识别 + 未配 Key"真实 UI 引导

v1.13.5

第四次卡红

终于找到真根因:WebView 双层权限不一致

修 Java 权限桥(挂起—补授权 + 3s 兜底 deny);APK 内置语音诊断页

4.3 真根因:一个"悬空的 Promise"

Android WebView 里,getUserMedia 要过两道关:

网页层:WebChromeClient.onPermissionRequest

系统层:RECORD_AUDIO 运行时权限

旧代码无条件执行 request.grant(...),于是:

网页层被放行,getUserMedia 不抛 NotAllowedError;

但系统层其实没有权限;

结果 getUserMedia 返回的 Promise 既不 resolve 也不 reject——它悬在半空;

ASR.state 永远停在 'starting';

而 UI 把 starting 也算作 listening → 按钮永久红。

一个没有落定的 Promise,让整个状态机卡死在原地。

4.4 为什么仿真环境永远复现不了

这是最值钱的一课:

仿真环境里,两层权限要么都给,要么都不给;不存在"网页层放行 + 系统层无权限"这个中间态。

所以前四次修复,等于在仿真环境里"盲修"。每次改完,仿真环境都告诉你"好了"——因为它从来就复现不出这个问题。

方法论教训:真机问题不要在仿真环境里复现。 v1.13.5 起改策略:让真机自己记录现场——APK 内置语音诊断页,包含环境快照、一键测麦克风、指针事件监视器、全程埋点、一键复制。不再指望"远程复现",而是把现场证据打包带回来。

4.5 从四轮拉锯里提炼出的调试方法论

① 现象 ≠ 根因,中间隔着一层"翻译"。

同一个项目里还有一组对照案例。跑成长中心自动领取脚本时,点击/探测频繁失败,第一反应是"选择器失效、目标元素丢了",改了半天选择器,没用。真实根因是 Chrome 沙箱进程树被回收——受限环境会杀掉浏览器进程,CDP 长连接随之断开。表象像选择器问题,根子在进程层。

对策:连接失效,先查进程与连接存活,再谈选择器。顺序不能反。

② 让失败"说人话",禁止"提示音冒充成功"。

早期版本里,TTS 失败时会播放一个兜底提示音,用户误以为"成功了只是没声"。真实的 401 Key 失效报错,被这声提示音盖住了。

对策:每一层失败都必须渲染明确原因。 TTS 按失败渲染完整原因链 + Key 对照提示;ASR 四级降级链每层失败都给真实可照做的 UI 引导。

16 条踩坑里,近半数源于同一类病根——假成功 / 静默失败。

③ 所有"可能永不落定"的异步,都必须有兜底。

getUserMedia 可能永不 resolve/reject。修法是加 3 秒兜底 deny:宁可明确失败,绝不悬空。

通用规则:一切等待都要有兜底答复,一切可能挂死的调用都要有超时。

④ 流式/增量结果:中间结果只更新内容,不更新阶段。

partial 结果提前切状态,就是 v1.13.2 的坑。

通用规则:语音识别、实时转写、渐进式搜索的 UI 状态机——阶段转移只能由明确的生命周期事件驱动,中间结果不许碰状态。

⑤ stop() / close() / cancel() 必须幂等且返回布尔。

v1.13.3 的坑:stop() 内部三层分支全靠内部状态判断,却没有兜底 else;引擎实际已结束、内部状态没同步时,所有分支都不命中,stop() 变成空操作。

通用规则:内部状态机不可信时,API 必须返回"这次调用到底做了没有",并具备幂等兜底路径。

⑥ 平台差异要"按平台验证",不能靠推理。

原生/云端两套独立音频采集,如果设计成"先 A 失败再补录 B",会录到静音。对策是按下即选通道,不做串行降级。

4.6 6 条脱离本项目也成立的工程铁律

原文 16 条坑里,有 6 条在任何项目上都成立:

悬空 Promise 必须有兜底

stop() 必须返回布尔

界面有值 ≠ input.value 有值

部分结果不得提前切状态

CDN 边缘缓存需 cache-busting

overlayfs 多副本必须"先删后写"

五、上线发布:发布成功 ≠ 用户拿到

结论:发布是一个"闭环动作",不是一次"上传动作"。

5.1 CDN 缓存:最经典的"假发布"

v1.10.0 和 v1.13.5 都撞上过同一个问题:APK 已经更新,下载页还是旧包;复核脚本读到的还是旧代码。查响应头,eo-cache-status: HIT——下载页的 index.html 裸 URL 被边缘缓存命中了。发布是真的发布了,但请求打到了旧缓存上。

发布三件套(缺一不可):

版本号参数:APK 直链带 ?v=<version>

主动 cache-busting:下载页 HTML 复核必须带 ?_=Date.now()

文件指纹校验:发布后核对 MD5,确认拿到的真的是新包

补充一条硬规则:发布流水线写七副本时,先 rm -f 再 dd(overlayfs 必须"先删后写"),并以 MD5 唯一性做最终校验。

通用规则:任何走 CDN / 边缘缓存的静态资源发布,都要过这三件套。缓存不会报错,它只会安静地骗你。

5.2 后端部署:API 优先,绕开环境依赖

平台选的是 Supabase Edge Functions(Deno 运行时),方式明确走 Management API + 零依赖工具包(deploy.mjs / deploy.sh / secrets.sh),不走 CLI / Docker。

这条路线的好处在受限环境里特别明显:CLI 要装、Docker 要起、网络还要通,每一环都是新的失败点。用 Management API,把这些环境依赖全砍掉,把"部署"压缩成一个纯 HTTP 调用。

交付标准是"一次到位":部署完直接给可访问的 http URL,不留半截。

5.3 部署成功 ≠ 调用方可用

上线后的 video-search 函数遇到了一个典型问题:函数部署成功,日志正常,但 App 端一调就截图报"代理不可用"。

排查下来的高风险点集中在三类:

对端接口需要 Cookie / 鉴权态,请求里没带;

跨域或请求头处理缺失;

返回字段名与 App 期望不一致——别名没对齐。

修法是:函数内补 Cookie 处理 + 字段别名映射,让出参严格对齐 App 侧字段;然后用零依赖工具包重部署,绕开 CLI/Docker 环境差异。

最关键的一条:必须让真实调用方实测,才算闭环。 部署返回 200 只能证明"函数活着",不能证明"链路通了"。

复用场景:任何"第三方 API 代理类"边缘函数,必须处理对端鉴权态 + 字段对齐,且由真实调用方验证。

5.4 临时凭证:过期即重发,别复用

公益项目推荐流程里,生成过一次微信小程序授权码给用户扫——码过期了,授权链路没走通。解法很朴素:重新生成第二次,并在有效期内引导用户到小程序里点"同意"。

通用规则:所有临时凭证(小程序码、登录二维码、一次性 token)——生成后尽快引导用户操作,过期即重发,绝不复用旧码。

5.5 发布检查清单

APK 直链带版本号

?v=<version> 存在

下载页复核

请求带 ?_=Date.now(),响应非 HIT

文件指纹

MD5 与本地包一致

后端端点

返回 200 且调用方实测通过

多副本一致性

七副本 MD5 唯一且一致

真机验收

关键链路(如语音)真机跑通

六、环境与协作:不在代码里的那些坑

结论:代码之外的坑,往往比代码里的更贵。

6.1 网络与依赖

git clone 开源仓库一直 443 失败(GitHub 直连被墙)。反复重试没用,改用镜像 zip 下载——拉仓库 zip 包、本地解压、落到技能目录,再走安全审查。

通用规则:受限网络环境拉第三方代码或依赖,第一备选永远是镜像源,而不是反复重试直连。

6.2 跨环境版本差

本地 Windows 跑得好好的脚本,换到另一个环境行为就不一样。根因是运行时版本差——本地 Node 22.22.2-3,目标环境 Node 22.13,小版本差也可能踩到 API / 行为差异。

通用规则:跨环境脚本避免依赖某个小版本特有的 API;部署类任务固定运行时版本,或在工具包里声明兼容范围。给别人的可执行脚本,标注兼容版本——避免"我这边能跑"。

6.3 第三方技能安装:四步 SOP

装外部技能前,必跑完整审查:

获取:直连失败 → 镜像 zip 下载解压

审查:逐个过 SKILL.md + 全部脚本 + 配置文件

扫描:危险模式扫描——网络请求、subprocess、eval、非标准库依赖

确认 + 安装:把审查结论交给人确认,通过后才落到技能目录

实测一份第三方技能包:4 个 SKILL.md + 5 个 Python 脚本 + 1 个 openai.yaml,全部干净(仅用标准库,无网络、无子进程、无 eval)——这个结论不是"看着像安全",是逐项扫出来的。

6.4 协作节奏:双阶段变更

多次任务里,用户反复要求"先给建议/评审,确认后再执行",UI 评审还要高保真参考样例对照。

这不是 bug,是协作偏好:变更前先看结果、确认后再落地,避免返工和信息错位。

对策:养成双阶段节奏——① 出建议 / 评审 / 参考样例(不落地)→ ② 等确认 → 再执行。而部署类任务反过来,追求一次到位交付(直接给可访问 URL)。这套节奏本身就能减少反复调试的总量。

6.5 知识沉淀:置信度标注 + 未闭环清单

整个项目沉淀下来的做法里有两条特别值得抄:

置信度标注:每条结论标 ★确证(有直接来源)/ ☆推断(合理归纳但待核实)。含糊比错误更贵,标注清楚反而更快。

未闭环事项清单:把"待真机验证""字段对齐待观察""授权后中断待继续"逐条列出来,标明下一步。没做完的事被写下来,才不会假装做完了。

七、总结

7.1 全流程避坑总表

场景选择

能力边界后知后觉

把"能做"当"该做"

先探边界,再圈范围

文档准备

反复返工

需求没落成文字

四段式:功能 + 结构 + 验收 + 约束

开发

环境错当成逻辑错

平台差异后置暴露

Web 先跑通,越早上真机越好

调试

连接"元素丢失"

其实是进程树被回收

先查进程存活,再改选择器

调试

真机"卡红"连修 4 版

仿真环境复现不出中间态

让真机自己记录现场

调试

状态机卡死

悬空 Promise 无兜底

一切等待必有兜底答复

调试

失败被误认为成功

提示音掩盖了真错误

禁止假成功,失败给完整原因链

调试

stop() 变空操作

分支无兜底 else

停止型 API 返回布尔 + 幂等

调试

按钮状态错乱

partial 提前切状态

中间结果只更新内容,不更新阶段

开发

配置被静默抹掉

界面有值 ≠ input.value 有值

取值三级回落 + 双处拦截

发布

"已发布"还是旧版

CDN 边缘缓存 HIT

版本号 + cache-busting + 指纹

发布

App 报"代理不可用"

部署成功 ≠ 链路通

Cookie/字段对齐 + 调用方实测

发布

授权码扫了没反应

临时凭证过期

过期即重发,不复用

环境

clone 一直 443

直连被墙

第一备选是镜像源

环境

跨环境行为不一致

运行时小版本差

固定版本 + 标注兼容范围

协作

反复返工

直接落地未经确认

先评审后执行,双阶段节奏

7.2 五条心法

心法一:先想清楚,再动手。

场景选择的成本是 1,开发返工的成本是 10,上线后返工的成本是 100。把时间花在前面。

心法二:把"怎么验证"写进需求里。

写不出验收标准的需求,都是愿望。验收标准前置,是唯一能对抗"自我感觉良好"的东西。

心法三:越真实的运行环境,越早介入。

仿真环境只能验证"理想态"。真机的中间态、沙箱的进程回收、CDN 的边缘缓存——这些坑只在真实环境里存在,也只在真实环境里能被解决。

心法四:让失败说话,别让它沉默。

假成功比失败更危险。一个消音的错误,会让排查成本翻十倍。宁可明确失败,绝不悬空、不掩盖、不糊弄。

心法五:闭环才算完成。

部署成 200 不算完成,调用方实测通过才算;文件传上去不算完成,用户拿到新版才算;文档写完不算完成,落成可复用的资产才算。

7.3 一句话收尾

用 WorkBuddy 搭应用,它替你写代码,但替不了你思考。

AI 能把"写"这件事的成本压到接近零,于是"想"和"验"的价值就凸显出来了——代码越廉价,判断力越贵。

那些坑不是障碍,是路径。踩过、记下、写进文档,它们就从"学费"变成了"资产"。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 全流程地图
  • 一、场景选择:AI 能做很多,你该做很少
  • 二、文档准备:把需求写成"陌生人也能看懂的施工图"
  • 三、开发:先跑通骨架,再长出原生能力
  • 四、反复调试:一个"卡红"按钮,修了 4 个版本
    • 4.1 现象
    • 4.2 四轮拉锯
    • 4.3 真根因:一个"悬空的 Promise"
    • 4.4 为什么仿真环境永远复现不了
    • 4.5 从四轮拉锯里提炼出的调试方法论
    • 4.6 6 条脱离本项目也成立的工程铁律
  • 五、上线发布:发布成功 ≠ 用户拿到
    • 5.1 CDN 缓存:最经典的"假发布"
    • 5.2 后端部署:API 优先,绕开环境依赖
    • 5.3 部署成功 ≠ 调用方可用
    • 5.4 临时凭证:过期即重发,别复用
    • 5.5 发布检查清单
  • 六、环境与协作:不在代码里的那些坑
    • 6.1 网络与依赖
    • 6.2 跨环境版本差
    • 6.3 第三方技能安装:四步 SOP
    • 6.4 协作节奏:双阶段变更
    • 6.5 知识沉淀:置信度标注 + 未闭环清单
  • 七、总结
    • 7.1 全流程避坑总表
    • 7.2 五条心法
    • 7.3 一句话收尾
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档