发行渠道怎么完结iOS证书更新主动化的作业流

布景

在日常作业中,咱们经过会遇到打包前才发现出包证书过期了的问题。虽然苹果证书,是时隔一年才会过期。但是对于发行公司来说几乎每个月都有新游戏发行意味着证书在下一年的每个月都或许遇到证书过期的情况。而咱们敬业的研制CP大大,更新版本时一般打包会不守时,比方等咱们下班后才出得来包。成果打包时发现证书过期了,就会火速回联系我方技能支撑要求发过去新的证书。依据这样一个需求布景,怎么做到提早提示,防止这种后知后觉的作业发生呢? 就此衍生出一个将证书办理主动化的项目。

怎么将更新打包证书的更新主动化呢?苹果人家早就想到了咱需求用到这一点了,供给AppStore Connect API支撑咱们将作业流程主动化。仅仅需求咱们自己将此流程串起来。此开放的API答应咱们遵从协议,经过对接就可以将在AppStore Connect上的日常使命主动化,以进步作业效率和削减过错。

针对以上问题,咱们创立了一个后台服务,定期查询苹果后台的证书。一旦发现某个证书快过期,就提早将证书和描绘文件做一次更新编辑,再下载下来存好。然后,经过推送的办法告知对应担任证书办理的搭档。那她就可以直接在手机上操作,将新的证书转发给研制了。

开始我对其他言语不是很熟悉,经过了一段时刻的调研后发现,想要完结这样的功用后台服务是防止不了的,最终决议仍用自己熟悉的Swift相关结构和言语来完结。

首要,早已知晓 Swift 的服务端作业组现已供给了后台开发的处理计划Vapor。针对我这样的后端开发新手来入门,再合适不过了。不需求舍进求远学习Python、Go等后端结构来完结。

其次,还进一步了解OpenAPI 文档可直接经过 swift-openapi-generator 或 CreateAPI 等工具生成 Swift 客户端和服务端的代码。这些库经过将AppStore Connect API 的 openapi.json 转化为 Swift 言语封装后的接口和数据模型,大大削减接口方面的作业量,可以Quick-Start,愈加聚焦于事务完结。

最终,客户端与后端是运用相同言语完结,假如今后交给其他人保护,其不必再另行学习其他技能栈了。

因而,我决议服务端选用目前盛行的开发结构 Vapor + Fluent + mysql 架构、客户端选用Swift UI 进行开发,来完结这样一个证书主动化办理项目,以处理以上证书更新痛点。

项目

证书办理项目,要处理的问题是证书检查和过期办理。那么客户端App就需求展现数据展现和推送音讯接纳。而后端应进行权限认证、守时更新快过期证书等,然后经过APNs(Apple的长途推送服务)将证书更新这一音讯推送给到客户端App。介于此,梳理出以下技能架构。

●技能架构

依据调研承认的处理计划,输出以下技能架构图:

○证书办理架构

发行平台如何实现iOS证书更新自动化的工作流

1. 装备预备阶段

首要要处理的问题便是怎么调起App-Store Connect 供给的 API。那么怎么进行其API接口认证、接口标准文档是怎样的,是在项目预备阶段重点处理的问题。

●1.1 App-Store-Connect Open Api 的两种认证办法

○办法1:秘钥认证

App Store Connect API 需求JWT来授权向 API 宣布的每个恳求。可以运用从 App Store Connect 下载的 API 密钥生成 JWT。

API 密钥由两部分组成:Apple 保存的公共部分和下载的私钥。运用私钥可以签署令牌,授权拜访在 App Store Connect 和 Apple Developer 网站中的数据。

调用API需求JSON Web Tokens (JWT)进行授权;可以从开发者后台的App Store Connect账户获取创立这些令牌的密钥。请参阅为App Store Connect API创立API密钥以创立你的密钥和令牌。如下图所示,点击“生成API秘钥”,按提审引导进行秘钥创立:

发行平台如何实现iOS证书更新自动化的工作流

创立App Store Connect API秘钥后,可以获得三个必要参数:

参数 阐明 详情
issuerID 私钥 ID 例如:3NLKFTPBTY
privateKeyID API 密钥页面中的您的颁布者 ID 例如:fc88b58a-8075-434b-a891-bd5de1169b7e
privateKey 私钥文本 从苹果后台下载下来的p8文件中获取,去掉—Base—信息今后的示例内容(共200个字符)。例如:MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgIJgRpoXgbjqiZkMQCqqF4LkigC2lBhSt4M6X8WCBhyegCgYIKoZIzj0DAQehRANCAASnhuEens2k1d8cHdBJv/ca5dbxhXCGI7f1CDY8octcYIb4gIhtfsHp/sbxd5kqs0hKRWC9Ur3f6v2QqoYAN6AK

以上三个参数怎么在代码中运用,在下文获取证书列表时初始化 APIProvider 的 APIConfiguration

会提到。假如想要进一步研究怎么生成JWT,可以拜访此处供给的官方计算恳求Authorization Bearer Token的文档。

○办法2:开发者账密认证

运用账密办法可以模仿苹果后台登录。登录协议来源于,经过抓包获取到的苹果开发者后台的账密登录登录办法的协议。

以下是登录接口:

登录办法 API 参数
Signin idmsa.apple.com/appleauth/a… accountName:String 账号password:String 暗码rememberMe: Bool 是否记住暗码

苹果开发者账密登录的API调用完结详请,参见友链项目苹果派。

假如运用用此种办法完结登录,难免涉及要将账密保存在后端装备中。涉及到账密存储安全,有必定的风险。另外,此种办法涉及到双重认证,办理开发者账号的搭档也很难赞同供给。因而,咱们在此不选用此办法,账密办法仅提出供群友们参考。

本项目选用的是第一种计划:秘钥认证。

●1.2 关于苹果App-Store-Connect Open Api 接口文档

由于苹果供给了App Store Connect API,使得咱们可以把App Store Connect的证书操作串联起来。为便于大家验证准确性,随文附上完整版App-Store Connect API 链接如下:点击OpenAPI标准以下载标准文件。

完整的开发API文件过于庞大(3M左右),而咱们只需求办理套装ID(bundleId)、签名证书、开发设备和预置描绘文件。因而对此API文件中的接口进行了减缩。只保存了与打包证书和描绘文件相关的API。可以将以上OpenAPI标准文件经过Postman导入,愈加直观的查阅和测验相关接口。

API 如下图所示:

发行平台如何实现iOS证书更新自动化的工作流

在实践的项目开发中,假如自己完结API往往较为低效。依据不重复造轮子的原则,我挑选了经过CreateAPI导出接口计划、选用依据此进行封装的第三方库 Appstoreconnect-swift-sdk 来做 AppStore Connect API 对接。

●1.3 关于推送服务

另外此项目涉及到长途推送服务 APNs,接下来也对推送原理进行了扼要的调研:

○首要,完结iOS推送有以下两种办法:

■运用验证令牌与 APNs 通信(本文选用)

■运用 TLS 证书与 APNs 通信

○然后,创立推送证书进程

●经过证书助理生成一个CA恳求证书

●上传CA恳求证书以创立推送证书

●关联上某个Bundle id

●下载p8证书

这儿要提的是,推送证书系手艺创立,这有违咱们作业流程主动化初衷。但限于篇幅,咱们不再此展开完结主动创立推送证书的进程。此处仅做证书和描绘文件过期提示时用一下推送服务。

2. 工程创立阶段

环境阐明

2.1 装置装备

2.1.1 装置 brew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Homebrew 官方文档 brew.sh/

2.1.2 装置 Vapor:

brew install vapor

2.1 工程创立

2.2.1 创立服务端工程

●创立项目
vapor new AppStoreConnectCerts
# 以下是创立时选项挑选引导:
# 挑选支撑: Fluent
# 数据库挑选: Mysql
# 挑选不支撑: Leaf

创立好的服务端工程目录如下:

发行平台如何实现iOS证书更新自动化的工作流

创立时不但生成了服务端运用的发动模版,还将部署环境 Docker 所需的装备文件 docker-compose、Dockerfile 也做了默认创立。

●增加依靠库

打开Package.swift文件,增加AppStore Connect API完结的依靠库、推送依靠等。如下所示:

dependencies: [
    //  App-Store Connect API for Swift
    .package(url: "https://github.com/AvdLee/appstoreconnect-swift-sdk.git", .upToNextMajor(from: "2.0.0")),
    //  APNSwift 
    .package(url: "https://github.com/vapor/apns.git", from: "4.0.0")
]

然后将其依靠库称号和包称号增加给主程序可履行 Target 依靠中。如下所示:

.executableTarget(
        name: "App",
        dependencies: [
            .product(name: "AppStoreConnect-Swift-SDK", package: "AppStoreConnect-Swift-SDK"),
            .product(name: "VaporAPNS", package: "apns"),
            .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")
        ]
    )

2.2.2 创立客户端工程

创立一个多渠道的新项目:

发行平台如何实现iOS证书更新自动化的工作流

挑选 “MultiPlatform” 标签,挑选 “App” 类型的项目:AppCerts。

创立好的客户端工程目录如下:

发行平台如何实现iOS证书更新自动化的工作流

2.3 环境运转

2.3.1 运转指令

# 在终端运转履行指令:
swift run App

2.3.2 拜访测验

curl http://127.0.0.1:8080/

2.3.3 测验成果

首次发动App运转成果如下图所示:

发行平台如何实现iOS证书更新自动化的工作流

以上标明咱们创立的环境现已没有问题了。

2.4 项目部署

Swift-Server 首选的开发环境是macOS;而生产环境引荐运用 docker + ubuntu。履行以下 docker-compose 指令,可将当前项目在docker中运转。 docker-compose 是一个编列工具,可以省掉较多手艺装备docker的细节,经过装备文件即可自界说装备项而无需处理众多的 docker 依靠。

cd AppStoreConnectCerts
# start
sudo docker-compose run -d app

3. 功用完结阶段

接下来,咱们将经过运用Vapor、Fluent、swift-openapi-generator来完结用户登录、证书续期、下载授权和音讯推送。

3.1 数据存储

完结 ORM

在 Swift – Server 中,怎么完结 ORM(目标联系映射)数据库操作呢?

在 Swift 中,要完结 ORM 操作,可以运用 Fluent。它是 Swift 的 ORM 结构,它答应以一种类型安全的办法与数据库进行交互。Fluent 支撑多种数据库,包含 PostgreSQL、MySQL、SQLite 和 MongoDB。Fluent 具有类型安全、数据库无关性、易于运用的特点,供给了一套强壮的查询 API.

3.1.1 数据模型

●用户表 & 令牌表

发行平台如何实现iOS证书更新自动化的工作流

发行平台如何实现iOS证书更新自动化的工作流

在实践中应该把 token 信息保存到 redis.

●证书表

在引入 Fluent 咱们可以不在数据库中直接创立表而只需求完结对应的 Model, Content 协议即可运用 Migration 方法 将表主动创立出来

final class Certificate: Model, Content {
    static var schema: String = "certificates"
    init() {}
    @ID(key: .id)
    var id:UUID?
    @Field(key: "uuid")
    var uuid: String
    @Field(key: "sha1")
    var sha1: String
    @Field(key: "name")
    var name: String
}

创立表代码

import Fluent
struct CreateCertificate: AsyncMigration {
    func prepare(on database: Database) async throws {
        try await database.schema("certificates")
            .id()
            .field("name", .string, .required)
            .create()
    }
    func revert(on database: Database) async throws {
        try await database.schema("certificates").delete()
    }
}

●描绘文件表

同理

3.1.1 数据库初始化

增加依靠项后,在 configure.swift 中运用 app.databases.use 装备数据库的凭据。

app.databases.use(.mysql(hostname: "localhost", username: "root", password: "123456", database: "appcerts_db"), as: .mysql)

实践项目中我引荐以上数据库衔接,但为了便于展现操作流程,以下以sqlite替代mysql。

所以需求再在依靠库中加多一个 sqlite 驱动依靠库装备:

dependencies: [
        .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"),    
]
targets: [
        .executableTarget(
            name: "App",
            dependencies: [
                    .product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
            ])
     ]
)

3.1.2 数据搬迁

为了便于保护和办理运用程序的数据库结构,确保数据的一致性和完整性。在运用Vapor结构开发的Swift运用程序中设置和办理数据库的数据搬迁。要运转搬迁,这一般是在运用发动后进行的,以确保数据库结构是最新的。这个进程或许包含创立新的表、更新现有表结构或删去旧表等操作。以下是操作进程

在 configure.swift 中运用 app.migrations.add 装备数据库的数据表。

// docs:https://docs.vapor.codes/zh/fluent/overview/#migrate
app.migrations.add(CreateUser())
app.migrations.add(CreateCertificate())

在指令行中调用 App migrate 指令如下:

swift run App migrate

履行此指令是对数据库进行了建表操作(为了演示以Sqlite为例),输出日志如下:

# Run
%> swift run App migrate
# Create ‘User’ table
[ DEBUG ] CREATE TABLE IF NOT EXISTS "_fluent_migrations"(
                    "id" UUID PRIMARY KEY, 
                    "name" TEXT NOT NULL, 
                    "batch" INTEGER NOT NULL, 
                    "created_at" REAL, 
                    "updated_at" REAL, 
                    CONSTRAINT "uq:_fluent_migrations.name" 
                    UNIQUE ("name")) [] [database-id: sqlite]
[ DEBUG ] CREATE TABLE "certificates"("id" UUID PRIMARY KEY, "name" TEXT NOT NULL) [] [database-id: sqlite]

3.1.3 运用Fluent装备

接下来,咱们将完结用户登录功用。完结进程如下:

// 创立一个登录恳求的数据模型
struct LoginRequest: Content {
    var email: String
    var password: String
}
// 创立一个登录呼应的数据模型
struct LoginResponse: Content {
    var token: String
}
// 创立一个登录的路由处理器
app.post("login") { req -> EventLoopFuture<LoginResponse> in
    // 解析登录恳求
    let loginRequest = try req.content.decode(LoginRequest.self)
    // 验证用户名和暗码
    let user = try User.query(on: req.db).filter(.$email == loginRequest.email).first().unwrap(or: Abort(.unauthorized))
    // 验证暗码
    guard try Bcrypt.verify(loginRequest.password, created: user.password) else {
      throw Abort(.unauthorized)
}
// 生成令牌
let token = try Token.generate(for: user)
// 回来登录呼应
return token.save(on: req.db).map { LoginResponse(token: token.value) }

以上代码中,用户登录功用的完结进程包含创立登录恳求和呼应的数据模型,创立登录的路由处理器,解析登录恳求,再验证用户名和暗码,再生成令牌,最终回来登录呼应等进程。

3.2 服务端功用完结

●拉取证书列表

在这功用中,首要创立一个APIEndpoint.v1.certificates.get的恳求,用来获取一切的证书。然后,运用provider实例来发送这个恳求,并获取呼应数据。最终,回来获取到的证书。

import Vapor
import AppStoreConnect_Swift_SDK
struct CertificateController: RouteCollection {
    static let configuration = try APIConfiguration(issuerID: issuerID, 
                                                 privateKeyID: privateKeyID, 
                                                   privateKey: privateKey)
    let provider: APIProvider = APIProvider(configuration: configuration)
    // Impl Route Collection
    func boot(routes: RoutesBuilder) throws {
        let certificateRoute = routes.grouped("certificates")
        certificateRoute.get(use: certificate)    }
    func certificate(req: Vapor.Request) async throws -> [Certificate] {
        let request = APIEndpoint.v1.certificates.get(parameters: .init())
        let certificates = try await provider.request(request).data
        return certificates
    }
}

以下是代码完结:运用了AppStoreConnect_Swift_SDK库。首要功用是获取App Store Connect API的证书。

首要,导入了所需的库,Vapor和AppStoreConnect_Swift_SDK。

然后,界说了一个名为CertificateController的结构体,它完结了RouteCollection协议。在Vapor中,RouteCollection是一个可以组织和装备路由的协议。

在CertificateController中,有两个首要的部分:

1. API装备

APIConfiguration是AppStoreConnect_Swift_SDK中的一个结构体,用于装备API的拜访权限。为了确保程序有权限拜访API,这儿需求传入上一章节‘私钥认证’中的三个参数:

●issuerID(发行者ID)

●privateKeyID(私钥ID)

●privateKey(私钥)

2. API供给者:

APIProvider是AppStoreConnect_Swift_SDK中的一个类,它运用APIConfiguration来发送恳求到API,并创立了一个名为provider的实例。

最终,界说了一个名为certificate的异步函数,它接受一个Request类型的参数,表明一个HTTP恳求,并回来一个Certificate数组。Certificate是AppStoreConnect_Swift_SDK中界说的一个模型,代表App Store Connect API的证书。

接口测验:

// Test:
curl 'http://127.0.0.1:8080/certificates'
# Response:
[
    {
        "id": "DUXXXXXZT6",
        "links": {
            "self": "https://api.appstoreconnect.apple.com/v1/certificates/DUXXXXXZT6"
        },
        "type": "certificates",
        "attributes": {
            "serialNumber": "3F5B88FB0E01B6479FCFHOXXXXXXXER",
            "certificateContent": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTIyMTIyMDA5NDY1M1oXDTIzMTIyMDA5NDY1MlowgZUxGjAYBgoJkiaJk/IsZAEBDAozVTI0UFc1VzkzMTgwNgYDVQQDDC9BcHBsZSBEZXZlbG9wbWVudDogS0lSSyBIT0ZNRUlTVEVSIChBRjc2UThDR0ZCKTETMBEGA1UECwwKUUtCNU05ODZCOTEbMBkGA1UECgwSSEVBUlRRVUFLRSBMSU1JVEVEMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqpNtt8QpCnfvwTCt+t5Ugvlnob/A+zhDUtcN9cF49IYXkmqLsJJLYXsHkLpmeM5xwlRHIhCfHwZhsZqTqJls95myFzWXno43zsx4Gtbnq0oIrZCv4B9M5EvByyx7h9ew2MZEF9sZ+5R+IfWOu/GHY8l/UX5XUUD5Ic74epSVknRZaeGd1eg2Xn62rM+Lbw01RF+myv40XBJ1JsmeG57aeiC+7mnq8+mHsoqr0LnVTvrGlfhx8SDN23zNEmh+2tSWiWo5odM3AxwOlxfmW59KlcDMKgMW9ArJE/srbyYA5wlWHo3NS9cnNeGiaphsZbvU1hkxt73TSOB3jAlod0T7sCAwEAAaOCAjgwggI0MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUCf7AFZD5r2QKkhK5JihjDJfsp7IwcAYIKwYBBQUHAQEEZDBiMC0GCCsGAQUFBzAChiFodHRwOi8vY2VydHMuYXBwbGUuY29tL3d3ZHJnMy5kZXIwMQYIKwYBBQUHMAGGJWh0dHA6Ly9vY3NwLmFwcGxlLmNvbS9vY3NwMDMtd3dkcmczMDQwggEeBgNVHSAEggEVMIIBETCCAQ0GCSqGSIb3Y2QFATCB/zCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA3BggrBgEFBQcCARYraHR0cHM6Ly93d3cuYXBwbGUuY29tL2NlcnRpZmljYXRlYXV0aG9yaXR5LzAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU7pQRUfQlY//FnhJcRFjnVDS3bkowDgYDVR0PAQH/BAQDAgeAMBMGCiqGSIb3Y2QGAQIBAf8EAgUAMBMGCiqGSIb3Y2QGAQwBAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQCKZyHAGJFsaYmtSKoqQ6de0VLh5GR9ZnKQRCobjm6N7HQz7UF2OlojgWc/c2DbOIEk/zSI9CNDXPqk9eDqrQrm9XfAHg/74WMwbVhSRlm+cHDiQqvC+ucmb+beT4MhyNBNawGiU4b4nfBYlH2S5vhXcoxajLYWsTR6kdNLv6otFA8xSi6L47OURDZ9T0Ln9ZHGpC9vISnvcNTxK7BSPPEmzwQBMFJ0R9DxjemiOs11rQ2GW6D4LP5/K4DcYnsQD/LmSqvf+J1PgJoOqlh5sFG2BSiXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
            "expirationDate": "2023-12-20T09:46:52Z",
            "certificateType": "DEVELOPMENT",
            "name": "Apple Development: KXXK HOXXXXXXXER",
            "displayName": "KXXK HOXXXXXXXER"
        }
    }
]

○展现一个月内行将过期的证书

■后端轮训判断是否续期

○下载证书

■证书处理下载时,有未授权问题,后续处理了在代码仓库中提交。

●描绘文件列表

与拉取以上证书列表相似,完结代码如下:

import Vapor
import AppStoreConnect_Swift_SDK
struct ProfileController: RouteCollection {
    static let configuration = try APIConfiguration(issuerID: issuerID, 
                                                 privateKeyID: privateKeyID, 
                                                   privateKey: privateKey)
    let provider: APIProvider = APIProvider(configuration: configuration)
    func boot(routes: RoutesBuilder) throws {
        let profilesRoute = routes.grouped("profiles")
        profilesRoute.get(use: profile)
    }
    func profile(req: Vapor.Request) async throws -> [Profile] {
        let request = APIEndpoint.v1.profiles.get(parameters: .init())
        let profiles = try await provider.request(request).data
        return profiles
    }
}

接口测验:

// Test:
curl 'http://127.0.0.1:8080/profiles'
[
    {
        "id": "XXXXX6CUXK",
        "relationships": {
            "devices": {
                "links": {
                    "related": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/devices",
                    "self": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/relationships/devices"
                },
                "meta": {
                    "paging": {
                        "total": 0,
                        "limit": 2147483647
                    }
                }
            },
            "certificates": {
                "links": {
                    "related": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/certificates",
                    "self": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/relationships/certificates"
                },
                "meta": {
                    "paging": {
                        "total": 0,
                        "limit": 2147483647
                    }
                }
            },
            "bundleId": {
                "links": {
                    "related": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/bundleId",
                    "self": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK/relationships/bundleId"
                }
            }
        },
        "links": {
            "self": "https://api.appstoreconnect.apple.com/v1/profiles/XXXXX6CUXK"
        },
        "type": "profiles",
        "attributes": {
            "expirationDate": "2024-05-17T02:26:54Z",
            "createdDate": "2023-05-18T02:26:54Z",
            "profileContent": "MIJIegYxxxxxxxxF3r6...7Hhhs7mtunqFXwvvWg/CF3ICaBqYF2xTntrMnRtS3di",
            "platform": "IOS",
            "uuid": "c756cc03-a3f1-4b3d-8e8a-2e341a5baa70",
            "profileType": "IOS_APP_DEVELOPMENT",
            "profileState": "INVALID",
            "name": "xxxxxDevTest"
        }
    }
]

●推送音讯完结

推送架构如下图所示:

发行平台如何实现iOS证书更新自动化的工作流

后端推送的核心代码如下:

○推送参数初始化
import Vapor
import APNS
public func configure(_ app: Application) async throws {
    static let appleECP8PrivateKey = ""
    let keyIdentifier = "K6U6H66666"
    let teamIdentifier = "QKB6M66666"
    let apnsConfig = APNSClientConfiguration(
        authenticationMethod: .jwt(
            privateKey: try .loadFrom(string: appleECP8PrivateKey),
            keyIdentifier: keyIdentifier,
            teamIdentifier: teamIdentifier
        ),
        environment: .sandbox
    )
    app.apns.containers.use(
        apnsConfig,
        eventLoopGroupProvider: .shared(app.eventLoopGroup),
        responseDecoder: JSONDecoder(),
        requestEncoder: JSONEncoder(),
        as: .default
    )
    try routes(app)
}

以上代码运用 Vapor 和 APNS (Apple Push Notification Service) 来装备一个运用的推送告诉服务的。

static let appleECP8PrivateKey = “”:这是一个静态常量,用于存储你的 Apple ECP8 私钥。这个私钥是用于 APNS 验证的。

let keyIdentifier = “K6U6H66666” 和 let teamIdentifier = “QKB6M66666″:这两个是你的 key identifier 和 team identifier。这些都是用于 APNS 验证的。

接下来的几行代码,是创立一个 APNSClientConfiguration 目标。这个目标包含了 APNS 验证所需的一切信息,包含认证办法(这儿运用的是 JWT)、私钥、key identifier、team identifier 和沙箱环境。

最终, app.apns.containers.use():这行代码是装备你的运用运用上面创立的 APNSClientConfiguration 目标。

○音讯推送
import Vapor
import APNS
import APNSCore
import VaporAPNS
struct PushController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let todos = routes.grouped("push")
        todos.get(use: push)
    }
    func push(req: Request) async throws -> HTTPStatus {
        // Create push notification Alert
        let dt = "66ccf6a66a6c66a6d6c66af66e66e666edc6666a573495925c8af29c9c4db7"
        let payload = Payload(acme1: "Hey", acme2: 2)
        let alert = APNSAlertNotification(
            alert: .init(
                title: .raw("Certificate is almost expired"),
                subtitle: .raw("chuanba ‘s Certificate is almost expired! Plz Update as soon as possible.")
            ),
            expiration: .immediately,
            priority: .immediately,
            topic: "com.sy.sq.appcerts", // Bundle Id
            payload: EmptyPayload()
        )
        // Send the notification
        do {
            try! await req.apns.client.sendAlertNotification(alert, deviceToken: dt)
        } catch {
            print(error.localizedDescription)
        }
        return .ok
    }
}

首要,这段代码界说了一个名为PushController的结构体,它遵从了RouteCollection协议,这是Vapor结构的一部分,用于界说路由。

在 boot 函数中,设置了一个路由组,该组的路径为”push”,而且当这个路由接纳到GET恳求时,会调用 push(req: Request) 函数。

push 函数是一个异步函数,它的首要使命是发送一个推送告诉。在函数中,首要创立了一个推送告诉的有效载荷(Payload),然后创立了一个APNSAlertNotification目标,用于指定推送告诉的各种参数,如标题、副标题、过期时刻、优先级、主题等。

然后,运用 req.apns.client.sendAlertNotification 办法发送推送告诉。这儿的deviceToken是设备的推送唯一标识,用于指定接纳推送告诉的设备。

假如在发送推送告诉进程中发生过错,那么会捕获这个过错,并打印过错信息。

最终,假如推送告诉发送成功,那么回来HTTP状况码200(.ok)。

以上代码便是运用APNS和VaporAPNS扩展,完结证书行将过期的推送告诉功用。不过以上代码还省掉了保存客户端上传的推送令牌的进程。

●用户验证

以下是用户认证的流程:

发行平台如何实现iOS证书更新自动化的工作流

1.1. 完结权限认证

接下来,咱们将完结权限认证功用。完结进程如下:

// 创立一个认证用户的中间件
struct AuthMiddleware: ServerMiddleware {
    func intercept(
        _ request: Request,
        metadata: ServerRequestMetadata,
        operationID: String,
        next: (Request, ServerRequestMetadata) async throws -> Response
    ) async throws -> Response {
        // 获取令牌
        let hasAuthorization = request.headerFields.contains { field in
            field.name == "Authorization"
        }
        if !hasAuthorization {
            print("认证: 未认证 (AuthenticationError.notAuthenticated.description)")
            throw AuthenticationError.notAuthenticated
        }
        do {
            // TODO: 查库可以是查Redis等验证令牌
            //       当前示例运用查库替代
            // 调用下一个中间件
            let response = try await next(request, metadata)
            print("认证后: 调用下一个中间件")
            return response
        } catch {
            print("认证: Error (error.localizedDescription)")
            throw error
        }
    }
}

中间件注册:

// Craete server Middleware
let authMiddleware = AuthMiddleware()        
// Call the generated protocol function on the handler to configure the Vapor application.
try handler.registerHandlers(on: transport, serverURL: Servers.server1(), middlewares: [permissionMiddleware, authMiddleware, loggingMiddleware])

3.3 客户端完结

以下客户端功用待项目更新至 GitHub 今后在代码仓库中检查。功用列表如下:

●证书列表

●授权文件列表

●推送初始化

●推送音讯接纳

四、 测验

怎么判断写的服务是否有必定的抗压能力呢?

经过查阅介绍其他后端言语结构的开发形式,了解到用AB指令可以做到压力测验。

以下是我运用AB指令对缩写的服务进行压力测验。(ab是一个HTTP服务器的基准化剖析工具,运用灵活,输出的陈述也简略易懂)

1. 运用ab指令做一次接口压测

履行以下指令:

ab -n 10000 -c 10 -p ~/Desktop/post.txt -T 'application/json; charset=utf-8' -H 'Authorization: Bearer DFERFER23454323bbwer456' "http://127.0.0.1:8080/auth/login"

2. ab测验陈述

ab测验输出日下:

发行平台如何实现iOS证书更新自动化的工作流

以上测验成果表明在Release形式下的平均呼应时刻在10毫秒,最长79毫秒。

3. 系统资源实时耗费

发行平台如何实现iOS证书更新自动化的工作流

以上测验陈述显现内存耗费安稳,压测是CPU耗费高到达672%,另一个视点看其充分利用了多核CPU资源。

待处理的问题

●编译耗时长

在开发机开编译了11.6 分钟(MBP 2.2 GHz Intel i7 Core 6 Build Release Cost(1st))

发行平台如何实现iOS证书更新自动化的工作流

项目未来的开发方向

●支撑扫码获取设备号(UDID)

获取设备号,然后主动将UDID主动增加到苹果后台,编辑描绘文件并下来,再将描绘文件发送至打包渠道运用,完结继续的集成。

●财政结算数据同步

除了出包相关的事务外,还可以经过App-Store Connect API 将每月的财政结算数据导出,将其发送给计算端,将结算作业流也主动化起来。

●推送功用强化

推送微服务化。只需依据bundleid,就可以将想要转发的音讯,发送给指定的客户端。可用于承载更多需求不守时提示的事务场景。

总结

以上便是iOS证书办理主动化的项目的完结计划。项目运用了Vapor、Fluent、App-Store Connect API 、Swift OpenAPI Generator等技能来完结。包含用户登录、权限认证、证书办理等功用。

●知识点总结:

○Swift具有现代盛行的编程言语书写办法,完善的后端根底组件,是后端言语中具有强壮潜力的开发言语。

○Swift-Server 根底库逐渐丰厚。

○Fluent是Swift-Server端的一种ORM(目标联系映射)结构,可以让开发者更方便地操作数据库。它运用Swift特性:属性包装器。

○Swift OpenAPI Generator是一个可以主动生成Swift 代码的API工具,它可以协助开发者快速生成接口数据模型代码和快速生成。

○async/await “异步””等待”。在 Swift 5.5 中,Apple 引入了 async/await 形式,这是一种新的办法来处理异步操作。这种新的形式使得编写异步代码变得愈加简略和直观。

经过输出文章记载,比完结项目本身更需求查阅大量的材料。这个进程使我对怎么自学不熟悉的范畴有了自己的办法办法。一起对App推送完结原理、后端服务可扩展性、以及怎么进行异步编程(async/await)有了愈加深化的知道。一起,知道到写作本身便是一种更高效的学习。

期望这篇文章可以协助你更好地理解和运用这个开发结构。感谢阅读。

附录:

Github:

客户端: github.com/37MobileTea…

服务端:github.com/37MobileTea…

(继续更新中..)

参考材料:

1. Getting Started with Vapor 4 Lesson 2

2. Apple Developer Documentation – App Store Connect API

3. 凭借 App Store Connect API 完结作业流程主动化

4. JWT OpenApi3 完结认证授权

5. 怎么运用ab做接口压力测验