Skip to content

Commit b5ffc9c

Browse files
committed
feat(apkupload): 增加 API 上传 APK 的文档
1 parent 2223da9 commit b5ffc9c

File tree

2 files changed

+245
-0
lines changed

2 files changed

+245
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"label": "上传 APK",
3+
"collapsed": true,
4+
"position": 14
5+
}

cn/docs/sdk/apk-upload/guide.mdx

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
---
2+
title: TapTap API 上传 APK 开发指南
3+
sidebar_label: 开发指南
4+
sidebar_position: 1
5+
---
6+
7+
import useBaseUrl from '@docusaurus/useBaseUrl';
8+
import { Red, Blue, Black, Gray } from "/src/docComponents/doc";
9+
10+
## 业务流程
11+
12+
通过 API 上传 APK 的流程图如下:
13+
14+
<img src={useBaseUrl('https://capacity-files.lcfile.com/IfLh6E9YbU4WfHwwQCPBGc8fVNF0iAIB/r.png')} alt="" width="800" />
15+
16+
开发者需要实现其中的两个步骤:
17+
18+
1. 调用 TapTap 提供的接口,获取上传请求参数
19+
2. 根据 1 中的返回数据,拼装 curl 命令,将本地文件直传到云存储
20+
21+
上传完成后,你可以在 <Blue>商店 >> 游戏资料 >> 商店资料</Blue> 找到刚刚上传成功的 APK,进行提交审核等操作
22+
23+
:::tip
24+
25+
- TapTap 支持查看和提审近七天内上传成功的 APK 列表
26+
27+
:::
28+
29+
## 准备工作
30+
31+
在调用接口前,你需要先 [开启应用配置](/sdk/access/get-ready/) ,以获取到用于请求签算的 `Client ID``Server Secret`
32+
33+
## 获取上传请求参数
34+
35+
`GET` `https://cloud.tapapis.cn/apk/v1/upload-params`
36+
37+
### 请求参数
38+
39+
| 字段名 | 描述 | 类型 | 示例 |
40+
|-----------|-----------------------------------------------------|--------|--------------------|
41+
| client_id | TapTap 开放平台 ID | string | s7ui6smunrk7tmt4m6 |
42+
| app_id | TapTap 上架游戏的数字 ID | uint64 | 58881 |
43+
| file_name | 要上传的 APK 文件名,必须以 .apk 为扩展名,且只允许包含以下字符:字母、数字、下划线、中横线 | string | example.apk |
44+
45+
### 请求签算
46+
47+
请求接口时,必须携带以下请求头
48+
49+
| 字段名 | 描述 | 类型 | 示例 |
50+
|-------------|------------|--------|----------------------------------------------|
51+
| X-Tap-Ts | 请求时间,秒级时间戳 | uint64 | 1692347090 |
52+
| X-Tap-Nonce | 8 位随机字符串 | string | q1w2e3r4 |
53+
| X-Tap-Sign | 签算结果 | string | TZ76PQthw6mjaEPMbdyFjHyXvH7yAr2+IahMgX9ue8M= |
54+
55+
其中 `X-Tap-Sign` 是对整个请求进行签算所得,具体的签算步骤如下
56+
57+
#### 一、构造待签名的请求内容 `SignParts`
58+
59+
格式
60+
61+
```
62+
{method}\n
63+
{url_path_and_query}\n
64+
{headers}\n
65+
{body}\n
66+
```
67+
68+
参数说明如下
69+
70+
| 字段名 | 描述 | 示例 |
71+
|--------------------|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
72+
| method | HTTP 方法,如 `GET` / `POST` (须为全部大写) | GET |
73+
| url_path_and_query | 完整请求路径及 QueryString | /apk/v1/upload-params?app_id=58881&file_name=xxx.apk&client_id=rfciqabirt4vqav7io |
74+
| headers | 所有以 `X-Tap-` 为前缀的请求头(`X-Tap-Sign` 除外),将其 keys 全部转小写并按字典排序后,key 和 value 以 `:` 分隔,各个 header 以换行符 `\n` 分隔,拼接成字符串 | x-tap-nonce:q1w2e3r4\nx-tap-ts:1692347090 |
75+
| body | 请求体 | {"key":"value"} |
76+
77+
#### 二、计算签名
78+
79+
```
80+
Signature = Base64Encode(HMAC-SHA256(Server Secret, SignParts))
81+
```
82+
83+
其中
84+
- `HMAC-SHA256` 方法的第一个参数是 key,第二个参数是待签名字符串
85+
- `Server Secret` 为开放平台的服务端密钥
86+
- `SignParts` 为第一步计算所得字符串
87+
88+
#### 签名计算示例
89+
90+
<details>
91+
<summary>Go 请求示例</summary>
92+
93+
```go
94+
package tapsign
95+
96+
import (
97+
"crypto/hmac"
98+
"crypto/sha256"
99+
"encoding/base64"
100+
"errors"
101+
"fmt"
102+
"io"
103+
"net/http"
104+
"sort"
105+
"strings"
106+
)
107+
108+
func Sign(req *http.Request, secret string) (string, error) {
109+
methodPart := req.Method
110+
urlPathAndQueryPart := req.URL.RequestURI()
111+
headersPart, err := getHeadersPart(req.Header)
112+
if err != nil {
113+
return "", err
114+
}
115+
116+
bodyPart, err := io.ReadAll(req.Body)
117+
if err != nil {
118+
return "", err
119+
}
120+
121+
signParts := methodPart + "\n" + urlPathAndQueryPart + "\n" + headersPart + "\n" + string(bodyPart) + "\n"
122+
123+
fmt.Println(signParts)
124+
125+
h := hmac.New(sha256.New, []byte(secret))
126+
h.Write([]byte(signParts))
127+
rawSign := h.Sum(nil)
128+
129+
sign := base64.StdEncoding.EncodeToString(rawSign)
130+
131+
return sign, nil
132+
}
133+
134+
func getHeadersPart(header http.Header) (string, error) {
135+
var headerKeys []string
136+
137+
for k, v := range header {
138+
k = strings.ToLower(k)
139+
if !strings.HasPrefix(k, "x-tap-") {
140+
continue
141+
}
142+
if len(v) > 1 {
143+
return "", errors.New(fmt.Sprintf("invalid header, %q has multiple values", k))
144+
}
145+
146+
headerKeys = append(headerKeys, k)
147+
}
148+
149+
sort.Strings(headerKeys)
150+
151+
headers := make([]string, 0, len(headerKeys))
152+
for _, k := range headerKeys {
153+
headers = append(headers, fmt.Sprintf("%s:%s", k, header.Get(k)))
154+
}
155+
156+
return strings.Join(headers, "\n"), nil
157+
}
158+
```
159+
160+
</details>
161+
162+
### 响应数据
163+
164+
| 字段名 | 描述 | 类型 |
165+
|-----------------------|-------------------|-------------------|
166+
| success | 请求是否成功 | bool |
167+
| now | 当前服务器时间 | uint64 |
168+
| data | 上传请求参数结构体 | ApkUploadParams |
169+
| ApkUploadParams 的结构如下 |
170+
| url | 上传请求的 URL | string |
171+
| method | 上传请求的 HTTP Method | string |
172+
| headers | 上传请求的请求头列表 | map[string]string |
173+
174+
以下是一个完整的响应示例
175+
176+
```json
177+
{
178+
"data": {
179+
"url": "https://rnd-taptap.oss-cn-shanghai.aliyuncs.com/upload/20240923/58881-iOWGlETz.apk",
180+
"method": "PUT",
181+
"headers": {
182+
"authorization": "OSS4-HMAC-SHA256 Credential=xqKABkhrbAOqd20s/20240923/cn-shanghai/oss/aliyun_v4_request,AdditionalHeaders=host,Signature=1d26b0964e93eb9e47d22fda47a9cbd73a2f2bea13bbba569cdc4c6e1c1a4990",
183+
"content-type": "application/vnd.android.package-archive",
184+
"host": "rnd-taptap.oss-cn-shanghai.aliyuncs.com",
185+
"x-oss-callback": "eyJjYWxsYmFja1VybCI6Imh0dHBzOi8vcGFydG5lci5hcGkueGRybmQuY24vY2FsbGJhY2svdjIvYXBrL3VwbG9hZGVkLWZyb20tYXBpIiwiY2FsbGJhY2tIb3N0IjoicGFydG5lci5hcGkueGRybmQuY24iLCJjYWxsYmFja1NOSSI6dHJ1ZSwiY2FsbGJhY2tCb2R5Ijoie1wiZmlsZW5hbWVcIjoke29iamVjdH0sXCJzaXplXCI6JHtzaXplfSxcIm1pbWVUeXBlXCI6JHttaW1lVHlwZX0sXCJldGFnXCI6JHtldGFnfSxcImNvZGVcIjoke3g6Y29kZX19IiwiY2FsbGJhY2tCb2R5VHlwZSI6ImFwcGxpY2F0aW9uL2pzb24ifQ==",
186+
"x-oss-callback-var": "eyJ4OmNvZGUiOiJRN3pnSmpHNCJ9",
187+
"x-oss-content-sha256": "UNSIGNED-PAYLOAD",
188+
"x-oss-date": "20240923T113220Z"
189+
}
190+
},
191+
"now": 1727091140,
192+
"success": true
193+
}
194+
```
195+
196+
## 上传文件
197+
198+
调用接口获取到上传请求参数后,开发者即可自行将参数拼装为 curl 命令,发起文件上传,拼接方式如下
199+
200+
```
201+
curl -X ${method} \
202+
-T ${local_file_path} \
203+
FOREACH ${headers} AS ${key} => ${value}
204+
-H "${key}:${value} \
205+
ENDFOREACH
206+
${url}
207+
```
208+
209+
其中
210+
- `method` 是接口返回的 HTTP Method
211+
- `local_file_path` 是本地待上传的文件路径
212+
- `headers` 是接口返回的请求头列表
213+
- `url` 是接口返回的 URL
214+
215+
以下是一个完整的 curl 示例
216+
217+
```
218+
curl -X PUT \
219+
-T app-release-rel.apk \
220+
-H "host:tap-apk.oss-cn-beijing.aliyuncs.com" \
221+
-H "x-oss-content-sha256:UNSIGNED-PAYLOAD" \
222+
-H "x-oss-date:20240914T074000Z" \
223+
-H "Authorization:OSS4-HMAC-SHA256 Credential=BJqv1THhNPjsGGum/20240914/cn-beijing/oss/aliyun_v4_request,AdditionalHeaders=host,Signature=6a5329b20c47f83b4870e61cc6b5de8d5d0716cb4265e643b716f10ecdc1d734" \
224+
https://tap-apk.oss-cn-beijing.aliyuncs.com/upload/20240914/cuQaR3bC-app-release-rel.apk
225+
```
226+
227+
## 提交审核
228+
229+
上传成功后,等待约 3-5min(具体时间跟上传包的大小有关),你就可以在 <Blue>商店 >> 游戏资料 >> 商店资料</Blue> 里面找到刚刚上传的 APK 包,并将其作为版本资料的一部分进行提交审核操作
230+
231+
<video
232+
src="https://capacity-files.lcfile.com/bhXUeo0WS77u1flBeTQVcefzS17bJiHl/%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B62024-09-26%2023.41.37.mp4"
233+
controls
234+
muted
235+
preload="auto"
236+
width="100%"
237+
height="100%"
238+
>
239+
Video not supported.
240+
</video>

0 commit comments

Comments
 (0)