MediaApi.java

package com.foxinmy.weixin4j.mp.api;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.deserializer.ExtraProcessor;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.ContentType;
import com.foxinmy.weixin4j.http.HttpHeaders;
import com.foxinmy.weixin4j.http.HttpMethod;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.HttpResponse;
import com.foxinmy.weixin4j.http.MimeType;
import com.foxinmy.weixin4j.http.apache.content.ByteArrayBody;
import com.foxinmy.weixin4j.http.apache.content.InputStreamBody;
import com.foxinmy.weixin4j.http.apache.content.StringBody;
import com.foxinmy.weixin4j.http.apache.mime.FormBodyPart;
import com.foxinmy.weixin4j.http.entity.StringEntity;
import com.foxinmy.weixin4j.http.weixin.ApiResult;
import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.media.MediaCounter;
import com.foxinmy.weixin4j.model.media.MediaDownloadResult;
import com.foxinmy.weixin4j.model.media.MediaItem;
import com.foxinmy.weixin4j.model.media.MediaRecord;
import com.foxinmy.weixin4j.model.media.MediaUploadResult;
import com.foxinmy.weixin4j.model.paging.Pageable;
import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.tuple.MpArticle;
import com.foxinmy.weixin4j.tuple.MpVideo;
import com.foxinmy.weixin4j.type.MediaType;
import com.foxinmy.weixin4j.util.Consts;
import com.foxinmy.weixin4j.util.FileUtil;
import com.foxinmy.weixin4j.util.IOUtil;
import com.foxinmy.weixin4j.util.ObjectId;
import com.foxinmy.weixin4j.util.RegexUtil;
import com.foxinmy.weixin4j.util.StringUtil;

/**
 * 素材相关API
 *
 * @className MediaApi
 * @author jinyu(foxinmy@gmail.com)
 * @date 2014年9月25日
 * @since JDK 1.6
 */
public class MediaApi extends MpApi {

	private final TokenManager tokenManager;

	public MediaApi(TokenManager tokenManager) {
		this.tokenManager = tokenManager;
	}

	/**
	 * 上传图片获取URL
	 * 请注意,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。
	 *
	 * @param is
	 *            图片数据流
	 * @param fileName
	 *            文件名 为空时将自动生成
	 * @return 图片URL 可用于群发消息中的图片链接和创建卡券logo链接
	 * @throws WeixinException
	 */
	public String uploadImage(InputStream is, String fileName)
			throws WeixinException {
		if (StringUtil.isBlank(fileName)) {
			fileName = ObjectId.get().toHexString();
		}
		if (StringUtil.isBlank(FileUtil.getFileExtension(fileName))) {
			fileName = String.format("%s.jpg", fileName);
		}
		String image_upload_uri = getRequestUri("image_upload_uri");
		MimeType mimeType = new MimeType("image",
				FileUtil.getFileExtension(fileName));
		Token token = tokenManager.getCache();
		WeixinResponse response = weixinExecutor.post(
				String.format(image_upload_uri, token.getAccessToken()),
				new FormBodyPart("media", new InputStreamBody(is, mimeType
						.toString(), fileName)));
		return response.getAsJson().getString("url");
	}

	/**
	 * 上传群发中的视频素材
	 *
	 * @param is
	 *            图片数据流
	 * @param fileName
	 *            文件名 为空时将自动生成
	 * @param title
	 *            视频标题 非空
	 * @param description
	 *            视频描述 可为空
	 * @return 群发视频消息对象
	 * @throws WeixinException
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">高级群发</a>
	 * @see com.foxinmy.weixin4j.tuple.MpVideo
	 */
	public MpVideo uploadVideo(InputStream is, String fileName, String title,
			String description) throws WeixinException {
		MediaUploadResult uploadResult = uploadMedia(false, is, fileName);
		JSONObject obj = new JSONObject();
		obj.put("media_id", uploadResult.getMediaId());
		obj.put("title", title);
		obj.put("description", description);
		String video_upload_uri = getRequestUri("video_upload_uri");
		Token token = tokenManager.getCache();
		WeixinResponse response = weixinExecutor.post(
				String.format(video_upload_uri, token.getAccessToken()),
				obj.toJSONString());

		String mediaId = response.getAsJson().getString("media_id");
		return new MpVideo(mediaId);
	}

	/**
	 * 上传媒体文件:图片(image)、语音(voice)、视频(video)和缩略图(thumb) </br> <font
	 * color="red">此接口只包含图片、语音、缩略图、视频(临时)四种媒体类型的上传</font>
	 * <p>
	 * 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789},
	 * 否则抛出异常.
	 * </p>
	 *
	 * @param isMaterial
	 *            是否永久上传
	 * @param is
	 *            媒体数据流
	 * @param fileName
	 *            文件名
	 * @return 上传到微信服务器返回的媒体标识
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738726&token=&lang=zh_CN">上传临时素材</a>
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久素材</a>
	 * @see com.foxinmy.weixin4j.model.media.MediaUploadResult
	 * @see com.foxinmy.weixin4j.type.MediaType
	 * @throws WeixinException
	 */
	public MediaUploadResult uploadMedia(boolean isMaterial, InputStream is,
			String fileName) throws WeixinException {
		byte[] content;
		try {
			content = IOUtil.toByteArray(is);
		} catch (IOException e) {
			throw new WeixinException(e);
		}
		if (StringUtil.isBlank(fileName)) {
			fileName = ObjectId.get().toHexString();
		}
		String suffixName = FileUtil.getFileExtension(fileName);
		if (StringUtil.isBlank(suffixName)) {
			suffixName = FileUtil
					.getFileType(new ByteArrayInputStream(content));
			fileName = String.format("%s.%s", fileName, suffixName);
		}
		MediaType mediaType;
		if (",bmp,png,jpeg,jpg,gif,"
				.contains(String.format(",%s,", suffixName))) {
			mediaType = MediaType.image;
		} else if (",mp3,wma,wav,amr,".contains(String.format(",%s,",
				suffixName))) {
			mediaType = MediaType.voice;
		} else if (",rm,rmvb,wmv,avi,mpg,mpeg,mp4,".contains(String.format(
				",%s,", suffixName))) {
			mediaType = MediaType.video;
		} else {
			throw new WeixinException("cannot handle mediaType:" + suffixName);
		}
		if (mediaType == MediaType.video && isMaterial) {
			throw new WeixinException(
					"please invoke uploadMaterialVideo method");
		}
		Token token = tokenManager.getCache();
		WeixinResponse response = null;
		try {
			if (isMaterial) {
				String material_media_upload_uri = getRequestUri("material_media_upload_uri");
				response = weixinExecutor.post(
						String.format(material_media_upload_uri,
								token.getAccessToken()),
						new FormBodyPart("media", new ByteArrayBody(content,
								mediaType.getMimeType().toString(), fileName)),
						new FormBodyPart("type", new StringBody(mediaType
								.name(), Consts.UTF_8)));
				JSONObject obj = response.getAsJson();
				return new MediaUploadResult(obj.getString("media_id"),
						mediaType, new Date(), obj.getString("url"));
			} else {
				String media_upload_uri = getRequestUri("media_upload_uri");
				response = weixinExecutor.post(String.format(media_upload_uri,
						token.getAccessToken(), mediaType.name()),
						new FormBodyPart("media", new InputStreamBody(
								new ByteArrayInputStream(content), mediaType
										.getMimeType().toString(), fileName)));
				JSONObject obj = response.getAsJson();
				return new MediaUploadResult(obj.getString("media_id"),
						obj.getObject("type", MediaType.class), new Date(
								obj.getLong("created_at") * 1000l),
						obj.getString("url"));
			}
		} catch (UnsupportedEncodingException e) {
			throw new WeixinException(e);
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				;
			}
		}
	}

	/**
	 * 下载媒体素材
	 *
	 * @param mediaId
	 *            媒体ID
	 * @param isMaterial
	 *            是否下载永久素材
	 * @return 媒体下载结果
	 *
	 * @throws WeixinException
	 * @see com.foxinmy.weixin4j.model.media.MediaDownloadResult
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727&token=&lang=zh_CN">下载临时媒体素材</a>
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738730&token=&lang=zh_CN">下载永久媒体素材</a>
	 */
	public MediaDownloadResult downloadMedia(String mediaId, boolean isMaterial)
			throws WeixinException {
		Token token = tokenManager.getCache();
		HttpRequest request = null;
		if (isMaterial) {
			String material_media_download_uri = getRequestUri("material_media_download_uri");
			request = new HttpRequest(HttpMethod.POST, String.format(
					material_media_download_uri, token.getAccessToken()));
			request.setEntity(new StringEntity(String.format(
					"{\"media_id\":\"%s\"}", mediaId)));
		} else {
			String meida_download_uri = getRequestUri("meida_download_uri");
			request = new HttpRequest(HttpMethod.GET, String.format(
					meida_download_uri, token.getAccessToken(), mediaId));
		}
		HttpResponse response = weixinExecutor.doRequest(request);
		HttpHeaders headers = response.getHeaders();
		String contentType = headers.getFirst(HttpHeaders.CONTENT_TYPE);
		String disposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
		String fileName = RegexUtil
				.regexFileNameFromContentDispositionHeader(disposition);
		if (StringUtil.isBlank(fileName)) {
			fileName = String.format("%s.%s", mediaId,
					contentType.split("/")[1]);
		}
		return new MediaDownloadResult(response.getContent(),
				ContentType.create(contentType), fileName);
	}

	/**
	 * 上传永久图文素材
	 * <p>
	 * 、新增的永久素材也可以在公众平台官网素材管理模块中看到,永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,
	 * 其他类型为1000
	 * </P>
	 *
	 * @param articles
	 *            图文列表
	 * @return 上传到微信服务器返回的媒体标识
	 * @throws WeixinException
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久媒体素材</a>
	 * @see com.foxinmy.weixin4j.tuple.MpArticle
	 */
	public String uploadMaterialArticle(List<MpArticle> articles)
			throws WeixinException {
		Token token = tokenManager.getCache();
		String material_article_upload_uri = getRequestUri("material_article_upload_uri");
		JSONObject obj = new JSONObject();
		obj.put("articles", articles);
		WeixinResponse response = weixinExecutor.post(
				String.format(material_article_upload_uri,
						token.getAccessToken()), obj.toJSONString());

		return response.getAsJson().getString("media_id");
	}

	/**
	 * 下载永久图文素材
	 *
	 * @param mediaId
	 *            媒体ID
	 * @return 图文列表
	 * @throws WeixinException
	 * @see {@link #downloadMedia(String, boolean)}
	 * @see com.foxinmy.weixin4j.tuple.MpArticle
	 */
	public List<MpArticle> downloadArticle(String mediaId)
			throws WeixinException {
		MediaDownloadResult result = downloadMedia(mediaId, true);
		byte[] content = result.getContent();
		JSONObject obj = JSON.parseObject(content, 0, content.length,
				Consts.UTF_8.newDecoder(), JSONObject.class);
		return JSON.parseArray(obj.getString("news_item"), MpArticle.class);
	}

	/**
	 * 更新永久图文素材
	 *
	 * @param mediaId
	 *            要修改的图文消息的id
	 * @param index
	 *            要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为0
	 * @param article
	 *            图文对象
	 * @return 处理结果
	 * @throws WeixinException
	 * @see com.foxinmy.weixin4j.tuple.MpArticle
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738732&token=&lang=zh_CN">更新永久图文素材</a>
	 */
	public ApiResult updateMaterialArticle(String mediaId, int index,
			MpArticle article) throws WeixinException {
		Token token = tokenManager.getCache();
		String material_article_update_uri = getRequestUri("material_article_update_uri");
		JSONObject obj = new JSONObject();
		obj.put("articles", article);
		obj.put("media_id", mediaId);
		obj.put("index", index);
		WeixinResponse response = weixinExecutor.post(
				String.format(material_article_update_uri,
						token.getAccessToken()), obj.toJSONString());

		return response.getAsResult();
	}

	/**
	 * 删除永久媒体素材
	 *
	 * @param mediaId
	 *            媒体素材的media_id
	 * @return 处理结果
	 * @throws WeixinException
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738731&token=&lang=zh_CN">删除永久媒体素材</a>
	 */
	public ApiResult deleteMaterialMedia(String mediaId) throws WeixinException {
		Token token = tokenManager.getCache();
		String material_media_del_uri = getRequestUri("material_media_del_uri");
		JSONObject obj = new JSONObject();
		obj.put("media_id", mediaId);
		WeixinResponse response = weixinExecutor.post(
				String.format(material_media_del_uri, token.getAccessToken()),
				obj.toJSONString());

		return response.getAsResult();
	}

	/**
	 * 上传永久视频素材
	 *
	 * @param is
	 *            大小不超过1M且格式为MP4的视频文件
	 * @param fileName
	 *            文件名 为空时将自动生成
	 * @param title
	 *            视频标题
	 * @param introduction
	 *            视频描述
	 * @return 上传到微信服务器返回的媒体标识
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">上传永久媒体素材</a>
	 * @throws WeixinException
	 */
	public String uploadMaterialVideo(InputStream is, String fileName,
			String title, String introduction) throws WeixinException {
		if (StringUtil.isBlank(fileName)) {
			fileName = ObjectId.get().toHexString();
		}
		if (StringUtil.isBlank(FileUtil.getFileExtension(fileName))) {
			fileName = String.format("%s.mp4", fileName);
		}
		String material_media_upload_uri = getRequestUri("material_media_upload_uri");
		MimeType mimeType = new MimeType("video",
				FileUtil.getFileExtension(fileName));
		Token token = tokenManager.getCache();
		try {
			JSONObject description = new JSONObject();
			description.put("title", title);
			description.put("introduction", introduction);
			WeixinResponse response = weixinExecutor.post(
					String.format(material_media_upload_uri,
							token.getAccessToken()),
					new FormBodyPart("media", new InputStreamBody(is, mimeType
							.toString(), fileName)),
					new FormBodyPart("description", new StringBody(description
							.toJSONString(), Consts.UTF_8)));
			return response.getAsJson().getString("media_id");
		} catch (UnsupportedEncodingException e) {
			throw new WeixinException(e);
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {
					;
				}
			}
		}
	}

	/**
	 * 获取永久媒体素材的总数</br> .图片和图文消息素材(包括单图文和多图文)的总数上限为5000,其他素材的总数上限为1000
	 *
	 * @return 总数对象
	 * @throws WeixinException
	 * @see com.foxinmy.weixin4j.model.media.MediaCounter
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738733&token=&lang=zh_CN">获取素材总数</a>
	 */
	public MediaCounter countMaterialMedia() throws WeixinException {
		Token token = tokenManager.getCache();
		String material_media_count_uri = getRequestUri("material_media_count_uri");
		WeixinResponse response = weixinExecutor.get(String.format(
				material_media_count_uri, token.getAccessToken()));

		return response.getAsObject(new TypeReference<MediaCounter>() {
		});
	}

	/**
	 * 获取媒体素材记录列表
	 *
	 * @param mediaType
	 *            素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)
	 * @param pageable
	 *            分页数据
	 * @return 媒体素材的记录对象
	 * @throws WeixinException
	 * @see com.foxinmy.weixin4j.model.media.MediaRecord
	 * @see com.foxinmy.weixin4j.type.MediaType
	 * @see com.foxinmy.weixin4j.model.media.MediaItem
	 * @see com.foxinmy.weixin4j.model.paging.Pageable
	 * @see com.foxinmy.weixin4j.model.paging.Pagedata
	 * @see <a href=
	 *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738734&token=&lang=zh_CN">获取素材列表</a>
	 */
	public MediaRecord listMaterialMedia(MediaType mediaType, Pageable pageable)
			throws WeixinException {
		Token token = tokenManager.getCache();
		String material_media_list_uri = getRequestUri("material_media_list_uri");
		JSONObject obj = new JSONObject();
		obj.put("type", mediaType.name());
		obj.put("offset", pageable.getOffset());
		obj.put("count", pageable.getPageSize());
		WeixinResponse response = weixinExecutor.post(
				String.format(material_media_list_uri, token.getAccessToken()),
				obj.toJSONString());
		obj = response.getAsJson();
		obj.put("items", obj.remove("item"));
		MediaRecord mediaRecord = null;
		if (mediaType == MediaType.news) {
			mediaRecord = JSON.parseObject(obj.toJSONString(),
					MediaRecord.class, new ExtraProcessor() {
						@Override
						public void processExtra(Object object, String key,
								Object value) {
							if (key.equals("content")) {
								((MediaItem) object).setArticles(JSON
										.parseArray(((JSONObject) value)
												.getString("news_item"),
												MpArticle.class));
							}
						}
					});
		} else {
			mediaRecord = JSON.toJavaObject(obj, MediaRecord.class);
		}
		mediaRecord.setMediaType(mediaType);
		mediaRecord.setPageable(pageable);
		return mediaRecord;
	}

	/**
	 * 获取全部的媒体素材
	 *
	 * @param mediaType
	 *            媒体类型
	 * @return 素材列表
	 * @see {@link #listMaterialMedia(MediaType, Pageable)}
	 * @throws WeixinException
	 */
	public List<MediaItem> listAllMaterialMedia(MediaType mediaType)
			throws WeixinException {
		Pageable pageable = new Pageable(1, 20);
		List<MediaItem> mediaList = new ArrayList<MediaItem>();
		MediaRecord mediaRecord = null;
		for (;;) {
			mediaRecord = listMaterialMedia(mediaType, pageable);
			if (mediaRecord.getItems() == null
					|| mediaRecord.getItems().isEmpty()) {
				break;
			}
			mediaList.addAll(mediaRecord.getItems());
			if (!mediaRecord.getPagedata().hasNext()) {
				break;
			}
			pageable = pageable.next();
		}
		return mediaList;
	}
}