拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WorkBuddy+Supabase快速开发上线App实战:六阶段与十六坑

WorkBuddy+Supabase快速开发上线App实战:六阶段与十六坑

1. 从零到上线:为什么我选择 WorkBuddy 作为主力开发工具

去年年底我接了一个私活,客户要求两周内出一个能装到手机上的 App,功能不复杂——用户注册登录、发帖、看图、简单聊天。预算不高,但要求“看起来像个正经产品”。我手里没有原生开发团队,自己写 Android 和 iOS 两套代码时间根本不够。试了几条路之后,我最终用 WorkBuddy 配合 Supabase 把这件事跑通了,从搭建到上架测试版一共花了十一天。

WorkBuddy 在这类场景里的定位很清晰:它把 AI 辅助编码、项目脚手架、常用能力封装这几件事揉在一起,让你用接近写脚本的方式产出一个可安装的 App。配合 Supabase 做后端,WebView 做混合渲染,整个链路是通的。这篇文章我会把六个阶段完整拆开,把中间踩过的十六个坑一个一个标出来,包括 Supabase 连接报read ECONNRESET、WebView 在 Android 上不打印日志、iOS 浏览器唤起安装 App 失败这些具体问题。如果你也是一个人或者小团队要快速交付一个能上线的 App,这篇内容可以直接抄作业。

先说清楚适合谁看。第一类是有前端基础、想往移动端延伸的开发者,你会 HTML、JavaScript,但对 Android 和 iOS 的打包流程不熟。第二类是用过 AI 编程工具、但没完整走过上线流程的人,你可能让 AI 生成过页面,但不知道怎么把它变成能安装的包。第三类是做外包或者接私活的独立开发者,时间紧、预算有限,需要一个能快速复用的技术组合。如果你属于这三类中的任何一类,接下来的内容会对你有直接帮助。

整个项目我用的技术栈是:WorkBuddy 做项目生成和 AI 辅助编码,Supabase 做数据库、认证和存储,WebView 做混合页面的承载容器,最后用 WorkBuddy 自带的打包能力出安装包。这个组合的核心逻辑是——把重活交给后端服务,把界面交给 Web 技术,把胶水代码交给 AI 生成。你不需要精通原生开发,但需要理解每个环节在干什么,否则出了问题你连排查方向都没有。

2. 六个阶段拆解:从项目初始化到可安装包

2.1 阶段一:环境准备与 WorkBuddy 初始化

这一步看起来简单,但坑最多。我第一次装 WorkBuddy 的时候,在 Linux 环境下直接卡在了依赖安装上。WorkBuddy 对 Node 版本有要求,我当时系统里是 Node 16,它需要 18 以上。版本不对不会给你明确报错,而是安装到一半卡住,日志里只有一行模糊的提示。后来我换成 Node 20 LTS 才顺利跑通。

安装流程我整理成可直接执行的步骤:

# 确认 Node 版本,必须 18 以上 node -v # 如果版本不对,用 nvm 切换 nvm install 20 nvm use 20 # 全局安装 WorkBuddy CLI npm install -g workbuddy-cli # 初始化项目 workbuddy init my-app

初始化的时候会让你选项目模板。这里有个选择逻辑:如果你要做的是内容型 App,选带 WebView 容器的基础模板;如果要做工具型 App,选带原生能力桥接的模板。我选的是 WebView 基础模板,因为我的界面全部用 HTML 写,只需要一个壳来承载。

注意:WorkBuddy 初始化时会生成一个workbuddy.config.json文件,里面的appId和appName必须和后面打包时填写的一致,否则安装包会装不上或者覆盖安装失败。这个细节文档里没写,我是踩了一次才发现的。

环境准备阶段还有一个容易忽略的点:Android SDK 和 JDK 的路径配置。WorkBuddy 打包 Android 包的时候会调用本地的 Gradle,如果你的ANDROID_HOME没配好,打包会直接失败,报错信息是SDK location not found。解决办法是在项目根目录建一个local.properties文件,写入:

sdk.dir=/your/path/to/android-sdk

这个文件不要提交到 Git,因为每个人的路径不一样。我一般会把它加到.gitignore里,然后在 README 里写清楚怎么配置。

2.2 阶段二:Supabase 后端搭建与连接配置

Supabase 在这个项目里承担了数据库、用户认证、文件存储三个角色。选它的原因很直接:免费额度够用,自带 REST API 和实时订阅,不需要自己写后端接口。对于我这种一个人干活的情况,省掉后端开发至少省了三四天。

创建项目之后,你会在 Supabase 控制台拿到两个关键信息:Project URL和Anon Key。这两个东西要填到 WorkBuddy 项目的环境变量里。我建议建一个.env文件:

SUPABASE_URL=https://xxxxx.supabase.co SUPABASE_ANON_KEY=eyJhbGciOi...

然后在代码里通过process.env.SUPABASE_URL读取。这里有个坑:WorkBuddy 打包的时候不会自动读取.env文件,你需要在workbuddy.config.json里显式声明环境变量,或者在构建脚本里注入。我第一次打包后发现 App 里读不到环境变量,排查了半天才发现是这个问题。

数据库表的设计我走了弯路。一开始我想把所有字段都塞到一张posts表里,结果查询越来越慢,代码也越来越乱。后来拆成三张表:users存用户信息,posts存帖子内容,files存文件元信息。Supabase 自带auth.users表,你只需要建一张profiles表来存额外信息,通过id关联。

-- 用户扩展信息表 create table profiles ( id uuid references auth.users on delete cascade primary key, nickname text, avatar_url text, created_at timestamp default now() ); -- 帖子表 create table posts ( id bigint generated by default as identity primary key, user_id uuid references profiles(id), content text, image_url text, created_at timestamp default now() );

权限方面,Supabase 的行级安全策略(RLS)一定要开。我一开始图省事没开,结果任何人拿到 Anon Key 就能读写所有数据。开启 RLS 之后,你需要为每张表写策略。比如posts表的策略是:所有人可读,只有登录用户可以插入,只有作者可以删除自己的帖子。

实操心得:Supabase 的 RLS 策略写起来有点绕,我建议先在控制台的 SQL Editor 里测试,确认策略生效后再写到代码里。测试方法是:用 Anon Key 发一个请求,看能不能拿到不该拿的数据。

连接 Supabase 的时候我遇到了read ECONNRESET这个报错。这个问题的原因是 Supabase 的免费项目在一段时间不活动后会进入休眠状态,第一次连接需要唤醒,如果超时时间设得太短就会报这个错。解决办法是在 Supabase 客户端初始化时把超时时间调大:

import { createClient } from '@supabase/supabase-js' const supabase = createClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { auth: { persistSession: true }, global: { fetch: (...args) => { // 把超时时间从默认的 10 秒调到 30 秒 const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), 30000) return fetch(...args, { signal: controller.signal }) .finally(() => clearTimeout(timeout)) } } } )

这个改动之后,read ECONNRESET的出现频率明显下降。如果还是偶尔出现,可以在 App 启动时先发一个轻量请求“预热”一下连接。

2.3 阶段三:WebView 容器搭建与页面通信

WebView 是这个项目里最关键的环节,也是最容易出问题的地方。我的方案是用 WorkBuddy 生成一个原生壳,里面放一个全屏 WebView,加载本地的 HTML 文件。这样界面用 Web 技术写,原生能力通过桥接调用。

WebView 的配置有几个关键参数:

// Android 端 WebView 配置 WebView webView = findViewById(R.id.webview); WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setAllowFileAccess(true); settings.setAllowContentAccess(true); settings.setMediaPlaybackRequiresUserGesture(false); webView.setWebChromeClient(new WebChromeClient()); webView.setWebViewClient(new WebViewClient()); webView.loadUrl("file:///android_asset/www/index.html");

setDomStorageEnabled(true)必须开,否则 localStorage 用不了,Supabase 的会话持久化会失效。setMediaPlaybackRequiresUserGesture(false)是为了让音频和视频能自动播放,如果你的 App 不需要这个能力可以不开。

WebView 和原生之间的通信我用的是addJavascriptInterface:

webView.addJavascriptInterface(new WebAppInterface(this), "AndroidBridge");

然后在 HTML 里这样调用:

// 调用原生方法 window.AndroidBridge.showToast("操作成功"); // 原生调用网页方法 // Android 端:webView.evaluateJavascript("javascript:onNativeCallback('data')", null)

这里有个坑:addJavascriptInterface在 Android 4.2 以下有安全漏洞,虽然现在最低版本都高于这个,但如果你要兼容老设备,需要做版本判断。另外,注入的对象名不要用window或者document这种,会冲突。

WebView 不打印日志这个问题我卡了整整一个下午。现象是:网页里的console.log在 Android Studio 的 Logcat 里完全看不到。原因是 WebView 默认不把 console 输出转发到原生日志。解决办法是重写WebChromeClient的onConsoleMessage方法:

webView.setWebChromeClient(new WebChromeClient() { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { Log.d("WebView", consoleMessage.message() + " -- From line " + consoleMessage.lineNumber() + " of " + consoleMessage.sourceId()); return true; } });

加上这段之后,网页里的日志就能在 Logcat 里看到了。这个技巧在调试 WebView 页面时非常有用,建议一开始就加上。

还有一个问题是 WebView 的历史版本兼容。不同 Android 版本自带的 WebView 内核不一样,老设备上的内核可能不支持某些 ES6+ 语法。我的做法是在 WorkBuddy 的构建配置里开启 Babel 转译,把代码降级到 ES5。这样虽然包体积会大一点,但兼容性有保障。

2.4 阶段四:AI 辅助编码与 WorkBuddy Skill 使用

WorkBuddy 的 AI 辅助编码能力是我用它的主要原因之一。它和普通的代码补全不一样,你可以用自然语言描述需求,它直接生成可运行的代码块。比如我说“帮我写一个用户登录页面,包含邮箱和密码输入框,点击登录调用 Supabase 认证”,它会生成完整的 HTML、CSS 和 JavaScript。

但 AI 生成的东西不能直接用,必须过一遍。我总结了几类常见问题:

第一类是环境变量引用错误。AI 不知道你的环境变量名是什么,它会用YOUR_SUPABASE_URL这种占位符。你需要全局替换成实际的变量名。

第二类是 API 版本不匹配。Supabase 的 JavaScript 客户端从 v1 到 v2 有破坏性变更,AI 可能生成 v1 的写法。你需要检查createClient的调用方式,v2 的写法是createClient(url, key),v1 是createClient(url, key, options)。

第三类是缺少错误处理。AI 生成的代码通常只写成功路径,网络请求失败、用户输入非法这些情况它不管。你需要自己补上try-catch和表单校验。

WorkBuddy Skill 是它的自定义指令功能。你可以把常用的操作写成 Skill,之后直接调用。我建了几个常用的:

  • create-page:生成一个带导航栏和内容区的基础页面
  • supabase-query:生成一个带错误处理的 Supabase 查询函数
  • webview-bridge:生成 WebView 和原生通信的桥接代码

Skill 的写法是在项目根目录建一个skills文件夹,里面每个.md文件就是一个 Skill。文件开头用 YAML 写元信息,后面写指令内容。比如:

--- name: supabase-query description: 生成带错误处理的 Supabase 查询函数 --- 请生成一个 Supabase 查询函数,要求: 1. 使用 async/await 语法 2. 包含 try-catch 错误处理 3. 错误时返回 { data: null, error } 4. 成功时返回 { data, error: null }

这个功能用熟了之后,编码效率提升很明显。但要注意,Skill 的指令要写得具体,越具体生成的结果越可用。模糊的指令会得到模糊的代码。

2.5 阶段五:打包与签名配置

打包是上线前的最后一道关卡,也是坑最密集的地方。WorkBuddy 支持打包 Android APK 和 iOS IPA,我主要说 Android 的流程,因为 iOS 需要苹果开发者账号,流程更复杂。

Android 打包分两步:生成签名密钥,然后打包。签名密钥用keytool生成:

keytool -genkeypair -v \ -keystore my-release-key.keystore \ -alias my-key-alias \ -keyalg RSA \ -keysize 2048 \ -validity 10000

执行后会让你输入密码和一堆信息,密码一定要记住,后面打包和上架都要用。validity设 10000 天,差不多 27 年,够用了。

然后在workbuddy.config.json里配置签名信息:

{ "android": { "signing": { "keystore": "my-release-key.keystore", "alias": "my-key-alias", "password": "your-password" } } }

注意:密码不要直接写在配置文件里提交到 Git。我一般用环境变量的方式注入,或者在 CI 里配置。本地开发可以用一个keystore.properties文件,然后加到.gitignore。

打包命令:

workbuddy build android --release

打包过程中最常见的错误是资源文件缺失。WorkBuddy 会把www目录下的文件打包进 APK,如果你的 HTML 里引用了外部 CDN 的资源,打包后可能加载不出来。解决办法是把所有依赖下载到本地,或者确保 App 有网络权限。

另一个坑是appId冲突。如果你之前用同一个appId装过测试版,再装正式版会提示签名不一致。解决办法是卸载旧版本再装,或者换一个appId。我一般会在测试阶段用com.example.myapp.debug,正式版用com.example.myapp,这样两个可以共存。

2.6 阶段六:上线前的检查与 iOS 唤起安装

Android 包打出来之后,不要急着分发。先做一轮检查:

  • 安装到真机上,确认能正常启动
  • 测试注册、登录、发帖、看图这些核心流程
  • 断网测试,看错误提示是否友好
  • 检查权限申请是否合理,不要一上来就要一堆权限

iOS 这边,如果你没有开发者账号,可以用 TestFlight 做内测,或者用 Ad Hoc 分发。但 Ad Hoc 需要收集设备 UDID,比较麻烦。我用的方案是:先出 Android 包给客户看效果,iOS 等确认后再走正式流程。

iOS 浏览器唤起安装 App 这个需求,实现方式是在网页里放一个链接,指向 IPA 文件的下载地址,然后通过itms-services协议唤起安装:

<a href="itms-services://?action=download-manifest&url=https://your-server.com/manifest.plist"> 安装 iOS 版 </a>

这个manifest.plist文件需要包含 IPA 的下载地址、Bundle ID、版本号等信息。而且这个链接必须在 Safari 里打开才有效,微信内置浏览器不行。所以通常的做法是引导用户“在 Safari 中打开”。

这里有个坑:iOS 对itms-services的链接有证书要求,你的下载地址必须是 HTTPS,而且证书要受信任。自签证书不行。我用的是 Supabase Storage 存 IPA 文件,它自带 HTTPS,省去了配证书的麻烦。

3. 十六个坑的完整清单与排查方法

3.1 环境与依赖类问题

坑 1:Node 版本不匹配导致安装卡住。现象是npm install -g workbuddy-cli执行到一半没反应,也不报错。解决办法是确认 Node 版本在 18 以上,推荐 20 LTS。

坑 2:Android SDK 路径未配置导致打包失败。报错信息是SDK location not found。解决办法是在项目根目录建local.properties,写入sdk.dir路径。

坑 3:JDK 版本不兼容。WorkBuddy 打包需要 JDK 17,如果你系统里是 JDK 8 或 11,会报Unsupported class file major version。解决办法是安装 JDK 17 并设置JAVA_HOME。

坑 4:Gradle 下载超时。第一次打包会下载 Gradle 依赖,网络不好的话会卡住。解决办法是配置国内镜像源,在build.gradle里把仓库地址换成阿里云镜像。

3.2 Supabase 连接类问题

坑 5:read ECONNRESET报错。原因是 Supabase 免费项目休眠后首次连接超时。解决办法是调大 fetch 超时时间,并在 App 启动时预热连接。

坑 6:RLS 策略未开启导致数据泄露。现象是未登录用户也能读写数据。解决办法是为每张表开启 RLS 并编写策略。

坑 7:环境变量打包后丢失。现象是 App 里读不到SUPABASE_URL。解决办法是在workbuddy.config.json里显式声明环境变量。

坑 8:Supabase 认证会话不持久。现象是每次打开 App 都要重新登录。解决办法是确保 WebView 的setDomStorageEnabled(true)已开启,并且 Supabase 客户端配置了persistSession: true。

3.3 WebView 类问题

坑 9:WebView 不打印日志。现象是console.log在 Logcat 里看不到。解决办法是重写WebChromeClient.onConsoleMessage。

坑 10:WebView 加载本地文件失败。现象是白屏。原因是文件路径不对,Android 的本地文件路径是file:///android_asset/www/index.html,注意是三个斜杠。

坑 11:WebView 和原生通信失败。现象是window.AndroidBridge是 undefined。原因是addJavascriptInterface在页面加载完成后才注入,需要在onPageFinished里调用,或者确保注入的对象名不冲突。

坑 12:老设备 WebView 内核不支持 ES6。现象是页面报语法错误。解决办法是开启 Babel 转译,把代码降级到 ES5。

3.4 打包与上线类问题

坑 13:签名不一致导致安装失败。现象是提示“应用未安装”。解决办法是卸载旧版本,或者确保签名密钥一致。

坑 14:appId冲突。现象是两个 App 互相覆盖。解决办法是测试版和正式版用不同的appId。

坑 15:iOSitms-services链接无效。现象是点击没反应。原因是链接必须在 Safari 打开,且下载地址必须是受信任的 HTTPS。

坑 16:打包后资源文件缺失。现象是图片或样式加载不出来。解决办法是把 CDN 资源下载到本地,或者确保 App 有网络权限。

4. 可复用的经验与后续扩展方向

这套方案跑通之后,我把它整理成了一个项目模板,下次接类似需求可以直接复用。模板里包含了 WorkBuddy 的基础配置、Supabase 的建表 SQL、WebView 的桥接代码、以及打包脚本。新项目只需要改appId、appName和 Supabase 的连接信息,就能在一天内出一个可安装的测试包。

有几个经验我觉得值得单独拿出来说。第一,Supabase 的 RLS 策略一定要在项目初期就配好,不要等到上线前才补,因为后期改策略会影响已有数据。第二,WebView 的日志转发一定要在开发阶段就加上,否则调试效率极低。第三,打包签名密钥一定要备份,丢了就没办法给已安装的用户推送更新。

后续如果要扩展,我建议从两个方向入手。一是加推送通知,Supabase 支持 Edge Functions,可以配合第三方推送服务实现。二是加离线缓存,用 Service Worker 把核心页面缓存到本地,弱网环境下也能打开。这两个方向我都试过,推送的坑主要在证书配置,离线缓存的坑主要在缓存更新策略,后面有机会再单独写。

最后分享一个我常用的调试技巧:在 WebView 里注入一个全局的错误捕获,把错误信息通过桥接发给原生,原生再写到日志里。这样即使页面崩溃,你也能知道是哪一行出的问题。

window.onerror = function(message, source, lineno, colno, error) { if (window.AndroidBridge) { window.AndroidBridge.logError( JSON.stringify({ message, source, lineno, colno }) ); } return false; };

这个技巧帮我定位过好几次只在真机上出现的诡异问题。网页在浏览器里跑得好好的,一到 WebView 里就白屏,加上这个之后就能看到具体的报错信息了。

返回列表