摘要
App 上架后,独立开发者的一天是什么样的?刷 App Store Connect、刷 App Store Connect、还是刷 App Store Connect。 销量、评论、审核状态,三个答案都藏在一个网页后面,一天重复几十次。 我花了大半年做了一个常驻 Mac 菜单栏的工具,把这三种焦虑全部变成通知: 每日早报、差评提醒、审核状态追踪——并且没有一行后端代码。
本文记录这个产品的完整思路:痛点、架构、几个值得聊的技术细节(ASC API 接入、限流、幂等通知、多端数据共享),以及踩过的坑。
- 产品:CoderPulse(独立开发者的 App Store「早报 + 警报」)
- 落地页:coderren.com/apps/apppul…
- 下载(Mac / iPhone 同一 App):apps.apple.com/cn/app/code…
一、痛点:三个问题,一个网页,无数次点击
对独立开发者来说,App 上架之后最关心三件事:
- 今天卖了多少?(下载、收入)
- 有没有差评?(要不要赶紧去回复 / 修 bug)
- 审核过了没?(提审之后什么时候能上)
这三个答案都在 App Store Connect(ASC)网页里。看一次的成本是:开浏览器 → 登录 → 翻菜单 → 找数据。 重度用户一天重复几十次。更致命的是坏消息发现得晚:
- 差评晚回复一天,就发酵一天;
- 审核被拒晚发现一天,就晚上线一天。
苹果官方有 ASC App,但它是 iOS App——没有 Mac 菜单栏常驻形态,没有差评定向警报,审核状态通知也不可靠、没有历史记录。这就是我做这个工具的出发点。
二、产品形态:早报 + 警报 + 驾驶舱
CoderPulse 是一个常驻 Mac 菜单栏的小工具(免费 + Pro 订阅),核心是四件事:
- 🌅 每日早报:每天早上一条本地通知,汇总昨日下载、收入、新评论,10 秒了解大盘;
- ⚠️ 差评提醒:新评论按你设置的周期自动检查,可以只关注 ≤3 星;
- 🔍 审核状态追踪:每次状态变化(In Review / Rejected / Ready…)都有通知,并保留完整历史时间线;
- 📊 菜单栏驾驶舱:点图标 5 秒扫完昨日概览、30 天趋势、最新评论流、各 App 版本状态。
iPhone / Apple Watch 提供只读伴侣体验,桌面和锁屏还有小组件:
三、架构决策:为什么「无后端」是认真设计过的
整个产品最重要的一条架构铁律是:无自建服务器。
┌─ Mac App (SwiftUI, macOS 14+) ─────────────────────┐
│ 菜单栏面板(NSPanel) ─ Settings ─ Onboarding ─ Paywall │
│ │ │
│ AlertEngine(事件比对 / 通知决策) │
│ │ │
│ SyncScheduler(轮询调度) │
│ │ │
│ ASCKit(API 客户端:JWT / 端点 / TSV 解析) │
│ │ │
│ Storage: SwiftData(业务数据)+ Keychain(API Key) │
└────────┴──────────────────────────────────────────┘
理由有三层:
- 隐私即卖点。App 业务数据直接用用户自己的 ASC API Key 直连苹果官方 API, 开发者(我)的服务器根本不存在,谈不上泄露。App Store 隐私标签可以直接声明「不收集任何数据」。
- 零运维。没有服务器 = 没有域名、没有备案、没有半夜告警、没有账单。 这对个人开发者是巨大的成本优势。
- 上架资格。对大陆区上架而言,「无自有联网功能」的声明是 App 能上架中国大陆的依据之一 (本 App 未走 ICP / App 备案,是与苹果沟通评估、由作者提交声明承担相应责任后获准)。 任何引入自建服务端的改动都会动摇这项资格——这条约束反过来锁死了架构方向。
代价也很明确:Mac 关机、合盖睡眠时没有数据被拉取,通知会延迟到唤醒后补发。 这是「无后端」方案必须接受的取舍,我在文档里如实记录,不吹成「全天候实时推送」。
四、关键技术细节(干货)
4.1 ASC API 接入:JWT ES256 手写,约 40 行
App Store Connect API 用 JWT 认证,需要三要素:Issuer ID、Key ID、.p8 私钥文件。
JWT 的签名算法是 ES256,标准实现:
- header:
{"alg":"ES256","kid":"<Key ID>"} - payload:
{"iss":"<Issuer ID>","aud":"appstoreconnect-v1","exp":<≤20min>}
用 CryptoKit 的 P256.Signing 实现,约 40 行,不引第三方 JWT 库。
.p8 私钥内容只进 Keychain(kSecAttrAccessibleAfterFirstUnlock),内存中用后即焚,绝不落盘。
4.2 限流与容错:读响应头自适应降频
ASC API 的配额是 3600 次/小时/Key(另有约 300 次/分钟的未公开限制)。 我们的用量(每 15 分钟 ≤ 2+N 个请求,N = App 数)余量巨大,但实现仍然:
- 读取
x-rate-limit响应头,余量 <10% 时自动降频; - 指数退避重试(1s / 4s / 16s,只对 5xx 与网络错误);
- 429 直接等下一个周期。
所有请求串行经过一个 actor,避免并发请求打爆限流。
4.3 数据模型:VersionEvent 只追加,白捡一个「审核历史」
核心是 5 个 SwiftData 实体:Account、App、DailySales、Review、VersionEvent。
值得一提的设计是 VersionEvent 只追加、不更新:每次轮询发现审核状态变化就插入一条新记录,
于是「版本审核历史时间线」这个功能是免费的——不需要任何额外的历史表。
另外两个细节:
- 审核状态字段做过迁移(
appStoreState→appVersionState,2025 年起),解码用双字段兼容; - ASC 枚举用
RawRepresentablestruct 而不是 enum,未知新值不会导致解码失败(前向兼容)。
4.4 通知决策:diff + 幂等,误报为零
AlertEngine 是产品的灵魂,逻辑要求最严谨:
- 输入新一轮快照,与本地上一状态做 diff:
- 新评论 =
reviewId不在库中(按设置过滤:仅 ≤3 星 / 全部); - 审核状态变化 =
(appId, version)的状态与库中不同 → 必通知(质量红线:漏报为零); - 早报 = 昨日销量报表首次解析成功时触发。
- 新评论 =
- 防误报:首次接入只入库不通知(避免开机轰炸);同一事件幂等(事件表记录已通知指纹)。
通知用 UNUserNotificationCenter(本机通知,不是推送),附深链:点击直接打开面板定位到对应 App,
或跳转 ASC 网页对应页。
4.5 多端:单一多平台 target + 菜单栏自绘面板
- 单一多平台 target:macOS / iOS / visionOS 共享 SwiftData 模型和仪表盘视图,
平台差异用
#if os(macOS)分流。iOS 伴侣不用新建工程。 - 菜单栏不用
MenuBarExtra:MenuBarExtra的 popover 在内容变宽时会整体重绘并重定位, 二级页(设置)展开时一、二级页一起闪烁。改为自定义透明无边框NSPanel(NSStatusItem+AppDelegate),尺寸由 SwiftUI 内容单一驱动,右上角锚定、向左生长。 - iOS / Watch 只读伴侣:iCloud Keychain 同步凭据(
kSecAttrSynchronizable), Watch 通过 WatchConnectivity 收 iPhone 摘要。 - 小组件:Widget 是独立进程,通过 App Group 的
UserDefaults(suiteName:)读 KB 级快照 (昨日下载 / 7 日 / 收入 / 最新评论 / 审核状态),WidgetCenter.reloadAllTimelines()刷新。 小组件渲染直接用产品源码里的AppPulseWidget.swift原样编译,保证宣传图 ≠ 实物。
4.6 质量红线
- 审核状态变化 100% 捕获(该事件不可被任何规则过滤);
- 误报为零(事件指纹幂等);
- 资源占用:CPU < 1%、内存 < 80MB(常驻菜单栏 App 的基本素养)。
五、踩过的坑(真实复盘)
- 审核状态字段迁移:
appStoreState→appVersionState,2025 年新字段上线后旧字段不可靠。 解法是双字段兼容读取,并订阅 ASC API changelog。 - 多平台版本资源混在一起:单一多平台 target 下,一个 App 的各平台版本资源在同一个接口里混着返回,
limit=2(纯 macOS 时期的取值)会漏掉平台导致状态卡在旧值——limit 必须覆盖全平台当前版本。 - 销量日报「当日不可得」:苹果销量日报次日 ~8am PT 才生成(全行业同此约束), 所以产品定位是「次日早报」而不是实时销量。每天 9:00 尝试拉昨日报表,404 则 30 分钟后重试至多 6 次。
- 菜单栏面板性能:柱状图悬停曾出现过明显卡顿,根因是 Swift Charts 的响应式重绘—— 修法涉及惰性截断与重绘范围收窄,这属于常驻菜单栏 App 特有的性能约束(详见项目复盘文档)。
- 「买断」改「年订阅」:上线后把一次性买断改成按年订阅,老买断用户的 entitlement 双轨归并、
永久保留,App 内价格一律读
Product.displayPrice、不留硬编码——价格只在 ASC 配置。
六、商业化:免费 + Pro
- 免费版:接入 1 个 App + 内置 Demo 模式(没有 API Key 也能把完整界面逛一遍,审核员也能体验)。
- Pro(按年订阅):无限 App、自动监控与全部通知;价格以 App 内为准(美国基准 $11.99/年,中国区 ¥12/年), 随时可在 App Store 账户设置中取消;老买断用户权益永久保留。
七、链接
- 落地页(中文):coderren.com/apps/apppul…
- App Store(中国区,Mac / iPhone 同一 App):apps.apple.com/cn/app/code…
- App Store(国际区):apps.apple.com/us/app/code…
- 隐私政策:coderren.com/apps/apppul…
结尾
做完这个产品我最大的体会是:独立开发者最缺的不是「看数据」,而是「别被坏消息打个措手不及」。 把网页里的焦虑变成一条早上 9 点的通知,是我觉得最值得的一笔投入。
如果你也是独立开发者,欢迎下载试试——免费版就够用一阵子,Demo 模式不需要任何 Key。 有任何问题、建议,或者想讨论无后端架构的取舍,评论区见,我会认真回复每一条。
本文为产品作者的实践分享,非广告软文模板——所有技术细节均为真实实现(单测 119 项全绿, ASCKit / AlertCore 两包纯逻辑全单测,CI 双平台编译)。