上传文件分块
更新时间:2025.09.29上传单个分块的文件数据,需指定上传ID和分块序号。
注:上传任务需归属于本请求的同一品牌商家,否则将无法通过权限校验。
接口说明
支持商户:【品牌商户】
请求方式:【PUT】/brand/marketing/user-package/uploads/{upload_id}/{part_number}
请求域名:【主域名】https://api.mch.weixin.qq.com 使用该域名将访问就近的接入点
【备域名】https://api2.mch.weixin.qq.com 使用该域名将访问异地的接入点 ,指引点击查看
接口限频:10/秒(品牌ID维度)
请求参数
折叠全部参数
Header HTTP头参数
Authorization 必填 string
请参考签名认证生成认证信息
Accept 必填 string
请设置为application/json
Content-Type 必填 string
请设置为multipart/form-data
Wechatpay-Serial 必填 string
【微信支付公钥ID】 请传入brand_id对应的微信支付公钥ID,接口将会校验两者的关联关系,参考微信支付公钥产品简介及使用说明获取微信支付公钥ID和相关的介绍。以下两种场景将使用到微信支付公钥: 1、接收到接口的返回内容,需要使用微信支付公钥进行验签; 2、调用含有敏感信息参数(如姓名、身份证号码)的接口时,需要使用微信支付公钥加密敏感信息后再传输参数,加密指引请参考微信支付公钥加密敏感信息指引。
path 路径参数
upload_id 必填 string(128)
【上传ID】 上传ID,调用初始化上传时返回的上传ID
part_number 必填 integer
【分块序号】 分块序号,从1开始,最大值为初始化上传接口返回的文件分块数量。
Form 表单
file 必填 file
【文件数据】 文件数据,人群号码包文件的二进制内容。 注:请严格按初始化上传接口返回的文件分块大小和文件分块数量进行分块上传,最后一块文件数据大小可以小于文件分块大小。
meta 必填 object
【文件元信息】 当前上传的文件元信息
| 属性 | |
filename 必填 string(128) 【文件名】 当前上传的文件名。可包含英文字母,数字,_,.,-等内容,不允许出现其他不合法符号。 digest 必填 string(64) 【文件摘要】 当前上传的文件摘要,二进制内容进行sha256计算得到的值 |
请求示例
PUT
1curl -X PUT \ 2 https://api.mch.weixin.qq.com/brand/marketing/user-package/uploads/17587186700323d11ac17d8d3d4c8c4e5a250c3e804977e89af6a05ffa93751d0ec0179ca8/1 \ 3 -H "Authorization: WECHATPAY-BRAND-SHA256-RSA2048 brand_id=\"XXXX\",..." \ 4 -H "Accept: application/json" \ 5 -H "Wechatpay-Serial: PUB_KEY_ID_XXXX" \ 6 -H "Content-Type: multipart/form-data" \ 7 -F file=@"/path/to/file" \ 8 -F meta="{ 9 "filename" : "1_active_user.csv", 10 "digest" : "addca90a6a290b9642dbaccffdf01e8c22aa348940b2e96754169ba08c19e5db" 11 }"
需配合微信支付工具库 WXPayUtility 使用,请参考Java
1package com.java.demo; 2 3import com.java.utils.WXPayBrandUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/brand/4015826861 4 5import com.google.gson.annotations.SerializedName; 6import com.google.gson.annotations.Expose; 7import okhttp3.MediaType; 8import okhttp3.OkHttpClient; 9import okhttp3.Request; 10import okhttp3.RequestBody; 11import okhttp3.Response; 12 13import java.io.IOException; 14import okhttp3.MultipartBody; 15import java.io.UncheckedIOException; 16import java.security.PrivateKey; 17import java.security.PublicKey; 18import java.util.ArrayList; 19import java.util.HashMap; 20import java.util.List; 21import java.util.Map; 22 23/** 24 * 上传文件分块 25 */ 26public class UploadPart { 27 private static String HOST = "https://api.mch.weixin.qq.com"; 28 private static String METHOD = "PUT"; 29 private static String PATH = "/brand/marketing/user-package/uploads/{upload_id}/{part_number}"; 30 31 public static void main(String[] args) { 32 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/brand/4015415289 33 UploadPart client = new UploadPart( 34 "xxxxxxxx", // 品牌ID,是由微信支付系统生成并分配给每个品牌方的唯一标识符,品牌ID获取方式参考 https://pay.weixin.qq.com/doc/brand/4015415289 35 "1DDE55AD98Exxxxxxxxxx", // 品牌API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/brand/4015407570 36 "/path/to/apiclient_key.pem", // 品牌API证书私钥文件路径,本地文件路径 37 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/brand/4015453439 38 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 39 ); 40 41 UploadPartRequest request = new UploadPartRequest(); 42 request.uploadId = "17587186700323d11ac17d8d3d4c8c4e5a250c3e804977e89af6a05ffa93751d0ec0179ca8"; 43 request.partNumber = 1L; 44 request.file = "file_bytes".getBytes(); 45 request.meta = new FileMeta(); 46request.meta.filename = "1_active_user.csv"; 47request.meta.digest = "addca90a6a290b9642dbaccffdf01e8c22aa348940b2e96754169ba08c19e5db"; 48 try { 49 UploadPartResponse response = client.run(request); 50 // TODO: 请求成功,继续业务逻辑 51 System.out.println(response); 52 } catch (WXPayBrandUtility.ApiException e) { 53 // TODO: 请求失败,根据状态码执行不同的逻辑 54 e.printStackTrace(); 55 } 56 } 57 58 public UploadPartResponse run(UploadPartRequest request) { 59 String uri = PATH; 60 // 构建multipart/form-data请求体 61 String metaJson = WXPayBrandUtility.toJson(request.meta); 62 RequestBody metaPart = RequestBody.create(MediaType.parse("application/json"), metaJson); 63 // todo: you_file_name替换为meta里面的文件名字段 64 String contentType = WXPayBrandUtility.getContentTypeByFileName("your_file_name"); 65 RequestBody filePart = RequestBody.create(MediaType.parse(contentType), request.file); 66 MultipartBody requestBody = new MultipartBody.Builder() 67 .setType(MultipartBody.FORM) 68 .addFormDataPart("meta", null, metaPart) 69 .addFormDataPart("file", "your_file_name", filePart) 70 .build(); 71 72 Request.Builder reqBuilder = new Request.Builder().url(HOST + uri); 73 reqBuilder.addHeader("Accept", "application/json"); 74 reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); 75 reqBuilder.addHeader("Authorization", WXPayBrandUtility.buildAuthorization(brand_id, certificateSerialNo, privateKey, METHOD, uri, metaJson)); 76 reqBuilder.method(METHOD, requestBody); 77 Request httpRequest = reqBuilder.build(); 78 79 // 发送HTTP请求 80 OkHttpClient client = new OkHttpClient.Builder().build(); 81 try (Response httpResponse = client.newCall(httpRequest).execute()) { 82 String respBody = WXPayBrandUtility.extractBody(httpResponse); 83 if (httpResponse.code() >= 200 && httpResponse.code() < 300) { 84 // 2XX 成功,验证应答签名 85 WXPayBrandUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey, 86 httpResponse.headers(), respBody); 87 88 // 从HTTP应答报文构建返回数据 89 return WXPayBrandUtility.fromJson(respBody, UploadPartResponse.class); 90 } else { 91 throw new WXPayBrandUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); 92 } 93 } catch (IOException e) { 94 throw new UncheckedIOException("Sending request to " + uri + " failed.", e); 95 } 96 } 97 98 private final String brand_id; 99 private final String certificateSerialNo; 100 private final PrivateKey privateKey; 101 private final String wechatPayPublicKeyId; 102 private final PublicKey wechatPayPublicKey; 103 104 public UploadPart(String brand_id, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { 105 this.brand_id = brand_id; 106 this.certificateSerialNo = certificateSerialNo; 107 this.privateKey = WXPayBrandUtility.loadPrivateKeyFromPath(privateKeyFilePath); 108 this.wechatPayPublicKeyId = wechatPayPublicKeyId; 109 this.wechatPayPublicKey = WXPayBrandUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 110 } 111 112 public static class UploadPartRequest { 113 @SerializedName("upload_id") 114 @Expose(serialize = false) 115 public String uploadId; 116 117 @SerializedName("part_number") 118 @Expose(serialize = false) 119 public Long partNumber; 120 121 @SerializedName("file") 122 public byte[] file; 123 124 @SerializedName("meta") 125 public FileMeta meta; 126 } 127 128 public static class UploadPartResponse { 129 @SerializedName("part_number") 130 public Long partNumber; 131 } 132 133 public static class FileMeta { 134 @SerializedName("filename") 135 public String filename; 136 137 @SerializedName("digest") 138 public String digest; 139 } 140 141} 142
需配合微信支付工具库 wxpay_utility 使用,请参考Go
1package main 2 3import ( 4 "bytes" 5 "demo/wxpay_brand_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/brand/4015826866 6 "encoding/json" 7 "fmt" 8 "mime" 9 "mime/multipart" 10 "net/http" 11 "net/textproto" 12 "net/url" 13 "path/filepath" 14 "strings" 15) 16 17func main() { 18 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/brand/4015415289 19 config, err := wxpay_brand_utility.CreateBrandConfig( 20 "xxxxxxxx", // 品牌ID,是由微信支付系统生成并分配给每个品牌方的唯一标识符,品牌ID获取方式参考 https://pay.weixin.qq.com/doc/brand/4015415289 21 "1DDE55AD98Exxxxxxxxxx", // 品牌API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/brand/4015407570 22 "/path/to/apiclient_key.pem", // 品牌API证书私钥文件路径,本地文件路径 23 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/brand/4015453439 24 "/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径 25 ) 26 if err != nil { 27 fmt.Println(err) 28 return 29 } 30 31 request := &UploadPartRequest{ 32 UploadId: wxpay_brand_utility.String("17587186700323d11ac17d8d3d4c8c4e5a250c3e804977e89af6a05ffa93751d0ec0179ca8"), 33 PartNumber: wxpay_brand_utility.Int64(1), 34 File: wxpay_brand_utility.Bytes([]byte("file data")), 35 Meta: &FileMeta{ 36 Filename: wxpay_brand_utility.String("1_active_user.csv"), 37 Digest: wxpay_brand_utility.String("addca90a6a290b9642dbaccffdf01e8c22aa348940b2e96754169ba08c19e5db"), 38 }, 39 } 40 41 response, err := UploadPart(config, request) 42 if err != nil { 43 fmt.Printf("请求失败: %+v\n", err) 44 // TODO: 请求失败,根据状态码执行不同的处理 45 return 46 } 47 48 // TODO: 请求成功,继续业务逻辑 49 fmt.Printf("请求成功: %+v\n", response) 50} 51 52func UploadPart(config *wxpay_brand_utility.BrandConfig, request *UploadPartRequest) (response *UploadPartResponse, err error) { 53 const ( 54 host = "https://api.mch.weixin.qq.com" 55 method = "PUT" 56 path = "/brand/marketing/user-package/uploads/{upload_id}/{part_number}" 57 ) 58 59 reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path)) 60 if err != nil { 61 return nil, err 62 } 63 reqUrl.Path = strings.Replace(reqUrl.Path, "{upload_id}", url.PathEscape(*request.UploadId), -1) 64 reqUrl.Path = strings.Replace(reqUrl.Path, "{part_number}", url.PathEscape(fmt.Sprintf("%v", *request.PartNumber)), -1) 65 // 构造 multipart/form-data 请求体 66 body := &bytes.Buffer{} 67 writer := multipart.NewWriter(body) 68 69 // todo: your_file_name替换为meta里面的文件名字段 70 fileHeader := make(textproto.MIMEHeader) 71 fileHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, "your_file_name")) 72 ext := filepath.Ext("your_file_name") 73 contentType := mime.TypeByExtension(ext) 74 if contentType == "" { 75 contentType = "application/octet-stream" 76 } 77 fileHeader.Set("Content-Type", contentType) 78 fileWriter, err := writer.CreatePart(fileHeader) 79 if err != nil { 80 return nil, err 81 } 82 _, err = fileWriter.Write(*request.File) 83 if err != nil { 84 return nil, err 85 } 86 87 // meta 字段 88 metaJSON, err := json.Marshal(request.Meta) 89 if err != nil { 90 return nil, err 91 } 92 metaWriter, err := writer.CreateFormField("meta") 93 if err != nil { 94 return nil, err 95 } 96 _, err = metaWriter.Write(metaJSON) 97 if err != nil { 98 return nil, err 99 } 100 101 err = writer.Close() 102 if err != nil { 103 return nil, err 104 } 105 httpRequest, err := http.NewRequest(method, reqUrl.String(), body) 106 if err != nil { 107 return nil, err 108 } 109 httpRequest.Header.Set("Accept", "application/json") 110 httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId()) 111 httpRequest.Header.Set("Content-Type", writer.FormDataContentType()) 112 // 签名时使用 meta JSON 113 metaJson, err := json.Marshal(request.Meta) 114 if err != nil { 115 return nil, err 116 } 117 authorization, err := wxpay_brand_utility.BuildAuthorization(config.BrandId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), metaJson) 118 if err != nil { 119 return nil, err 120 } 121 httpRequest.Header.Set("Authorization", authorization) 122 123 client := &http.Client{} 124 httpResponse, err := client.Do(httpRequest) 125 if err != nil { 126 return nil, err 127 } 128 respBody, err := wxpay_brand_utility.ExtractResponseBody(httpResponse) 129 if err != nil { 130 return nil, err 131 } 132 if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 { 133 // 2XX 成功,验证应答签名 134 err = wxpay_brand_utility.ValidateResponse( 135 config.WechatPayPublicKeyId(), 136 config.WechatPayPublicKey(), 137 &httpResponse.Header, 138 respBody, 139 ) 140 if err != nil { 141 return nil, err 142 } 143 response := &UploadPartResponse{} 144 if err := json.Unmarshal(respBody, response); err != nil { 145 return nil, err 146 } 147 148 return response, nil 149 } else { 150 return nil, wxpay_brand_utility.NewApiException( 151 httpResponse.StatusCode, 152 httpResponse.Header, 153 respBody, 154 ) 155 } 156} 157 158type UploadPartRequest struct { 159 UploadId *string `json:"upload_id,omitempty"` 160 PartNumber *int64 `json:"part_number,omitempty"` 161 File *[]byte `json:"file,omitempty"` 162 Meta *FileMeta `json:"meta,omitempty"` 163} 164 165func (o *UploadPartRequest) MarshalJSON() ([]byte, error) { 166 type Alias UploadPartRequest 167 a := &struct { 168 UploadId *string `json:"upload_id,omitempty"` 169 PartNumber *int64 `json:"part_number,omitempty"` 170 *Alias 171 }{ 172 // 序列化时移除非 Body 字段 173 UploadId: nil, 174 PartNumber: nil, 175 Alias: (*Alias)(o), 176 } 177 return json.Marshal(a) 178} 179 180type UploadPartResponse struct { 181 PartNumber *int64 `json:"part_number,omitempty"` 182} 183 184type FileMeta struct { 185 Filename *string `json:"filename,omitempty"` 186 Digest *string `json:"digest,omitempty"` 187} 188
应答参数
200 OK
part_number 必填 integer
【分块序号】 人群包分块文件的分块序号
应答示例
200 OK
1{ 2 "part_number" : 1 3} 4
错误码
以下是本接口返回的错误码列表。详细错误码规则,请参考微信支付接口规则-错误码和错误提示
状态码 | 错误码 | 描述 | 解决方案 |
|---|---|---|---|
400 | PARAM_ERROR | 参数错误 | 请根据错误提示正确传入参数 |
400 | INVALID_REQUEST | HTTP 请求不符合微信支付 APIv3 接口规则 | 请参阅 接口规则 |
401 | SIGN_ERROR | 验证不通过 | 请参阅 签名常见问题 |
500 | SYSTEM_ERROR | 系统异常,请稍后重试 | 请稍后重试 |
400 | PARAM_ERROR | 参数过短 | 请参考文档中对每个字段的要求以及组合要求,确认请求参数是否满足 |
400 | PARAM_ERROR | 参数错误 | 请参考文档中对每个字段的要求以及组合要求,确认请求参数是否满足 |
400 | INVALID_REQUEST | Http头缺少Accept或User-Agent | Http头缺少Accept或User-Agent,请根据API文档在请求头中补充该参数 |
400 | PARAM_ERROR | 请求中含有未在API文档中定义的参数 | 请求中含有未在API文档中定义的参数,请检查传递的参数列表是否有编写错误的字段名 |
