我是一个独立开发者。今年想给自己和另一半做一个 App,功能都很小:看看对方现在在干嘛、随手连个麦、发点只给对方看的动态、一起玩个小游戏。它现在叫「咫尺」。
代码大部分是我和 AI 编程助手一起写的。写功能比想象中快,真正耗时间的是功能以外的事:账号、签名、装机、上架、合规。这篇写给会写代码、但没完整走过发布流程的个人开发者。前半部分国内海外都一样,后半部分的合规分开讲。
先交代进度:写这篇的这一天(2026 年 10 月 9 日),两台手机装着开发版在测,TestFlight 上传了两个构建;正式服务器还没上线,也还没提交审核。没走完的地方我会直接说。
一、账号:先弄清三种身份#
我自己注册 Apple 开发者账号没成功。最后请一位朋友用他的身份注册了「个人」会员,这个账号平时由我在另一台 Mac 上登录使用;我自己常用的 Apple ID 则加进 App Store Connect,角色是 Admin。
所以我手上同时有两个账号:持有人账号和我自己的 Admin 账号。我以为 Admin 什么都能做,结果不是:
- 进不了证书页面。 个人会员的团队里,Admin 只能用 App Store Connect,进不了开发者网站上的「证书、标识符和描述文件」。
- Xcode 里签不了名。 Xcode 能看到这个团队,但建不了证书,真机调试一直失败。
- 团队密钥页进不去。 用我的 Admin 账号打开建团队 API 密钥的页面,会直接被跳走。
这是个人会员本来的设计:账号属于持有人一个人,加进来的用户只能管 App 和商店。连 AI 助手一开始也以为 Admin 能自己建团队密钥,让我去点;我把被跳走的页面截图给它,它才改口。
| 身份 | 是谁 | 能做什么 | 做不了什么 |
|---|---|---|---|
| 账号持有人 | 注册开发者账号的人 | 一切:签协议、建证书和各种密钥、建标识符、加用户 | 没限制,但每件事都要登录持有人账号操作 |
| App Store Connect 的 Admin | 我自己的 Apple ID | 建 App 记录、填商店信息、管内购商品、管 TestFlight | 个人会员里:进不了证书页面、Xcode 签不了名、建不了团队 API 密钥 |
| 团队 API 密钥(访问选「管理」) | 一个 .p8 文件 | 命令行签名(注册 Bundle ID 和能力、建证书、登记设备、生成描述文件)、上传 TestFlight、查构建状态 | 签协议、加用户;还有 iCloud 容器(下文细说) |
思路由此而来:只有持有人能做的事,集中做一次;日常的签名、打包、上传,全交给团队 API 密钥,在主力 Mac 上用命令行完成。
二、同一天,两个账号各做了什么#
把今天从「装不上手机」到「TestFlight 里有构建」的过程按顺序列出来,分工就很清楚:
| 顺序 | 谁 | 在哪 | 做了什么 |
|---|---|---|---|
| 1 | 持有人账号 | App Store Connect › 团队密钥 | 新建一把「管理」权限的团队密钥,下载 .p8,隔空投送到主力 Mac |
| 2 | 命令行 | 主力 Mac | 带上密钥第一次打包:自动建好 App ID、开发证书,登记两台手机;卡在 iCloud 容器 |
| 3 | 持有人账号 | 开发者网站 › 标识符 | 注册 iCloud 容器,再把它勾到 App ID 上 |
| 4 | 命令行 | 主力 Mac | 同一条命令重新打包,两台手机装好、配对成功 |
| 5 | 我的 Admin 账号 | App Store Connect › App | 建 App 记录(名字被占,换了一个) |
| 6 | 命令行 | 主力 Mac | 打正式包、检查、上传 TestFlight,用接口查处理状态 |
| 7 | 我的 Admin 账号 | App Store Connect › TestFlight | 建内部测试组,打开自动分发,加测试员 |
持有人这边:只做「建身份」的事#
持有人账号今天只点了三处:建团队密钥、注册 iCloud 容器、把容器勾到 App ID 上。这些都是密钥、标识符一类的东西,团队密钥替代不了。
iCloud. 前缀,自己再加一遍就重复了。容器注册后删不掉,确认页上看清再点 Register。右上角账号已打码。
以后持有人还要再出手的事,我列成了清单,攒到一起约一次:推送密钥、「通过 Apple 登录」的密钥(删号时撤销授权要用)、付费 App 协议和税务银行、在 CloudKit 控制台把数据结构发布到正式环境;要在 App Store 以外发 Mac 程序的话,还有 Developer ID 证书。
非持有人这边:管 App 和测试,签名交给命令行#
我的 Admin 账号今天做的是 App Store Connect 里的事:建 App 记录、建 TestFlight 测试组。这里踩了两个小坑:
- 先确认团队。 我的 Apple ID 同时在公司的团队和这个个人团队里。建 App 记录前要在右上角确认当前团队,选错就建到公司那边去了。套装 ID 下拉里找不到自己的 App ID,多半就是团队选错了。
- 测试员要是团队里的人。 内部测试只能加团队里的用户。另一台手机如果用别的 Apple ID,要先在「用户和访问」里邀请进团队,权限给最小,只开放这一个 App。
签名、装机、上传这些,Admin 账号一概不碰,全部由命令行带着团队密钥完成。
三、一把密钥管所有 App#
怎么建#
请持有人登录 App Store Connect,进「用户和访问 › 集成 › App Store Connect API › 团队密钥」:
- 第一次要点「请求访问」,只有持有人能点,点一次就行。
- 点「+」新建,名字起得通用一点,比如「签名(全部 App)」,访问权限选「管理」。
- 下载得到
AuthKey_<密钥 ID>.p8,同时记下页面上的 Issuer ID 和这把密钥的密钥 ID。
团队 API 密钥按团队发,不绑定某个 App,iPhone、iPad、Mac 都能用。所以一把密钥管团队里所有 App,以后做新 App 不用再麻烦持有人。
踩的坑#
- 我们一度以为是权限不够。 命令行签名一直报
Authentication failed,团队里原有的两把密钥访问都是「开发者」,于是请持有人新建了一把「管理」的。结果换了照样失败,真正的原因在下一节。「开发者」权限的密钥到底够不够做云签名,我没再单独验证,现在统一用「管理」那把。 .p8只能下载一次。 页面上的下载按钮点过就没了,弄丢只能撤销重建。- 传密钥别用聊天工具。 持有人账号那台 Mac 下载后,用隔空投送传给主力 Mac。聊天工具和邮件会在服务器和多台设备上留副本,很难确认删干净了。
- 备份别把钥匙挂在锁上。 我翻出几个月前的签名备份:加密磁盘映像和它的口令文件放在同一个 iCloud 文件夹里,旧的
.p8还改成.txt明文放着。现在口令放密码管理器,密钥只放签名库,备份进加密盘。
每台 Mac 一个「签名库」#
每台用来打包的 Mac 上,我建了一个固定目录(比如 ~/.config/<签名库目录>/),权限只给自己,里面放密钥文件和一个三行的配置:
APPLE_TEAM_ID=<团队 ID>
APPLE_API_KEY_ID=<密钥 ID>
APPLE_API_ISSUER=<Issuer ID>
所有项目的打包脚本都从这里读,项目里不放任何密钥。脚本先检查:工程里的团队 ID 和签名库一致才往下走。 我电脑上还有公司团队的签名材料,这能防止哪天手滑用错密钥。以后上自动打包(CI),另建一把密钥放进 CI 的 Secrets,出了事只撤那一把。
四、命令行签名、装机和 TestFlight#
一条命令搞定签名#
签名的核心就是给 xcodebuild 带上团队 API 密钥:
xcodebuild -project <工程>.xcodeproj -scheme <方案> -configuration Debug \
-destination "id=<手机 UDID>" \
-allowProvisioningUpdates -allowProvisioningDeviceRegistration \
-authenticationKeyPath <签名库>/AuthKey_<密钥 ID>.p8 \
-authenticationKeyID <密钥 ID> -authenticationKeyIssuerID <Issuer ID> \
build
第一次跑,它会自动注册 Bundle ID(比如 com.example.app)并打开工程里声明的能力,建开发证书,登记连着的手机,生成描述文件。我把它写成装机脚本:读签名库 → 校验团队 → 构建 → 用 xcrun devicectl 装到手机。插上手机跑一次就装好。 新手机记得先打开「开发者模式」、点「信任此电脑」。
真正的坑:iCloud 容器#
换了「管理」权限的密钥还是 Authentication failed,这回没再换东西,而是一步步排除:
- 用这把密钥直接调 App Store Connect 的接口,列 App、证书、设备都正常返回,说明密钥本身没问题;
- 对了一下服务器时间和本机时间,没有偏差;
- 翻 Xcode 的系统日志,一个请求一个请求地看:查证书、登记设备、建 App ID 都成功了,只有查 iCloud 容器的那一个请求被拒,返回的正是
Authentication failed。
结论是:团队 API 密钥建不了 iCloud 容器,要持有人在开发者网站上注册容器、勾到 App ID 上(就是第二节那两张图)。持有人点完以后,同一条命令直接通过。在那之前,我们临时去掉了 iCloud 权限打了一个包,先把两台手机的测试跑起来。
运行 /usr/bin/log show --last 10m --info --debug --predicate 'process == "xcodebuild"',搜 Received response 和后面的返回内容。zsh 自带一个同名的 log 命令,所以要写全路径;日志里有访问令牌,别外传。所有请求都失败,多半是密钥被撤销、ID 写错或者电脑时间不对;只有 iCloud 容器那一个失败,就请持有人建容器。App Groups 可能也有同样的限制,我没核实。
两台手机一起测#
这个 App 大部分功能要两个人才能测:
- Mac 上起一个局域网测试服务器,两台手机连同一个 Wi-Fi,都连这台 Mac;
- 每台手机一个测试账号,装机脚本按手机分配,装完直接是「已配对」;
- 开发包要申请「本地网络」权限,Info.plist 里写
NSLocalNetworkUsageDescription,否则请求会静默失败。
这样能测 App 自己的逻辑:状态同步、动态、游戏、连麦。测不了的有三类:真实登录(要连真实服务)、推送(要推送密钥和发推送的服务器)、App 内购买(要沙盒账号和服务器通知)。
上传 TestFlight#
上传脚本也只用同一把团队密钥:xcodebuild archive 打包 → 检查包里的配置 → -exportArchive 签名并上传。过程中改了几处:
- 清掉开发配置。 经 xcconfig 写进 Info.plist 的开发令牌和测试账号,会跟着包一起发出去。脚本打正式包时清空它们,再检查包里确实没有。
- 「本地网络」说明只进开发包。 正式包里这一项是空的,打包会报警告;改成只在 Debug 配置里写入。
- 隐私清单。 加了
PrivacyInfo.xcprivacy,声明用到的需要说明理由的接口(比如UserDefaults)。 - 构建号自动递增,用日期加时间,每次都比上一次大。
- 第三方库没带调试符号的警告,只影响崩溃日志的符号化,不影响上传。
这次两个构建传完都是几分钟就处理好、变成「有效」。我用接口轮询处理状态,不用一直刷网页。另外,TestFlight 版和开发版是同一个 Bundle ID,装了会替换开发版;正式服务器还没上线时,TestFlight 版只能看到同意页,所以先只装一台,确认流程走得通。
五、名字:商店名、桌面名、备案名#
想好的名字「咫尺」,我用 App Store 的公开搜索接口查了中国、日本、美国三个区,都没有同名的 App。到建 App 记录时却提示已被使用:
- 商店名和桌面名是两回事。 商店名必须唯一,最后填了「咫尺 - 情侣和家人的小空间」(30 个字以内);手机桌面上显示的名字是工程里另一个设置,仍然叫「咫尺」。
- 中国区的商店名要和备案名逐字一致,所以要在办备案之前定下来。
- 常用词可能已被注册商标。 定之前去中国商标网查软件、通信、社交服务相关的类别,这一步我还没做完。
- 工程里名字只在一处配置,CI 检查包里不残留旧名字,以后再改只动一行。
六、和 AI 编程助手怎么配合#
这个项目我的角色更像产品经理加技术负责人。
- 我:提需求,做决定。比如「中国版先不带 AI 画图」「删号只删这个 App 的数据」。
- 助手:调研、实现、写测试。调研结论写进仓库文档,标清楚哪些联网核对过、哪些没核实。
- 另一个助手:独立审查,只看改动、挑毛病,修完再往下走。
开发、推送、开 PR、合并、发布,每一步单独授权。今天上传 TestFlight 时,助手先被权限检查拦下,等我明确说了「确认上传这一版到 TestFlight」才传;推送开 PR、合并也各说了一次。密钥不进仓库,也不贴进对话,助手的脚本只知道去签名库目录读。
三个例子:
审查抓到了一个能白拿积分的漏洞。 正常流程是:手机上付款,App 把交易交给服务器,服务器加分;Apple 退款时通知服务器,服务器扣回。审查的助手发现,如果 App 还没交单,用户就先退了款,服务器收到退款通知时手里没这笔交易,扣不了;之后 App 再交单,服务器照样加分。修法是服务器记下「没交单就已退款」的交易,之后交上来的同一笔一律拒绝。
它也会判断错。 一天里错了两次:以为 Admin 能建团队密钥;以为签名失败是权限不够,让我白建了一把密钥。后来的规矩是:先找到失败的那一个请求,再动手换东西。 截图和日志是最好的证据。
一个只在 CI 上出现的 bug。 脚本里有变量后面直接跟中文标点,像 $BUNDLE_ID,。在 UTF-8 环境下,macOS 自带的 bash 3.2 会把中文的第一个字节当成变量名的一部分,读到一个不存在的变量,脚本直接退出。助手执行命令的环境恰好不是 UTF-8,本机一直没暴露,到 CI 上才失败。修法是一律写成 ${BUNDLE_ID},再在 CI 里加一条检查。脚本里写中文的朋友可以留意。
七、共享后端:几个 App 共用登录、AI 和存储#
我不止做这一个 App。登录、AI 调用、文件存储这些每个 App 都要的东西,放在我自己的一个共享后端;业务数据(这里是配对关系、动态、积分)放各自的后端。好处是加一个新 App 很快:在共享后端登记一下应用和它的 Bundle ID,登录、AI、存储就都能用了。
要注意的是删号。App Store 要求能在 App 里删账号,但共享后端里一个账号可能同时在用我好几个产品。所以删号必须按 App 删:只删这个 App 的业务数据和它在共享后端留下的部分(比如这个 App 的 Apple 登录授权),不能把用户在我别的产品里的账号一起删掉。
八、国内和海外的合规,以及我的选择#
写于 2026 年 10 月。以下是我作为个人开发者查到的要求和自己的做法,规则以官方原文为准,不构成法律意见。标「我没核实」的地方,请自己去查原文或请教专业人士。
国内#
1. App 备案。 在国内上架,App 要先备案,App Store Connect 里中国大陆的版本要填备案号。我看到的个人办理流程是通过云服务商提交:身份证件、人脸核验,再加上 Bundle ID 和签名证书的公钥信息。
关键一条:备案主体要和开发者账号持有人一致。 这对我是个大坑:账号持有人不是我。要么用持有人的名义备案,要么以后用我自己名下的账号发中国版。另外,有付费内容算不算「经营性」、要不要另办许可证,我没查到能下定论的说法。
2. 生成式 AI。 按我查到的资料:只是调用已备案大模型的应用,也要去属地网信办登记,并公示模型名称和备案号;AI 生成的图片还要加标识,包括看得见的「AI 生成」字样和文件里的隐式标识。个人主体能不能办登记,我没查到明确答复,所以中国版首发先不带 AI 画图。
3. 个人信息保护。 首次打开先弹《用户协议》《隐私政策》,同意之前不联网、不注册推送;位置这类敏感信息,在用到的功能里单独再征求一次同意;设置里放「个人信息收集清单」和「第三方共享清单」。
4. 数据出境。 连麦的声音画面如果走境外服务器,就可能涉及数据出境。我没把这块研究透,选择直接避开:中国版的通话服务部署在境内。系统级来电界面(CallKit)在中国区有限制(目前执行情况我没核实),中国版也不用。
海外#
我的海外版先考虑日本,所以重点看了日本:
- 个人信息保护法(APPI):隐私政策里要告知数据存在哪个国家及该国的相关制度。海外版上线前,我会把海外用户的数据迁到日本的独立后端和数据库。
- App 内积分:用钱买的积分一般属于「前払式支払手段」(预付式支付手段)。Apple 不允许 App 内购买的货币过期,用不上「短期过期」的豁免。按我查到的资料,余额到一定规模要申报、缴存保证金,还有表示义务;境外的发行人可能还要先在日本设据点。我先把付费积分和赠送积分分开记账,怎么卖还没定。
- 特定商取引法:上线前放一份「特定商取引法に基づく表記」,写清销售者、联系方式、价格、退款规则等。
- 用户之间的消息和通话:按我查到的资料,上线前可能要向总务省申报,住在境外的还要指定国内代表,我还在确认。
这几条有个共同点:国内的备案主体、日本的积分发行人和通信申报人,按我查到的资料,都要和 App Store 上的开发者是同一个人。开发者账号是谁的名字,影响远不止签名。
欧盟和英国先不上。 当地代表、跨境传输的合同和评估、公开经营者地址电话、英国的在线安全风险评估、欧盟 AI 法案的透明度义务……对一个人太重了,首发直接排除这些地区。
我的选择:两个 App、一套代码#
一开始我做的是「一个包,按用户的商店地区切换登录方式」。合规查下来发现不够:审核看的是整个包,包里的 AI 画图、CallKit、海外登录都在;一个 App 只有一份隐私标签、年龄分级和协议;用户换了商店地区或者出差,数据可能落到错的地区。所以最后定成两个 App:
| 中国版 | 海外版 | |
|---|---|---|
| Bundle ID | com.example.app | com.example.app.global |
| 上架地区 | 只上中国大陆 | 排除中国大陆(首发也排除欧盟、英国) |
| 登录 | 手机号验证码 | 通过 Apple 登录、第三方登录、邮箱验证码 |
| 后端和数据 | 境内独立一套 | 日本独立一套 |
| AI | 首发不带 | 海外的 AI 服务 |
| 通话 | 境内自建 | 海外的云服务 |
两个版本是同一个仓库、同一套代码,用构建配置区分。用户的数据归属在注册时就定下,以后不变。
代价也很明确:每次发版多出两三成工作(两次构建、两套商品和截图);两套后端要维护;两个版本之间不能配对,两个人分属两个地区就用不到一起;审核指南不鼓励发重复的 App,我的应对是上架地区不重叠、名称和截图有区分、审核备注写明各地法律要求。这一步还没走到,结果以后补。
九、一份可照抄的清单#
每个开发者账号做一次(持有人)#
- 协议:让付费 App 协议生效,填好税务和银行,否则 App 内购买也卖不了。
- 团队 API 密钥:点「请求访问」,建一把「管理」权限的密钥;
.p8只能下载一次,用隔空投送或 U 盘交接。 - 推送密钥(要推送时):一把给团队所有 App 用,只放在发推送的服务器上。
- 通过 Apple 登录的密钥(要用 Apple 登录时):删号时撤销授权要用。
- 签名库:每台打包的 Mac 一个只有自己能读的目录,放密钥和三行配置;脚本校验团队 ID。
- CI 专用密钥:上自动打包时另建一把,出事只撤那一把。
每个新 App 做一次#
- 定 Bundle ID 和地区(自己):建 App 记录前定好,之后改不了;分国内海外的一开始就定两个,内购商品编号用 Bundle ID 做前缀。
- 开能力(自己):工程里选对团队、自动签名,把推送、通过 Apple 登录、iCloud 等能力写进 entitlements。
- iCloud 容器(持有人):用 iCloud 的,注册容器并勾到 App ID 上,团队密钥建不了。
- 命令行第一次打包(团队密钥):Bundle ID、能力、证书、设备、描述文件一次到位。
- 建 App 记录(Admin):先确认团队;名称要唯一,建不了就换一个。
- 商店信息(Admin):隐私政策和支持网址、App 隐私标签、年龄分级、出口合规、截图和描述。
- 内购(Admin + 服务器):建商品,填服务器通知地址(生产、沙盒各一个),服务器端校验交易、处理退款。
- TestFlight(团队密钥 + Admin):清开发配置、加隐私清单、上传;建内部测试组、打开自动分发、加测试员。Xcode 里手动归档、上传、提审的点击步骤,我之前写过一篇《苹果 App 上架流程》。
- 上中国区:办 App 备案(主体和账号持有人一致),备案号填进 App Store Connect;有生成式 AI 功能的先办登记。
十、最后三条建议#
1. 先打通账号和签名,再写功能。 账号身份、密钥权限、iCloud 容器、备案主体,这些问题和代码无关,却能卡住整个项目。开发者账号用谁的名字,后面还会影响备案和海外合规。第一周就该做到:一条命令装到真机。
2. 一开始就定好地区和 Bundle ID。 上哪些地区,决定了要几个 App、几套后端、哪种登录、能用哪家 AI。Bundle ID 建了记录就改不了,商店名要唯一、要和备案一致。值得在写第一行代码前花一个晚上想清楚。
3. 让 AI 干活,但授权和密钥自己握着。 AI 编程助手调研、写代码、写测试、互相审查都很能干,但它也会判断错。让它先拿出证据(失败的请求、截图、日志)再动手;推送、合并、上传、发布这些出了门就收不回来的动作,每一步由我点头;密钥只在我自己的签名库里,不进仓库、不进对话。