🎨 #3864 【公众号】通用上传方法增加“额外表单字段”支持,解决上传永久视频素材时需同时提交文件与description表单字段的问题
This commit is contained in:
169
docs/CommonUploadParam-FormFields-Usage.md
Normal file
169
docs/CommonUploadParam-FormFields-Usage.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# CommonUploadParam 额外表单字段功能使用示例
|
||||
|
||||
## 背景
|
||||
|
||||
微信公众号在上传永久视频素材时,需要在POST请求中同时提交文件和一个名为`description`的表单字段,该字段包含视频的描述信息(JSON格式)。
|
||||
|
||||
根据微信公众号文档:
|
||||
> 在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON,格式如下:
|
||||
> ```json
|
||||
> {
|
||||
> "title": "VIDEO_TITLE",
|
||||
> "introduction": "INTRODUCTION"
|
||||
> }
|
||||
> ```
|
||||
|
||||
## 解决方案
|
||||
|
||||
`CommonUploadParam` 类已经扩展支持额外的表单字段,可以在上传文件的同时提交其他表单数据。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 基本用法 - 上传永久视频素材
|
||||
|
||||
```java
|
||||
import me.chanjar.weixin.common.bean.CommonUploadParam;
|
||||
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class VideoMaterialUploadExample {
|
||||
|
||||
public void uploadVideoMaterial(WxMpService wxMpService) throws Exception {
|
||||
// 准备视频文件
|
||||
File videoFile = new File("/path/to/video.mp4");
|
||||
|
||||
// 创建上传参数
|
||||
CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", videoFile);
|
||||
|
||||
// 准备视频描述信息(JSON格式)
|
||||
Map<String, String> description = new HashMap<>();
|
||||
description.put("title", "我的视频标题");
|
||||
description.put("introduction", "这是一个精彩的视频介绍");
|
||||
String descriptionJson = WxGsonBuilder.create().toJson(description);
|
||||
|
||||
// 添加description表单字段
|
||||
uploadParam.addFormField("description", descriptionJson);
|
||||
|
||||
// 调用微信API上传
|
||||
String url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video";
|
||||
String response = wxMpService.upload(url, uploadParam);
|
||||
|
||||
System.out.println("上传成功:" + response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 链式调用风格
|
||||
|
||||
```java
|
||||
import me.chanjar.weixin.common.bean.CommonUploadParam;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class ChainStyleExample {
|
||||
|
||||
public void uploadWithChainStyle(WxMpService wxMpService) throws Exception {
|
||||
File videoFile = new File("/path/to/video.mp4");
|
||||
|
||||
// 准备描述信息
|
||||
JsonObject description = new JsonObject();
|
||||
description.addProperty("title", "视频标题");
|
||||
description.addProperty("introduction", "视频介绍");
|
||||
|
||||
// 使用链式调用
|
||||
String response = wxMpService.upload(
|
||||
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video",
|
||||
CommonUploadParam.fromFile("media", videoFile)
|
||||
.addFormField("description", description.toString())
|
||||
);
|
||||
|
||||
System.out.println("上传成功:" + response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 多个额外表单字段
|
||||
|
||||
```java
|
||||
import me.chanjar.weixin.common.bean.CommonUploadParam;
|
||||
|
||||
public class MultipleFormFieldsExample {
|
||||
|
||||
public void uploadWithMultipleFields(WxMpService wxMpService) throws Exception {
|
||||
File file = new File("/path/to/file.jpg");
|
||||
|
||||
// 可以添加多个表单字段
|
||||
CommonUploadParam uploadParam = CommonUploadParam.fromFile("media", file)
|
||||
.addFormField("field1", "value1")
|
||||
.addFormField("field2", "value2")
|
||||
.addFormField("field3", "value3");
|
||||
|
||||
String response = wxMpService.upload("https://api.weixin.qq.com/some/upload/url", uploadParam);
|
||||
|
||||
System.out.println("上传成功:" + response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 从字节数组上传并添加表单字段
|
||||
|
||||
```java
|
||||
import me.chanjar.weixin.common.bean.CommonUploadParam;
|
||||
|
||||
public class ByteArrayUploadExample {
|
||||
|
||||
public void uploadFromBytes(WxMpService wxMpService) throws Exception {
|
||||
// 从字节数组创建上传参数
|
||||
byte[] fileBytes = getFileBytes();
|
||||
|
||||
CommonUploadParam uploadParam = CommonUploadParam
|
||||
.fromBytes("media", "video.mp4", fileBytes)
|
||||
.addFormField("description", "{\"title\":\"标题\",\"introduction\":\"介绍\"}");
|
||||
|
||||
String response = wxMpService.upload(
|
||||
"https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=video",
|
||||
uploadParam
|
||||
);
|
||||
|
||||
System.out.println("上传成功:" + response);
|
||||
}
|
||||
|
||||
private byte[] getFileBytes() {
|
||||
// 获取文件字节数组的逻辑
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API 说明
|
||||
|
||||
### CommonUploadParam 类
|
||||
|
||||
#### 构造方法
|
||||
- `fromFile(String name, File file)` - 从文件创建上传参数
|
||||
- `fromBytes(String name, String fileName, byte[] bytes)` - 从字节数组创建上传参数
|
||||
|
||||
#### 方法
|
||||
- `addFormField(String fieldName, String fieldValue)` - 添加额外的表单字段,返回当前对象支持链式调用
|
||||
- `getFormFields()` - 获取所有额外的表单字段(Map类型)
|
||||
- `setFormFields(Map<String, String> formFields)` - 设置额外的表单字段
|
||||
|
||||
#### 属性
|
||||
- `name` - 文件对应的接口参数名称(如:media)
|
||||
- `data` - 上传数据(CommonUploadData对象)
|
||||
- `formFields` - 额外的表单字段(可选,Map<String, String>类型)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **表单字段是可选的**:如果不需要额外的表单字段,可以不调用`addFormField`方法
|
||||
2. **JSON格式**:对于需要JSON格式的表单字段(如description),需要先将对象转换为JSON字符串
|
||||
3. **编码**:表单字段值会使用UTF-8编码
|
||||
4. **所有HTTP客户端支持**:该功能在所有HTTP客户端实现中都得到支持(OkHttp、Apache HttpClient、HttpComponents、JoddHttp)
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 对于通过 `fromFile`、`fromBytes` 等工厂方法创建 `CommonUploadParam` 的代码,本功能在行为层面是向后兼容的,现有代码无需修改即可继续工作。
|
||||
- 如果之前直接使用构造函数(例如 `new CommonUploadParam(name, data)`)创建对象,由于新增了 `formFields` 字段,构造函数签名可能发生变化,升级后需要改为使用上述工厂方法或根据新构造函数签名调整代码。
|
||||
@@ -10,6 +10,8 @@ import org.springframework.lang.Nullable;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用文件上传参数
|
||||
@@ -34,6 +36,27 @@ public class CommonUploadParam implements Serializable {
|
||||
@NotNull
|
||||
private CommonUploadData data;
|
||||
|
||||
/**
|
||||
* 额外的表单字段,用于在上传文件的同时提交其他表单数据
|
||||
* 例如:上传视频素材时需要提交description字段(JSON格式的视频描述信息)
|
||||
*/
|
||||
@Nullable
|
||||
private Map<String, String> formFields;
|
||||
|
||||
/**
|
||||
* 为保持向后兼容保留的 2 参数构造函数。
|
||||
* <p>
|
||||
* 仅设置文件参数名和上传数据,额外表单字段将为 {@code null}。
|
||||
*
|
||||
* @param name 参数名,如:media
|
||||
* @param data 上传数据
|
||||
* @deprecated 请使用包含 formFields 参数的构造函数或静态工厂方法 {@link #fromFile(String, File)}、{@link #fromBytes(String, String, byte[])}
|
||||
*/
|
||||
@Deprecated
|
||||
public CommonUploadParam(@NotNull String name, @NotNull CommonUploadData data) {
|
||||
this(name, data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件构造
|
||||
*
|
||||
@@ -43,7 +66,7 @@ public class CommonUploadParam implements Serializable {
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static CommonUploadParam fromFile(String name, File file) {
|
||||
return new CommonUploadParam(name, CommonUploadData.fromFile(file));
|
||||
return new CommonUploadParam(name, CommonUploadData.fromFile(file), null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,11 +78,32 @@ public class CommonUploadParam implements Serializable {
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static CommonUploadParam fromBytes(String name, @Nullable String fileName, byte[] bytes) {
|
||||
return new CommonUploadParam(name, new CommonUploadData(fileName, new ByteArrayInputStream(bytes), bytes.length));
|
||||
return new CommonUploadParam(name, new CommonUploadData(fileName, new ByteArrayInputStream(bytes), bytes.length), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加额外的表单字段
|
||||
*
|
||||
* @param fieldName 表单字段名
|
||||
* @param fieldValue 表单字段值
|
||||
* @return 当前对象,支持链式调用
|
||||
*/
|
||||
public CommonUploadParam addFormField(String fieldName, String fieldValue) {
|
||||
if (fieldName == null || fieldName.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("表单字段名不能为空");
|
||||
}
|
||||
if (fieldValue == null) {
|
||||
throw new IllegalArgumentException("表单字段值不能为null");
|
||||
}
|
||||
if (this.formFields == null) {
|
||||
this.formFields = new HashMap<>();
|
||||
}
|
||||
this.formFields.put(fieldName, fieldValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("{name:%s, data:%s}", name, data);
|
||||
return String.format("{name:%s, data:%s, formFields:%s}", name, data, formFields);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,19 @@ public class CommonUploadRequestExecutorApacheImpl extends CommonUploadRequestEx
|
||||
if (param != null) {
|
||||
CommonUploadData data = param.getData();
|
||||
InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
|
||||
HttpEntity entity = MultipartEntityBuilder
|
||||
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder
|
||||
.create()
|
||||
.addPart(param.getName(), part)
|
||||
.setMode(HttpMultipartMode.RFC6532)
|
||||
.build();
|
||||
.setMode(HttpMultipartMode.RFC6532);
|
||||
|
||||
// 添加额外的表单字段
|
||||
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
|
||||
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
|
||||
entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
HttpEntity entity = entityBuilder.build();
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
|
||||
|
||||
@@ -41,11 +41,19 @@ public class CommonUploadRequestExecutorHttpComponentsImpl extends CommonUploadR
|
||||
if (param != null) {
|
||||
CommonUploadData data = param.getData();
|
||||
InnerStreamBody part = new InnerStreamBody(data.getInputStream(), ContentType.DEFAULT_BINARY, data.getFileName(), data.getLength());
|
||||
HttpEntity entity = MultipartEntityBuilder
|
||||
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder
|
||||
.create()
|
||||
.addPart(param.getName(), part)
|
||||
.setMode(HttpMultipartMode.EXTENDED)
|
||||
.build();
|
||||
.setMode(HttpMultipartMode.EXTENDED);
|
||||
|
||||
// 添加额外的表单字段
|
||||
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
|
||||
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
|
||||
entityBuilder.addTextBody(entry.getKey(), entry.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
HttpEntity entity = entityBuilder.build();
|
||||
httpPost.setEntity(entity);
|
||||
}
|
||||
String responseContent = requestHttp.getRequestHttpClient().execute(httpPost, Utf8ResponseHandler.INSTANCE);
|
||||
|
||||
@@ -39,6 +39,14 @@ public class CommonUploadRequestExecutorJoddHttpImpl extends CommonUploadRequest
|
||||
}
|
||||
request.withConnectionProvider(requestHttp.getRequestHttpClient());
|
||||
request.form(param.getName(), new CommonUploadParamToUploadableAdapter(param.getData()));
|
||||
|
||||
// 添加额外的表单字段
|
||||
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
|
||||
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
|
||||
request.form(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse response = request.send();
|
||||
response.charset(StandardCharsets.UTF_8.name());
|
||||
String responseContent = response.bodyText();
|
||||
|
||||
@@ -31,10 +31,18 @@ public class CommonUploadRequestExecutorOkHttpImpl extends CommonUploadRequestEx
|
||||
@Override
|
||||
public String execute(String uri, CommonUploadParam param, WxType wxType) throws WxErrorException, IOException {
|
||||
RequestBody requestBody = new CommonUpdateDataToRequestBodyAdapter(param.getData());
|
||||
RequestBody body = new MultipartBody.Builder()
|
||||
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
|
||||
.setType(MediaType.get("multipart/form-data"))
|
||||
.addFormDataPart(param.getName(), param.getData().getFileName(), requestBody)
|
||||
.build();
|
||||
.addFormDataPart(param.getName(), param.getData().getFileName(), requestBody);
|
||||
|
||||
// 添加额外的表单字段
|
||||
if (param.getFormFields() != null && !param.getFormFields().isEmpty()) {
|
||||
for (java.util.Map.Entry<String, String> entry : param.getFormFields().entrySet()) {
|
||||
bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
RequestBody body = bodyBuilder.build();
|
||||
Request request = new Request.Builder().url(uri).post(body).build();
|
||||
|
||||
try (Response response = requestHttp.getRequestHttpClient().newCall(request).execute()) {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package me.chanjar.weixin.common.bean;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CommonUploadParam 单元测试
|
||||
*
|
||||
* @author Binary Wang
|
||||
*/
|
||||
@Test
|
||||
public class CommonUploadParamTest {
|
||||
|
||||
@Test
|
||||
public void testFromFile() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file);
|
||||
|
||||
Assert.assertNotNull(param);
|
||||
Assert.assertEquals(param.getName(), "media");
|
||||
Assert.assertNotNull(param.getData());
|
||||
Assert.assertNull(param.getFormFields());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromBytes() {
|
||||
byte[] bytes = "test content".getBytes();
|
||||
CommonUploadParam param = CommonUploadParam.fromBytes("media", "test.txt", bytes);
|
||||
|
||||
Assert.assertNotNull(param);
|
||||
Assert.assertEquals(param.getName(), "media");
|
||||
Assert.assertNotNull(param.getData());
|
||||
Assert.assertEquals(param.getData().getFileName(), "test.txt");
|
||||
Assert.assertNull(param.getFormFields());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddFormField() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file);
|
||||
|
||||
// 添加单个表单字段
|
||||
param.addFormField("title", "测试标题");
|
||||
|
||||
Assert.assertNotNull(param.getFormFields());
|
||||
Assert.assertEquals(param.getFormFields().size(), 1);
|
||||
Assert.assertEquals(param.getFormFields().get("title"), "测试标题");
|
||||
|
||||
// 添加多个表单字段
|
||||
param.addFormField("introduction", "测试介绍");
|
||||
|
||||
Assert.assertEquals(param.getFormFields().size(), 2);
|
||||
Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddFormFieldChaining() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file)
|
||||
.addFormField("title", "测试标题")
|
||||
.addFormField("introduction", "测试介绍");
|
||||
|
||||
Assert.assertNotNull(param.getFormFields());
|
||||
Assert.assertEquals(param.getFormFields().size(), 2);
|
||||
Assert.assertEquals(param.getFormFields().get("title"), "测试标题");
|
||||
Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorWithFormFields() {
|
||||
CommonUploadData data = new CommonUploadData("test.txt", null, 0);
|
||||
Map<String, String> formFields = new HashMap<>();
|
||||
formFields.put("title", "测试标题");
|
||||
formFields.put("introduction", "测试介绍");
|
||||
|
||||
CommonUploadParam param = new CommonUploadParam("media", data, formFields);
|
||||
|
||||
Assert.assertNotNull(param.getFormFields());
|
||||
Assert.assertEquals(param.getFormFields().size(), 2);
|
||||
Assert.assertEquals(param.getFormFields().get("title"), "测试标题");
|
||||
Assert.assertEquals(param.getFormFields().get("introduction"), "测试介绍");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file)
|
||||
.addFormField("title", "测试标题");
|
||||
|
||||
String str = param.toString();
|
||||
Assert.assertTrue(str.contains("name:media"));
|
||||
Assert.assertTrue(str.contains("formFields:"));
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void testAddFormFieldWithNullFieldName() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file);
|
||||
param.addFormField(null, "value");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void testAddFormFieldWithEmptyFieldName() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file);
|
||||
param.addFormField("", "value");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void testAddFormFieldWithNullFieldValue() {
|
||||
File file = new File("test.txt");
|
||||
CommonUploadParam param = CommonUploadParam.fromFile("media", file);
|
||||
param.addFormField("fieldName", null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user