1
0
mirror of synced 2026-10-09 00:12:37 +08:00

🆕 #4105 【视频号】增加微信小店商家客服接口

This commit is contained in:
Binary Wang
2026-08-22 20:56:47 +08:00
committed by GitHub
parent 5bb9deeb5d
commit 0122878b14
14 changed files with 594 additions and 1 deletions

View File

@@ -0,0 +1,45 @@
# 视频号小店商家客服 API Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为 `weixin-java-channel` 增加商家客服媒体上传和消息发送 API。
**Architecture:** 以独立的客服子服务封装两条官方 API,通过既有 `BaseWxChannelServiceImpl` 完成鉴权、JSON 请求与 multipart 上传。请求模型保持强类型,服务层测试使用可记录调用的测试替身,避免真实网络依赖。
**Tech Stack:** Java 8、Maven、TestNG、Lombok、Jackson 注解。
## Global Constraints
- Java 8 兼容,不新增依赖。
- API 路径固定为 `/channels/ec/commkf/cosupload` 和 `/channels/ec/commkf/sendmsg`。
- 使用 TestNG,所有新增测试不使用真实微信凭据。
---
### Task 1: 请求与响应模型
**Files:**
- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfCosUploadResponse.java`
- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgParam.java`
- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfSendMsgResponse.java`
- Test: `weixin-java-channel/src/test/java/me/chanjar/weixin/channel/bean/kf/WxChannelKfBeanTest.java`
- [ ] Write JSON encode/decode tests for `request_id`, `open_id`, `msg_type`, `text.content`, `cos_url` and `msg_id`.
- [ ] Run the test and verify it fails because the classes do not exist.
- [ ] Add the minimal annotated model classes and nested message content types.
- [ ] Run the test and verify it passes.
### Task 2: 服务入口与请求执行
**Files:**
- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelKfService.java`
- Create: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImpl.java`
- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java`
- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java`
- Modify: `weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java`
- Test: `weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelKfServiceImplTest.java`
- [ ] Write tests proving the service uses the documented URLs, uploads `file`, `open_id`, `msg_type`, decodes both responses, and caches `getKfService()`.
- [ ] Run the test and verify it fails because the API is absent.
- [ ] Add the minimal service API, implementation, constants and cached service entry point.
- [ ] Run the focused tests and module test suite, then inspect `git diff --check`.

View File

@@ -0,0 +1,18 @@
# 视频号小店商家客服 API 设计
## 目标
实现 Issue #3991 所列的商家客服媒体上传与消息发送 API,并使调用入口、请求模型和响应解析与现有 `weixin-java-channel` 服务保持一致。
## 设计
- 在 `WxChannelService` 暴露 `getKfService()`,由 `BaseWxChannelServiceImpl` 缓存并懒加载 `WxChannelKfServiceImpl`。
- `WxChannelKfService` 提供媒体上传(带文件名和便捷重载)以及接收强类型请求参数的消息发送方法。上传请求通过既有 `CommonUploadParam` 发送 multipart 数据。
- 消息模型用 `@JsonProperty` 显式映射微信字段,支持 text、image、video、file、product_share 和 order_share 六类内容;响应继承项目既有基础响应。
- API 常量使用官方文档确认的 `/channels/ec/commkf/cosupload` 和 `/channels/ec/commkf/sendmsg` 路径。
## 质量边界
- 保持 Java 8 兼容,不增加依赖,不变更现有公共 API。
- 使用 TestNG 覆盖请求/响应 JSON 映射、服务 URL、上传表单字段及服务入口缓存;测试不依赖真实微信凭据。
- PR 使用 `Closes #3991` 关联并关闭原始 Issue;旧 PR #4037 在新 PR 创建后以替代说明关闭。

View File

@@ -0,0 +1,41 @@
package me.chanjar.weixin.channel.api;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse;
import me.chanjar.weixin.common.error.WxErrorException;
/** 视频号小店商家客服服务。 */
public interface WxChannelKfService {
/**
* 上传多媒体资源。
*
* @param openId 用户 open_id
* @param msgType 文件类型,仅支持 video、file、image
* @param file 文件字节内容
* @return COS 地址
* @throws WxErrorException 微信异常
*/
String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException;
/**
* 上传多媒体资源。
*
* @param openId 用户 open_id
* @param msgType 文件类型,仅支持 video、file、image
* @param fileName 文件名
* @param file 文件字节内容
* @return COS 地址
* @throws WxErrorException 微信异常
*/
String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException;
/**
* 发送客服消息。
*
* @param param 请求参数
* @return 发送结果
* @throws WxErrorException 微信异常
*/
WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException;
}

View File

@@ -7,6 +7,15 @@ package me.chanjar.weixin.channel.api;
*/
public interface WxChannelService extends BaseWxChannelService {
/**
* 商家客服服务。
*
* @return 商家客服服务
*/
default WxChannelKfService getKfService() {
throw new UnsupportedOperationException("WxChannelService implementation does not support getKfService()");
}
/**
* 基础接口服务
*

View File

@@ -72,6 +72,7 @@ public abstract class BaseWxChannelServiceImpl<H, P> implements WxChannelService
private WxTalentService talentService = null;
private WxChannelFavoriteService favoriteService = null;
private WxChannelEwaybillService ewaybillService = null;
private WxChannelKfService kfService = null;
protected WxChannelConfig config;
private int retrySleepMillis = 1000;
@@ -548,4 +549,12 @@ public abstract class BaseWxChannelServiceImpl<H, P> implements WxChannelService
return ewaybillService;
}
@Override
public synchronized WxChannelKfService getKfService() {
if (kfService == null) {
kfService = new WxChannelKfServiceImpl(this);
}
return kfService;
}
}

View File

@@ -0,0 +1,45 @@
package me.chanjar.weixin.channel.api.impl;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.COS_UPLOAD_URL;
import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Kf.SEND_MSG_URL;
import me.chanjar.weixin.channel.api.WxChannelKfService;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfCosUploadResponse;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.channel.util.ResponseUtils;
import me.chanjar.weixin.common.bean.CommonUploadParam;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
/** 视频号小店商家客服服务实现。 */
public class WxChannelKfServiceImpl implements WxChannelKfService {
private final BaseWxChannelServiceImpl<?, ?> channelService;
public WxChannelKfServiceImpl(BaseWxChannelServiceImpl<?, ?> channelService) {
this.channelService = channelService;
}
@Override
public String uploadMedia(String openId, String msgType, byte[] file) throws WxErrorException {
return uploadMedia(openId, msgType, null, file);
}
@Override
public String uploadMedia(String openId, String msgType, String fileName, byte[] file) throws WxErrorException {
CommonUploadParam uploadParam = CommonUploadParam.fromBytes("file", fileName, file)
.addFormField("open_id", openId)
.addFormField("msg_type", msgType);
String responseJson = channelService.upload(COS_UPLOAD_URL, uploadParam);
return ResponseUtils.decode(responseJson, WxChannelKfCosUploadResponse.class).getCosUrl();
}
@Override
public WxChannelKfSendMsgResponse sendMessage(WxChannelKfSendMsgParam param) throws WxErrorException {
String responseJson = channelService.executeWithoutLog(SimplePostRequestExecutor.create(channelService), SEND_MSG_URL,
JsonUtils.encode(param));
return ResponseUtils.decode(responseJson, WxChannelKfSendMsgResponse.class);
}
}

View File

@@ -0,0 +1,20 @@
package me.chanjar.weixin.channel.bean.kf;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
/** 客服素材上传响应。 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class WxChannelKfCosUploadResponse extends WxChannelBaseResponse {
private static final long serialVersionUID = 1L;
/** 素材在 COS 上的地址。 */
@JsonProperty("cos_url")
private String cosUrl;
}

View File

@@ -0,0 +1,90 @@
package me.chanjar.weixin.channel.bean.kf;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/** 发送客服消息请求参数。 */
@Data
@NoArgsConstructor
public class WxChannelKfSendMsgParam implements Serializable {
private static final long serialVersionUID = 1L;
/** 请求幂等标识。 */
@JsonProperty("request_id")
private String requestId;
/** 接收消息的用户 openid。 */
@JsonProperty("open_id")
private String openId;
/** 消息类型。 */
@JsonProperty("msg_type")
private String msgType;
/** 文本消息内容。 */
@JsonProperty("text")
private Text text;
/** 图片消息内容。 */
@JsonProperty("image")
private CosUrlMessage image;
/** 视频消息内容。 */
@JsonProperty("video")
private CosUrlMessage video;
/** 文件消息内容。 */
@JsonProperty("file")
private CosUrlMessage file;
/** 商品卡片消息内容。 */
@JsonProperty("product_share")
private ProductShareMessage productShare;
/** 订单卡片消息内容。 */
@JsonProperty("order_share")
private OrderShareMessage orderShare;
@Data
@NoArgsConstructor
public static class Text implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("content")
private String content;
}
@Data
@NoArgsConstructor
public static class CosUrlMessage implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("cos_url")
private String cosUrl;
}
@Data
@NoArgsConstructor
public static class ProductShareMessage implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("product_id")
private String productId;
}
@Data
@NoArgsConstructor
public static class OrderShareMessage implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("order_id")
private String orderId;
}
}

View File

@@ -0,0 +1,20 @@
package me.chanjar.weixin.channel.bean.kf;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse;
/** 发送客服消息响应。 */
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class WxChannelKfSendMsgResponse extends WxChannelBaseResponse {
private static final long serialVersionUID = 1L;
/** 消息 id。 */
@JsonProperty("msg_id")
private String msgId;
}

View File

@@ -49,6 +49,15 @@ public class WxChannelApiUrlConstants {
String GET_FAVORITE_COUNT = "https://api.weixin.qq.com/channels/ec/favorites/count/get";
}
/** 商家客服相关接口 */
public interface Kf {
/** 上传客服素材 */
String COS_UPLOAD_URL = "https://api.weixin.qq.com/channels/ec/commkf/cosupload";
/** 发送客服消息 */
String SEND_MSG_URL = "https://api.weixin.qq.com/channels/ec/commkf/sendmsg";
}
/** 商品类目相关接口 */
public interface Category {

View File

@@ -0,0 +1,120 @@
package me.chanjar.weixin.channel.api.impl;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertSame;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgParam;
import me.chanjar.weixin.channel.bean.kf.WxChannelKfSendMsgResponse;
import me.chanjar.weixin.channel.util.JsonUtils;
import me.chanjar.weixin.common.bean.CommonUploadParam;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import org.testng.annotations.Test;
/** 商家客服服务离线测试。 */
public class WxChannelKfServiceImplTest {
@Test
public void shouldUploadMediaWithDocumentedUrlAndMultipartFields() throws WxErrorException {
RecordingChannelService channelService = new RecordingChannelService();
channelService.uploadResult = "{\"errcode\":0,\"errmsg\":\"ok\",\"cos_url\":\"https://cos.example.com/image.png\"}";
String cosUrl = new WxChannelKfServiceImpl(channelService)
.uploadMedia("open-id", "image", "image.png", new byte[]{1, 2, 3});
assertEquals(channelService.uploadUrl, "https://api.weixin.qq.com/channels/ec/commkf/cosupload");
assertNotNull(channelService.uploadParam);
assertEquals(channelService.uploadParam.getName(), "file");
assertEquals(channelService.uploadParam.getData().getFileName(), "image.png");
assertEquals(channelService.uploadParam.getData().readAllBytes(), new byte[]{1, 2, 3});
assertEquals(channelService.uploadParam.getFormFields().get("open_id"), "open-id");
assertEquals(channelService.uploadParam.getFormFields().get("msg_type"), "image");
assertEquals(cosUrl, "https://cos.example.com/image.png");
}
@Test
public void shouldSendJsonMessageAndDecodeResponse() throws WxErrorException {
RecordingChannelService channelService = new RecordingChannelService();
channelService.postResult = "{\"errcode\":0,\"errmsg\":\"ok\",\"msg_id\":\"message-id\"}";
WxChannelKfSendMsgParam param = new WxChannelKfSendMsgParam();
param.setRequestId("request-id");
param.setOpenId("open-id");
param.setMsgType("text");
WxChannelKfSendMsgParam.Text text = new WxChannelKfSendMsgParam.Text();
text.setContent("hello");
param.setText(text);
WxChannelKfSendMsgResponse response = new WxChannelKfServiceImpl(channelService).sendMessage(param);
assertEquals(channelService.postUrl, "https://api.weixin.qq.com/channels/ec/commkf/sendmsg");
assertEquals(channelService.postJson,
"{\"request_id\":\"request-id\",\"open_id\":\"open-id\",\"msg_type\":\"text\",\"text\":{\"content\":\"hello\"}}");
assertEquals(channelService.executeWithoutLogCalled, true);
assertEquals(response.getMsgId(), "message-id");
assertEquals(response.getErrCode(), 0);
}
@Test
public void shouldProvideFreshUploadStreamForEachAttempt() throws IOException {
CommonUploadParam uploadParam = CommonUploadParam.fromBytes("file", "image.png", new byte[]{1, 2, 3});
assertEquals(readAllBytes(uploadParam.getData().getInputStream()), new byte[]{1, 2, 3});
assertEquals(readAllBytes(uploadParam.getData().getInputStream()), new byte[]{1, 2, 3});
}
@Test
public void shouldCacheKfServiceEntryPoint() {
WxChannelServiceImpl channelService = new WxChannelServiceImpl();
assertSame(channelService.getKfService(), channelService.getKfService());
}
private static class RecordingChannelService extends WxChannelServiceImpl {
private String uploadResult;
private String postResult;
private String uploadUrl;
private CommonUploadParam uploadParam;
private String postUrl;
private String postJson;
private boolean executeWithoutLogCalled;
@Override
public String upload(String url, CommonUploadParam param) {
this.uploadUrl = url;
this.uploadParam = param;
return uploadResult;
}
@Override
public String post(String url, Object obj) {
this.postUrl = url;
this.postJson = JsonUtils.encode(obj);
return postResult;
}
@Override
@SuppressWarnings("unchecked")
public <T, E> T executeWithoutLog(RequestExecutor<T, E> executor, String uri, E data) {
this.executeWithoutLogCalled = true;
this.postUrl = uri;
this.postJson = (String) data;
return (T) postResult;
}
}
private byte[] readAllBytes(InputStream inputStream) throws IOException {
try (InputStream stream = inputStream; ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[16];
int count;
while ((count = stream.read(buffer)) != -1) {
outputStream.write(buffer, 0, count);
}
return outputStream.toByteArray();
}
}
}

View File

@@ -0,0 +1,141 @@
package me.chanjar.weixin.channel.bean.kf;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import me.chanjar.weixin.channel.util.JsonUtils;
import org.testng.annotations.Test;
/** JSON serialization tests for channel customer service models. */
public class WxChannelKfBeanTest {
@Test
public void testSendMsgParamJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("text");
WxChannelKfSendMsgParam.Text text = new WxChannelKfSendMsgParam.Text();
text.setContent("hello");
param.setText(text);
String json = JsonUtils.encode(param);
assertNotNull(json);
assertFalse(json.contains("requestId"));
assertFalse(json.contains("openId"));
assertFalse(json.contains("msgType"));
WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class);
assertEquals(decoded.getRequestId(), "request-1");
assertEquals(decoded.getOpenId(), "open-1");
assertEquals(decoded.getMsgType(), "text");
assertNotNull(decoded.getText());
assertEquals(decoded.getText().getContent(), "hello");
}
@Test
public void testImageMessageJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("image");
WxChannelKfSendMsgParam.CosUrlMessage image = new WxChannelKfSendMsgParam.CosUrlMessage();
image.setCosUrl("https://example.test/image");
param.setImage(image);
assertCosUrlMessageJson(param, "image", "https://example.test/image");
}
@Test
public void testVideoMessageJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("video");
WxChannelKfSendMsgParam.CosUrlMessage video = new WxChannelKfSendMsgParam.CosUrlMessage();
video.setCosUrl("https://example.test/video");
param.setVideo(video);
assertCosUrlMessageJson(param, "video", "https://example.test/video");
}
@Test
public void testFileMessageJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("file");
WxChannelKfSendMsgParam.CosUrlMessage file = new WxChannelKfSendMsgParam.CosUrlMessage();
file.setCosUrl("https://example.test/file");
param.setFile(file);
assertCosUrlMessageJson(param, "file", "https://example.test/file");
}
@Test
public void testProductShareMessageJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("product_share");
WxChannelKfSendMsgParam.ProductShareMessage product =
new WxChannelKfSendMsgParam.ProductShareMessage();
product.setProductId("product-1");
param.setProductShare(product);
String json = JsonUtils.encode(param);
assertTrue(json.contains("\"product_share\":{\"product_id\":\"product-1\"}"));
assertFalse(json.contains("productShare"));
assertFalse(json.contains("productId"));
WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class);
assertEquals(decoded.getProductShare().getProductId(), "product-1");
}
@Test
public void testOrderShareMessageJson() {
WxChannelKfSendMsgParam param = createSendMsgParam("order_share");
WxChannelKfSendMsgParam.OrderShareMessage order = new WxChannelKfSendMsgParam.OrderShareMessage();
order.setOrderId("order-1");
param.setOrderShare(order);
String json = JsonUtils.encode(param);
assertTrue(json.contains("\"order_share\":{\"order_id\":\"order-1\"}"));
assertFalse(json.contains("orderShare"));
assertFalse(json.contains("orderId"));
WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class);
assertEquals(decoded.getOrderShare().getOrderId(), "order-1");
}
@Test
public void testCosUploadResponseJson() {
WxChannelKfCosUploadResponse response = JsonUtils.decode(
"{\"errcode\":0,\"errmsg\":\"ok\",\"cos_url\":\"https://example.test/media\"}",
WxChannelKfCosUploadResponse.class);
assertEquals(response.getErrCode(), 0);
assertEquals(response.getCosUrl(), "https://example.test/media");
assertEquals(JsonUtils.decode(JsonUtils.encode(response), WxChannelKfCosUploadResponse.class)
.getCosUrl(), "https://example.test/media");
}
@Test
public void testSendMsgResponseJson() {
WxChannelKfSendMsgResponse response = JsonUtils.decode(
"{\"errcode\":0,\"errmsg\":\"ok\",\"msg_id\":\"msg-1\"}",
WxChannelKfSendMsgResponse.class);
assertEquals(response.getErrCode(), 0);
assertEquals(response.getMsgId(), "msg-1");
assertEquals(JsonUtils.decode(JsonUtils.encode(response), WxChannelKfSendMsgResponse.class)
.getMsgId(), "msg-1");
}
private WxChannelKfSendMsgParam createSendMsgParam(String msgType) {
WxChannelKfSendMsgParam param = new WxChannelKfSendMsgParam();
param.setRequestId("request-1");
param.setOpenId("open-1");
param.setMsgType(msgType);
return param;
}
private void assertCosUrlMessageJson(WxChannelKfSendMsgParam param, String fieldName,
String cosUrl) {
String json = JsonUtils.encode(param);
assertTrue(json.contains("\"" + fieldName + "\":{\"cos_url\":\"" + cosUrl + "\"}"));
assertFalse(json.contains("cosUrl"));
WxChannelKfSendMsgParam decoded = JsonUtils.decode(json, WxChannelKfSendMsgParam.class);
if ("image".equals(fieldName)) {
assertEquals(decoded.getImage().getCosUrl(), cosUrl);
} else if ("video".equals(fieldName)) {
assertEquals(decoded.getVideo().getCosUrl(), cosUrl);
} else {
assertEquals(decoded.getFile().getCosUrl(), cosUrl);
}
}
}

View File

@@ -26,4 +26,10 @@
<class name="me.chanjar.weixin.channel.api.impl.WxChannelProductAssistantServiceImplTest"/>
</classes>
</test>
<test name="Kf_Service_Test">
<classes>
<class name="me.chanjar.weixin.channel.bean.kf.WxChannelKfBeanTest"/>
<class name="me.chanjar.weixin.channel.api.impl.WxChannelKfServiceImplTest"/>
</classes>
</test>
</suite>

View File

@@ -78,7 +78,27 @@ 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), null);
return new CommonUploadParam(name, new ByteArrayUploadData(fileName, bytes), null);
}
private static class ByteArrayUploadData extends CommonUploadData {
private final byte[] bytes;
private ByteArrayUploadData(@Nullable String fileName, byte[] bytes) {
super(fileName, new ByteArrayInputStream(bytes), bytes.length);
this.bytes = bytes;
}
@Override
public ByteArrayInputStream getInputStream() {
return new ByteArrayInputStream(bytes);
}
@Override
public byte[] readAllBytes() {
return bytes.clone();
}
}
/**