From 6786d7888ed796bb58d3f6232a54aecd55f305bc Mon Sep 17 00:00:00 2001 From: "jy.hu" Date: Mon, 5 May 2014 23:09:34 +0800 Subject: [PATCH] first commit --- .gitignore | 28 + pom.xml | 189 ++++ .../com/foxinmy/weixin4j/WeixinProxy.java | 912 ++++++++++++++++++ .../weixin4j/exception/WeixinException.java | 38 + .../foxinmy/weixin4j/http/HttpRequest.java | 133 +++ .../com/foxinmy/weixin4j/http/Parameter.java | 58 ++ .../com/foxinmy/weixin4j/http/Response.java | 122 +++ .../foxinmy/weixin4j/model/AuthResult.java | 89 ++ .../com/foxinmy/weixin4j/model/Button.java | 123 +++ .../com/foxinmy/weixin4j/model/Following.java | 86 ++ .../com/foxinmy/weixin4j/model/Group.java | 96 ++ .../com/foxinmy/weixin4j/model/MpArticle.java | 75 ++ .../foxinmy/weixin4j/model/QRParameter.java | 90 ++ .../com/foxinmy/weixin4j/model/Token.java | 75 ++ .../java/com/foxinmy/weixin4j/model/User.java | 205 ++++ .../com/foxinmy/weixin4j/model/UserToken.java | 47 + .../com/foxinmy/weixin4j/msg/BaseMessage.java | 126 +++ .../foxinmy/weixin4j/msg/ErrorMessage.java | 54 ++ .../com/foxinmy/weixin4j/msg/TextMessage.java | 53 + .../weixin4j/msg/event/EventMessage.java | 47 + .../msg/event/LocationEventMessage.java | 70 ++ .../weixin4j/msg/event/MassEventMessage.java | 82 ++ .../weixin4j/msg/event/MenuEventMessage.java | 47 + .../weixin4j/msg/event/ScanEventMessage.java | 62 ++ .../msg/event/ScribeEventMessage.java | 32 + .../foxinmy/weixin4j/msg/in/ImageMessage.java | 58 ++ .../foxinmy/weixin4j/msg/in/LinkMessage.java | 69 ++ .../weixin4j/msg/in/LocationMessage.java | 80 ++ .../foxinmy/weixin4j/msg/in/VideoMessage.java | 58 ++ .../foxinmy/weixin4j/msg/in/VoiceMessage.java | 71 ++ .../weixin4j/msg/notify/ArticleNotify.java | 105 ++ .../weixin4j/msg/notify/BaseNotify.java | 71 ++ .../weixin4j/msg/notify/ImageNotify.java | 49 + .../weixin4j/msg/notify/MusicNotify.java | 67 ++ .../weixin4j/msg/notify/TextNotify.java | 48 + .../weixin4j/msg/notify/VideoNotify.java | 58 ++ .../weixin4j/msg/notify/VoiceNotify.java | 49 + .../weixin4j/msg/out/ArticleMessage.java | 93 ++ .../weixin4j/msg/out/ImageMessage.java | 49 + .../weixin4j/msg/out/MusicMessage.java | 76 ++ .../weixin4j/msg/out/VideoMessage.java | 69 ++ .../weixin4j/msg/out/VoiceMessage.java | 55 ++ .../com/foxinmy/weixin4j/type/EventType.java | 30 + .../com/foxinmy/weixin4j/type/MediaType.java | 32 + .../foxinmy/weixin4j/type/MessageType.java | 39 + .../foxinmy/weixin4j/util/WeixinConfig.java | 41 + .../com/foxinmy/weixin4j/util/WeixinUtil.java | 8 + .../com/foxinmy/weixin4j/xml/XStream.java | 44 + src/main/resource/config.properties | 70 ++ src/main/resource/error_code.xml | 85 ++ src/main/resource/logback.xml | 62 ++ .../com/foxinmy/weixin4j/test/ButtonTest.java | 71 ++ .../com/foxinmy/weixin4j/test/GroupTest.java | 78 ++ .../com/foxinmy/weixin4j/test/MediaTest.java | 48 + .../foxinmy/weixin4j/test/MessageInTest.java | 270 ++++++ .../weixin4j/test/MessageNotifyTest.java | 89 ++ .../foxinmy/weixin4j/test/MessageOutTest.java | 74 ++ .../com/foxinmy/weixin4j/test/MpNewsTest.java | 67 ++ .../com/foxinmy/weixin4j/test/QRTest.java | 48 + .../com/foxinmy/weixin4j/test/TokenTest.java | 32 + .../com/foxinmy/weixin4j/test/UserTest.java | 50 + 61 files changed, 5302 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/foxinmy/weixin4j/WeixinProxy.java create mode 100644 src/main/java/com/foxinmy/weixin4j/exception/WeixinException.java create mode 100644 src/main/java/com/foxinmy/weixin4j/http/HttpRequest.java create mode 100644 src/main/java/com/foxinmy/weixin4j/http/Parameter.java create mode 100644 src/main/java/com/foxinmy/weixin4j/http/Response.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/AuthResult.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/Button.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/Following.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/Group.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/MpArticle.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/QRParameter.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/Token.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/User.java create mode 100644 src/main/java/com/foxinmy/weixin4j/model/UserToken.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/BaseMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/ErrorMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/TextMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/EventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/LocationEventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/MassEventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/MenuEventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/ScanEventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/event/ScribeEventMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/in/ImageMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/in/LinkMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/in/LocationMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/in/VideoMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/in/VoiceMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/ArticleNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/BaseNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/ImageNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/MusicNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/TextNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/VideoNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/notify/VoiceNotify.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/out/ArticleMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/out/ImageMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/out/MusicMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/out/VideoMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/msg/out/VoiceMessage.java create mode 100644 src/main/java/com/foxinmy/weixin4j/type/EventType.java create mode 100644 src/main/java/com/foxinmy/weixin4j/type/MediaType.java create mode 100644 src/main/java/com/foxinmy/weixin4j/type/MessageType.java create mode 100644 src/main/java/com/foxinmy/weixin4j/util/WeixinConfig.java create mode 100644 src/main/java/com/foxinmy/weixin4j/util/WeixinUtil.java create mode 100644 src/main/java/com/foxinmy/weixin4j/xml/XStream.java create mode 100644 src/main/resource/config.properties create mode 100644 src/main/resource/error_code.xml create mode 100644 src/main/resource/logback.xml create mode 100644 src/test/java/com/foxinmy/weixin4j/test/ButtonTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/GroupTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/MediaTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/MessageInTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/MessageNotifyTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/MessageOutTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/MpNewsTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/QRTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/TokenTest.java create mode 100644 src/test/java/com/foxinmy/weixin4j/test/UserTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4898b587 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +ass + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# eclipse ignore +*.settings/* +/.project +/.classpath +/.tomcatplugin + +# maven ignore +target/* + +# other ignore +*.log +*.tmp +Thumbs.db diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..7210f2e8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,189 @@ + + + 4.0.0 + com.foxinmy.weixin + 0.0.1-SNAPSHOT + weixin4j-sdk + weixin4j-sdk + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.7 + 1.7 + + 3.0 + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + ${project.build.sourceEncoding} + + + + + + + junit + junit + ${junit.version} + test + + + com.thoughtworks.xstream + xstream + ${xstream.version} + + + commons-codec + commons-codec + ${commons.codec.version} + + + commons-httpclient + commons-httpclient + ${commons.httpclient.version} + + + commons-logging + commons-logging + + + commons-codec + commons-codec + + + + + com.alibaba + fastjson + ${fastjson.version} + + + dom4j + dom4j + ${dom4j.version} + + + jaxen + jaxen + ${jaxen.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + logback-core + ch.qos.logback + + + + + org.slf4j + jcl-over-slf4j + ${jcl.over.version} + + + org.slf4j + slf4j-api + + + + + org.slf4j + log4j-over-slf4j + ${log4j.over.version} + + + org.slf4j + slf4j-api + + + + + org.jsoup + jsoup + 1.7.3 + + + + + + releases + Internal Releases + http://42.62.64.62:8081/nexus/content/repositories/releases/ + + + + snapshots + Internal Snapshots + http://42.62.64.62:8081/nexus/content/repositories/snapshots/ + + + + + + + true + + + true + + public + Public Repositories + http://42.62.64.62:8081/nexus/content/groups/public/ + + + + + + true + + + true + + public + Public Repositories + http://42.62.64.62:8081/nexus/content/groups/public/ + + + + 4.8.2 + 1.6.1 + 1.0.9 + 1.7.6 + 1.7.6 + 1.4.7 + 3.1 + 1.9 + 1.1.9 + 1.1.4 + UTF-8 + + + + default + + true + + + true + + + + diff --git a/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java b/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java new file mode 100644 index 00000000..17a2b0b0 --- /dev/null +++ b/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java @@ -0,0 +1,912 @@ +package com.foxinmy.weixin4j; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.foxinmy.weixin4j.exception.WeixinException; +import com.foxinmy.weixin4j.http.HttpRequest; +import com.foxinmy.weixin4j.http.Response; +import com.foxinmy.weixin4j.model.Button; +import com.foxinmy.weixin4j.model.Following; +import com.foxinmy.weixin4j.model.Group; +import com.foxinmy.weixin4j.model.MpArticle; +import com.foxinmy.weixin4j.model.QRParameter; +import com.foxinmy.weixin4j.model.Token; +import com.foxinmy.weixin4j.model.User; +import com.foxinmy.weixin4j.model.UserToken; +import com.foxinmy.weixin4j.msg.BaseMessage; +import com.foxinmy.weixin4j.msg.notify.BaseNotify; +import com.foxinmy.weixin4j.type.EventType; +import com.foxinmy.weixin4j.type.MediaType; +import com.foxinmy.weixin4j.type.MessageType; +import com.foxinmy.weixin4j.util.WeixinConfig; +import com.foxinmy.weixin4j.util.WeixinUtil; +import com.foxinmy.weixin4j.xml.XStream; + +/** + * 微信服务实现 + * + * @className WeixinServiceImpl + * @author jy.hu + * @date 2014年3月23日 + * @since JDK 1.7 + * @see api文档 + */ +public class WeixinProxy { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private HttpRequest request = new HttpRequest(); + + /** + * 验证微信签名 + * + * @param echostr + * 随机字符串 + * @param timestamp + * 时间戳 + * @param nonce + * 随机数 + * @param signature + * 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数 + * @return + * 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效 + * ,成为开发者成功,否则接入失败 + * @see 接入指南 + */ + public String signature(String echostr, String timestamp, String nonce, + String signature) { + String app_token = WeixinConfig.getValue("app_token"); + if (WeixinUtil.isBlank(app_token)) { + log.error("signature fail : token is null!"); + return null; + } + if (WeixinUtil.isBlank(echostr) || WeixinUtil.isBlank(timestamp) + || WeixinUtil.isBlank(nonce)) { + log.error("signature fail : invalid parameter!"); + return null; + } + String _signature = null; + try { + String[] a = { app_token, timestamp, nonce }; + Arrays.sort(a); + StringBuilder sb = new StringBuilder(3); + for (String str : a) { + sb.append(str); + } + _signature = DigestUtils.sha1Hex(sb.toString()); + } catch (Exception e) { + log.error("signature error", e); + } + if (signature.equals(_signature)) { + return echostr; + } else { + log.error("signature fail : invalid signature!"); + return null; + } + } + + /** + * xml消息转换为消息对象 + *

+ * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 + *

+ * + * @param xml + * 消息字符串 + * @return 消息对象 + * @throws DocumentException + * @see 验证消息的合法性 + * @see 普通消息 + * @see 事件触发 + * @see com.foxinmy.weixin4j.type.MessageType + * @see com.feican.weixin.msg.BaeMessage + * @see com.foxinmy.weixin4j.msg.TextMessage + * @see com.foxinmy.weixin4j.msg.in.ImageMessage + * @see com.foxinmy.weixin4j.msg.in.VoiceMessage + * @see com.foxinmy.weixin4j.msg.in.VideoMessage + * @see com.foxinmy.weixin4j.msg.in.LocationMessage + * @see com.foxinmy.weixin4j.msg.in.LinkMessage + * @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage + * @see com.foxinmy.weixin4j.msg.event.ScanEventMessage + * @see com.foxinmy.weixin4j.msg.event.LocationEventMessage + * @see com.foxinmy.weixin4j.msg.event.MenuEventMessage + */ + public BaseMessage xml2msg(String xmlStr) throws DocumentException { + if (WeixinUtil.isBlank(xmlStr)) + return null; + Document doc = DocumentHelper.parseText(xmlStr); + String type = doc.selectSingleNode("/xml/MsgType").getStringValue(); + if (WeixinUtil.isBlank(type)) { + return null; + } + XStream xstream = new XStream(); + MessageType messageType = MessageType.valueOf(type.toLowerCase()); + Class messageClass = messageType + .getMessageClass(); + if (messageType == MessageType.event) { + type = doc.selectSingleNode("/xml/Event").getStringValue(); + messageClass = EventType.valueOf(type.toLowerCase()) + .getEventClass(); + } + xstream.alias("xml", messageClass); + xstream.ignoreUnknownElements(); + xstream.autodetectAnnotations(true); + xstream.processAnnotations(messageClass); + return xstream.fromXML(doc.asXML(), messageClass); + } + + /** + * xml消息转换为消息对象 + * + * @param inputStream + * 带消息字符串的输入流 + * @return 消息对象 + * @throws DocumentException + * @see {@link com.foxinmy.weixin4j.WeixinProxy#xml2msg(String)} + */ + public BaseMessage xml2msg(InputStream inputStream) + throws DocumentException { + SAXReader reader = new SAXReader(); + Document doc = reader.read(inputStream); + return xml2msg(doc.asXML()); + } + + /** + * 获取token + *

+ * 正常情况下返回{"access_token":"ACCESS_TOKEN","expires_in":7200},否则抛出异常. + *

+ * + * @return token对象 + * @throws WeixinException + * @see 获取token说明 + * @see com.foxinmy.weixin4j.model.Token + */ + public Token getToken() throws WeixinException { + XStream xstream = new XStream(); + xstream.autodetectAnnotations(true); + xstream.processAnnotations(Token.class); + String appOpenId = WeixinConfig.getValue("app_openId"); + String token_path = WeixinConfig.getValue("token_path"); + File token_file = new File(String.format("%s/%s_token.xml", token_path, + appOpenId)); + Token token = null; + Calendar ca = Calendar.getInstance(); + long now_time = ca.getTimeInMillis(); + try { + if (token_file.exists()) { + token = (Token) xstream.fromXML(token_file); + + long expise_time = token.getTime() + + (token.getExpires_in() * 1000) - 5; + if (expise_time > now_time) { + return token; + } + } else { + try { + token_file.createNewFile(); + } catch (IOException e) { + token_file.getParentFile().mkdirs(); + } + } + token_path = WeixinConfig.getValue("api_token_uri"); + Response response = request.get(token_path); + token = response.getAsObject(Token.class); + token.setTime(now_time); + token.setOpenid(appOpenId); + xstream.toXML(token, new FileOutputStream(token_file)); + } catch (IOException e) { + log.error("获取token出错", e); + } + return token; + } + + /** + * 获取token + * + * @param code + * 用户授权后返回的code + * @return token对象 + * @throws WeixinException + * @see 获取用户token + * @see com.foxinmy.weixin4j.model.UserToken + */ + public UserToken getAccessToken(String code) throws WeixinException { + String user_token_uri = WeixinConfig.getValue("sns_user_token_uri"); + Response response = request.get(String.format(user_token_uri, code)); + // 暂时不保存用户token + return response.getAsObject(UserToken.class); + } + + /** + * 获取用户信息 + * + * @param token + * 授权票据 + * @return 用户对象 + * @throws WeixinException + * @see 拉取用户信息 + * @see com.foxinmy.weixin4j.model.User + * @see com.foxinmy.weixin4j.model.UserToken + * {@link com.foxinmy.weixin4j.WeixinProxy#getAccessToken(String)} + */ + public User getUser(UserToken token) throws WeixinException { + String user_info_uri = WeixinConfig.getValue("sns_user_info_uri"); + Response response = request.get(String.format(user_info_uri, + token.getAccess_token(), token.getOpenid())); + return response.getAsObject(User.class); + } + + /** + * 获取用户信息 + *

+ * 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的,对于不同公众号, + * 同一用户的openid不同),公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间 + *

+ * + * @param openId + * 用户对应的ID + * @return 用户对象 + * @throws WeixinException + * @see 获取用户信息 + * @see com.foxinmy.weixin4j.model.User + */ + public User getUser(String openId) throws WeixinException { + String user_info_uri = WeixinConfig.getValue("api_user_info_uri"); + Token token = getToken(); + Response response = request.get(String.format(user_info_uri, + token.getAccess_token(), openId)); + return response.getAsObject(User.class); + } + + /** + * 生成带参数的二维码 + * + * @param parameter + * @return byte数据包 + * @throws WeixinException + * @see {@link com.foxinmy.weixin4j.WeixinProxy#getQR(QRParameter)} + */ + public byte[] getQRData(QRParameter parameter) throws WeixinException { + Token token = getToken(); + String qr_uri = WeixinConfig.getValue("qr_ticket_uri"); + Response response = null; + try { + response = request.post(String.format(qr_uri, + token.getAccess_token()), + new StringRequestEntity(parameter.toJson(), + "application/json", "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + String ticket = response.getAsJson().getString("ticket"); + qr_uri = WeixinConfig.getValue("qr_image_uri"); + response = request.get(String.format(qr_uri, ticket)); + + return response.getBody(); + } + + /** + * 生成带参数的二维码 + *

+ * 二维码分为临时跟永久两种,扫描时触发推送带参数事件 + *

+ * + * @param parameter + * 二维码参数 + * @return 硬盘存储的文件对象 + * @throws WeixinException + * @see 二维码 + * @see com.foxinmy.weixin4j.model.QRParameter + */ + public File getQR(QRParameter parameter) throws WeixinException { + String qr_path = WeixinConfig.getValue("qr_path"); + String filename = String + .format("%s_%d_%d.jpg", parameter.getQrType().name(), + parameter.getScene_id(), parameter.getExpire_seconds()); + File file = new File(qr_path + File.separator + filename); + if (file.exists()) { + return file; + } + FileOutputStream out = null; + try { + try { + file.createNewFile(); + } catch (IOException e) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + out = new FileOutputStream(file); + byte[] b = getQRData(parameter); + out.write(b); + } catch (IOException e) { + + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + + } + } + return file; + } + + /** + * 上传媒体文件 + *

+ * 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}, + * 否则抛出异常. + *

+ * + * @param file + * 文件对象 + * @param mediaType + * 媒体类型 + * @return 上传到微信服务器返回的媒体标识 + * @throws WeixinException + * @see 上传下载说明 + * @see com.foxinmy.weixin4j.type.MediaType + */ + public String uploadMedia(File file, MediaType mediaType) + throws WeixinException { + byte[] b = null; + ByteArrayOutputStream out = null; + FileInputStream in = null; + try { + b = new byte[4096]; + out = new ByteArrayOutputStream(); + in = new FileInputStream(file); + int len = -1; + while ((len = in.read(b)) != -1) { + out.write(b, 0, len); + } + b = out.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + } + } + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + return uploadMedia(file.getName(), b, mediaType); + } + + /** + * 上传媒体文件 + * + * @param bytes + * 媒体数据包 + * @param mediaType + * 媒体类型 + * @return 上传到微信服务器返回的媒体标识 + * @throws WeixinException + * @see {@link com.foxinmy.weixin4j.WeixinProxy#uploadMedia(File, MediaType)} + */ + public String uploadMedia(String fileName, byte[] bytes, MediaType mediaType) + throws WeixinException { + Token token = getToken(); + String file_upload_uri = WeixinConfig.getValue("file_upload_uri"); + Response response = null; + + response = request.post(String.format(file_upload_uri, + token.getAccess_token(), mediaType.name()), new FilePart( + "media", new ByteArrayPartSource(fileName, bytes), null, + "UTF-8")); + + return response.getAsJson().getString("media_id"); + } + + /** + * 下载媒体文件 + *

+ * 正常情况下返回表头如Content-Type: image/jpeg,否则抛出异常. + *

+ * + * @param mediaId + * 存储在微信服务器上的媒体标识 + * @param mediaType + * 媒体类型 + * @return 写入硬盘后的文件对象 + * @throws WeixinException + * @see 上传下载说明 + * @see com.foxinmy.weixin4j.type.MediaType + */ + public File downloadMedia(String mediaId, MediaType mediaType) + throws WeixinException { + String media_path = WeixinConfig.getValue("media_path"); + String filename = mediaId + mediaType.getFormatType(); + File file = new File(media_path + File.separator + filename); + if (file.exists()) { + return file; + } + FileOutputStream out = null; + try { + try { + file.createNewFile(); + } catch (IOException e) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + out = new FileOutputStream(file); + byte[] b = downloadMediaData(mediaId, mediaType); + out.write(b); + } catch (IOException e) { + + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + + } + } + return file; + } + + /** + * 下载媒体文件 + * + * @param mediaId + * @param mediaType + * @return 二进制数据包 + * @throws WeixinException + * @see {@link com.foxinmy.weixin4j.WeixinProxy#downloadMedia(String, MediaType)} + */ + public byte[] downloadMediaData(String mediaId, MediaType mediaType) + throws WeixinException { + Token token = getToken(); + String file_download_uri = WeixinConfig.getValue("file_download_uri"); + Response response = request.get(String.format(file_download_uri, + token.getAccess_token(), mediaId)); + return response.getBody(); + } + + /** + * 发送客服消息(在48小时内不限制发送次数) + * + * @param notify + * 客服消息对象 + * @throws WeixinException + * @see 发送客服消息 + */ + public void sendNotify(BaseNotify notify) throws WeixinException { + String custom_notify_uri = WeixinConfig.getValue("custom_notify_uri"); + Token token = getToken(); + try { + request.post(String.format(custom_notify_uri, + token.getAccess_token(), notify.getTouser()), + new StringRequestEntity(notify.toJson(), + "application/json", "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + /** + * 创建分组 + * + * @param name + * 组名称 + * @return group对象 + * @throws WeixinException + * @see 创建分组 + * @see com.foxinmy.weixin4j.model.Group + * @see com.foxinmy.weixin4j.model.Group#toCreateJson() + */ + public Group createGroup(String name) throws WeixinException { + String group_create_uri = WeixinConfig.getValue("group_create_uri"); + Token token = getToken(); + Response response = null; + Group group = new Group(name); + try { + response = request.post(String.format(group_create_uri, + token.getAccess_token()), + new StringRequestEntity(group.toCreateJson(), + "application/json", "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + JSONObject obj = response.getAsJson(); + return JSON.parseObject(obj.getString("group"), Group.class); + } + + /** + * 查询所有分组 + * + * @return 组集合 + * @throws WeixinException + * @see 查询所有分组 + * @see com.foxinmy.weixin4j.model.Group + */ + public List getGroups() throws WeixinException { + String group_get_uri = WeixinConfig.getValue("group_get_uri"); + Token token = getToken(); + Response response = request.get(String.format(group_get_uri, + token.getAccess_token())); + JSONObject obj = response.getAsJson(); + return JSON.parseArray(obj.getString("groups"), Group.class); + } + + /** + * 查询用户所在分组 + * + * @param openId + * 用户对应的ID + * @return 组ID + * @throws WeixinException + * @see 查询用户所在分组 + * @see com.foxinmy.weixin4j.model.Group + */ + public int findGroupByOpenId(String openId) throws WeixinException { + String group_getid_uri = WeixinConfig.getValue("group_getid_uri"); + Token token = getToken(); + Response response = null; + try { + response = request.post( + String.format(group_getid_uri, token.getAccess_token()), + new StringRequestEntity(String.format( + "{\"openid\":\"%s\"}", openId), "application/json", + "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return response.getAsJson().getIntValue("groupid"); + } + + /** + * 修改分组名 + * + * @param groupId + * 组ID + * @param name + * 组名称 + * @throws WeixinException + * @see 修改分组名 + * @see com.foxinmy.weixin4j.model.Group + * @see com.foxinmy.weixin4j.model.Group#toModifyJson() + */ + public void modifyGroup(int groupId, String name) throws WeixinException { + String group_modify_uri = WeixinConfig.getValue("group_modify_uri"); + Token token = getToken(); + Group group = new Group(groupId, name); + try { + request.post(String.format(group_modify_uri, + token.getAccess_token()), + new StringRequestEntity(group.toModifyJson(), + "application/json", "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + /** + * 移动分组 + * + * @param openId + * 用户对应的ID + * @param groupId + * 组ID + * @throws WeixinException + * @see 移动分组 + * @see com.foxinmy.weixin4j.model.Group + */ + public void moveGroup(String openId, int groupId) throws WeixinException { + String group_move_uri = WeixinConfig.getValue("group_move_uri"); + Token token = getToken(); + try { + request.post( + String.format(group_move_uri, token.getAccess_token()), + new StringRequestEntity(String.format( + "{\"openid\":\"%s\",\"to_groupid\":%d}", openId, + groupId), "application/json", "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + /** + * 获取用户一定数量(10000)的关注者列表 + * + * @param nextOpenId + * 下一次拉取数据的openid + * @return 关注信息 + * @throws WeixinException + * @see 获取关注者列表 + * @see com.foxinmy.weixin4j.model.Following + */ + public Following getFollowing(String nextOpenId) throws WeixinException { + String fllowing_uri = WeixinConfig.getValue("following_uri"); + Token token = getToken(); + Response response = request.get(String.format(fllowing_uri, + token.getAccess_token(), nextOpenId == null ? "" : nextOpenId)); + + Following following = response.getAsObject(Following.class); + + if (following.getCount() > 0) { + List openIds = JSON.parseArray(following.getDataJson() + .getString("openid"), String.class); + List userList = new ArrayList(); + for (String openId : openIds) { + userList.add(getUser(openId)); + } + following.setUserList(userList); + } + return following; + } + + /** + * 获取用户全部的关注者列表 + *

+ * 当公众号关注者数量超过10000时,可通过填写next_openid的值,从而多次拉取列表的方式来满足需求, + * 将上一次调用得到的返回中的next_openid值,作为下一次调用中的next_openid值 + *

+ * + * @return 用户对象集合 + * @throws WeixinException + * @see 获取关注者列表 + * @see com.foxinmy.weixin4j.model.Following + * @see com.foxinmy.weixin4j.WeixinProxy#getFollowing(String) + */ + public List getAllFollowing() throws WeixinException { + List userList = new ArrayList(); + String nextOpenId = null; + Following f = null; + for (;;) { + f = getFollowing(nextOpenId); + if (f.getCount() == 0) { + break; + } + userList.addAll(f.getUserList()); + nextOpenId = f.getNextOpenId(); + } + return userList; + } + + /** + * 自定义菜单 + * + * @param btnList + * @throws WeixinException + * @see 创建自定义菜单 + * @see com.foxinmy.weixin4j.model.Button + */ + public void createMenu(List