View Javadoc
1   package com.foxinmy.weixin4j.mp.api;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.UnsupportedEncodingException;
7   import java.util.ArrayList;
8   import java.util.Date;
9   import java.util.List;
10  
11  import com.alibaba.fastjson.JSON;
12  import com.alibaba.fastjson.JSONObject;
13  import com.alibaba.fastjson.TypeReference;
14  import com.alibaba.fastjson.parser.deserializer.ExtraProcessor;
15  import com.foxinmy.weixin4j.exception.WeixinException;
16  import com.foxinmy.weixin4j.http.ContentType;
17  import com.foxinmy.weixin4j.http.HttpHeaders;
18  import com.foxinmy.weixin4j.http.HttpMethod;
19  import com.foxinmy.weixin4j.http.HttpRequest;
20  import com.foxinmy.weixin4j.http.HttpResponse;
21  import com.foxinmy.weixin4j.http.MimeType;
22  import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
23  import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
24  import com.foxinmy.weixin4j.http.apache.content.StringBody;
25  import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
26  import com.foxinmy.weixin4j.http.entity.StringEntity;
27  import com.foxinmy.weixin4j.http.weixin.ApiResult;
28  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
29  import com.foxinmy.weixin4j.model.Token;
30  import com.foxinmy.weixin4j.model.media.MediaCounter;
31  import com.foxinmy.weixin4j.model.media.MediaDownloadResult;
32  import com.foxinmy.weixin4j.model.media.MediaItem;
33  import com.foxinmy.weixin4j.model.media.MediaRecord;
34  import com.foxinmy.weixin4j.model.media.MediaUploadResult;
35  import com.foxinmy.weixin4j.model.paging.Pageable;
36  import com.foxinmy.weixin4j.token.TokenManager;
37  import com.foxinmy.weixin4j.tuple.MpArticle;
38  import com.foxinmy.weixin4j.tuple.MpVideo;
39  import com.foxinmy.weixin4j.type.MediaType;
40  import com.foxinmy.weixin4j.util.Consts;
41  import com.foxinmy.weixin4j.util.FileUtil;
42  import com.foxinmy.weixin4j.util.IOUtil;
43  import com.foxinmy.weixin4j.util.ObjectId;
44  import com.foxinmy.weixin4j.util.RegexUtil;
45  import com.foxinmy.weixin4j.util.StringUtil;
46  
47  /**
48   * 素材相关API
49   *
50   * @className MediaApi
51   * @author jinyu(foxinmy@gmail.com)
52   * @date 2014年9月25日
53   * @since JDK 1.6
54   */
55  public class MediaApi extends MpApi {
56  
57  	private final TokenManager tokenManager;
58  
59  	public MediaApi(TokenManager tokenManager) {
60  		this.tokenManager = tokenManager;
61  	}
62  
63  	/**
64  	 * 上传图片获取URL
65  	 * 请注意,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。
66  	 *
67  	 * @param is
68  	 *            图片数据流
69  	 * @param fileName
70  	 *            文件名 为空时将自动生成
71  	 * @return 图片URL 可用于群发消息中的图片链接和创建卡券logo链接
72  	 * @throws WeixinException
73  	 */
74  	public String uploadImage(InputStream is, String fileName)
75  			throws WeixinException {
76  		if (StringUtil.isBlank(fileName)) {
77  			fileName = ObjectId.get().toHexString();
78  		}
79  		if (StringUtil.isBlank(FileUtil.getFileExtension(fileName))) {
80  			fileName = String.format("%s.jpg", fileName);
81  		}
82  		String image_upload_uri = getRequestUri("image_upload_uri");
83  		MimeType mimeType = new MimeType("image",
84  				FileUtil.getFileExtension(fileName));
85  		Token token = tokenManager.getCache();
86  		WeixinResponse response = weixinExecutor.post(
87  				String.format(image_upload_uri, token.getAccessToken()),
88  				new FormBodyPart("media", new InputStreamBody(is, mimeType
89  						.toString(), fileName)));
90  		return response.getAsJson().getString("url");
91  	}
92  
93  	/**
94  	 * 上传群发中的视频素材
95  	 *
96  	 * @param is
97  	 *            图片数据流
98  	 * @param fileName
99  	 *            文件名 为空时将自动生成
100 	 * @param title
101 	 *            视频标题 非空
102 	 * @param description
103 	 *            视频描述 可为空
104 	 * @return 群发视频消息对象
105 	 * @throws WeixinException
106 	 * @see <a href=
107 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">高级群发</a>
108 	 * @see com.foxinmy.weixin4j.tuple.MpVideo
109 	 */
110 	public MpVideo uploadVideo(InputStream is, String fileName, String title,
111 			String description) throws WeixinException {
112 		MediaUploadResult uploadResult = uploadMedia(false, is, fileName);
113 		JSONObject obj = new JSONObject();
114 		obj.put("media_id", uploadResult.getMediaId());
115 		obj.put("title", title);
116 		obj.put("description", description);
117 		String video_upload_uri = getRequestUri("video_upload_uri");
118 		Token token = tokenManager.getCache();
119 		WeixinResponse response = weixinExecutor.post(
120 				String.format(video_upload_uri, token.getAccessToken()),
121 				obj.toJSONString());
122 
123 		String mediaId = response.getAsJson().getString("media_id");
124 		return new MpVideo(mediaId);
125 	}
126 
127 	/**
128 	 * 上传媒体文件:图片(image)、语音(voice)、视频(video)和缩略图(thumb) </br> <font
129 	 * color="red">此接口只包含图片、语音、缩略图、视频(临时)四种媒体类型的上传</font>
130 	 * <p>
131 	 * 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789},
132 	 * 否则抛出异常.
133 	 * </p>
134 	 *
135 	 * @param isMaterial
136 	 *            是否永久上传
137 	 * @param is
138 	 *            媒体数据流
139 	 * @param fileName
140 	 *            文件名
141 	 * @return 上传到微信服务器返回的媒体标识
142 	 * @see <a href=
143 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738726&token=&lang=zh_CN">上传临时素材</a>
144 	 * @see <a href=
145 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久素材</a>
146 	 * @see com.foxinmy.weixin4j.model.media.MediaUploadResult
147 	 * @see com.foxinmy.weixin4j.type.MediaType
148 	 * @throws WeixinException
149 	 */
150 	public MediaUploadResult uploadMedia(boolean isMaterial, InputStream is,
151 			String fileName) throws WeixinException {
152 		byte[] content;
153 		try {
154 			content = IOUtil.toByteArray(is);
155 		} catch (IOException e) {
156 			throw new WeixinException(e);
157 		}
158 		if (StringUtil.isBlank(fileName)) {
159 			fileName = ObjectId.get().toHexString();
160 		}
161 		String suffixName = FileUtil.getFileExtension(fileName);
162 		if (StringUtil.isBlank(suffixName)) {
163 			suffixName = FileUtil
164 					.getFileType(new ByteArrayInputStream(content));
165 			fileName = String.format("%s.%s", fileName, suffixName);
166 		}
167 		MediaType mediaType;
168 		if (",bmp,png,jpeg,jpg,gif,"
169 				.contains(String.format(",%s,", suffixName))) {
170 			mediaType = MediaType.image;
171 		} else if (",mp3,wma,wav,amr,".contains(String.format(",%s,",
172 				suffixName))) {
173 			mediaType = MediaType.voice;
174 		} else if (",rm,rmvb,wmv,avi,mpg,mpeg,mp4,".contains(String.format(
175 				",%s,", suffixName))) {
176 			mediaType = MediaType.video;
177 		} else {
178 			throw new WeixinException("cannot handle mediaType:" + suffixName);
179 		}
180 		if (mediaType == MediaType.video && isMaterial) {
181 			throw new WeixinException(
182 					"please invoke uploadMaterialVideo method");
183 		}
184 		Token token = tokenManager.getCache();
185 		WeixinResponse response = null;
186 		try {
187 			if (isMaterial) {
188 				String material_media_upload_uri = getRequestUri("material_media_upload_uri");
189 				response = weixinExecutor.post(
190 						String.format(material_media_upload_uri,
191 								token.getAccessToken()),
192 						new FormBodyPart("media", new ByteArrayBody(content,
193 								mediaType.getMimeType().toString(), fileName)),
194 						new FormBodyPart("type", new StringBody(mediaType
195 								.name(), Consts.UTF_8)));
196 				JSONObject obj = response.getAsJson();
197 				return new MediaUploadResult(obj.getString("media_id"),
198 						mediaType, new Date(), obj.getString("url"));
199 			} else {
200 				String media_upload_uri = getRequestUri("media_upload_uri");
201 				response = weixinExecutor.post(String.format(media_upload_uri,
202 						token.getAccessToken(), mediaType.name()),
203 						new FormBodyPart("media", new InputStreamBody(
204 								new ByteArrayInputStream(content), mediaType
205 										.getMimeType().toString(), fileName)));
206 				JSONObject obj = response.getAsJson();
207 				return new MediaUploadResult(obj.getString("media_id"),
208 						obj.getObject("type", MediaType.class), new Date(
209 								obj.getLong("created_at") * 1000l),
210 						obj.getString("url"));
211 			}
212 		} catch (UnsupportedEncodingException e) {
213 			throw new WeixinException(e);
214 		} finally {
215 			try {
216 				is.close();
217 			} catch (IOException e) {
218 				;
219 			}
220 		}
221 	}
222 
223 	/**
224 	 * 下载媒体素材
225 	 *
226 	 * @param mediaId
227 	 *            媒体ID
228 	 * @param isMaterial
229 	 *            是否下载永久素材
230 	 * @return 媒体下载结果
231 	 *
232 	 * @throws WeixinException
233 	 * @see com.foxinmy.weixin4j.model.media.MediaDownloadResult
234 	 * @see <a href=
235 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727&token=&lang=zh_CN">下载临时媒体素材</a>
236 	 * @see <a href=
237 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738730&token=&lang=zh_CN">下载永久媒体素材</a>
238 	 */
239 	public MediaDownloadResult downloadMedia(String mediaId, boolean isMaterial)
240 			throws WeixinException {
241 		Token token = tokenManager.getCache();
242 		HttpRequest request = null;
243 		if (isMaterial) {
244 			String material_media_download_uri = getRequestUri("material_media_download_uri");
245 			request = new HttpRequest(HttpMethod.POST, String.format(
246 					material_media_download_uri, token.getAccessToken()));
247 			request.setEntity(new StringEntity(String.format(
248 					"{\"media_id\":\"%s\"}", mediaId)));
249 		} else {
250 			String meida_download_uri = getRequestUri("meida_download_uri");
251 			request = new HttpRequest(HttpMethod.GET, String.format(
252 					meida_download_uri, token.getAccessToken(), mediaId));
253 		}
254 		HttpResponse response = weixinExecutor.doRequest(request);
255 		HttpHeaders headers = response.getHeaders();
256 		String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
257 		String disposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
258 		String fileName = RegexUtil
259 				.regexFileNameFromContentDispositionHeader(disposition);
260 		if (StringUtil.isBlank(fileName)) {
261 			fileName = String.format("%s.%s", mediaId,
262 					contentType.split("/")[1]);
263 		}
264 		return new MediaDownloadResult(response.getContent(),
265 				ContentType.create(contentType), fileName);
266 	}
267 
268 	/**
269 	 * 上传永久图文素材
270 	 * <p>
271 	 * 、新增的永久素材也可以在公众平台官网素材管理模块中看到,永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,
272 	 * 其他类型为1000
273 	 * </P>
274 	 *
275 	 * @param articles
276 	 *            图文列表
277 	 * @return 上传到微信服务器返回的媒体标识
278 	 * @throws WeixinException
279 	 * @see <a href=
280 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久媒体素材</a>
281 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
282 	 */
283 	public String uploadMaterialArticle(List<MpArticle> articles)
284 			throws WeixinException {
285 		Token token = tokenManager.getCache();
286 		String material_article_upload_uri = getRequestUri("material_article_upload_uri");
287 		JSONObject obj = new JSONObject();
288 		obj.put("articles", articles);
289 		WeixinResponse response = weixinExecutor.post(
290 				String.format(material_article_upload_uri,
291 						token.getAccessToken()), obj.toJSONString());
292 
293 		return response.getAsJson().getString("media_id");
294 	}
295 
296 	/**
297 	 * 下载永久图文素材
298 	 *
299 	 * @param mediaId
300 	 *            媒体ID
301 	 * @return 图文列表
302 	 * @throws WeixinException
303 	 * @see {@link #downloadMedia(String, boolean)}
304 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
305 	 */
306 	public List<MpArticle> downloadArticle(String mediaId)
307 			throws WeixinException {
308 		MediaDownloadResult result = downloadMedia(mediaId, true);
309 		byte[] content = result.getContent();
310 		JSONObject obj = JSON.parseObject(content, 0, content.length,
311 				Consts.UTF_8.newDecoder(), JSONObject.class);
312 		return JSON.parseArray(obj.getString("news_item"), MpArticle.class);
313 	}
314 
315 	/**
316 	 * 更新永久图文素材
317 	 *
318 	 * @param mediaId
319 	 *            要修改的图文消息的id
320 	 * @param index
321 	 *            要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为0
322 	 * @param article
323 	 *            图文对象
324 	 * @return 处理结果
325 	 * @throws WeixinException
326 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
327 	 * @see <a href=
328 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738732&token=&lang=zh_CN">更新永久图文素材</a>
329 	 */
330 	public ApiResult updateMaterialArticle(String mediaId, int index,
331 			MpArticle article) throws WeixinException {
332 		Token token = tokenManager.getCache();
333 		String material_article_update_uri = getRequestUri("material_article_update_uri");
334 		JSONObject obj = new JSONObject();
335 		obj.put("articles", article);
336 		obj.put("media_id", mediaId);
337 		obj.put("index", index);
338 		WeixinResponse response = weixinExecutor.post(
339 				String.format(material_article_update_uri,
340 						token.getAccessToken()), obj.toJSONString());
341 
342 		return response.getAsResult();
343 	}
344 
345 	/**
346 	 * 删除永久媒体素材
347 	 *
348 	 * @param mediaId
349 	 *            媒体素材的media_id
350 	 * @return 处理结果
351 	 * @throws WeixinException
352 	 * @see <a href=
353 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738731&token=&lang=zh_CN">删除永久媒体素材</a>
354 	 */
355 	public ApiResult deleteMaterialMedia(String mediaId) throws WeixinException {
356 		Token token = tokenManager.getCache();
357 		String material_media_del_uri = getRequestUri("material_media_del_uri");
358 		JSONObject obj = new JSONObject();
359 		obj.put("media_id", mediaId);
360 		WeixinResponse response = weixinExecutor.post(
361 				String.format(material_media_del_uri, token.getAccessToken()),
362 				obj.toJSONString());
363 
364 		return response.getAsResult();
365 	}
366 
367 	/**
368 	 * 上传永久视频素材
369 	 *
370 	 * @param is
371 	 *            大小不超过1M且格式为MP4的视频文件
372 	 * @param fileName
373 	 *            文件名 为空时将自动生成
374 	 * @param title
375 	 *            视频标题
376 	 * @param introduction
377 	 *            视频描述
378 	 * @return 上传到微信服务器返回的媒体标识
379 	 * @see <a href=
380 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久媒体素材</a>
381 	 * @throws WeixinException
382 	 */
383 	public String uploadMaterialVideo(InputStream is, String fileName,
384 			String title, String introduction) throws WeixinException {
385 		if (StringUtil.isBlank(fileName)) {
386 			fileName = ObjectId.get().toHexString();
387 		}
388 		if (StringUtil.isBlank(FileUtil.getFileExtension(fileName))) {
389 			fileName = String.format("%s.mp4", fileName);
390 		}
391 		String material_media_upload_uri = getRequestUri("material_media_upload_uri");
392 		MimeType mimeType = new MimeType("video",
393 				FileUtil.getFileExtension(fileName));
394 		Token token = tokenManager.getCache();
395 		try {
396 			JSONObject description = new JSONObject();
397 			description.put("title", title);
398 			description.put("introduction", introduction);
399 			WeixinResponse response = weixinExecutor.post(
400 					String.format(material_media_upload_uri,
401 							token.getAccessToken()),
402 					new FormBodyPart("media", new InputStreamBody(is, mimeType
403 							.toString(), fileName)),
404 					new FormBodyPart("description", new StringBody(description
405 							.toJSONString(), Consts.UTF_8)));
406 			return response.getAsJson().getString("media_id");
407 		} catch (UnsupportedEncodingException e) {
408 			throw new WeixinException(e);
409 		} finally {
410 			if (is != null) {
411 				try {
412 					is.close();
413 				} catch (IOException e) {
414 					;
415 				}
416 			}
417 		}
418 	}
419 
420 	/**
421 	 * 获取永久媒体素材的总数</br> .图片和图文消息素材(包括单图文和多图文)的总数上限为5000,其他素材的总数上限为1000
422 	 *
423 	 * @return 总数对象
424 	 * @throws WeixinException
425 	 * @see com.foxinmy.weixin4j.model.media.MediaCounter
426 	 * @see <a href=
427 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738733&token=&lang=zh_CN">获取素材总数</a>
428 	 */
429 	public MediaCounter countMaterialMedia() throws WeixinException {
430 		Token token = tokenManager.getCache();
431 		String material_media_count_uri = getRequestUri("material_media_count_uri");
432 		WeixinResponse response = weixinExecutor.get(String.format(
433 				material_media_count_uri, token.getAccessToken()));
434 
435 		return response.getAsObject(new TypeReference<MediaCounter>() {
436 		});
437 	}
438 
439 	/**
440 	 * 获取媒体素材记录列表
441 	 *
442 	 * @param mediaType
443 	 *            素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)
444 	 * @param pageable
445 	 *            分页数据
446 	 * @return 媒体素材的记录对象
447 	 * @throws WeixinException
448 	 * @see com.foxinmy.weixin4j.model.media.MediaRecord
449 	 * @see com.foxinmy.weixin4j.type.MediaType
450 	 * @see com.foxinmy.weixin4j.model.media.MediaItem
451 	 * @see com.foxinmy.weixin4j.model.paging.Pageable
452 	 * @see com.foxinmy.weixin4j.model.paging.Pagedata
453 	 * @see <a href=
454 	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738734&token=&lang=zh_CN">获取素材列表</a>
455 	 */
456 	public MediaRecord listMaterialMedia(MediaType mediaType, Pageable pageable)
457 			throws WeixinException {
458 		Token token = tokenManager.getCache();
459 		String material_media_list_uri = getRequestUri("material_media_list_uri");
460 		JSONObject obj = new JSONObject();
461 		obj.put("type", mediaType.name());
462 		obj.put("offset", pageable.getOffset());
463 		obj.put("count", pageable.getPageSize());
464 		WeixinResponse response = weixinExecutor.post(
465 				String.format(material_media_list_uri, token.getAccessToken()),
466 				obj.toJSONString());
467 		obj = response.getAsJson();
468 		obj.put("items", obj.remove("item"));
469 		MediaRecord mediaRecord = null;
470 		if (mediaType == MediaType.news) {
471 			mediaRecord = JSON.parseObject(obj.toJSONString(),
472 					MediaRecord.class, new ExtraProcessor() {
473 						@Override
474 						public void processExtra(Object object, String key,
475 								Object value) {
476 							if (key.equals("content")) {
477 								((MediaItem) object).setArticles(JSON
478 										.parseArray(((JSONObject) value)
479 												.getString("news_item"),
480 												MpArticle.class));
481 							}
482 						}
483 					});
484 		} else {
485 			mediaRecord = JSON.toJavaObject(obj, MediaRecord.class);
486 		}
487 		mediaRecord.setMediaType(mediaType);
488 		mediaRecord.setPageable(pageable);
489 		return mediaRecord;
490 	}
491 
492 	/**
493 	 * 获取全部的媒体素材
494 	 *
495 	 * @param mediaType
496 	 *            媒体类型
497 	 * @return 素材列表
498 	 * @see {@link #listMaterialMedia(MediaType, Pageable)}
499 	 * @throws WeixinException
500 	 */
501 	public List<MediaItem> listAllMaterialMedia(MediaType mediaType)
502 			throws WeixinException {
503 		Pageable pageable = new Pageable(1, 20);
504 		List<MediaItem> mediaList = new ArrayList<MediaItem>();
505 		MediaRecord mediaRecord = null;
506 		for (;;) {
507 			mediaRecord = listMaterialMedia(mediaType, pageable);
508 			if (mediaRecord.getItems() == null
509 					|| mediaRecord.getItems().isEmpty()) {
510 				break;
511 			}
512 			mediaList.addAll(mediaRecord.getItems());
513 			if (!mediaRecord.getPagedata().hasNext()) {
514 				break;
515 			}
516 			pageable = pageable.next();
517 		}
518 		return mediaList;
519 	}
520 }