Skip to content

Commit 2a9cff0

Browse files
authored
🎨 #3864 【公众号】通用上传方法增加“额外表单字段”支持,解决上传永久视频素材时需同时提交文件与description表单字段的问题
1 parent 3cd05c8 commit 2a9cff0

File tree

7 files changed

+376
-12
lines changed

7 files changed

+376
-12
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# CommonUploadParam 额外表单字段功能使用示例
2+
3+
## 背景
4+
5+
微信公众号在上传永久视频素材时,需要在POST请求中同时提交文件和一个名为`description`的表单字段,该字段包含视频的描述信息(JSON格式)。
6+
7+
根据微信公众号文档:
8+
> 在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON,格式如下:
9+
> ```json
10+
> {
11+
> "title": "VIDEO_TITLE",
12+
> "introduction": "INTRODUCTION"
13+
> }
14+
> ```
15+
16+
## 解决方案
17+
18+
`CommonUploadParam` 类已经扩展支持额外的表单字段,可以在上传文件的同时提交其他表单数据。
19+
20+
## 使用示例
21+
22+
### 1. 基本用法 - 上传永久视频素材
23+
24+
```java
25+
import me.chanjar.weixin.common.bean.CommonUploadParam;
26+
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
27+
import me.chanjar.weixin.mp.api.WxMpService;
28+
29+
import java.io.File;
30+
import java.util.HashMap;
31+
import java.util.Map;
32+
33+
public class VideoMaterialUploadExample {
34+
35+
public void uploadVideoMaterial(WxMpService wxMpService) throws Exception {
36+
// 准备视频文件
37+
File videoFile = new File("/path/to/video.mp4");
38+
39+
// 创建上传参数
40+
CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", videoFile);
41+
42+
// 准备视频描述信息(JSON格式)
43+
Map<String, String> description = new HashMap<>();
44+
description.put("title", "我的视频标题");
45+
description.put("introduction", "这是一个精彩的视频介绍");
46+
String descriptionJson = WxGsonBuilder.create().toJson(description);
47+
48+
// 添加description表单字段
49+
uploadParam.addFormField("description", descriptionJson);
50+
51+
// 调用微信API上传
52+
String url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video";
53+
String response = wxMpService.upload(url, uploadParam);
54+
55+
System.out.println("上传成功:" + response);
56+
}
57+
}
58+
```
59+
60+
### 2. 链式调用风格
61+
62+
```java
63+
import me.chanjar.weixin.common.bean.CommonUploadParam;
64+
import com.google.gson.JsonObject;
65+
66+
public class ChainStyleExample {
67+
68+
public void uploadWithChainStyle(WxMpService wxMpService) throws Exception {
69+
File videoFile = new File("/path/to/video.mp4");
70+
71+
// 准备描述信息
72+
JsonObject description = new JsonObject();
73+
description.addProperty("title", "视频标题");
74+
description.addProperty("introduction", "视频介绍");
75+
76+
// 使用链式调用
77+
String response = wxMpService.upload(
78+
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video",
79+
CommonUploadParam.fromFile("media", videoFile)
80+
.addFormField("description", description.toString())
81+
);
82+
83+
System.out.println("上传成功:" + response);
84+
}
85+
}
86+
```
87+
88+
### 3. 多个额外表单字段
89+
90+
```java
91+
import me.chanjar.weixin.common.bean.CommonUploadParam;
92+
93+
public class MultipleFormFieldsExample {
94+
95+
public void uploadWithMultipleFields(WxMpService wxMpService) throws Exception {
96+
File file = new File("/path/to/file.jpg");
97+
98+
// 可以添加多个表单字段
99+
CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", file)
100+
.addFormField("field1", "value1")
101+
.addFormField("field2", "value2")
102+
.addFormField("field3", "value3");
103+
104+
String response = wxMpService.upload("https://api.weixin.qq.com/some/upload/url", uploadParam);
105+
106+
System.out.println("上传成功:" + response);
107+
}
108+
}
109+
```
110+
111+
### 4. 从字节数组上传并添加表单字段
112+
113+
```java
114+
import me.chanjar.weixin.common.bean.CommonUploadParam;
115+
116+
public class ByteArrayUploadExample {
117+
118+
public void uploadFromBytes(WxMpService wxMpService) throws Exception {
119+
// 从字节数组创建上传参数
120+
byte[] fileBytes = getFileBytes();
121+
122+
CommonUploadParam uploadParam = CommonUploadParam
123+
.fromBytes("media", "video.mp4", fileBytes)
124+
.addFormField("description", "{\"title\":\"标题\",\"introduction\":\"介绍\"}");
125+
126+
String response = wxMpService.upload(
127+
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video",
128+
uploadParam
129+
);
130+
131+
System.out.println("上传成功:" + response);
132+
}
133+
134+
private byte[] getFileBytes() {
135+
// 获取文件字节数组的逻辑
136+
return new byte[0];
137+
}
138+
}
139+
```
140+
141+
## API 说明
142+
143+
### CommonUploadParam 类
144+
145+
#### 构造方法
146+
- `fromFile(String name, File file)` - 从文件创建上传参数
147+
- `fromBytes(String name, String fileName, byte[] bytes)` - 从字节数组创建上传参数
148+
149+
#### 方法
150+
- `addFormField(String fieldName, String fieldValue)` - 添加额外的表单字段,返回当前对象支持链式调用
151+
- `getFormFields()` - 获取所有额外的表单字段(Map类型)
152+
- `setFormFields(Map<String, String> formFields)` - 设置额外的表单字段
153+
154+
#### 属性
155+
- `name` - 文件对应的接口参数名称(如:media)
156+
- `data` - 上传数据(CommonUploadData对象)
157+
- `formFields` - 额外的表单字段(可选,Map<String, String>类型)
158+
159+
## 注意事项
160+
161+
1. **表单字段是可选的**:如果不需要额外的表单字段,可以不调用`addFormField`方法
162+
2. **JSON格式**:对于需要JSON格式的表单字段(如description),需要先将对象转换为JSON字符串
163+
3. **编码**:表单字段值会使用UTF-8编码
164+
4. **所有HTTP客户端支持**:该功能在所有HTTP客户端实现中都得到支持(OkHttp、Apache HttpClient、HttpComponents、JoddHttp)
165+
166+
## 兼容性
167+
168+
- 对于通过 `fromFile``fromBytes` 等工厂方法创建 `CommonUploadParam` 的代码,本功能在行为层面是向后兼容的,现有代码无需修改即可继续工作。
169+
- 如果之前直接使用构造函数(例如 `new CommonUploadParam(name, data)`)创建对象,由于新增了 `formFields` 字段,构造函数签名可能发生变化,升级后需要改为使用上述工厂方法或根据新构造函数签名调整代码。

weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/CommonUploadParam.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.io.ByteArrayInputStream;
1111
import java.io.File;
1212
import java.io.Serializable;
13+
import java.util.HashMap;
14+
import java.util.Map;
1315

1416
/**
1517
* 通用文件上传参数
@@ -34,6 +36,27 @@ public class CommonUploadParam implements Serializable {
3436
@NotNull
3537
private CommonUploadData data;
3638

39+
/**
40+
* 额外的表单字段,用于在上传文件的同时提交其他表单数据
41+
* 例如:上传视频素材时需要提交description字段(JSON格式的视频描述信息)
42+
*/
43+
@Nullable
44+
private Map<String, String> formFields;
45+
46+
/**
47+
* 为保持向后兼容保留的 2 参数构造函数。
48+
* <p>
49+
* 仅设置文件参数名和上传数据,额外表单字段将为 {@code null}。
50+
*
51+
* @param name 参数名,如:media
52+
* @param data 上传数据
53+
* @deprecated 请使用包含 formFields 参数的构造函数或静态工厂方法 {@link #fromFile(String, File)}、{@link #fromBytes(String, String, byte[])}
54+
*/
55+
@Deprecated
56+
public CommonUploadParam(@NotNull String name, @NotNull CommonUploadData data) {
57+
this(name, data, null);
58+
}
59+
3760
/**
3861
* 从文件构造
3962
*
@@ -43,7 +66,7 @@ public class CommonUploadParam implements Serializable {
4366
*/
4467
@SneakyThrows
4568
public static CommonUploadParam fromFile(String name, File file) {
46-
return new CommonUploadParam(name, CommonUploadData.fromFile(file));
69+
return new CommonUploadParam(name, CommonUploadData.fromFile(file), null);
4770
}
4871

4972
/**
@@ -55,11 +78,32 @@ public static CommonUploadParam fromFile(String name, File file) {
5578
*/
5679
@SneakyThrows
5780
public static CommonUploadParam fromBytes(String name, @Nullable String fileName, byte[] bytes) {
58-
return new CommonUploadParam(name, new CommonUploadData(fileName, new ByteArrayInputStream(bytes), bytes.length));
81+
return new CommonUploadParam(name, new CommonUploadData(fileName, new ByteArrayInputStream(bytes), bytes.length), null);
82+
}
83+
84+
/**
85+
* 添加额外的表单字段
86+
*
87+
* @param fieldName 表单字段名
88+
* @param fieldValue 表单字段值
89+
* @return 当前对象,支持链式调用
90+
*/
91+
public CommonUploadParam addFormField(String fieldName, String fieldValue) {
92+
if (fieldName == null || fieldName.trim().isEmpty()) {
93+
throw new IllegalArgumentException("表单字段名不能为空");
94+
}
95+
if (fieldValue == null) {
96+
throw new IllegalArgumentException("表单字段值不能为null");
97+
}
98+
if (this.formFields == null) {
99+
this.formFields = new HashMap<>();
100+
}
101+
this.formFields.put(fieldName, fieldValue);
102+
return this;
59103
}
60104

61105
@Override
62106
public String toString() {
63-
return String.format("{name:%s, data:%s}", name, data);
107+
return String.format("{name:%s, data:%s, formFields:%s}", name, data, formFields);
64108
}
65109
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorApacheImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,19 @@ public String execute(String uri, CommonUploadParam param, WxType wxType) throws
4444
if (param != null) {
4545
CommonUploadData data = param.getData();
4646
InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
47-
HttpEntity entity = MultipartEntityBuilder
47+
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder
4848
.create()
4949
.addPart(param.getName(), part)
50-
.setMode(HttpMultipartMode.RFC6532)
51-
.build();
50+
.setMode(HttpMultipartMode.RFC6532);
51+
52+
// 添加额外的表单字段
53+
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
54+
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
55+
entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8"));
56+
}
57+
}
58+
59+
HttpEntity entity = entityBuilder.build();
5260
httpPost.setEntity(entity);
5361
}
5462
String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);

weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorHttpComponentsImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ public String execute(String uri, CommonUploadParam param, WxType wxType) throws
4141
if (param != null) {
4242
CommonUploadData data = param.getData();
4343
InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
44-
HttpEntity entity = MultipartEntityBuilder
44+
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder
4545
.create()
4646
.addPart(param.getName(), part)
47-
.setMode(HttpMultipartMode.EXTENDED)
48-
.build();
47+
.setMode(HttpMultipartMode.EXTENDED);
48+
49+
// 添加额外的表单字段
50+
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
51+
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
52+
entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8"));
53+
}
54+
}
55+
56+
HttpEntity entity = entityBuilder.build();
4957
httpPost.setEntity(entity);
5058
}
5159
String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);

weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorJoddHttpImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ public String execute(String uri, CommonUploadParam param, WxType wxType) throws
3939
}
4040
request.withConnectionProvider(requestHttp.getRequestHttpClient());
4141
request.form(param.getName(), new CommonUploadParamToUploadableAdapter(param.getData()));
42+
43+
// 添加额外的表单字段
44+
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
45+
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
46+
request.form(entry.getKey(), entry.getValue());
47+
}
48+
}
49+
4250
HttpResponse response = request.send();
4351
response.charset(StandardCharsets.UTF_8.name());
4452
String responseContent = response.bodyText();

weixin-java-common/src/main/java/me/chanjar/weixin/common/executor/CommonUploadRequestExecutorOkHttpImpl.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,18 @@ public CommonUploadRequestExecutorOkHttpImpl(RequestHttp<OkHttpClient, OkHttpPro
3131
@Override
3232
public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException {
3333
RequestBody requestBody = new CommonUpdateDataToRequestBodyAdapter(param.getData());
34-
RequestBody body = new MultipartBody.Builder()
34+
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
3535
.setType(MediaType.get("multipart/form-data"))
36-
.addFormDataPart(param.getName(), param.getData().getFileName(), requestBody)
37-
.build();
36+
.addFormDataPart(param.getName(), param.getData().getFileName(), requestBody);
37+
38+
// 添加额外的表单字段
39+
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
40+
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
41+
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
42+
}
43+
}
44+
45+
RequestBody body = bodyBuilder.build();
3846
Request request = new Request.Builder().url(uri).post(body).build();
3947

4048
try (Response response = requestHttp.getRequestHttpClient().newCall(request).execute()) {

0 commit comments

Comments
 (0)