diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java index 82f6db917..e874b26f4 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java @@ -2,6 +2,8 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult; import java.io.File; import java.io.IOException; @@ -133,4 +135,21 @@ public interface WxCpMediaService { * @throws WxErrorException the wx error exception */ String uploadImg(File file) throws WxErrorException; + + /** + * 生成异步上传任务 + * 跟上传临时素材拿到的media_id使用场景是不通用的,目前适配的接口如下:https://developer.work.weixin.qq.com/document/path/96488#%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF%E8%AF%B4%E6%98%8E + * @param req 请求参数 + * @return 返回异步任务id + * @throws WxErrorException the wx error exception + */ + String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException; + + /** + * 查询异步任务结果 + * @param jobId 任务id。最长为128字节,60分钟内有效 + * @return 返回异步任务结果 + * @throws WxErrorException the wx error exception + */ + MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java index 863dd7c1d..427ce9d89 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java @@ -1,5 +1,6 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.JsonObject; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.error.WxErrorException; @@ -9,8 +10,12 @@ import me.chanjar.weixin.common.util.http.BaseMediaDownloadRequestExecutor; import me.chanjar.weixin.common.util.http.InputStreamData; import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpMediaService; import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq; +import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult; import java.io.File; import java.io.IOException; @@ -20,7 +25,12 @@ import java.net.URL; import java.nio.file.Files; import java.util.UUID; -import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.*; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.GET_UPLOAD_BY_URL_RESULT; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.IMG_UPLOAD; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.JSSDK_MEDIA_GET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_GET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_UPLOAD; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.UPLOAD_BY_URL; /** *
@@ -119,4 +129,20 @@ public class WxCpMediaServiceImpl implements WxCpMediaService {
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file)
.getUrl();
}
+
+ @Override
+ public String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException {
+ final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPLOAD_BY_URL);
+ String responseContent = this.mainService.post(url, req.toJson());
+ return GsonHelper.getString(GsonParser.parse(responseContent), "jobid");
+ }
+
+ @Override
+ public MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException {
+ final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_UPLOAD_BY_URL_RESULT);
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("jobid", jobId);
+ String post = this.mainService.post(url, jsonObject.toString());
+ return MediaUploadByUrlResult.fromJson(post);
+ }
}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
new file mode 100644
index 000000000..c5cb21bde
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlReq.java
@@ -0,0 +1,58 @@
+package me.chanjar.weixin.cp.bean.media;
+
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * 生成异步上传任务
+ * @author imyzt
+ * @date 2025/04/27
+ */
+@Data
+public class MediaUploadByUrlReq {
+
+ /**
+ * 场景值。1-客户联系入群欢迎语素材(目前仅支持1)。 注意:每个场景值有对应的使用范围,详见上面的「使用场景说明」
+ */
+ private Integer scene;
+
+ /**
+ * 媒体文件类型。目前仅支持video-视频,file-普通文件 不超过32字节。
+ */
+ private String type;
+
+ /**
+ * 文件名,标识文件展示的名称。比如,使用该media_id发消息时,展示的文件名由该字段控制。 不超过128字节。
+ */
+ private String filename;
+
+ /**
+ * 文件cdn url。url要求支持Range分块下载 不超过1024字节。 如果为腾讯云cos链接,则需要设置为「公有读」权限。
+ */
+ private String url;
+
+ /**
+ * 文件md5。对比从url下载下来的文件md5是否一致。 不超过32字节。
+ */
+ private String md5;
+
+ /**
+ * From json wx cp base resp.
+ *
+ * @param json the json
+ * @return the wx cp base resp
+ */
+ public static MediaUploadByUrlReq fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlReq.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
new file mode 100644
index 000000000..cc931eed3
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/media/MediaUploadByUrlResult.java
@@ -0,0 +1,82 @@
+package me.chanjar.weixin.cp.bean.media;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import me.chanjar.weixin.cp.bean.WxCpBaseResp;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 异步上传企微素材
+ * @author imyzt
+ * @date 2025/4/27
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class MediaUploadByUrlResult extends WxCpBaseResp implements Serializable {
+
+ private static final long serialVersionUID = 330834334738622341L;
+
+ /**
+ * 任务状态。1-处理中,2-完成,3-异常失败
+ */
+ @SerializedName("status")
+ private Integer status;
+
+ @SerializedName("detail")
+ private Detail detail;
+
+ @Data
+ public static class Detail {
+
+ /**
+ * 任务失败返回码。当status为3时返回非0,其他返回0
+ * 830001 url非法 确认url是否支持Range分块下载
+ * 830003 url下载数据失败 确认url本身是否能正常访问
+ * 45001 文件大小超过限制 确认文件在5字节~200M范围内
+ * 301019 文件MD5不匹配 确认url对应的文件内容md5,跟所填的md5参数是否一致
+ * 注意: status=2时,此处微信并未返回任何值
+ */
+ @SerializedName("errcode")
+ private Integer errCode;
+
+ /**
+ * 注意: status=2时,此处微信并未返回任何值
+ */
+ @SerializedName("errmsg")
+ private String errMsg;
+
+ /**
+ * 媒体文件上传后获取的唯一标识,3天内有效。当status为2时返回。
+ */
+ @SerializedName("media_id")
+ private String mediaId;
+
+ /**
+ * 媒体文件创建的时间戳。当status为2时返回。
+ */
+ @SerializedName("created_at")
+ private String createdAt;
+ }
+
+ /**
+ * From json wx cp media upload by url result.
+ *
+ * @param json the json
+ * @return the wx cp media upload by url result
+ */
+ public static MediaUploadByUrlResult fromJson(String json) {
+ return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlResult.class);
+ }
+
+ /**
+ * To json string.
+ *
+ * @return the string
+ */
+ public String toJson() {
+ return WxCpGsonBuilder.create().toJson(this);
+ }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
index fb4213f50..81d09a11c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java
@@ -198,6 +198,13 @@ public class WxCpXmlMessage implements Serializable {
@XStreamAlias("SelectedItems")
private List selectedItems;
+ /**
+ * 异步任务id
+ */
+ @XStreamAlias("JobId")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String jobId;
+
/**
* 微信客服
* 调用拉取消息接口时,需要传此token,用于校验请求的合法性
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index d70f0ff4c..093d386e6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -238,6 +238,12 @@ public interface WxCpApiPathConsts {
* The constant JSSDK_MEDIA_GET.
*/
String JSSDK_MEDIA_GET = "/cgi-bin/media/get/jssdk";
+
+ /** The constant GET_UPLOAD_BY_URL_RESULT. */
+ String GET_UPLOAD_BY_URL_RESULT = "/cgi-bin/media/get_upload_by_url_result";
+
+ /** The constant UPLOAD_BY_URL. */
+ String UPLOAD_BY_URL = "/cgi-bin/media/upload_by_url";
}
/**
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
index 606dcea6d..3d51c9e2c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
@@ -219,6 +219,11 @@ public class WxCpConsts {
*/
public static final String CUSTOMER_ACQUISITION = "customer_acquisition";
+ /**
+ * 异步上传临时素材结果回调通知
+ */
+ public static final String UPLOAD_MEDIA_JOB_FINISH = "upload_media_job_finish";
+
}
/**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
index b964aad51..381a4c145 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java
@@ -7,6 +7,8 @@ import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.ApiTestModule;
import me.chanjar.weixin.cp.api.TestConstants;
import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq;
+import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
@@ -127,4 +129,38 @@ public class WxCpMediaServiceImplTest {
assertThat(file).isNotNull();
System.out.println(file);
}
+
+ /**
+ * Test upload media by url.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testUploadMediaByUrl() throws WxErrorException {
+ MediaUploadByUrlReq req = new MediaUploadByUrlReq();
+ req.setScene(1);
+ req.setType("video");
+ req.setFilename("mov_bbb");
+ req.setUrl("https://www.w3school.com.cn/example/html5/mov_bbb.mp4");
+ req.setMd5("198918f40ecc7cab0fc4231adaf67c96");
+ String jobId = this.wxService.getMediaService().uploadByUrl(req);
+ System.out.println(jobId);
+ }
+
+ /**
+ * Test upload media by url.
+ *
+ * @throws WxErrorException the wx error exception
+ */
+ @Test
+ public void testUploadMediaByUrlResult() throws WxErrorException, InterruptedException {
+ String jobId = "job1745801375_5GIKWuFF3M7hcIkeSNMqs_W26xy5VeSWjLaLFTEdSfQ";
+ MediaUploadByUrlResult result = this.wxService.getMediaService().uploadByUrl(jobId);
+ System.out.println(result);
+ }
+
+ @Test
+ public void testUploadMediaJobFinishEvent() throws WxErrorException {
+ File file = this.wxService.getMediaService().getJssdkFile("....");
+ }
}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
index a760a17ff..5bcfe9698 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java
@@ -6,6 +6,7 @@ import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
import org.testng.annotations.Test;
import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.TASKCARD_CLICK;
+import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.UPLOAD_MEDIA_JOB_FINISH;
import static org.assertj.core.api.Assertions.assertThat;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@@ -421,4 +422,24 @@ public class WxCpXmlMessageTest {
assertThat(wxCpXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemName()).isNotEmpty();
assertThat(wxCpXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemName()).isNotEmpty();
}
+
+ /**
+ * Test open approval change.
+ */
+ public void testUploadMediaJobFinishEvent() {
+ String xml = "\n" +
+ "\t \n" +
+ "\t \n" +
+ "\t1425284517 \n" +
+ "\t \n" +
+ "\t \n" +
+ "\t \n" +
+ " ";
+
+ WxCpXmlMessage wxCpXmlMessage = WxCpXmlMessage.fromXml(xml);
+ assertThat(wxCpXmlMessage).isNotNull();
+ assertThat(wxCpXmlMessage.getJobId()).isNotEmpty();
+ assertThat(wxCpXmlMessage.getJobId()).isEqualTo("jobid_S0MrnndvRG5fadSlLwiBqiDDbM143UqTmKP3152FZk4");
+ assertThat(wxCpXmlMessage.getEvent()).isEqualTo(UPLOAD_MEDIA_JOB_FINISH);
+ }
}