Skip to content

Commit 2d8d1df

Browse files
authored
🆕 #3320【企业微信】增加异步上传临时素材相关接口
1 parent 2279105 commit 2d8d1df

File tree

9 files changed

+261
-1
lines changed

9 files changed

+261
-1
lines changed

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
44
import me.chanjar.weixin.common.error.WxErrorException;
5+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq;
6+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult;
57

68
import java.io.File;
79
import java.io.IOException;
@@ -133,4 +135,21 @@ WxMediaUploadResult upload(String mediaType, String filename, String url)
133135
* @throws WxErrorException the wx error exception
134136
*/
135137
String uploadImg(File file) throws WxErrorException;
138+
139+
/**
140+
* 生成异步上传任务
141+
* 跟上传临时素材拿到的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
142+
* @param req 请求参数
143+
* @return 返回异步任务id
144+
* @throws WxErrorException the wx error exception
145+
*/
146+
String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException;
147+
148+
/**
149+
* 查询异步任务结果
150+
* @param jobId 任务id。最长为128字节,60分钟内有效
151+
* @return 返回异步任务结果
152+
* @throws WxErrorException the wx error exception
153+
*/
154+
MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException;
136155
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package me.chanjar.weixin.cp.api.impl;
22

3+
import com.google.gson.JsonObject;
34
import lombok.RequiredArgsConstructor;
45
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
56
import me.chanjar.weixin.common.error.WxErrorException;
@@ -9,8 +10,12 @@
910
import me.chanjar.weixin.common.util.http.InputStreamData;
1011
import me.chanjar.weixin.common.util.http.MediaInputStreamUploadRequestExecutor;
1112
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
13+
import me.chanjar.weixin.common.util.json.GsonHelper;
14+
import me.chanjar.weixin.common.util.json.GsonParser;
1215
import me.chanjar.weixin.cp.api.WxCpMediaService;
1316
import me.chanjar.weixin.cp.api.WxCpService;
17+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq;
18+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult;
1419

1520
import java.io.File;
1621
import java.io.IOException;
@@ -20,7 +25,12 @@
2025
import java.nio.file.Files;
2126
import java.util.UUID;
2227

23-
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.*;
28+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.GET_UPLOAD_BY_URL_RESULT;
29+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.IMG_UPLOAD;
30+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.JSSDK_MEDIA_GET;
31+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_GET;
32+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.MEDIA_UPLOAD;
33+
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Media.UPLOAD_BY_URL;
2434

2535
/**
2636
* <pre>
@@ -119,4 +129,20 @@ public String uploadImg(File file) throws WxErrorException {
119129
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file)
120130
.getUrl();
121131
}
132+
133+
@Override
134+
public String uploadByUrl(MediaUploadByUrlReq req) throws WxErrorException {
135+
final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPLOAD_BY_URL);
136+
String responseContent = this.mainService.post(url, req.toJson());
137+
return GsonHelper.getString(GsonParser.parse(responseContent), "jobid");
138+
}
139+
140+
@Override
141+
public MediaUploadByUrlResult uploadByUrl(String jobId) throws WxErrorException {
142+
final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_UPLOAD_BY_URL_RESULT);
143+
JsonObject jsonObject = new JsonObject();
144+
jsonObject.addProperty("jobid", jobId);
145+
String post = this.mainService.post(url, jsonObject.toString());
146+
return MediaUploadByUrlResult.fromJson(post);
147+
}
122148
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package me.chanjar.weixin.cp.bean.media;
2+
3+
import lombok.Data;
4+
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
5+
6+
/**
7+
* 生成异步上传任务
8+
* @author <a href="https://github.com/imyzt">imyzt</a>
9+
* @date 2025/04/27
10+
*/
11+
@Data
12+
public class MediaUploadByUrlReq {
13+
14+
/**
15+
* 场景值。1-客户联系入群欢迎语素材(目前仅支持1)。 注意:每个场景值有对应的使用范围,详见上面的「使用场景说明」
16+
*/
17+
private Integer scene;
18+
19+
/**
20+
* 媒体文件类型。目前仅支持video-视频,file-普通文件 不超过32字节。
21+
*/
22+
private String type;
23+
24+
/**
25+
* 文件名,标识文件展示的名称。比如,使用该media_id发消息时,展示的文件名由该字段控制。 不超过128字节。
26+
*/
27+
private String filename;
28+
29+
/**
30+
* 文件cdn url。url要求支持Range分块下载 不超过1024字节。 如果为腾讯云cos链接,则需要设置为「公有读」权限。
31+
*/
32+
private String url;
33+
34+
/**
35+
* 文件md5。对比从url下载下来的文件md5是否一致。 不超过32字节。
36+
*/
37+
private String md5;
38+
39+
/**
40+
* From json wx cp base resp.
41+
*
42+
* @param json the json
43+
* @return the wx cp base resp
44+
*/
45+
public static MediaUploadByUrlReq fromJson(String json) {
46+
return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlReq.class);
47+
}
48+
49+
/**
50+
* To json string.
51+
*
52+
* @return the string
53+
*/
54+
public String toJson() {
55+
return WxCpGsonBuilder.create().toJson(this);
56+
}
57+
58+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package me.chanjar.weixin.cp.bean.media;
2+
3+
import com.google.gson.annotations.SerializedName;
4+
import lombok.Data;
5+
import lombok.EqualsAndHashCode;
6+
import me.chanjar.weixin.cp.bean.WxCpBaseResp;
7+
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
8+
9+
import java.io.Serializable;
10+
11+
/**
12+
* 异步上传企微素材
13+
* @author <a href="https://github.com/imyzt">imyzt</a>
14+
* @date 2025/4/27
15+
*/
16+
@EqualsAndHashCode(callSuper = true)
17+
@Data
18+
public class MediaUploadByUrlResult extends WxCpBaseResp implements Serializable {
19+
20+
private static final long serialVersionUID = 330834334738622341L;
21+
22+
/**
23+
* 任务状态。1-处理中,2-完成,3-异常失败
24+
*/
25+
@SerializedName("status")
26+
private Integer status;
27+
28+
@SerializedName("detail")
29+
private Detail detail;
30+
31+
@Data
32+
public static class Detail {
33+
34+
/**
35+
* 任务失败返回码。当status为3时返回非0,其他返回0
36+
* 830001 url非法 确认url是否支持Range分块下载
37+
* 830003 url下载数据失败 确认url本身是否能正常访问
38+
* 45001 文件大小超过限制 确认文件在5字节~200M范围内
39+
* 301019 文件MD5不匹配 确认url对应的文件内容md5,跟所填的md5参数是否一致
40+
* 注意: status=2时,此处微信并未返回任何值
41+
*/
42+
@SerializedName("errcode")
43+
private Integer errCode;
44+
45+
/**
46+
* 注意: status=2时,此处微信并未返回任何值
47+
*/
48+
@SerializedName("errmsg")
49+
private String errMsg;
50+
51+
/**
52+
* 媒体文件上传后获取的唯一标识,3天内有效。当status为2时返回。
53+
*/
54+
@SerializedName("media_id")
55+
private String mediaId;
56+
57+
/**
58+
* 媒体文件创建的时间戳。当status为2时返回。
59+
*/
60+
@SerializedName("created_at")
61+
private String createdAt;
62+
}
63+
64+
/**
65+
* From json wx cp media upload by url result.
66+
*
67+
* @param json the json
68+
* @return the wx cp media upload by url result
69+
*/
70+
public static MediaUploadByUrlResult fromJson(String json) {
71+
return WxCpGsonBuilder.create().fromJson(json, MediaUploadByUrlResult.class);
72+
}
73+
74+
/**
75+
* To json string.
76+
*
77+
* @return the string
78+
*/
79+
public String toJson() {
80+
return WxCpGsonBuilder.create().toJson(this);
81+
}
82+
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ public class WxCpXmlMessage implements Serializable {
198198
@XStreamAlias("SelectedItems")
199199
private List<SelectedItem> selectedItems;
200200

201+
/**
202+
* <a href="https://developer.work.weixin.qq.com/document/path/96488#%E5%9B%9E%E8%B0%83%E5%BC%82%E6%AD%A5%E4%BB%BB%E5%8A%A1%E7%BB%93%E6%9E%9C">异步任务id</a>
203+
*/
204+
@XStreamAlias("JobId")
205+
@XStreamConverter(value = XStreamCDataConverter.class)
206+
private String jobId;
207+
201208
/**
202209
* 微信客服
203210
* 调用拉取消息接口时,需要传此token,用于校验请求的合法性

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ interface Media {
238238
* The constant JSSDK_MEDIA_GET.
239239
*/
240240
String JSSDK_MEDIA_GET = "/cgi-bin/media/get/jssdk";
241+
242+
/** The constant GET_UPLOAD_BY_URL_RESULT. */
243+
String GET_UPLOAD_BY_URL_RESULT = "/cgi-bin/media/get_upload_by_url_result";
244+
245+
/** The constant UPLOAD_BY_URL. */
246+
String UPLOAD_BY_URL = "/cgi-bin/media/upload_by_url";
241247
}
242248

243249
/**

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ public static class EventType {
219219
*/
220220
public static final String CUSTOMER_ACQUISITION = "customer_acquisition";
221221

222+
/**
223+
* <a href="https://developer.work.weixin.qq.com/document/path/96488#%E5%9B%9E%E8%B0%83%E5%BC%82%E6%AD%A5%E4%BB%BB%E5%8A%A1%E7%BB%93%E6%9E%9C">异步上传临时素材结果回调通知</a>
224+
*/
225+
public static final String UPLOAD_MEDIA_JOB_FINISH = "upload_media_job_finish";
226+
222227
}
223228

224229
/**

weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImplTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import me.chanjar.weixin.cp.api.ApiTestModule;
88
import me.chanjar.weixin.cp.api.TestConstants;
99
import me.chanjar.weixin.cp.api.WxCpService;
10+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlReq;
11+
import me.chanjar.weixin.cp.bean.media.MediaUploadByUrlResult;
1012
import org.testng.annotations.DataProvider;
1113
import org.testng.annotations.Guice;
1214
import org.testng.annotations.Test;
@@ -127,4 +129,38 @@ public void testGetJssdkFile() throws WxErrorException {
127129
assertThat(file).isNotNull();
128130
System.out.println(file);
129131
}
132+
133+
/**
134+
* Test upload media by url.
135+
*
136+
* @throws WxErrorException the wx error exception
137+
*/
138+
@Test
139+
public void testUploadMediaByUrl() throws WxErrorException {
140+
MediaUploadByUrlReq req = new MediaUploadByUrlReq();
141+
req.setScene(1);
142+
req.setType("video");
143+
req.setFilename("mov_bbb");
144+
req.setUrl("https://www.w3school.com.cn/example/html5/mov_bbb.mp4");
145+
req.setMd5("198918f40ecc7cab0fc4231adaf67c96");
146+
String jobId = this.wxService.getMediaService().uploadByUrl(req);
147+
System.out.println(jobId);
148+
}
149+
150+
/**
151+
* Test upload media by url.
152+
*
153+
* @throws WxErrorException the wx error exception
154+
*/
155+
@Test
156+
public void testUploadMediaByUrlResult() throws WxErrorException, InterruptedException {
157+
String jobId = "job1745801375_5GIKWuFF3M7hcIkeSNMqs_W26xy5VeSWjLaLFTEdSfQ";
158+
MediaUploadByUrlResult result = this.wxService.getMediaService().uploadByUrl(jobId);
159+
System.out.println(result);
160+
}
161+
162+
@Test
163+
public void testUploadMediaJobFinishEvent() throws WxErrorException {
164+
File file = this.wxService.getMediaService().getJssdkFile("....");
165+
}
130166
}

weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.testng.annotations.Test;
77

88
import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.TASKCARD_CLICK;
9+
import static me.chanjar.weixin.cp.constant.WxCpConsts.EventType.UPLOAD_MEDIA_JOB_FINISH;
910
import static org.assertj.core.api.Assertions.assertThat;
1011
import static org.testng.Assert.assertEquals;
1112
import static org.testng.Assert.assertNotNull;
@@ -421,4 +422,24 @@ public void testOpenApprovalChange() {
421422
assertThat(wxCpXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemName()).isNotEmpty();
422423
assertThat(wxCpXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemName()).isNotEmpty();
423424
}
425+
426+
/**
427+
* Test open approval change.
428+
*/
429+
public void testUploadMediaJobFinishEvent() {
430+
String xml = "<xml>\n" +
431+
"\t<ToUserName><![CDATA[wx28dbb14e3720FAKE]]></ToUserName>\n" +
432+
"\t<FromUserName><![CDATA[sys]]></FromUserName>\n" +
433+
"\t<CreateTime>1425284517</CreateTime>\n" +
434+
"\t<MsgType><![CDATA[event]]></MsgType>\n" +
435+
"\t<Event><![CDATA[upload_media_job_finish]]></Event>\n" +
436+
"\t<JobId><![CDATA[jobid_S0MrnndvRG5fadSlLwiBqiDDbM143UqTmKP3152FZk4]]></JobId>\n" +
437+
"</xml>";
438+
439+
WxCpXmlMessage wxCpXmlMessage = WxCpXmlMessage.fromXml(xml);
440+
assertThat(wxCpXmlMessage).isNotNull();
441+
assertThat(wxCpXmlMessage.getJobId()).isNotEmpty();
442+
assertThat(wxCpXmlMessage.getJobId()).isEqualTo("jobid_S0MrnndvRG5fadSlLwiBqiDDbM143UqTmKP3152FZk4");
443+
assertThat(wxCpXmlMessage.getEvent()).isEqualTo(UPLOAD_MEDIA_JOB_FINISH);
444+
}
424445
}

0 commit comments

Comments
 (0)