基于 SCF 的 IOS 应用分发

基于 SCF 的 IOS 应用分发

有时会有一些 APP 需要下载到手机进行测试,不过使用 蒲公英 等需要认证比较麻烦所以就自己写了一个小程序。

上传文件采用 SCF 生成直传 COS 的签名地址,避免转换成 Base64 上传到 SCF 带来的性能低效。创建则是生成相应的 plist 然后直接保存到 COS ,通过 API 网关来访问程序的接口包括静态页。

有些 IPA 是需要添加 UUID 的。可以使用 一步快速获取 iOS 设备的UDID 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<?php

require './cos-php-sdk-v5/vendor/autoload.php';

use Qcloud\Cos\Client;

function main_handler($event, $context) {
// 实例化 COS SDK 客户端
$cos = new Client([
'region' => getenv('cos_region'),
'schema' => 'https',
'credentials' => [
'secretId' => getenv('cos_secret_id'),
'secretKey' => getenv('cos_secret_key'),
],
]);

$bucket = getenv('cos_bucket');

// 获取域名
if(getenv('cdn_domain')) {
$cdn_domain = sprintf('https://%s/', getenv('cdn_domain'));
} else {
$cdn_domain = sprintf('https://%s.cos.%s.myqcloud.com/', $bucket, getenv('cos_region'));
}

$event = (array) $event;

$query_string = (array) $event['queryString'];

if(isset($query_string['type'])) {
if($query_string['type'] == 'sign') {
// 签名接口
$path = get_path(sprintf('files/%s.ipa', str_rand()));

try {
$sign = $cos->getPresignetUrl('putObject', [
'Bucket' => $bucket,
'Key' => $path,
'Body' => '',
], '+10 minutes');

return result([
'sign_url' => (string) $sign,
'cdn_url' => 'https://' . $sign->getHost() . $sign->getPath(),
]);
} catch (\Exception $e) {
return error('获取签名失败');
}
} else if($query_string['type'] == 'create') {
// 创建接口
if(!($query_string['name'] && $query_string['version'] && $query_string['url'] && $query_string['bundle_identifier'])) {
return error();
}

$url = parse_url($query_string['url']);

try {
$path = get_path(sprintf('plist/%s.plist', str_rand()));

$result = $cos->putObject([
'Bucket' => $bucket,
'Key' => $path,
'Body' => generated_plist($cdn_domain . $url['path'], $query_string),
'ContentType' => 'text/xml',
]);

return result([
'cdn_url' => $cdn_domain . $path,
]);
} catch (\Exception $e) {
return error('上传plist失败');
}
}
}

// 返回静态页
return [
'isBase64Encoded' => false,
'statusCode' => 200,
'headers' => [
'Content-Type' => 'text/html',
],
'body' => file_get_contents('./index.html'),
];
}

/*
* 随机字符串
*
* @param int $length 长度
* @return string
*/
function str_rand(int $length = 6){
return bin2hex(random_bytes(($length - ($length % 2)) / 2));
}

/*
* 生成 plist 内容
*
* @param string $ipa_path 安装文件地址
* @param array $body 内容
* @return string
*/
function generated_plist($ipa_path, array $body) {
return '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>' . $ipa_path . '</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>' . $body['bundle_identifier'] . '</string>
<key>bundle-version</key>
<string>' . $body['version'] . '</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>' . $body['name'] . '</string>
</dict>
</dict>
</array>
</dict>
</plist>';
}

/*
* 获取对象存储路径
*
* @param string $name 名称
* @return string
*/
function get_path(string $name) {
return getenv('storage_prefix') . $name;
}

/*
* 返回成功信息
*
* @param array $data 数据
* @return array
*/
function result($data = []) {
return [
'isBase64Encoded' => false,
'statusCode' => 200,
'headers' => [
'Content-Type' => 'application/json',
],
'body' => json_encode([
'code' => 1,
'data' => $data,
]),
];
}

/*
* 返回错误信息
*
* @param string $message 错误信息
* @return array
*/
function error($message = '参数错误') {
return [
'isBase64Encoded' => false,
'statusCode' => 200,
'headers' => [
'Content-Type' => 'application/json',
],
'body' => json_encode([
'code' => 0,
'message' => $message,
]),
];
}

配置

环境变量:

1
2
3
4
5
6
cos_secret_id=  # COS secret id
cos_secret_key= # COS secret key
cos_bucket= # COS 桶名
cos_region= # COS 地域
cdn_domain= # CDN 域名
storage_prefix= # 存储前缀

触发方式:

测试

生成的 itms-services 链接在 Safari 才会识别。


源码下载

往上