python上传APK至fir.im

Fir文档

发布应用操作分两步,

  • 获取上传凭证
  • 上传文件

获取上传凭证

API

发布应用获取上传凭证

POST http://api.fir.im/apps)

参数列表

名称 类型 必填 说明
type String ios 或者 android(发布新应用时必填)
bundle_id String App 的 bundleId(发布新应用时必填)
api_token String 长度为 32, 用户在 fir 的 api_token

上传文件

API

将 ICON 和安装包文件分别上传到上一步操作中获取到的 cert.iconcert.binary 中的 upload_url

POST upload_url

参数列表

名称 类型 必填 说明
key String 七牛上传 key
token String 七牛上传 token
file File 安装包文件
x:name String 应用名称(上传 ICON 时不需要)
x:version String 版本号(上传 ICON 时不需要)
x:build String Build 号(上传 ICON 时不需要)
x:release_type String 打包类型,只针对 iOS (Adhoc, Inhouse)(上传 ICON 时不需要)
x:changelog String 更新日志(上传 ICON 时不需要)

Python3脚本

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
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import requests
import json
import os
import sys

split = '-' * 20

proxies = {
'http': '127.0.0.1:8899',
'https': '127.0.0.1:8899',
}


def print_split(s):
print(split + '[ ' + s + ' ]' + split)


# 必填项,可以通过命令行参数传入

# fir 的token 和包信息
APP_INFO = {
'api_token': '04d3c6e1dc9b65977be388be3801asfdafsdasfd7',
'applicationId': 'com.xxx.yyy',
'versionName': '1.2.3',
'versionCode': '90',
'changelog': '暂无提交log'
}


# 必填项,可以通过命令行参数传入

def init_app_info():
if len(sys.argv) > 1:
print_split('获取到传递过来的参数')
print(str(sys.argv))
APP_INFO['api_token'] = sys.argv[1]
APP_INFO['applicationId'] = sys.argv[2]
APP_INFO['versionName'] = sys.argv[3]
APP_INFO['versionCode'] = sys.argv[4]
if len(sys.argv) > 4:
APP_INFO['changelog'] = sys.argv[5]


# 获取 fir 的上传凭证
def get_cert():
print_split('发起获取上传凭证请求')
data = {'type': 'android', 'bundle_id': APP_INFO['applicationId'],
'api_token': APP_INFO['api_token']}
print(data)
req = requests.post(url='http://api.fir.im/apps', data=data)
cert_resp = req.content
print_split('获取到 fir 响应')
print(str(cert_resp))
return cert_resp


# 遍历目录获取到 最后一次修改的apk文件(认为是需要上传的文件)
def get_last_modify_apk():
print_split('遍历获取 Apk')
# 遍历拿到最后一次修改的路径
last_m_time = 100
last_apk_path = ''
for (root, dirs, files) in os.walk('../'):
for item in files:
if os.path.splitext(item)[1] == '.apk':
abs_path = os.path.join(root, item)
print('检测到apk文件:' + abs_path)
if os.path.getmtime(abs_path) > last_m_time:
last_m_time = os.path.getmtime(abs_path)
last_apk_path = abs_path
print_split('获取到最后一次修改的 apk文件路径')
print(last_apk_path)
return last_apk_path


# 上传到fir
def upload_fir(binary, path):
# 拿到相应的token
cert_key = binary['key']
cert_token = binary['token']
cert_upload_url = binary['upload_url']

print_split('上传 Apk')
file = {'file': open(path, 'rb')}
param = {
"key": cert_key,
"token": cert_token,
'x:build': APP_INFO['versionCode'],
"x:name": ' ',
"x:changelog": APP_INFO['changelog']
}
requests.packages.urllib3.disable_warnings()
req = requests.post(cert_upload_url,files=file, data=param, verify=False)

print(req.content)


if __name__ == '__main__':
init_app_info()
cert_resp2 = get_cert()

# 拿到cert实体
cert_json = json.loads(cert_resp2)
binary_dirt = cert_json['cert']['binary']

apk_path = get_last_modify_apk()

upload_fir(binary_dirt, apk_path)

Gradle 脚本

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

复制下面的代码到 app/build.gradle

task uploadFir {
// dependsOn 'assembleRelease' (这个是可选的,标识是否构建最新Release版本)
doLast {

def apiToken = "04d3c6e1dc9b65977be388be38asfdasdfasdf"
def appId = project.android.defaultConfig.applicationId
def versionName = project.android.defaultConfig.versionName
def versionCode = project.android.defaultConfig.versionCode
def changeLog = "填写更新日志"

// 执行Python脚本
def process = "python3 fir.py ${apiToken} ${appId} ${versionName} ${versionCode} ${changeLog}".execute()

ByteArrayOutputStream result = new ByteArrayOutputStream()
def inputStream = process.getInputStream()
byte[] buffer = new byte[1024]
int length
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length)
}
println(result.toString("UTF-8"))
}
}

在 终端 执行 gradle uploadFir

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

> Task :app:uploadFir
--------------------[ 获取到传递过来的参数 ]--------------------
['fir.py', '04d3c6e1dc9b65977be388be3801c9c7', 'com.xxxx', '4.3.0', '150', '填写更新日志']
--------------------[ 发起获取上传凭证请求 ]--------------------
{'type': 'android', 'bundle_id': 'com.yoinsapp', 'api_token': '04d3c6e1dc9b65977be388be3801c9c7'}
--------------------[ 获取到 fir 响应 ]--------------------
b'{"id":"5af3a2e2959d693a4c99861a","type":"android","short":"ysandroid","app_user_id":"596c3640ca87a843e100000e","storage":"qiniu","form_method":"POST","cert":{"icon":{"key":"79993a222dfe8e55b8d6058f397dd52904901b9b","token":"这个值很长","upload_url":"https://upload.qbox.me","custom_headers":{},"custom_callback_data":{"original_key":"3957ad13cc2794e206f610f57f7e8507ec551808"}},"binary":{"key":"3cbed5d72486957b245ba81e7d1f9389f4f038df.apk","token":"这个值很长","upload_url":"https://upload.qbox.me","custom_headers":{}},"mqc":{"total":5,"used":0,"is_mqc_availabled":true},"support":"qiniu","prefix":"x:"}}'
--------------------[ 遍历获取 Apk ]--------------------
检测到apk文件:../XXXX/app/build/outputs/apk/release/app-release-unsigned.apk
检测到apk文件:../XXXX/app/build/outputs/apk/debug/app-debug.apk
检测到apk文件:../YYYYl/app/release/125.apk
检测到apk文件:../YYYY/app/build/outputs/apk/release/150.apk
检测到apk文件:../YYYY/app/build/outputs/apk/debug/150.apk
--------------------[ 获取到最后一次修改的 apk文件路径 ]--------------------
../YYYY/app/build/outputs/apk/release/150.apk
--------------------[ 上传 Apk ]--------------------
b'{"download_url":"https://pro-app-qn.fir.im/XXXXX.apk?e=1529662797\\u0026token=SSSSSS-T:js8wDCbtYglY5rf4YBHPofIL-PU=","is_completed":true,"release_id":"5b2cbf3c959d6926ef0420ca"}'


BUILD SUCCESSFUL in 42s
1 actionable task: 1 executed