符号表上传

当前平台支持两种 符号表上传方式 :

  • 手动上传
  • 接口上传

手动上传

  1. 按照文档说明将符号表文件压缩到一起:点击查看说明
  2. 登录平台,点击平台左下角的 设置 进入应用设置界面

    image.png

  3. 点击 版本管理 ,找到对应的版本,点击 上传 ,将第一步压缩好的符号表文件上传即可

image.png

接口上传

通过这种方式上传符号表的同时会自动创建解析版本
该接口的请求方式是POST
**

接口说明

https://wpk.uc.cn/rest/v1/api/symbol/upload?clientId=_${clientId}_&timestamp=_${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:

  1. {
  2. "id": "8cbdbe8a-173c-466c-82a2-41be0c2ef0f9",
  3. "code": "200",
  4. "message": "OK",
  5. "data": 123, // 符号表的唯一id
  6. "error": null
  7. }

上传代码:

python版本
  1. # !/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import hashlib
  4. import os
  5. import time
  6. import requests
  7. class SignBuilder(object):
  8. def __init__(self, client_id, secret, timestamp):
  9. self.__params = {}
  10. self.__client_id = client_id
  11. self.__secret = secret
  12. self.__timestamp = timestamp
  13. def add_params(self, key, value):
  14. self.__params[key] = value
  15. return self
  16. def body(self, value):
  17. self.__params["body"] = value
  18. return self
  19. def build(self):
  20. need_md5_txt = self.__client_id + self.__secret + self.__timestamp
  21. need_md5_txt = need_md5_txt + self.__build_params()
  22. md5_encoder = hashlib.md5()
  23. md5_encoder.update(need_md5_txt)
  24. signature = md5_encoder.hexdigest()
  25. return signature.lower()
  26. def __build_params(self):
  27. keys = self.__params.keys()
  28. keys = sorted(keys)
  29. body = ""
  30. # 组件请求参数体
  31. for key in keys:
  32. if key == "" or key in ["clientId", "timestamp", "signature"]:
  33. continue
  34. if self.__params[key] is None:
  35. body = body + key + "="
  36. else:
  37. body = body + key + '=' + str(self.__params[key])
  38. return body
  39. class SymbolType(object):
  40. TEST = "test"
  41. RELEASE = "release"
  42. class WpkApi(object):
  43. def __init__(self, host, client_id, secret):
  44. self.__host = host
  45. self.__client_id = client_id
  46. self.__secret = secret
  47. def upload_mapping(self, app, ver, symbol_type, file_path, ver_suffix=None):
  48. if file_path is None or not os.path.exists(file_path):
  49. raise Exception("Mapping文件不存在:" + str(file_path))
  50. api_path = "/rest/v1/api/symbol/upload"
  51. req_url = self.__host + api_path
  52. data = {
  53. "app": app,
  54. "version": ver,
  55. "symbolType": symbol_type
  56. }
  57. files = {
  58. "file": open(file_path, 'rb'),
  59. }
  60. timestamp = str(int(time.time() * 1000))
  61. sign_builder = SignBuilder(self.__client_id, self.__secret, timestamp)\
  62. .add_params("app", app) \
  63. .add_params("version", ver) \
  64. .add_params("symbolType", symbol_type)
  65. # 子版本号,非必选
  66. if ver_suffix is not None:
  67. data["versionSuffix"] = ver_suffix
  68. sign_builder.add_params("versionSuffix", ver_suffix)
  69. sign_str = sign_builder.build()
  70. req_url = req_url + "?clientId=%s&timestamp=%s&signature=%s" % (self.__client_id, timestamp, sign_str)
  71. response = requests.post(req_url, data=data, files=files)
  72. response_json = response.json()
  73. if response.status_code == requests.codes.ok and str(response_json["code"]) == "200":
  74. print response.text.encode('utf-8')
  75. response.close()
  76. return
  77. response.close()
  78. raise Exception("符号表上传失败,服务器响应:" + str(response.text.encode('utf-8')))

使用说明:

  1. wpk_api = WpkApi("https://wpk.uc.cn", "xxxx", "xxxxxxxx")
  2. symbol_type = SymbolType.TEST
  3. sub_ver = "release1d"
  4. if "release" in sub_ver:
  5. symbol_type = SymbolType.RELEASE
  6. wpk_api.upload_mapping("youyuandroid", "1.2.0(1901191131053)", symbol_type, \
  7. "/Users/kevin/Documents/youyuandroid_1.0.0.2_190119113105_dev_maps.zip", \
  8. ver_suffix=sub_ver)

groovy版本
  1. private void uploadMapping(String mappingZip) {
  2. if (mappingZip == null) {
  3. return
  4. }
  5. File mappingZipFile = new File(mappingZip)
  6. assert mappingZipFile.exists()
  7. String version = getBuildInfo().getVersion() + '(' + getBuildInfo().getBuildSequence() + ')'
  8. String subVersion = getBuildInfo().getSubversion()
  9. String app = 'novelapp'
  10. String symbolType = 'release'.equals(subVersion) ? 'release' : 'test'
  11. long timeStamp = System.currentTimeMillis()
  12. SignBuilder signBuilder = new SignBuilder()
  13. .addClientId(UcAndroidPluginConstant.WPK_CLIENT_ID)
  14. .addTimestamp(timeStamp)
  15. .addSecret(UcAndroidPluginConstant.WPK_CLIENT_SECRET)
  16. .addParam('versionSuffix', subVersion)
  17. .addParam('app', app)
  18. .addParam('version', version)
  19. .addParam('symbolType', symbolType)
  20. String signStr = signBuilder.buildSign()
  21. String targetMapping = UcAndroidPluginConstant.CRASH_SDK_APP_ID + "_" + getBuildInfo().getVersion() + "_" + getBuildInfo().getBuildSequence() + "_" + getBuildInfo().getSubversion() + "_maps.zip"
  22. logger.info('uploaded source file: {} , target name', mappingZip, targetMapping)
  23. println 'uploaded source file: {} , target name' + mappingZip + targetMapping
  24. String url = UcAndroidPluginConstant.CRASH_SDK_MAPPING_UPLOAD_URL + String.format('?clientId=%s&timestamp=%s&signature=%s&app=%s&version=%s&versionSuffix=%s&symbolType=%s', UcAndroidPluginConstant.WPK_CLIENT_ID, String.valueOf(timeStamp), signStr, app, version, subVersion, symbolType)
  25. println 'url: ' + url
  26. //upload
  27. def http = new HTTPBuilder(url)
  28. http.ignoreSSLIssues()
  29. http.request(Method.POST) { request ->
  30. def entityBuilder = MultipartEntityBuilder.create()
  31. .addBinaryBody('file', mappingZipFile)
  32. request.entity = entityBuilder.build()
  33. response.success = {
  34. println 'uploaded file: {}' + mappingZip
  35. HttpResponseUtil.logResponse(it)
  36. }
  37. response.failure = {
  38. println 'unable to upload file: ' + mappingZip
  39. HttpResponseUtil.logResponse(it)
  40. }
  41. }
  42. }
  1. package com.xxx.build.gradle.helper;
  2. import java.util.Map;
  3. import java.util.Map.Entry;
  4. import java.util.TreeMap;
  5. import org.apache.commons.codec.digest.DigestUtils;
  6. public class SignBuilder {
  7. private Map<String, String> params = new TreeMap<>();
  8. // json body
  9. private String body;
  10. private String clientId;
  11. private String secret;
  12. private Long timestamp;
  13. // 旧版1.0用的是随机串,并且不校验时常
  14. private String nonce;
  15. public SignBuilder addParams (Map<String, String> params) {
  16. this.params.clear();
  17. this.params.putAll(params);
  18. return this;
  19. }
  20. public SignBuilder addParam(String name, String value) {
  21. params.put(name, value);
  22. return this;
  23. }
  24. public SignBuilder addBody (String body) {
  25. this.addParam("body", body);
  26. return this;
  27. }
  28. public SignBuilder addClientId(String clientId) {
  29. this.clientId = clientId;
  30. return this;
  31. }
  32. public SignBuilder addSecret(String secret) {
  33. this.secret = secret;
  34. return this;
  35. }
  36. public SignBuilder addTimestamp(Long timestamp) {
  37. this.timestamp = timestamp;
  38. return this;
  39. }
  40. public SignBuilder nonce(String nonce) {
  41. this.nonce = nonce;
  42. return this;
  43. }
  44. /**
  45. * 构建签名信息
  46. * @return
  47. */
  48. public String buildSign() {
  49. StringBuilder singBuilder = new StringBuilder(clientId).append(secret).append(timestamp);
  50. singBuilder.append(this.buildParams());
  51. return DigestUtils.md5Hex(singBuilder.toString());
  52. }
  53. /**
  54. * 拼接请求参数
  55. * @return
  56. */
  57. private StringBuilder buildParams () {
  58. StringBuilder rst = new StringBuilder();
  59. for (Entry<String, String> entry : params.entrySet()) {
  60. if (isBlank(entry.getKey())) {
  61. continue;
  62. }
  63. if (null == entry.getValue()) {
  64. rst.append(entry.getKey()).append("=");
  65. } else {
  66. String value = entry.getValue();
  67. rst.append(entry.getKey()).append("=").append(value);
  68. }
  69. }
  70. return rst;
  71. }
  72. public static boolean isBlank(String str) {
  73. int strLen;
  74. if (str != null && (strLen = str.length()) != 0) {
  75. for(int i = 0; i < strLen; ++i) {
  76. if (!Character.isWhitespace(str.charAt(i))) {
  77. return false;
  78. }
  79. }
  80. return true;
  81. } else {
  82. return true;
  83. }
  84. }
  85. }