View Javadoc
1   package com.foxinmy.weixin4j.qy.api;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.StringWriter;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.Date;
10  import java.util.LinkedHashMap;
11  import java.util.List;
12  import java.util.Map;
13  
14  import com.alibaba.fastjson.JSON;
15  import com.alibaba.fastjson.JSONArray;
16  import com.alibaba.fastjson.JSONObject;
17  import com.alibaba.fastjson.serializer.PropertyFilter;
18  import com.foxinmy.weixin4j.exception.WeixinException;
19  import com.foxinmy.weixin4j.http.ContentType;
20  import com.foxinmy.weixin4j.http.HttpHeaders;
21  import com.foxinmy.weixin4j.http.HttpMethod;
22  import com.foxinmy.weixin4j.http.HttpRequest;
23  import com.foxinmy.weixin4j.http.HttpResponse;
24  import com.foxinmy.weixin4j.http.MimeType;
25  import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
26  import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
27  import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
28  import com.foxinmy.weixin4j.http.weixin.ApiResult;
29  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
30  import com.foxinmy.weixin4j.model.Token;
31  import com.foxinmy.weixin4j.model.media.MediaCounter;
32  import com.foxinmy.weixin4j.model.media.MediaDownloadResult;
33  import com.foxinmy.weixin4j.model.media.MediaItem;
34  import com.foxinmy.weixin4j.model.media.MediaRecord;
35  import com.foxinmy.weixin4j.model.media.MediaUploadResult;
36  import com.foxinmy.weixin4j.model.paging.Pageable;
37  import com.foxinmy.weixin4j.qy.model.Callback;
38  import com.foxinmy.weixin4j.qy.model.Party;
39  import com.foxinmy.weixin4j.qy.model.User;
40  import com.foxinmy.weixin4j.token.TokenManager;
41  import com.foxinmy.weixin4j.tuple.MpArticle;
42  import com.foxinmy.weixin4j.type.MediaType;
43  import com.foxinmy.weixin4j.util.Consts;
44  import com.foxinmy.weixin4j.util.FileUtil;
45  import com.foxinmy.weixin4j.util.IOUtil;
46  import com.foxinmy.weixin4j.util.ObjectId;
47  import com.foxinmy.weixin4j.util.RegexUtil;
48  import com.foxinmy.weixin4j.util.StringUtil;
49  
50  /**
51   * 媒体相关API
52   *
53   * @className MediaApi
54   * @author jinyu(foxinmy@gmail.com)
55   * @date 2014年9月25日
56   * @since JDK 1.6
57   * @see com.foxinmy.weixin4j.type.MediaType
58   */
59  public class MediaApi extends QyApi {
60  
61  	private final TokenManager tokenManager;
62  
63  	public MediaApi(TokenManager tokenManager) {
64  		this.tokenManager = tokenManager;
65  	}
66  
67  	/**
68  	 * 上传图文消息内的图片:用于上传图片到企业号服务端,接口返回图片url,请注意,该url仅可用于图文消息的发送,
69  	 * 且每个企业每天最多只能上传100张图片。
70  	 *
71  	 * @param is
72  	 *            图片数据
73  	 * @param fileName
74  	 *            文件名
75  	 * @see <a href=
76  	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E5%9B%BE%E6%96%87%E6%B6%88%E6%81%AF%E5%86%85%E7%9A%84%E5%9B%BE%E7%89%87">上传图文消息内的图片</a>
77  	 * @return 图片url
78  	 * @throws WeixinException
79  	 */
80  	public String uploadImage(InputStream is, String fileName)
81  			throws WeixinException {
82  		if (StringUtil.isBlank(fileName)) {
83  			fileName = ObjectId.get().toHexString();
84  		}
85  		if (StringUtil.isBlank(FileUtil.getFileExtension(fileName))) {
86  			fileName = String.format("%s.jpg", fileName);
87  		}
88  		String media_uploadimg_uri = getRequestUri("media_uploadimg_uri");
89  		MimeType mimeType = new MimeType("image",
90  				FileUtil.getFileExtension(fileName));
91  		Token token = tokenManager.getCache();
92  		WeixinResponse response = weixinExecutor.post(
93  				String.format(media_uploadimg_uri, token.getAccessToken()),
94  				new FormBodyPart("media", new InputStreamBody(is, mimeType
95  						.toString(), fileName)));
96  		return response.getAsJson().getString("url");
97  	}
98  
99  	/**
100 	 * 上传媒体文件:分别有图片(image)、语音(voice)、视频(video),普通文件(file)
101 	 * <p>
102 	 * 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789},
103 	 * 否则抛出异常.
104 	 * </p>
105 	 *
106 	 * @param agentid
107 	 *            企业应用ID(<font color="red">大于0时视为上传永久媒体文件</font>)
108 	 * @param is
109 	 *            媒体数据流
110 	 * @param fileName
111 	 *            文件名
112 	 * @return 上传到微信服务器返回的媒体标识
113 	 * @see com.foxinmy.weixin4j.model.media.MediaUploadResult
114 	 * @see <a href= "https://work.weixin.qq.com/api/doc#10112">上传临时素材文件说明</a>
115 	 * @see <a href=
116 	 *      "http://http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90">上传永久素材文件说明</a>
117 	 * @throws WeixinException
118 	 */
119 	public MediaUploadResult uploadMedia(int agentid, InputStream is,
120 			String fileName) throws WeixinException {
121 		byte[] content;
122 		try {
123 			content = IOUtil.toByteArray(is);
124 		} catch (IOException e) {
125 			throw new WeixinException(e);
126 		}
127 		if (StringUtil.isBlank(fileName)) {
128 			fileName = ObjectId.get().toHexString();
129 		}
130 		String suffixName = FileUtil.getFileExtension(fileName);
131 		if (StringUtil.isBlank(suffixName)) {
132 			suffixName = FileUtil
133 					.getFileType(new ByteArrayInputStream(content));
134 			fileName = String.format("%s.%s", fileName, suffixName);
135 		}
136 		MediaType mediaType = MediaType.file;
137 		if (",bmp,png,jpeg,jpg,gif,"
138 				.contains(String.format(",%s,", suffixName))) {
139 			mediaType = MediaType.image;
140 		} else if (",mp3,wma,wav,amr,".contains(String.format(",%s,",
141 				suffixName))) {
142 			mediaType = MediaType.voice;
143 		} else if (",rm,rmvb,wmv,avi,mpg,mpeg,mp4,".contains(String.format(
144 				",%s,", suffixName))) {
145 			mediaType = MediaType.video;
146 		}
147 		Token token = tokenManager.getCache();
148 		try {
149 			WeixinResponse response = null;
150 			if (agentid > 0) {
151 				String material_media_upload_uri = getRequestUri("material_media_upload_uri");
152 				response = weixinExecutor.post(String.format(
153 						material_media_upload_uri, token.getAccessToken(),
154 						mediaType.name(), agentid), new FormBodyPart("media",
155 						new ByteArrayBody(content, mediaType.getMimeType()
156 								.toString(), fileName)));
157 				JSONObject obj = response.getAsJson();
158 				return new MediaUploadResult(obj.getString("media_id"),
159 						mediaType, new Date(), obj.getString("url"));
160 			} else {
161 				String media_upload_uri = getRequestUri("media_upload_uri");
162 				response = weixinExecutor.post(String.format(media_upload_uri,
163 						token.getAccessToken(), mediaType.name()),
164 						new FormBodyPart("media", new ByteArrayBody(content,
165 								mediaType.getMimeType().toString(), fileName)));
166 				JSONObject obj = response.getAsJson();
167 				return new MediaUploadResult(obj.getString("media_id"),
168 						obj.getObject("type", MediaType.class), new Date(
169 								obj.getLong("created_at") * 1000l),
170 						obj.getString("url"));
171 			}
172 		} finally {
173 			if (is != null) {
174 				try {
175 					is.close();
176 				} catch (IOException e) {
177 					;
178 				}
179 			}
180 		}
181 	}
182 
183 	/**
184 	 * 下载媒体文件
185 	 *
186 	 * @param agentid
187 	 *            企业应用Id(<font color="red">大于0时视为获取永久媒体文件</font>)
188 	 * @param mediaId
189 	 *            媒体ID
190 	 * @return 媒体下载结果
191 	 * @see com.foxinmy.weixin4j.model.media.MediaDownloadResult
192 	 * @see <a href= "https://work.weixin.qq.com/api/doc#10115">获取临时媒体说明</a>
193 	 * @see <a href=
194 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90">获取永久媒体说明</a>
195 	 * @throws WeixinException
196 	 */
197 	public MediaDownloadResult downloadMedia(int agentid, String mediaId)
198 			throws WeixinException {
199 		Token token = tokenManager.getCache();
200 		HttpRequest request = null;
201 		if (agentid > 0) {
202 			String material_media_download_uri = getRequestUri("material_media_download_uri");
203 			request = new HttpRequest(HttpMethod.GET, String.format(
204 					material_media_download_uri, token.getAccessToken(),
205 					mediaId, agentid));
206 		} else {
207 			String media_download_uri = getRequestUri("media_download_uri");
208 			request = new HttpRequest(HttpMethod.GET, String.format(
209 					media_download_uri, token.getAccessToken(), mediaId));
210 		}
211 		HttpResponse response = weixinExecutor.doRequest(request);
212 		HttpHeaders headers = response.getHeaders();
213 		String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
214 		String disposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
215 		String fileName = RegexUtil
216 				.regexFileNameFromContentDispositionHeader(disposition);
217 		if (StringUtil.isBlank(fileName)) {
218 			fileName = String.format("%s.%s", mediaId,
219 					contentType.split("/")[1]);
220 		}
221 		return new MediaDownloadResult(response.getContent(),
222 				ContentType.create(contentType), fileName);
223 	}
224 
225 	/**
226 	 * 上传永久图文素材
227 	 * <p>
228 	 * 、新增的永久素材也可以在公众平台官网素材管理模块中看到,永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,
229 	 * 其他类型为1000
230 	 * </P>
231 	 *
232 	 * @param agentid
233 	 *            企业应用的id
234 	 * @param articles
235 	 *            图文列表
236 	 * @return 上传到微信服务器返回的媒体标识
237 	 * @throws WeixinException
238 	 * @see <a href=
239 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90">上传永久媒体素材</a>
240 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
241 	 */
242 	public String uploadMaterialArticle(int agentid, List<MpArticle> articles)
243 			throws WeixinException {
244 		Token token = tokenManager.getCache();
245 		String material_article_upload_uri = getRequestUri("material_article_upload_uri");
246 		JSONObject obj = new JSONObject();
247 		obj.put("agentid", agentid);
248 		JSONObject news = new JSONObject();
249 		news.put("articles", articles);
250 		obj.put("mpnews", news);
251 		WeixinResponse response = weixinExecutor.post(
252 				String.format(material_article_upload_uri,
253 						token.getAccessToken()), obj.toJSONString());
254 
255 		return response.getAsJson().getString("media_id");
256 	}
257 
258 	/**
259 	 * 删除永久媒体素材
260 	 *
261 	 * @param agentid
262 	 *            企业应用ID
263 	 * @param mediaId
264 	 *            媒体素材的media_id
265 	 * @return 处理结果
266 	 * @throws WeixinException
267 	 * @see <a href=
268 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E5%88%A0%E9%99%A4%E6%B0%B8%E4%B9%85%E7%B4%A0%E6%9D%90">删除永久媒体素材</a>
269 	 */
270 	public ApiResult deleteMaterialMedia(int agentid, String mediaId)
271 			throws WeixinException {
272 		Token token = tokenManager.getCache();
273 		String material_media_del_uri = getRequestUri("material_media_del_uri");
274 		WeixinResponse response = weixinExecutor.get(String.format(
275 				material_media_del_uri, token.getAccessToken(), mediaId,
276 				agentid));
277 		return response.getAsResult();
278 	}
279 
280 	/**
281 	 * 下载永久图文素材
282 	 *
283 	 * @param agentid
284 	 *            企业应用ID
285 	 * @param mediaId
286 	 *            媒体素材的media_id
287 	 * @return 图文列表
288 	 * @throws WeixinException
289 	 * @see {@link #downloadMedia(int, String)}
290 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
291 	 */
292 	public List<MpArticle> downloadArticle(int agentid, String mediaId)
293 			throws WeixinException {
294 		MediaDownloadResult result = downloadMedia(agentid, mediaId);
295 		byte[] content = result.getContent();
296 		JSONObject obj = JSON.parseObject(content, 0, content.length,
297 				Consts.UTF_8.newDecoder(), JSONObject.class);
298 		return JSON.parseArray(obj.getJSONObject("mpnews")
299 				.getString("articles"), MpArticle.class);
300 	}
301 
302 	/**
303 	 * 修改永久图文素材
304 	 *
305 	 * @param agentid
306 	 *            企业应用的id
307 	 * @param mediaId
308 	 *            上传后的media_id
309 	 * @param articles
310 	 *            图文列表
311 	 * @return 操作结果
312 	 * @throws WeixinException
313 	 * @see <a href=
314 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E4%BF%AE%E6%94%B9%E6%B0%B8%E4%B9%85%E5%9B%BE%E6%96%87%E7%B4%A0%E6%9D%90">修改永久媒体素材</a>
315 	 * @see com.foxinmy.weixin4j.tuple.MpArticle
316 	 */
317 	public String updateMaterialArticle(int agentid, String mediaId,
318 			List<MpArticle> articles) throws WeixinException {
319 		Token token = tokenManager.getCache();
320 		String material_article_update_uri = getRequestUri("material_article_update_uri");
321 		JSONObject obj = new JSONObject();
322 		obj.put("agentid", agentid);
323 		JSONObject news = new JSONObject();
324 		news.put("articles", articles);
325 		obj.put("mpnews", news);
326 		obj.put("media_id", mediaId);
327 		WeixinResponse response = weixinExecutor.post(
328 				String.format(material_article_update_uri,
329 						token.getAccessToken()), obj.toJSONString());
330 
331 		return response.getAsJson().getString("media_id");
332 	}
333 
334 	/**
335 	 * 获取永久媒体素材的总数
336 	 *
337 	 * @param agentid
338 	 *            企业应用id
339 	 * @return 总数对象
340 	 * @throws WeixinException
341 	 * @see com.foxinmy.weixin4j.model.media.MediaCounter
342 	 * @see <a href=
343 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E7%B4%A0%E6%9D%90%E6%80%BB%E6%95%B0">获取素材总数</a>
344 	 */
345 	public MediaCounter countMaterialMedia(int agentid) throws WeixinException {
346 		Token token = tokenManager.getCache();
347 		String material_media_count_uri = getRequestUri("material_media_count_uri");
348 		WeixinResponse response = weixinExecutor.get(String.format(
349 				material_media_count_uri, token.getAccessToken(), agentid));
350 		JSONObject result = response.getAsJson();
351 		MediaCounter counter = JSON.toJavaObject(result, MediaCounter.class);
352 		counter.setNewsCount(result.getIntValue("mpnews_count"));
353 		return counter;
354 	}
355 
356 	/**
357 	 * 获取媒体素材记录列表
358 	 *
359 	 * @param agentid
360 	 *            企业应用ID
361 	 * @param mediaType
362 	 *            素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)、文件(file)
363 	 * @param pageable
364 	 *            分页数据
365 	 * @return 媒体素材的记录对象
366 	 * @throws WeixinException
367 	 * @see com.foxinmy.weixin4j.model.media.MediaRecord
368 	 * @see com.foxinmy.weixin4j.type.MediaType
369 	 * @see com.foxinmy.weixin4j.model.media.MediaItem
370 	 * @see com.foxinmy.weixin4j.model.paging.Pageable
371 	 * @see com.foxinmy.weixin4j.model.paging.Pagedata
372 	 * @see <a href=
373 	 *      "http://qydev.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96%E7%B4%A0%E6%9D%90%E5%88%97%E8%A1%A8">获取素材列表</a>
374 	 */
375 	public MediaRecord listMaterialMedia(int agentid, MediaType mediaType,
376 			Pageable pageable) throws WeixinException {
377 		Token token = tokenManager.getCache();
378 		String material_media_list_uri = getRequestUri("material_media_list_uri");
379 		JSONObject obj = new JSONObject();
380 		obj.put("agentid", agentid);
381 		obj.put("type",
382 				mediaType == MediaType.news ? "mpnews" : mediaType.name());
383 		obj.put("offset", pageable.getOffset());
384 		obj.put("count", pageable.getPageSize());
385 		WeixinResponse response = weixinExecutor.post(
386 				String.format(material_media_list_uri, token.getAccessToken()),
387 				obj.toJSONString());
388 		obj = response.getAsJson();
389 
390 		obj.put("items", obj.remove("itemlist"));
391 		MediaRecord mediaRecord = JSON.toJavaObject(obj, MediaRecord.class);
392 		mediaRecord.setMediaType(mediaType);
393 		mediaRecord.setPageable(pageable);
394 		return mediaRecord;
395 	}
396 
397 	/**
398 	 * 获取全部的媒体素材
399 	 *
400 	 * @param agentid
401 	 *            企业应用id
402 	 * @param mediaType
403 	 *            媒体类型
404 	 * @return 素材列表
405 	 * @see {@link #listMaterialMedia(int,MediaType, Pageable)}
406 	 * @throws WeixinException
407 	 */
408 	public List<MediaItem> listAllMaterialMedia(int agentid, MediaType mediaType)
409 			throws WeixinException {
410 		Pageable pageable = new Pageable(1, 20);
411 		List<MediaItem> mediaList = new ArrayList<MediaItem>();
412 		MediaRecord mediaRecord = null;
413 		for (;;) {
414 			mediaRecord = listMaterialMedia(agentid, mediaType, pageable);
415 			if (mediaRecord.getItems() == null
416 					|| mediaRecord.getItems().isEmpty()) {
417 				break;
418 			}
419 			mediaList.addAll(mediaRecord.getItems());
420 			if (!mediaRecord.getPagedata().hasNext()) {
421 				break;
422 			}
423 			pageable = pageable.next();
424 		}
425 		return mediaList;
426 	}
427 
428 	/**
429 	 * 批量上传成员
430 	 *
431 	 * @param users
432 	 *            成员列表
433 	 * @see {@link BatchApi#syncUser(String,Callback)}
434 	 * @see {@link BatchApi#replaceUser(String,Callback)}
435 	 * @see <a href= "https://work.weixin.qq.com/api/doc#10138">批量任务</a>
436 	 * @return 上传后的mediaId
437 	 * @throws WeixinException
438 	 */
439 	public String batchUploadUsers(List<User> users) throws WeixinException {
440 		return batchUpload("batch_syncuser.cvs", users);
441 	}
442 
443 	/**
444 	 * 批量上传部门
445 	 *
446 	 * @param parties
447 	 *            部门列表
448 	 * @see {@link BatchApi#replaceParty(String,Callback)}
449 	 * @see <a href= "https://work.weixin.qq.com/api/doc#10138">批量任务</a>
450 	 * @return 上传后的mediaId
451 	 * @throws WeixinException
452 	 */
453 	public String batchUploadParties(List<Party> parties)
454 			throws WeixinException {
455 		return batchUpload("batch_replaceparty.cvs", parties);
456 	}
457 
458 	private <T> String batchUpload(String batchName, List<T> models)
459 			throws WeixinException {
460 		StringWriter writer = new StringWriter();
461 		try {
462 			JSONObject csvObj = JSON.parseObject(weixinBundle().getString(
463 					batchName));
464 			JSONArray columns = csvObj.getJSONArray("column");
465 			writer.write(csvObj.getString("header"));
466 			final Map<String, Object> column = new LinkedHashMap<String, Object>();
467 			for (Object col : columns) {
468 				column.put(col.toString(), "");
469 			}
470 			writer.write("\r\n");
471 			for (T model : models) {
472 				JSON.toJSONString(model, new PropertyFilter() {
473 					@Override
474 					public boolean apply(Object object, String name,
475 							Object value) {
476 						if (column.containsKey(name)) {
477 							if (value instanceof Collection) {
478 								column.put(name,
479 										StringUtil.join(((Collection<?>) value)
480 												.iterator(), ';'));
481 							} else {
482 								column.put(name, value);
483 							}
484 						}
485 						return true;
486 					}
487 				});
488 				writer.write(StringUtil.join(column.values(), ','));
489 				writer.write("\r\n");
490 			}
491 			return uploadMedia(
492 					0,
493 					new ByteArrayInputStream(writer.getBuffer().toString()
494 							.getBytes(Consts.UTF_8)), batchName).getMediaId();
495 		} finally {
496 			try {
497 				writer.close();
498 			} catch (IOException e) {
499 				;
500 			}
501 		}
502 	}
503 }