蒲公英:平台分发测试等等:使用脚本快速上传app(包含四个种类:.sh、.java、js、.php)

利用蒲公英提供的接口,第三方开发者可以把蒲公英提供的应用上传托管、安装等功能,接入到自己的应用中,并且可以根据数据接口,获取蒲公英提供的各种应用数据,以方便开发者更容易的进行内测应用的分发。

[【蒲公英官方文档 API 2.0】:](https://www.pgyer.com/doc/view/api#listMyReleased)

因为API 1.0即将废弃,2.0 APP上传文档github:

快速上传 App

脚本上传app:包含四个种类:demo.shAppUploadDemo.javademo.jsdemo.php

通过 API 快速上传 App 到蒲公英平台。github.com代码调用示例:https://github.com/PGYER/pgyer_api_example

1、获取上传的 token

通过该接口,开发者可以获取预上传 url 和相关的签名参数

API地址

POST https://www.pgyer.com/apiv2/app/getCOSToken

POST参数

参数 类型 说明
_api_key String (必填) API Key [点击获取_api_key](javascript:void(0))
buildType String (必填) 需要上传的应用类型,ios 或 android
buildInstallType Integer (选填)应用安装方式,值为(1,2,3,默认为1 公开安装)。1:公开安装,2:密码安装,3:邀请安装
buildPassword String (选填) 设置App安装密码,密码为空时默认公开安装
buildUpdateDescription String (选填) 版本更新描述,请传空字符串,或不传。
buildInstallDate Interger (选填)是否设置安装有效期,值为:1 设置有效时间, 2 长期有效,如果不填写不修改上一次的设置
buildInstallStartDate String (选填)安装有效期开始时间,字符串型,如:2018-01-01
buildInstallEndDate String (选填)安装有效期结束时间,字符串型,如:2018-12-31
buildChannelShortcut String (选填)所需更新的指定渠道的下载短链接,只可指定一个渠道,字符串型,如:abcd

返回数据

参数 类型 说明
key String key 上传文件存储标识唯一 key
endpoint String 上传文件的 URL
params Object 上传文件需要的参数,包含signature、x-cos-security-token、key

2、上传文件到第上一步获取的 URL

在这一步中上传 App 成功后,App 会自动进入服务器后台队列继续后续的发布流程。所以,在这一步中 App 上传完成后,并不代表 App 已经完成发布。一般来说,一般1分钟以内就能完成发布。要检查是否发布完成,请调用下一步中的 API。

API地址

POST 上一步响应中 endpoint 的值

POST参数

参数 类型 说明
key String (必填) 从上一步响应中得到
signature String (必填) 从上一步响应中得到
x-cos-security-token String (必填) 从上一步响应中得到
file String (必填) App 文件的本地路径

返回数据

如果上传成功:返回的 http 状态码为 204,内容为空;如果上传失败:返回相应错误信息说明。

Linux下的curl命令上传App示例

curl -D - --form-string 'key={key}' --form-string 'signature={signature}' --form-string 'x-cos-security-token={x-cos-security-token}' -F 'file=@/tmp/example.ipa' {endpoint}

demo示例:

第一种 demo.sh:

#!/bin/bash
#
# 通过shell脚本来实现将本地app文件通过API上传到蒲公英
# https://www.pgyer.com/doc/view/api#fastUploadApp
##
# 参数说明:
# $1: 蒲公英api_key
# $2: 要上传的文件路径(ipa/apk)
#

readonly api_key=$1
readonly file=$2

printHelp() {
    echo "Usage: $0 api_key file"
    echo "Example: $0 <your_api_key> <your_apk_or_ipa_path>"
}

# check api_key exists
if [ -z "$api_key" ]; then
    echo "api_key is empty"
    printHelp
    exit 1
fi

# check file exists
if [ ! -f "$file" ]; then
    echo "file not exists"
    printHelp
    exit 1
fi

if [[ $file =~ ipa$ ]]; then
    app_type="ios"
elif [[ $file =~ apk$ ]]; then
    app_type="android"
else
    echo "file type not support"
    printHelp
    exit 1
fi

# ---------------------------------------------------------------
# functions
# ---------------------------------------------------------------

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}

logTitle() {
    log "-------------------------------- $* --------------------------------"
}

execCommand() {
    log "$@"
    result=$(eval $@)
}

# ---------------------------------------------------------------
# 获取上传凭证
# ---------------------------------------------------------------

logTitle "获取凭证"

execCommand "curl -s -F '_api_key=${api_key}' -F 'buildType=${app_type}' http://www.pgyer.com/apiv2/app/getCOSToken"

[[ "${result}" =~ \"endpoint\":\"([\:\_\.\/\\A-Za-z0-9\-]+)\" ]] && endpoint=`echo ${BASH_REMATCH[1]} | sed 's!\\\/!/!g'`
[[ "${result}" =~ \"key\":\"([\.a-z0-9]+)\" ]] && key=`echo ${BASH_REMATCH[1]}`
[[ "${result}" =~ \"signature\":\"([\=\&\_\;A-Za-z0-9\-]+)\" ]] && signature=`echo ${BASH_REMATCH[1]}`
[[ "${result}" =~ \"x-cos-security-token\":\"([\_A-Za-z0-9\-]+)\" ]] && x_cos_security_token=`echo ${BASH_REMATCH[1]}`

if [ -z "$key" ] || [ -z "$signature" ] || [ -z "$x_cos_security_token" ] || [ -z "$endpoint" ]; then
    log "get upload token failed"
    exit 1
fi

# ---------------------------------------------------------------
# 上传文件
# ---------------------------------------------------------------

logTitle "上传文件"

execCommand "curl -s -o /dev/null -w '%{http_code}' --form-string 'key=${key}' --form-string 'signature=${signature}' --form-string 'x-cos-security-token=${x_cos_security_token}' -F 'file=@${file}' ${endpoint}"
if [ $result -ne 204 ]; then
    log "Upload failed"
    exit 1
fi

# ---------------------------------------------------------------
# 检查结果
# ---------------------------------------------------------------

logTitle "检查结果"

for i in {1..60}; do
    execCommand "curl -s http://www.pgyer.com/apiv2/app/buildInfo?_api_key=${api_key}\&buildKey=${key}"
    [[ "${result}" =~ \"code\":([0-9]+) ]] && code=`echo ${BASH_REMATCH[1]}`
    if [ $code -eq 0 ]; then
        log $result
        break
    else
        sleep 1
    fi
done

第二种 demo.php:

<?php
/**
 * 通过API上传App到蒲公英的示例代码
 *
 * 更多信息请见: https://www.pgyer.com/doc/view/api#fastUploadApp
 */

const API_KEY_DEV = "";
const API_KEY_PRODUCTION = "";
const APP_PATH = "";

// 获取上传凭证和上传地址
$res = sendRequest("http://www.pgyer.com/apiv2/app/getCOSToken", [
    "_api_key" => API_KEY_PRODUCTION,  // 填写您在平台注册的 API Key
    "buildType" => 'ios',  // 填写应用类型,可选值为: ios、android
    "buildInstallType" => 2,  // 填写安装类型,可选值为: 1 公开, 2密码
    "buildPassword" => '123456',  // 填写密码
]);
$res = json_decode($res, true);
// _log($res);

if ($res['code'] != 0 || empty($res['data'])) {
    exit($res['message']);
}
$key = $res['data']['key'];

// 开始上传
$params = $res['data']['params'];
$params['file'] = new CURLFile(APP_PATH);  // 填写待上传文件的路径
$httpcode = 0;

$result = sendRequest($res['data']['endpoint'], $params, $httpcode);
if ($httpcode == 204) {
    _log("upload success");
} else {
    _log("upload failed");
    _log($result);
    exit;
}

// 获取版本信息
getBuildInfo("http://www.pgyer.com/apiv2/app/buildInfo?_api_key=" . API_KEY_PRODUCTION . "&buildKey=$key");

// functions
function sendRequest($url, $params = [], &$httpcode = 0)
{
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    if (!empty($params)) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
    }

    $result = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    curl_close($ch);
    return $result;
}

function getBuildInfo($url)
{
    _log($url);
    for ($i = 0; $i < 60; $i++) {
        $res = json_decode(sendRequest($url), true);
        if ($res['code'] == 0) {
            _log($res['data']);
            break;
        } else {
            _log("[$i] get app build info...");
            sleep(1);
        }
    }
}

function _log($message) {
    if (is_array($message) || is_object($message)) {
        $message = var_export($message, true);
    }
    echo date("Y-m-d H:i:s") . " " . $message . "\n";
}

第三种 AppUploadDemo.java:

package com.pgyer.app_upload_demo;

import org.json.JSONObject;

import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;

/**
 * @describe
 * @author: Caoxy
 * @date: 2022/8/19
 */
public class AppUploadDemo implements AppUploadDemo.ProgressHttpEntityWrapper.ProgressCallback{
    public static final String API_KEY_DEV = "";//测试环境apikey
    public static final String API_KEY_PRODUCTION = "";//线上环境apikey
    public static final String APP_PATH = "";//文件地址
    public static final String  GET_TOKEN_URL = "https://www.pgyer.com/apiv2/app/getCOSToken";
    public static final int FILE_UPLOAD_SUCCESSFUL = 1001;

    private UploadFileToServiceCallback uploadFileToServiceCallback;
    Timer timers = null;


    //接口方法调用例子开始
    /**
     * 例子
     */
    public void uploadApk(){
        Map<String, String> params = new HashMap<>();
        params.put("_api_key", API_KEY_DEV);
        int buildInstallType = 1;
        params.put("buildInstallType", buildInstallType+"");//buildInstallType 1,2,3,默认为1 公开安装 1:公开安装,2:密码安装,3:邀请安装
        if (buildInstallType == 2) {
            params.put("buildPassword", "");//需要安装密码
        }
        params.put("buildUpdateDescription", "");//版本更新日志

        File uploadFile = new File(APP_PATH);//apk文件路径
        params.put("buildType", "android");
        String url = GET_TOKEN_URL;//
        getToken(params, url, new HttpCallback() {
            @java.lang.Override
            public void onSuccess(int code, String data) {
                JSONObject backData = new JSONObject(responseString);
                int code = backData.getInt("code");
                JSONObject jsonObject = backData.getJSONObject("data");
                if(code == 0 && jsonObject != null){//获取成功
                    JSONObject jsonObjectparams = jsonObject.getJSONObject("params");
                    Map<String, String> params = new HashMap<>();
                    String key = jsonObjectparams.getString("key");
                    params.put("key",key);
                    params.put("signature",jsonObjectparams.getString("signature"));
                    params.put("x-cos-security-token",jsonObjectparams.getString("x-cos-security-token"));
                    String url = jsonObject.getString("endpoint");
                    uploadFilr(params, url, uploadFile, API_KEY_DEV, key);
                } else {//获取失败

                }
            }

            @java.lang.Override
            public void onError(int code, String data) {

            }
        })
    }
    /**
     * 例子 上传文件
     * @param url
     */
    public void uploadFilr(Map<String, String> params, String url ,File files, String apikey, String buildKey){
        uploadFileToServer(params, url, files, new UploadFileToServiceCallback() {
            @java.lang.Override
            public void onUploadBack(int code, String msg) {
                if(code == FILE_UPLOAD_SUCCESSFUL){
                    //数据同步需要时间延时5秒请求同步结果
                    timers = new Timer(5000, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            if(timers != null){
                                timers.stop();
                                timers = null;
                                String url = "https://www.pgyer.com/apiv2/app/buildInfo?_api_key="+apikey+"&buildKey="+buildKey;
                                uploadResult(url,uploadFileToServiceCallback);
                            }
                        }
                    });
                    timers.start();
                }
            }

            @java.lang.Override
            public void onPackageSizeComputed(long param1Long) {//上传文件的大小 去做度更新UI

            }

            @java.lang.Override
            public void onProgressChanged(float param1Long) {//上传文件的进  去做度更新UI

            }

            @java.lang.Override
            public void onUploadError(int code, String error) {//文件上传失败返回

            }
        });
    }

    /**
     * 例子 获取同步结果
     * @param url
     */
    public void dataSynchronous(String url){
        uploadResult(url, new HttpCallback() {
            @java.lang.Override
            public void onSuccess(int code, String data) {
                JSONObject backDatas = new JSONObject(responseString);
                int code = backDatas.getInt("code");
                if(code == 0){//上传成功
                    backData = responseString;
                    JSONObject data = backDatas.getJSONObject("data");
                    //返回成功后相关文件信息
                    if (uploadFileToServiceCallback != null) {
                        uploadFileToServiceCallback.onUploadBack(code,responseString);
                    }
                } else if(code == 1246){//等待同步
                    //数据同步需要时间延时已经延时5秒还在同步中,再2秒后请求同步结果
                    timers = new Timer(2000, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            if(timers != null){
                                timers.stop();
                                timers = null;
                                dataSynchronous(url);
                            }
                        }
                    });
                    timers.start();
                } else {//上传失败
                    if (uploadFileToServiceCallback != null) {
                        uploadFileToServiceCallback.onUploadError(code,"上传失败");
                    }
                }
            }

            @java.lang.Override
            public void onError(int code, String data) {

            }
        });
    }
    //接口方法调用例子结束

    /**
     * 获取文件相关参数
     * @param params
     * @param url
     * @param httpCallback
     */
    public void getToken(Map<String, String> params, String url, HttpCallback httpCallback){
        sendRequest("POST",url, params ,httpCallback);
    }

    /**
     * 上传文件
     * @param params
     * @param url
     * @param files
     * @param apikey
     * @param buildKey
     * @param uploadFileToServiceCallback
     */
    public void uploadFileToServer(final Map<String, String> params, String url ,final File files, UploadFileToServiceCallback uploadFileToServiceCallback) {
        this.uploadFileToServiceCallback = uploadFileToServiceCallback;
        // TODO Auto-generated method stub
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    uploadFiles(params, url, files, uploadFileToServiceCallback);
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    uploadFileToServiceCallback.onUploadError(-1,e.getMessage());
                }
            }
        }).start();
    }

    /**
     * 上传文件实现
     * @param params
     * @param url
     * @param files
     * @param uploadFileToServiceCallback
     */
    public void uploadFiles(final Map<String, String> params, String url ,final File files, UploadFileToServiceCallback uploadFileToServiceCallback,) {
        // TODO Auto-generated method stub
        HttpClient client = HttpClientBuilder.create().build();// 开启一个客户端 HTTP 请求
        HttpPost post = new HttpPost(url);//创建 HTTP POST 请求
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);//设置浏览器兼容模式
        for(Map.Entry<String, String> entry:params.entrySet()){
            builder.addTextBody(entry.getKey(), entry.getValue());//设置请求参数
        }
        builder.addBinaryBody("file", files);
        if (this.uploadFileToServiceCallback != null)
            this.uploadFileToServiceCallback.onPackageSizeComputed(100);
        HttpEntity entity = builder.build();// 生成 HTTP POST 实体
        post.setEntity(new ProgressHttpEntityWrapper(entity,this));//设置请求参数
        HttpResponse response = client.execute(post);// 发起请求 并返回请求的响应
        int httpCode = response.getStatusLine().getStatusCode();
        if (httpCode == 204) {
            if(uploadFileToServiceCallback != null) {
                uploadFileToServiceCallback.onUploadBack(FILE_UPLOAD_SUCCESSFUL, "文件上传成功等待同步数据");
            }
        } else {
            if(uploadFileToServiceCallback != null){
                uploadFileToServiceCallback.onUploadError(httpCode,"上传失败!");
            }
        }

    }

    /**
     * 获取上传文件后同步信息
     * @param url
     */
    public void uploadResult(String url,HttpCallback httpCallback){
        sendRequest("GET",url, null ,httpCallback);
    }

    /**
     * 发起http 请求
     * @param httpModle
     * @param url
     * @param params
     * @param httpCallback
     */
    public void sendRequest(String httpModle, String url, Map<String, String> params, HttpCallback httpCallback){
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    HttpClient client = HttpClientBuilder.create().build();// 开启一个客户端 HTTP 请求
                    if(httpModle.equals("GET")){
                        HttpGet httpClient = new HttpGet(url);//创建 HTTP POST 请求
                    } else if(httpModle.equals("POST")){
                        HttpPost httpClient = new HttpPost(url);//创建 HTTP POST 请求
                        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
                        builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);//设置浏览器兼容模式
                        for(Map.Entry<String, String> entry:params.entrySet()){
                            builder.addTextBody(entry.getKey(), entry.getValue());//设置请求参数
                            if(entry.getKey().equals("buildUpdateDescription")){
                                builder.addTextBody(entry.getKey(), entry.getValue(), ContentType.create(entry.getValue(), Charset.forName("UTF-8")));
                            }
                        }
                        HttpEntity entity = builder.build();// 生成 HTTP POST 实体
                        httpClient.setEntity(new HttpEntityWrapper(entity));//设置请求参数
                    }
                    HttpResponse response = client.execute(httpClient);// 发起请求 并返回请求的响应
                    if (response.getStatusLine().getStatusCode() == 200) {
                        String responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
                        JSONObject backDatas = new JSONObject(responseString);
                        int code = backDatas.getInt("code");
                        if(httpCallback != null){
                            httpCallback.onSuccess(code,responseString);
                        }
                    }
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    httpCallback.onError(-1,e.getMessage());
                }
            }
        }).start();
    }

    @Override
    public void progress(float progress) {
        uploadFileToServiceCallback.onProgressChanged(progress);
    }

    /**
     * 上传文件封装监听
     */
    public class ProgressHttpEntityWrapper extends HttpEntityWrapper {

        private final ProgressCallback progressCallback;

        public static interface ProgressCallback {
            public void progress(float progress);
        }

        public ProgressHttpEntityWrapper(final HttpEntity entity, final ProgressCallback progressCallback) {
            super(entity);
            this.progressCallback = progressCallback;
        }

        @Override
        public void writeTo(final OutputStream out) throws IOException {
            this.wrappedEntity.writeTo(out instanceof ProgressFilterOutputStream ? out : new ProgressFilterOutputStream(out, this.progressCallback, getContentLength()));
        }

        public static class ProgressFilterOutputStream extends FilterOutputStream {

            private final ProgressCallback progressCallback;
            private long transferred;
            private long totalBytes;

            ProgressFilterOutputStream(final OutputStream out, final ProgressCallback progressCallback, final long totalBytes) {
                super(out);
                this.progressCallback = progressCallback;
                this.transferred = 0;
                this.totalBytes = totalBytes;
            }

            @Override
            public void write(final byte[] b, final int off, final int len) throws IOException {
                //super.write(byte b[], int off, int len) calls write(int b)
                out.write(b, off, len);
                this.transferred += len;
                this.progressCallback.progress(getCurrentProgress());
            }

            @Override
            public void write(final int b) throws IOException {
                out.write(b);
                this.transferred++;
                this.progressCallback.progress(getCurrentProgress());
            }

            private float getCurrentProgress() {
                return ((float) this.transferred / this.totalBytes) * 100;
            }

        }
    }

    /**
     * 上传文件监听回调
     */
    public interface UploadFileToServiceCallback {
        //上传成功 或者 同步数据接口成功返回
        void onUploadBack(int code,String msg);
        //上传文件大小
        void onPackageSizeComputed(long param1Long);
        //上传文件进度
        void onProgressChanged(float param1Long);
        //上传失败返回
        void onUploadError(int code,String error);
    }

    /**
     * http 请求回调
     */
    public interface HttpCallback{
        void onSuccess(int code, String data){

        }
        void onError(int code, String data){

        }
    }
}

第四种 demo.js:

// 此 Demo 用演示如何使用 PGYER API 上传 App
// 详细文档参照 https://www.pgyer.com/doc/view/api#fastUploadApp
// 适用于 nodejs 项目
// 本代码需要 npm 包 form-data 支持 运行 npm install --save form-data 即可

const https = require('https');
const fs = require('fs');
const querystring = require('querystring');
const FormData = require('form-data');

const API_KEY_PRODUCTION = '<your api key>';
const APP_PATH = '<your app path>';

function upload (apiKey, appPath, callback) {
  function getUploadResult (uploadData) {
    const uploadResultRequest = https.request({
      hostname: 'www.pgyer.com',
      path: '/apiv2/app/buildInfo?_api_key=' + apiKey + '&buildKey=' + uploadData.data.key,
      method: 'POST',
      headers: {
        'Content-Type' : 'application/x-www-form-urlencoded',
        'Content-Length' : 0
      }
    }, response => {
      if (response.statusCode !== 200) {
        throw new Error('PGYER Service is down.');
      }
    
      let responseData = '';
      response.on('data', data => {
        responseData += data.toString();
      })
    
      response.on('end', () => {
        const responseText = responseData.toString();
        try {
          const responseInfo = JSON.parse(responseText);
          if (responseInfo.code === 1247) {
            console.log('Parsing App Data ... Please Wait ... \n');
            setTimeout(() => getUploadResult(uploadData), 1000);
          } else if (responseInfo.code) {
            throw new Error('PGYER Service Error > ' + responseInfo.code + ': ' + responseInfo.message);
          }
          callback(responseInfo);
        } catch (error) {
          throw error;
        }
      })
    
    })
  
    uploadResultRequest.write(uploadTokenRequestData);
    uploadResultRequest.end();
  }

  function uploadApp(uploadData) {
    const uploadAppRequestData = new FormData()
    uploadAppRequestData.append('signature', uploadData.data.params.signature);
    uploadAppRequestData.append('x-cos-security-token', uploadData.data.params['x-cos-security-token']);
    uploadAppRequestData.append('key', uploadData.data.params.key);
    uploadAppRequestData.append('file', fs.createReadStream(appPath))
    
    uploadAppRequestData.submit(uploadData.data.endpoint, function (error, response) {
      if (response.statusCode === 204) {
        setTimeout(() => getUploadResult(uploadData), 1000);
      } else {
        throw new Error('Upload Error!')
      }
    });
  }

  const uploadTokenRequestData = querystring.stringify({
    _api_key: apiKey,  // 填写您在平台注册的 API Key (具体参照文档)
    buildType: 'ios',  // 填写应用类型,可选值为: ios、android (具体参照文档)
    buildInstallType: 2,  // 填写安装类型,可选值为: 1 公开, 2密码 (具体参照文档)
    buildPassword: '123456',  // 填写密码安装时的密码 (具体参照文档)
  });
  
  const uploadTokenRequest = https.request({
    hostname: 'www.pgyer.com',
    path: '/apiv2/app/getCOSToken',
    method: 'POST',
    headers: {
      'Content-Type' : 'application/x-www-form-urlencoded',
      'Content-Length' : uploadTokenRequestData.length
    }
  }, response => {
    if (response.statusCode !== 200) {
      throw new Error('PGYER Service is down.');
    }
  
    let responseData = '';
    response.on('data', data => {
      responseData += data.toString();
    })
  
    response.on('end', () => {
      const responseText = responseData.toString();
      try {
        const responseInfo = JSON.parse(responseText);
        if (responseInfo.code) {
          throw new Error('PGYER Service Error > ' + responseInfo.code + ': ' + responseInfo.message);
        }
        uploadApp(responseInfo);
      } catch (error) {
        throw error;
      }
    })
  
  })

  uploadTokenRequest.write(uploadTokenRequestData);
  uploadTokenRequest.end();
}

// 调用说明:
// upload(apikey, appPath, successCallback): void
// apikey: 你的 API Key
// appPath: 你的 app 路径,绝对路径相对路径均可
// successCallback: 成功后的回调函数, 传入一个参数为 App 信息 类似如下对象描述
// {
//   code: 0,
//   message: '',
//   data: {
//     buildKey: 'xxx',
//     buildType: '1',
//     buildIsFirst: '0',
//     buildIsLastest: '1',
//     buildFileKey: 'xxx.ipa',
//     buildFileName: '',
//     buildFileSize: '40095060',
//     buildName: 'xxx',
//     buildVersion: '2.2.0',
//     buildVersionNo: '1.0.1',
//     buildBuildVersion: '9',
//     buildIdentifier: 'xxx.xxx.xxx.xxx',
//     buildIcon: 'xxx',
//     buildDescription: '',
//     buildUpdateDescription: '',
//     buildScreenshots: '',
//     buildShortcutUrl: 'xxxx',
//     buildCreated: 'xxxx-xx-xx xx:xx:xx',
//     buildUpdated: 'xxxx-xx-xx xx:xx:xx',
//     buildQRCodeURL: 'https://www.pgyer.com/app/qrcodeHistory/xxxx'
//   }
// }

upload(API_KEY_PRODUCTION, APP_PATH, console.log);
结合第四种js:package-lock.json:
{
  "requires": true,
  "lockfileVersion": 1,
  "dependencies": {
    "asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
    },
    "combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
      "requires": {
        "delayed-stream": "~1.0.0"
      }
    },
    "delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
    },
    "form-data": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
      "requires": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "mime-types": "^2.1.12"
      }
    },
    "mime-db": {
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
    },
    "mime-types": {
      "version": "2.1.35",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
      "requires": {
        "mime-db": "1.52.0"
      }
    }
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容