news 2026/9/10 3:40:15

fastlane Spaceship App Store Connect API 完整实战指南:从登录鉴权到审核上架的自动化操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fastlane Spaceship App Store Connect API 完整实战指南:从登录鉴权到审核上架的自动化操作

fastlane Spaceship App Store Connect API 完整实战指南:从登录鉴权到审核上架的自动化操作

【免费下载链接】fastlane🚀 The easiest way to automate building and releasing your iOS and Android apps项目地址: https://gitcode.com/GitHub_Trending/fa/fastlane

本指南以 fastlane 仓库中 spaceship/docs/AppStoreConnect.md 为核心,系统讲解 Spaceship 如何通过App Store Connect API以脚本方式完成应用管理、版本元数据修改、TestFlight 分发、审核提交、评分评论获取乃至 Bundle Id 能力配置与 Webhook 管理。读完本文,你将掌握基于 API Key(Auth Key)与 Apple ID 两套登录体系的全部常用操作,并能将Spaceship::ConnectAPI的调用直接复用到自己的 CI/CD 流水线中。

一、背景:Spaceship 与两套 App Store 接口

Spaceship 是 fastlane 体系与 Apple 开发者后台打交道的底层库(仓库位置 spaceship/lib/spaceship)。从文档可以明确看到,Spaceship 实际封装了两代接口:

  • App Store Connect API(新版):所有模型类统一挂在Spaceship::ConnectAPI模块下,例如Spaceship::ConnectAPI::AppSpaceship::ConnectAPI::BuildSpaceship::ConnectAPI::BundleId。它使用 Apple 提供的 API Key(.p8文件)签发 JWT 进行鉴权。
  • Tunes(旧版 Web 接口)Tunes是 "iTunes Connect" 时代的命名遗存。当某些能力(如改价格、拉取评分与评论、App Analytics)尚未在新版 Connect API 中提供时,仍需通过Spaceship::Tunes::Application配合 Apple ID 登录来操作。

文档特别提醒:如果你同时使用 Developer Portal 与 App Store Connect,两处需要分别登录,因为两者可能使用不同的用户凭证(详见仓库内 spaceship/docs/Authentication.md)。

想快速体验,在终端执行irb后输入require "spaceship"即可开始交互式探索。

二、登录鉴权:两种方式的核心用法

2.1 API Key 方式(Connect API 首选)

token = Spaceship::ConnectAPI::Token.create( key_id: 'the-key-id', issuer_id: 'the-issuer-id', filepath: File.absolute_path("../AuthKey_the-key-id.p8") ) Spaceship::ConnectAPI.token = token

创建 token 后,将其赋给Spaceship::ConnectAPI.token,之后所有 ConnectAPI 调用都会自动携带该凭证。

从源码 token.rb 可以看出几个值得注意的实现细节:

  • 令牌时长:Apple 支持的最大过期时间为 20 分钟,源码中定义为MAX_TOKEN_DURATION = 1200(秒),默认值DEFAULT_TOKEN_DURATION = 500秒。可通过duration参数(或环境变量SPACESHIP_CONNECT_API_TOKEN_DURATION)调整。
  • JWT 载荷:使用 ES256 椭圆曲线签名,payload 中aud根据团队类型取appstoreconnect-v1apple-developer-enterprise-v1iat会刻意回拨 60 秒,避免本机时钟略快于 Apple 服务器导致令牌被拒。
  • 环境变量注入key_idissuer_idfilepathkeydurationin_house均可通过SPACESHIP_CONNECT_API_KEY_IDSPACESHIP_CONNECT_API_ISSUER_IDSPACESHIP_CONNECT_API_KEY_FILEPATHSPACESHIP_CONNECT_API_KEYSPACESHIP_CONNECT_API_TOKEN_DURATIONSPACESHIP_CONNECT_API_IN_HOUSE等环境变量提供,非常适合 CI 场景。
  • JSON Key 文件:除.p8路径外,Token.from_json_file(filepath)还支持包含key_idkey(base64 的私钥内容)的 JSON 文件,缺失必填字段时会明确报错。

2.2 Apple ID / 会话方式(Tunes 旧接口)

旧版接口能力(调价、评分评论、Analytics 等)依赖 Apple ID 登录态。仓库提供了对应的 CLI 会话持久化工具,运行spaceship login(通过 commands_generator.rb)即可复用 fastlane 的账号会话。两种方式务必按需选择,切不可混用ConnectAPI模型与 Tunes 模型的鉴权上下文。

三、应用(Applications)管理

3.1 查询与遍历

# Fetch all available applications all_apps = Spaceship::ConnectAPI::App.all # Find a specific app based on the bundle identifier app = Spaceship::ConnectAPI::App.find("com.krausefx.app") app = Spaceship::ConnectAPI.get_app(app_id: 1013943394).first # Access information about the app app.id # => 1013943394 app.name # => "Spaceship App" app.bundle_id # => "com.krausefx.app" app.sku # => "SpaceshipApp01" app.primary_locale # => "en-US" # Show the names of all your apps Spaceship::ConnectAPI::App.all.collect do |app| app.name end

对照模型 app.rb 可以看到:

  • App.all内部调用client.get_apps并自动翻页(all_pagesflat_map(&:to_models)),默认携带ESSENTIAL_INCLUDES = "appStoreVersions"
  • App.find(bundle_id)其实是"先按bundleId过滤拉全量,再做内存匹配",因此参数就是 Bundle ID 字符串。
  • 响应解析由 model.rb 中的Models.parse完成:它根据 JSON 中datatype查找对应模型类,再依据attr_mapping表把 API 的驼峰字段(如bundleId)映射为 Ruby 的蛇形属性(如bundle_id),同时生成别名读写方法。

3.2 创建新应用

# Currently only works with Apple ID login (not API Key) app = Spaceship::ConnectAPI::App.create(name: "App Name", version_string: "1.0", # initial version sku: "123", primary_locale: "en-us", bundle_id: "com.krausefx.app", platforms: ["IOS"], company_name: "krause inc")

App.create最终路由到client.post_app(app.rb)。文档明确标注:该操作目前仅支持 Apple ID 登录,不支持纯 API Key,需注意鉴权方式的限制。

3.3 修改非版本级信息与价格

元数据(App 名称、隐私政策 URL 等)以及价格修改走 Tunes 旧接口:

app = Spaceship::Tunes::Application.find("com.krausefx.app") details = app.details details.name['en-US'] = "App Name" details.privacy_url['en-US'] = "https://fastlane.tools" details.save! # To change the price of the app (it's not necessary to call save! when updating the price) app.update_price_tier!("3")

注意这里取值是按语言代码的 hash 结构(如details.name['en-US']);改价格时文档特别注明无需调用save!update_price_tier!会直接提交。

四、App 版本(AppVersions)与元数据修改

4.1 同一时刻的多个版本状态

同一时刻一个 App 最多存在 2 个 App Store 版本:通常是已在商店上架的版本(通过get_live_app_store_version获取)和正在编辑、尚未上架的版本get_edit_app_store_version)。生产版本部分字段(如应用描述)仍可修改,但大多数选项已被锁定。

app.get_live_app_store_version # the version that's currently available in the App Store app.get_edit_app_store_version # the version that's in `Prepare for Submission`, `Metadata Rejected`, `Rejected`, `Developer Rejected`, `Waiting for Review`, `Invalid Binary` mode app.get_latest_app_store_version # the version that's the latest one app.get_pending_release_app_store_version # the version that's in `Pending Developer Release` or `Pending Apple Release` mode app.get_in_review_app_store_version # the version that is in `In Review` mode

上述方法定义位于 app.rb。除get_latest_app_store_version外,其余默认都会带上AppStoreVersion::ESSENTIAL_INCLUDES关系;文档中提到的各种 App Store 状态(PREPARE_FOR_SUBMISSIONWAITING_FOR_REVIEWIN_REVIEWPENDING_DEVELOPER_RELEASEREADY_FOR_SALE等)在 app_store_version.rb 的AppStoreState/AppVersionState常量中有完整枚举。

4.2 读取与更新版本元数据

v = app.get_edit_app_store_version # Access information v.app_version_state # => "Waiting for Review" v.version_string # => "0.9.14" # Build is not always available in all app_version_state, e.g. not available in `Prepare for Submission` build_number = v.build.nil? ? nil : v.build.version # Update app metadata copyright = "#{Time.now.year} Felix Krause" v.update(attributes: { "copyright": copyright })

注意v.updateattributes会被 model.rb 中的reverse_attr_mapping反转为 API 所需的字段名(蛇形转驼峰),再执行 PATCH 请求,因此你始终用 Ruby 侧命名写即可。

4.3 本地化(Localization)内容

# Get a list of available languages for this app version = app.get_edit_app_store_version(includes: 'appStoreVersionSubmission,build,appStoreVersionLocalizations') localizations = version.appStoreVersionLocalizations localization = localizations.first localization.locale # => "en-GB" localization.description # => "App description" # Update localized app metadata localization.update(attributes: { description: "New Description" })

includes显式传成逗号分隔的关系列表(如appStoreVersionSubmission,build,appStoreVersionLocalizations),就能一次取回审核提交、构建与本地化条目,从而省去多次往返。

4.4 年龄分级(Age Rating)

# fetch_age_rating_declaration with `fetch_live_app_info` or `fetch_edit_app_info` app_info = app.fetch_edit_app_info declaration = app_info.fetch_age_rating_declaration unless app_info.nil? # update age_rating_declaration declaration.update(attributes: { "violenceCartoonOrFantasy": "NONE", "matureOrSuggestiveThemes": "NONE", "unrestrictedWebAccess": false })

年龄分级声明的修改路径是AppInfo → AgeRatingDeclaration两级对象。可用的分级取值可参考仓库内的 deliver/assets/example_rating_config.json(如各暴力/主题项的NONE/INFREQUENT/FREQUENT等级及布尔开关),以及 deliver/Reference.md 中关于分级配置的完整说明。

4.5 版本对象可用属性一览

文档整理出的可访问字段可分为三层:

#### # General app store version metadata (app_store_version) #### attr_accessor :platform attr_accessor :version_string attr_accessor :app_store_state attr_accessor :app_version_state attr_accessor :store_icon attr_accessor :watch_store_icon attr_accessor :copyright attr_accessor :release_type attr_accessor :earliest_release_date attr_accessor :is_watch_only attr_accessor :downloadable attr_accessor :created_date attr_accessor :app_store_version_submission attr_accessor :app_store_version_phased_release attr_accessor :app_store_review_detail attr_accessor :app_store_version_localizations #### # App Review Information (app_store_review_detail) #### attr_accessor :contact_first_name attr_accessor :contact_last_name attr_accessor :contact_phone attr_accessor :contact_email attr_accessor :demo_account_name attr_accessor :demo_account_password attr_accessor :demo_account_required attr_accessor :notes attr_accessor :app_store_review_attachments #### # Localized values (app_store_version_localization) #### attr_accessor :description attr_accessor :locale attr_accessor :keywords attr_accessor :marketing_url attr_accessor :promotional_text attr_accessor :support_url attr_accessor :whats_new attr_accessor :app_screenshot_sets attr_accessor :app_preview_sets

这些attr_accessor实际上由 model.rb 的attr_mapping根据模型内声明的映射表动态创建,app_store_version.rbapp_store_review_detail.rbapp_store_version_localization.rb等模型文件均位于 spaceship/lib/spaceship/connect_api/models 下,其中保留了每个属性的完整类型、描述与备注。

五、完整审核生命周期:选构建 → 提交 → 发布

5.1 选择用于审核的构建

version = app.get_edit_app_store_version build = Spaceship::ConnectAPI::Build.all(app_id: app.id, platform: platform).first version.select_build(build_id: build.id)

select_build在 app_store_version.rb 中定义,底层对应 Connect API 的版本关联构建操作。注意在Prepare for Submission状态下通常还没有可选构建,需要先上传构建并处理完成后才能关联。

5.2 提交审核

version.create_app_store_version_submission

提交动作会创建AppStoreVersionSubmission,对应 app_store_version.rb。提交审核前需要把完整的审核信息(联系信息、演示账号、审核备注、附件等,即上文app_store_review_detail那组属性)与所有本地化元数据准备齐全。完整可参考的实现范例在 deliver 的 submit_for_review.rb,fastlane 的deliveraction(对应测试 fastlane/spec/actions_specs/deliver_action_spec.rb)也正是走这套逻辑完成"准备提交 → 提交审核"的端到端流程。

5.3 发布已过审的构建

当版本处于Pending Developer ReleasePending Apple Release(由get_pending_release_app_store_version获取)时,可主动触发发布。文档给出两种等价写法:

version = app.get_pending_release_app_store_version unless version.nil? Spaceship::ConnectAPI.post_app_store_version_release_request(app_store_version_id: version.id) end

或直接调用模型实例方法:

version = app.get_pending_release_app_store_version version.create_app_store_version_release_request unless version.nil?

两种方式最终都会创建AppStoreVersionReleaseRequest(模型见 app_store_version_release_request.rb),适合"人工点选过审后自动上架"的自动化发布场景。

六、Build Trains 与构建管理(TestFlight)

6.1 理解版本号与构建号

文档先用一张图澄清两个最易混淆的概念:

  • version number(版本号):由CFBundleShortVersionString决定,是 App Store 上对用户展示的版本,如0.9.21
  • build number(构建号):由CFBundleVersion决定,商店页面不可见,上传新构建前必须递增,如99993

Build Train(构建序列)指同一version number下的全部构建集合;序列内部可以有n个构建,每个构建拥有不同的build number

6.2 遍历 Build Trains(Tunes 接口)

app = Spaceship::Tunes::Application.find("com.krausefx.app") # Access all build trains for an app app.all_build_train_numbers # => ["0.9.21"] # Access the build train via the version number train = app.build_trains["0.9.21"] # Access all builds for a given train train.count # => 1 build = train.first

6.3 读取构建详情并提交 Beta 审核

# Continue from the BuildTrains example build.build_version # => "99993" (the build number) build.train_version # => "0.9.21" (the version number) build.install_count # => 1 build.crash_count # => 0 build.internal_state # => testflight.build.state.testing.ready build.external_state # => testflight.build.state.submit.ready

build.internal_state/build.external_state分别表示内测/外测状态。在设置好全部必要的 TestFlight 元数据后,可以直接把构建提交给外部 Beta 审核:

build.submit_for_testflight_review!

构建的底层模型见 spaceship/lib/spaceship/connect_api/models/build.rb,旧版接口实现在 test_flight/build.rb。关于 TestFlight 的深入用法(Beta 组、构建状态流转等),仓库内还有专门的 spaceship/docs/TestFlightTesting.md 可以参考。

6.4 处理长期卡在 Processing 的构建

当构建在 App Store Connect 端长时间停留在Processing状态时,可通过 Connect API 主动轮询取出这些构建:

Spaceship::ConnectAPI::Build.all(app_id: app.id, processing_states: "PROCESSING") # => Array of processing builds for this application

配合 CI 做"上传后等待处理完成再提交审核"时,这个过滤条件非常实用。

七、测试员(Testers)管理

文档明确区分了三类测试员:

  • External testers(外部测试员):通常不属于你的团队,最多可邀请 10000 名。向外部测试员分发构建前,必须先提交 Beta 审核;
  • Internal testers(内部测试员):注册在 App Store Connect 团队中的员工,无需等待审核即可访问所有构建;
  • Sandbox testers(沙盒测试员):用于在开发模式下测试应用内购买或 Apple Pay 的虚拟账号。
# Find a tester based on the email address tester = Spaceship::TestFlight::Tester.find(app_id: "some_app_id", email: "felix@krausefx.com") tester = Spaceship::ConnectAPI::BetaTester.find(email: "felix@krausefx.com") # Creating new testers Spaceship::TestFlight::Tester.create_app_level_tester( app_id: "io.myapp", email: "github@krausefx.com", first_name: "Felix", last_name: "Krause" )

文档说明当前spaceship 尚不能修改或创建 internal testers。外部测试员既可用旧接口Spaceship::TestFlight::Tester,也可用新版Spaceship::ConnectAPI::BetaTester(模型见 beta_tester.rb)。

沙盒测试员的增删(走 Connect API)示例:

# Load all sandbox testers testers = Spaceship::ConnectAPI::SandboxTester.all # Delete sandbox testers testers.each do |tester| if UI.confirm("Delete #{tester.email}?") tester.delete! end end # Create a sandbox tester Spaceship::ConnectAPI::SandboxTester.create( first_name: "Test", # required last_name: "Three", # required email: "sandbox@test.com", # required password: "Passwordtest1", # required. Must contain >=8 characters, >=1 uppercase, >=1 lowercase, >=1 numeric. confirm_password: "Passwordtest1", # required secret_question: "Question", # required. Must contain >=6 characters secret_answer: "Answer", # required. Must contain >=6 characters birth_date: "1980-03-01", # required app_store_territory: "USA" # required )

口令与密保的复杂度约束(长度、大小写、数字)文档均给出明确要求,脚本化创建时应一并校验,避免 API 报错。

八、评分与评论(Ratings & Reviews)

评分评论能力走 Tunes 旧版接口(Apple ID 鉴权):

app = Spaceship::Tunes::Application.find("com.krausefx.app") # Get the rating summary for an application ratings = app.ratings # => Spaceship::Tunes::AppRatings # Get the number of 5 star ratings five_star_count = ratings.five_star_rating_count # Find the average rating across all stores average_rating = ratings.average_rating # Find the average rating for a given store front average_rating = app.ratings(storefront: "US").average_rating # Get reviews for a given store front reviews = ratings.reviews("US") # => Array of hashes representing review data

可以通过storefront参数限定地区(如"US")获取某商店前端的平均分与评论数组,用于质量监控与舆情分析。

九、App Analytics(应用分析)

同样基于 Tunes 接口,app.analytics返回Spaceship::Tunes::AppAnalytics,默认覆盖最近 7 天,可拉取下列全部指标(返回值是逐日原始数据的日期数组):

app = Spaceship::Tunes::Application.find("com.krausefx.app") analytics = app.analytics # => Spaceship::Tunes::AppAnalytics units = analytics.app_units # => App units(下载单位) views = analytics.app_views # => App Store page views(商店页浏览量) impressions = analytics.app_impressions # => Impressions(曝光量) sales = analytics.app_sales # => App sales(销售额) users = analytics.paying_users # => Paying users(付费用户数) iap = analytics.app_in_app_purchases # => In app purchases(内购数据) installs = analytics.app_installs # => App installs(安装量) sessions = analytics.app_sessions # => App sessions(会话数) devices = analytics.app_active_devices # => Active devices(活跃设备数) crashes = analytics.app_crashes # => Crashes(崩溃次数)

这套接口适合做发布后的数据看板脚本,把这些日报数据按天落库即可生成自己的监控报表。

十、Bundle Id 管理(Auth Key 场景)

10.1 查询与创建 Bundle Identifier

# Fetch all bundle identifiers all_identifiers = Spaceship::ConnectAPI::BundleId.all # Find a specific identifier based on the bundle identifier bundle_id = Spaceship::ConnectAPI::BundleId.find("com.krausefx.app") # Access information about the bundle identifier bundle_id.name bundle_id.platform bundle_id.identifier bundle_id.seed_id # Create a new identifier identifier = Spaceship::ConnectAPI::BundleId.create(name: "Description of the identifier", identifier: "com.krausefx.app")

该能力只走 API Key 鉴权(因此标题标注 Auth Key)。实现对应 bundle_id.rb。

文档标注了一个平台相关的事实:无论指定IOS还是MAC_OSplatform都会被设为UNIVERSAL;若不指定,seed_id默认取 team_id。编写依赖平台判断的脚本时务必留意这一行为。

10.2 Bundle Id Capability(能力配置)

# Fetch all capabilities for bundle identifier bundle_id = Spaceship::ConnectAPI::BundleId.find("com.krausefx.app") capabilities = bundle_id.get_capabilities # Create a new capability for bundle identifier bundle_id.create_capability(Spaceship::ConnectAPI::BundleIdCapability::Type::MAPS) # Create a new capability with known bundle identifier id bundle_id_capability = Spaceship::ConnectAPI::BundleIdCapability.create(bundle_id_id: "123456789", capability_type: Spaceship::ConnectAPI::BundleIdCapability::Type::MAPS) # Delete an capability from bundle identifier capabilities.each do |capability| if capability.capability_type == Spaceship::ConnectAPI::BundleIdCapability::Type::MAPS capability.delete! end end

能力类型以常量枚举形式组织在 capabilities.rb 与 bundle_id_capability.rb 中(如Type::MAPS)。增删能力支持"已知 bundle_id_id 直接创建"与"通过 BundleId 实例操作"两种方式,删除时先遍历匹配capability_type再调用delete!

十一、Webhook 管理(Auth Key 场景)

通过 Connect API 可以对单个 App 注册、删除事件通知 Webhook:

app = Spaceship::ConnectAPI::App.find("com.krausefx.app") # Fetch all webhooks webhooks = Spaceship::ConnectAPI::Webhook.all(app_id: app.id) # Create a new webhook new_webhook = Spaceship::ConnectAPI::Webhook.create( app_id: app.id, event_types: [ Spaceship::ConnectAPI::Webhook::EventType::APP_STORE_VERSION_APP_VERSION_STATE_UPDATED, ], name: 'Webhook Name', secret: 'secret1234', url: 'https://webhook.example.com' ) # Delete a webhook webhooks.first.delete! new_webhook.delete!

事件类型(EventType)常量集中在 webhook.rb,上例监听的是"App Store 版本状态更新"事件。典型的落地场景是:提交审核后,通过 Webhook 把IN_REVIEWREADY_FOR_SALEREJECTED等状态变化实时推送到自己的服务,替代 CI 里的轮询逻辑。回调 URL 需要支持 App Store Connect 的签名校验,secret即用于验证消息来源。

十二、许可证与使用边界

仓库在文档末尾对 Spaceship 及整个 fastlane 生态作了如下澄清:本项目与 fastlane 所有工具均与 Apple Inc. 无任何从属关系;项目以 MIT 协议开源,你拥有源码的完整访问权并可自行修改以适配需求;所有 fastlane 工具运行在你自己的电脑或服务器上,因此你的凭证或其他敏感信息永远不会离开你自己的机器——你对自己如何使用 fastlane 工具负责。

延伸阅读

若要继续深入,仓库内的以下文档与源码与本篇主题直接相关,可作为下一步阅读材料:

  • spaceship/docs/Authentication.md:两种登录方式的细节与 2FA 处理;
  • spaceship/docs/DeveloperPortal.md:开发者门户(证书、描述文件)侧的 API;
  • spaceship/docs/TestFlightTesting.md:TestFlight 测试的专项指南;
  • spaceship/lib/spaceship/connect_api/models:本篇涉及的全部 ConnectAPI 模型与属性定义;
  • deliver/lib/deliver/submit_for_review.rb:把"选构建 + 提交审核"做成真实生产流程的完整实现范例;
  • deliver/assets/example_rating_config.json:年龄分级等提交配置的合法取值示例。

【免费下载链接】fastlane🚀 The easiest way to automate building and releasing your iOS and Android apps项目地址: https://gitcode.com/GitHub_Trending/fa/fastlane

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 3:39:47

深度学习图像去雨:端到端雨层分离与重建实战

简介:本资源是一套基于PyTorch实现的深度学习图像去雨完整实践方案,面向人工智能方向的研究者、高校学生及计算机视觉工程师,聚焦真实场景中雨雾干扰导致的图像质量退化问题,提供从数据加载、模型训练、推理测试到指标评估的一站式…

作者头像 李华
网站建设 2026/9/10 3:38:12

C语言实现二叉树中序后序非递归遍历,栈模拟与标记法详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华