# 符号表上传说明
# 符号表上传
当前平台支持两种 符号表上传方式。
- 手动上传
- 接口上传
# 1、手动上传
- 按照文档说明将符号表文件压缩到一起:点击查看说明
- 访问应用,点击应用左侧菜单的 【管理工具 -> 版本管理】进入版本设置界面
- 找到对应的版本,点击 上传 ,将第一步压缩好的符号表文件上传即可
# 2、接口上传
通过这种方式上传符号表的同时会自动创建解析版本。
该接口的请求方式是POST。
# 接口说明
https://yueying.effirst.com/rest/v1/api/symbol/upload?clientId=${clientId}×tamp=${timestamp}&signature=${signature}
# 请求时头部注意添加: content-type: multipart/form-data;
# request字段说明:
字段名 | 说明 | 示例 | 是否必填 |
clientId | 调用方的clientId(clientId需要联系岳鹰申请) | 是 | |
app | 应用标识 | UCBrowser | 是 |
version | 主版本号(若版本不存在,会为其创建该版本),如果有流水号,需要用于反混淆/符号化,则需要添加到括号中 | 默认版本号:12.1.0.12 如果带有流水号: 12.1.0.12(190219173829) | 是 |
versionSuffix | 版本后缀,一般为子版本号 | appc\beta\trial | 否 |
symbolType | 类型,测试版传test、正式版传release | test、release | 是 |
file | 符号文件,使用zip压缩,压缩格式参照《符号表格式》 | 是 |
# response:
# 上传代码
# python版本
# !/usr/bin/python
# -*- coding: utf-8 -*-
import hashlib
import os
import time
import requests
class SignBuilder(object):
def __init__(self, client_id, secret, timestamp):
self.__params = {}
self.__client_id = client_id
self.__secret = secret
self.__timestamp = timestamp
def add_params(self, key, value):
self.__params[key] = value
return self
def body(self, value):
self.__params["body"] = value
return self
def build(self):
need_md5_txt = self.__client_id + self.__secret + self.__timestamp
need_md5_txt = need_md5_txt + self.__build_params()
md5_encoder = hashlib.md5()
md5_encoder.update(need_md5_txt)
signature = md5_encoder.hexdigest()
return signature.lower()
def __build_params(self):
keys = self.__params.keys()
keys = sorted(keys)
body = ""
# 组件请求参数体
for key in keys:
if key == "" or key in ["clientId", "timestamp", "signature"]:
continue
if self.__params[key] is None:
body = body + key + "="
else:
body = body + key + '=' + str(self.__params[key])
return body
class SymbolType(object):
TEST = "test"
RELEASE = "release"
class WpkApi(object):
def __init__(self, host, client_id, secret):
self.__host = host
self.__client_id = client_id
self.__secret = secret
def upload_mapping(self, app, ver, symbol_type, file_path, ver_suffix=None):
if file_path is None or not os.path.exists(file_path):
raise Exception("Mapping文件不存在:" + str(file_path))
api_path = "/rest/v1/api/symbol/upload"
req_url = self.__host + api_path
data = {
"app": app,
"version": ver,
"symbolType": symbol_type
}
files = {
"file": open(file_path, 'rb'),
}
timestamp = str(int(time.time() * 1000))
sign_builder = SignBuilder(self.__client_id, self.__secret, timestamp)\
.add_params("app", app) \
.add_params("version", ver) \
.add_params("symbolType", symbol_type)
# 子版本号,非必选
if ver_suffix is not None:
data["versionSuffix"] = ver_suffix
sign_builder.add_params("versionSuffix", ver_suffix)
sign_str = sign_builder.build()
req_url = req_url + "?clientId=%s×tamp=%s&signature=%s" % (self.__client_id, timestamp, sign_str)
response = requests.post(req_url, data=data, files=files)
response_json = response.json()
if response.status_code == requests.codes.ok and str(response_json["code"]) == "200":
print response.text.encode('utf-8')
response.close()
return
response.close()
raise Exception("符号表上传失败,服务器响应:" + str(response.text.encode('utf-8')))
# 使用说明
wpk_api = WpkApi("https://yueying.effirst.com", "xxxx", "xxxxxxxx")
symbol_type = SymbolType.TEST
sub_ver = "release1d"
if "release" in sub_ver:
symbol_type = SymbolType.RELEASE
wpk_api.upload_mapping("yueyingandroid", "1.2.0(1901191131053)", symbol_type, \
"/Users/yueying/Documents/yueyingandroid_1.0.0.2_190119113105_dev_maps.zip", \
ver_suffix=sub_ver)
# groovy版本
private void uploadMapping(String mappingZip) {
if (mappingZip == null) {
return
}
File mappingZipFile = new File(mappingZip)
assert mappingZipFile.exists()
String version = getBuildInfo().getVersion() + '(' + getBuildInfo().getBuildSequence() + ')'
String subVersion = getBuildInfo().getSubversion()
String app = 'yueyingandroid'
String symbolType = 'release'.equals(subVersion) ? 'release' : 'test'
long timeStamp = System.currentTimeMillis()
SignBuilder signBuilder = new SignBuilder()
.addClientId(UcAndroidPluginConstant.WPK_CLIENT_ID)
.addTimestamp(timeStamp)
.addSecret(UcAndroidPluginConstant.WPK_CLIENT_SECRET)
.addParam('versionSuffix', subVersion)
.addParam('app', app)
.addParam('version', version)
.addParam('symbolType', symbolType)
String signStr = signBuilder.buildSign()
String targetMapping = UcAndroidPluginConstant.CRASH_SDK_APP_ID + "_" + getBuildInfo().getVersion() + "_" + getBuildInfo().getBuildSequence() + "_" + getBuildInfo().getSubversion() + "_maps.zip"
logger.info('uploaded source file: {} , target name', mappingZip, targetMapping)
println 'uploaded source file: {} , target name' + mappingZip + targetMapping
String url = UcAndroidPluginConstant.CRASH_SDK_MAPPING_UPLOAD_URL + String.format('?clientId=%s×tamp=%s&signature=%s&app=%s&version=%s&versionSuffix=%s&symbolType=%s', UcAndroidPluginConstant.WPK_CLIENT_ID, String.valueOf(timeStamp), signStr, app, version, subVersion, symbolType)
println 'url: ' + url
//upload
def http = new HTTPBuilder(url)
http.ignoreSSLIssues()
http.request(Method.POST) { request ->
def entityBuilder = MultipartEntityBuilder.create()
.addBinaryBody('file', mappingZipFile)
request.entity = entityBuilder.build()
response.success = {
println 'uploaded file: {}' + mappingZip
HttpResponseUtil.logResponse(it)
}
response.failure = {
println 'unable to upload file: ' + mappingZip
HttpResponseUtil.logResponse(it)
}
}
}
# Java版本
package com.xxx.build.gradle.helper;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.commons.codec.digest.DigestUtils;
public class SignBuilder {
private Map<String, String> params = new TreeMap<>();
// json body
private String body;
private String clientId;
private String secret;
private Long timestamp;
// 旧版1.0用的是随机串,并且不校验时常
private String nonce;
public SignBuilder addParams (Map<String, String> params) {
this.params.clear();
this.params.putAll(params);
return this;
}
public SignBuilder addParam(String name, String value) {
params.put(name, value);
return this;
}
public SignBuilder addBody (String body) {
this.addParam("body", body);
return this;
}
public SignBuilder addClientId(String clientId) {
this.clientId = clientId;
return this;
}
public SignBuilder addSecret(String secret) {
this.secret = secret;
return this;
}
public SignBuilder addTimestamp(Long timestamp) {
this.timestamp = timestamp;
return this;
}
public SignBuilder nonce(String nonce) {
this.nonce = nonce;
return this;
}
/**
* 构建签名信息
* @return
*/
public String buildSign() {
StringBuilder singBuilder = new StringBuilder(clientId).append(secret).append(timestamp);
singBuilder.append(this.buildParams());
return DigestUtils.md5Hex(singBuilder.toString());
}
/**
* 拼接请求参数
* @return
*/
private StringBuilder buildParams () {
StringBuilder rst = new StringBuilder();
for (Entry<String, String> entry : params.entrySet()) {
if (isBlank(entry.getKey())) {
continue;
}
if (null == entry.getValue()) {
rst.append(entry.getKey()).append("=");
} else {
String value = entry.getValue();
rst.append(entry.getKey()).append("=").append(value);
}
}
return rst;
}
public static boolean isBlank(String str) {
int strLen;
if (str != null && (strLen = str.length()) != 0) {
for(int i = 0; i < strLen; ++i) {
if (!Character.isWhitespace(str.charAt(i))) {
return false;
}
}
return true;
} else {
return true;
}
}
}