From 328b3d373a6e3e570067e81488452d85115f5670 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Mon, 25 Aug 2014 14:11:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BE=A4=E5=8F=91=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chanjarster/weixin/api/WxService.java | 26 +++- .../chanjarster/weixin/api/WxServiceImpl.java | 40 +++-- .../weixin/bean/WxMassGroupMessage.java | 69 +++++++++ .../chanjarster/weixin/bean/WxMassNews.java | 23 ++- ...Message.java => WxMassOpenIdsMessage.java} | 43 ++---- .../chanjarster/weixin/bean/WxMassVideo.java | 18 ++- .../weixin/bean/result/WxMassSendResult.java | 6 + ...oadResult.java => WxMassUploadResult.java} | 11 +- .../weixin/util/json/WxGsonBuilder.java | 6 +- .../util/json/WxMassMessageGsonAdapter.java | 32 ++-- .../json/WxMassOpenIdsMessageGsonAdapter.java | 68 +++++++++ .../chanjarster/weixin/api/ApiTestModule.java | 24 ++- .../chanjarster/weixin/api/WxBaseAPITest.java | 91 ++--------- .../weixin/api/WxCustomMessageAPITest.java | 18 ++- .../weixin/api/WxMassMessageAPITest.java | 142 ++++++++++++++++++ .../weixin/api/WxMediaAPITest.java | 72 +++++++++ .../chanjarster/weixin/api/WxMenuAPITest.java | 5 + .../weixin/api/WxMessageRouterTest.java | 5 + src/test/resources/testng.xml | 5 +- 19 files changed, 532 insertions(+), 172 deletions(-) create mode 100644 src/main/java/chanjarster/weixin/bean/WxMassGroupMessage.java rename src/main/java/chanjarster/weixin/bean/{WxMassMessage.java => WxMassOpenIdsMessage.java} (63%) rename src/main/java/chanjarster/weixin/bean/result/{WxMassMaterialUploadResult.java => WxMassUploadResult.java} (73%) create mode 100644 src/main/java/chanjarster/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java create mode 100644 src/test/java/chanjarster/weixin/api/WxMassMessageAPITest.java create mode 100644 src/test/java/chanjarster/weixin/api/WxMediaAPITest.java diff --git a/src/main/java/chanjarster/weixin/api/WxService.java b/src/main/java/chanjarster/weixin/api/WxService.java index 1de32003..fffc5c69 100644 --- a/src/main/java/chanjarster/weixin/api/WxService.java +++ b/src/main/java/chanjarster/weixin/api/WxService.java @@ -5,11 +5,12 @@ import java.io.IOException; import java.io.InputStream; import chanjarster.weixin.bean.WxCustomMessage; -import chanjarster.weixin.bean.WxMassMessage; +import chanjarster.weixin.bean.WxMassGroupMessage; import chanjarster.weixin.bean.WxMassNews; +import chanjarster.weixin.bean.WxMassOpenIdsMessage; import chanjarster.weixin.bean.WxMassVideo; import chanjarster.weixin.bean.WxMenu; -import chanjarster.weixin.bean.result.WxMassMaterialUploadResult; +import chanjarster.weixin.bean.result.WxMassUploadResult; import chanjarster.weixin.bean.result.WxMassSendResult; import chanjarster.weixin.bean.result.WxMediaUploadResult; import chanjarster.weixin.exception.WxErrorException; @@ -104,7 +105,7 @@ public interface WxService { * @param news * @throws WxErrorException */ - public WxMassMaterialUploadResult uploadMassNews(WxMassNews news) throws WxErrorException; + public WxMassUploadResult uploadMassNews(WxMassNews news) throws WxErrorException; /** *
@@ -114,11 +115,11 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public WxMassMaterialUploadResult uploadMassVideo(WxMassVideo video) throws WxErrorException;
+  public WxMassUploadResult uploadMassVideo(WxMassVideo video) throws WxErrorException;
 
   /**
    * 
-   * 群发消息
+   * 分组群发消息
    * 如果发送图文消息,必须先使用 {@link #uploadMassNews(WxMassNews)} 获得media_id,然后再发送
    * 如果发送视频消息,必须先使用 {@link #uploadMassVideo(WxMassVideo)} 获得media_id,然后再发送
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
@@ -127,8 +128,21 @@ public interface WxService {
    * @throws WxErrorException
    * @return 
    */
-  public WxMassSendResult sendMassMessage(WxMassMessage message) throws WxErrorException;
+  public WxMassSendResult sendMassMessageByGroup(WxMassGroupMessage message) throws WxErrorException;
   
+  /**
+   * 
+   * 按openId列表群发消息
+   * 如果发送图文消息,必须先使用 {@link #uploadMassNews(WxMassNews)} 获得media_id,然后再发送
+   * 如果发送视频消息,必须先使用 {@link #uploadMassVideo(WxMassVideo)} 获得media_id,然后再发送
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
+   * 
+ * @param message + * @return + * @throws WxErrorException + */ + public WxMassSendResult sendMassMessageByOpenIds(WxMassOpenIdsMessage message) throws WxErrorException; + /** *
    * 自定义菜单创建接口
diff --git a/src/main/java/chanjarster/weixin/api/WxServiceImpl.java b/src/main/java/chanjarster/weixin/api/WxServiceImpl.java
index 826bef93..2a0ad0eb 100644
--- a/src/main/java/chanjarster/weixin/api/WxServiceImpl.java
+++ b/src/main/java/chanjarster/weixin/api/WxServiceImpl.java
@@ -18,13 +18,14 @@ import org.apache.http.impl.client.HttpClients;
 
 import chanjarster.weixin.bean.WxAccessToken;
 import chanjarster.weixin.bean.WxCustomMessage;
-import chanjarster.weixin.bean.WxMassMessage;
+import chanjarster.weixin.bean.WxMassGroupMessage;
 import chanjarster.weixin.bean.WxMassNews;
+import chanjarster.weixin.bean.WxMassOpenIdsMessage;
 import chanjarster.weixin.bean.WxMassVideo;
 import chanjarster.weixin.bean.WxMenu;
 import chanjarster.weixin.bean.result.WxError;
-import chanjarster.weixin.bean.result.WxMassMaterialUploadResult;
 import chanjarster.weixin.bean.result.WxMassSendResult;
+import chanjarster.weixin.bean.result.WxMassUploadResult;
 import chanjarster.weixin.bean.result.WxMediaUploadResult;
 import chanjarster.weixin.exception.WxErrorException;
 import chanjarster.weixin.util.fs.FileUtil;
@@ -47,6 +48,8 @@ public class WxServiceImpl implements WxService {
   
   protected WxConfigStorage wxConfigStorage;
   
+  protected final ThreadLocal retryTimes = new ThreadLocal();
+
   public boolean checkSignature(String timestamp, String nonce, String signature) {
     try {
       String token = wxConfigStorage.getToken();
@@ -156,24 +159,30 @@ public class WxServiceImpl implements WxService {
     return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id);
   }
 
-  public WxMassMaterialUploadResult uploadMassNews(WxMassNews news) throws WxErrorException {
+  public WxMassUploadResult uploadMassNews(WxMassNews news) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
     String responseContent = execute(new SimplePostRequestExecutor(), url, news.toJson());
-    return WxMassMaterialUploadResult.fromJson(responseContent);
+    return WxMassUploadResult.fromJson(responseContent);
   }
   
-  public WxMassMaterialUploadResult uploadMassVideo(WxMassVideo video) throws WxErrorException {
-    String url = " https://file.api.weixin.qq.com/cgi-bin/media/uploadvideo";
+  public WxMassUploadResult uploadMassVideo(WxMassVideo video) throws WxErrorException {
+    String url = "http://file.api.weixin.qq.com/cgi-bin/media/uploadvideo";
     String responseContent = execute(new SimplePostRequestExecutor(), url, video.toJson());
-    return WxMassMaterialUploadResult.fromJson(responseContent);
+    return WxMassUploadResult.fromJson(responseContent);
   }
   
-  public WxMassSendResult sendMassMessage(WxMassMessage message) throws WxErrorException {
+  public WxMassSendResult sendMassMessageByGroup(WxMassGroupMessage message) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
     String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
     return WxMassSendResult.fromJson(responseContent);
   }
 
+  public WxMassSendResult sendMassMessageByOpenIds(WxMassOpenIdsMessage message) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
+    String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
+    return WxMassSendResult.fromJson(responseContent);
+  }
+  
   /**
    * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
    * @param executor
@@ -205,12 +214,21 @@ public class WxServiceImpl implements WxService {
         return execute(executor, uri, data);
       }
       /**
-       * -1 系统繁忙, 500ms后重试
+       * -1 系统繁忙, 1000ms后重试
        */
       if (error.getErrcode() == -1) {
+        if(retryTimes.get() == null) {
+          retryTimes.set(0);
+        }
+        if (retryTimes.get() > 5) {
+          retryTimes.set(0);
+          throw new RuntimeException("微信服务端异常,超出重试次数");
+        }
+        int sleepMillis = 1000 *  (1 >> (retryTimes.get() - 1));
         try {
-          System.out.println("微信系统繁忙,500ms后重试");
-          Thread.sleep(500);
+          System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
+          Thread.sleep(sleepMillis);
+          retryTimes.set(retryTimes.get() + 1);
           return execute(executor, uri, data);
         } catch (InterruptedException e1) {
           throw new RuntimeException(e1);
diff --git a/src/main/java/chanjarster/weixin/bean/WxMassGroupMessage.java b/src/main/java/chanjarster/weixin/bean/WxMassGroupMessage.java
new file mode 100644
index 00000000..a0b692d0
--- /dev/null
+++ b/src/main/java/chanjarster/weixin/bean/WxMassGroupMessage.java
@@ -0,0 +1,69 @@
+package chanjarster.weixin.bean;
+
+import chanjarster.weixin.api.WxConsts;
+import chanjarster.weixin.util.json.WxGsonBuilder;
+
+/**
+ * 分组群发的消息
+ * 
+ * @author chanjarster
+ */
+public class WxMassGroupMessage {
+  
+  protected String group_id;
+  protected String msgtype;
+  protected String content;
+  protected String media_id;
+
+  public WxMassGroupMessage() {
+    super();
+  }
+  
+  public String getMsgtype() {
+    return msgtype;
+  }
+
+  /**
+   * 
+   * 请使用
+   * {@link WxConsts#MASS_MSG_IMAGE}
+   * {@link WxConsts#MASS_MSG_NEWS}
+   * {@link WxConsts#MASS_MSG_TEXT}
+   * {@link WxConsts#MASS_MSG_VIDEO}
+   * {@link WxConsts#MASS_MSG_VOICE}
+   * 
+ * @param msgtype + */ + public void setMsgtype(String msgtype) { + this.msgtype = msgtype; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getMedia_id() { + return media_id; + } + + public void setMedia_id(String media_id) { + this.media_id = media_id; + } + + public String toJson() { + return WxGsonBuilder.INSTANCE.create().toJson(this); + } + + public String getGroup_id() { + return group_id; + } + + public void setGroup_id(String group_id) { + this.group_id = group_id; + } + +} diff --git a/src/main/java/chanjarster/weixin/bean/WxMassNews.java b/src/main/java/chanjarster/weixin/bean/WxMassNews.java index 8207d80b..42d4a774 100644 --- a/src/main/java/chanjarster/weixin/bean/WxMassNews.java +++ b/src/main/java/chanjarster/weixin/bean/WxMassNews.java @@ -17,14 +17,29 @@ public class WxMassNews { public List getArticles() { return articles; } - public void setArticles(List articles) { - this.articles = articles; + + public void addArticle(WxMassNewsArticle article) { + this.articles.add(article); } public String toJson() { return WxGsonBuilder.INSTANCE.create().toJson(this); } + /** + *
+   * 群发图文消息article
+   * 1. thumb_media_id  (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得
+   * 2. author          图文消息的作者
+   * 3. title           (必填) 图文消息的标题
+   * 4. content_source_url 在图文消息页面点击“阅读原文”后的页面链接
+   * 5. content (必填)  图文消息页面的内容,支持HTML标签
+   * 6. digest          图文消息的描述
+   * 7, show_cover_pic  是否显示封面,true为显示,false为不显示
+   * 
+ * @author chanjarster + * + */ public static class WxMassNewsArticle { /** * (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 @@ -39,7 +54,7 @@ public class WxMassNews { */ protected String title; /** - * 在图文消息页面点击“阅读原文”后的页面 + * 在图文消息页面点击“阅读原文”后的页面链接 */ protected String content_source_url; /** @@ -51,7 +66,7 @@ public class WxMassNews { */ protected String digest; /** - * 是否显示封面,1为显示,0为不显示 + * 是否显示封面,true为显示,false为不显示 */ protected boolean show_cover_pic; diff --git a/src/main/java/chanjarster/weixin/bean/WxMassMessage.java b/src/main/java/chanjarster/weixin/bean/WxMassOpenIdsMessage.java similarity index 63% rename from src/main/java/chanjarster/weixin/bean/WxMassMessage.java rename to src/main/java/chanjarster/weixin/bean/WxMassOpenIdsMessage.java index 706ed0e9..d790f8ba 100644 --- a/src/main/java/chanjarster/weixin/bean/WxMassMessage.java +++ b/src/main/java/chanjarster/weixin/bean/WxMassOpenIdsMessage.java @@ -4,40 +4,24 @@ import java.util.ArrayList; import java.util.List; import chanjarster.weixin.api.WxConsts; -import chanjarster.weixin.bean.result.WxMassMaterialUploadResult; import chanjarster.weixin.util.json.WxGsonBuilder; /** - * 群发消息 + * OpenId列表群发的消息 * * @author chanjarster */ -public class WxMassMessage { +public class WxMassOpenIdsMessage { protected List touser = new ArrayList(); - protected String group_id; protected String msgtype; protected String content; protected String media_id; - public WxMassMessage() { + public WxMassOpenIdsMessage() { super(); } - /** - * 利用上传群发所用的素材的结果,构造群发消息 - * @param r - * @return - */ - public WxMassMessage(WxMassMaterialUploadResult r) { - setMedia_id(r.getMedia_id()); - if("video".equals(r.getType())) { - setMsgtype(WxConsts.MASS_MSG_VIDEO); - } else { - setMsgtype(r.getType()); - } - } - public String getMsgtype() { return msgtype; } @@ -77,20 +61,19 @@ public class WxMassMessage { return WxGsonBuilder.INSTANCE.create().toJson(this); } + /** + * OpenId列表,最多支持10,000个 + * @return + */ public List getTouser() { return touser; } - public void setTouser(List touser) { - this.touser = touser; - } - - public String getGroup_id() { - return group_id; - } - - public void setGroup_id(String group_id) { - this.group_id = group_id; + /** + * 添加OpenId,最多支持10,000个 + * @param openId + */ + public void addUser(String openId) { + this.touser.add(openId); } - } diff --git a/src/main/java/chanjarster/weixin/bean/WxMassVideo.java b/src/main/java/chanjarster/weixin/bean/WxMassVideo.java index 1084ad83..bd455e97 100644 --- a/src/main/java/chanjarster/weixin/bean/WxMassVideo.java +++ b/src/main/java/chanjarster/weixin/bean/WxMassVideo.java @@ -4,30 +4,40 @@ import chanjarster.weixin.util.json.WxGsonBuilder; /** * 群发时用到的视频素材 + * * @author chanjarster - * */ public class WxMassVideo { protected String media_id; protected String title; protected String description; - + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } - + + public String getMedia_id() { + return media_id; + } + + public void setMedia_id(String media_id) { + this.media_id = media_id; + } + public String toJson() { return WxGsonBuilder.INSTANCE.create().toJson(this); } - } diff --git a/src/main/java/chanjarster/weixin/bean/result/WxMassSendResult.java b/src/main/java/chanjarster/weixin/bean/result/WxMassSendResult.java index 92a53523..c40c710e 100644 --- a/src/main/java/chanjarster/weixin/bean/result/WxMassSendResult.java +++ b/src/main/java/chanjarster/weixin/bean/result/WxMassSendResult.java @@ -46,4 +46,10 @@ public class WxMassSendResult { public static WxMassSendResult fromJson(String json) { return WxGsonBuilder.create().fromJson(json, WxMassSendResult.class); } + + @Override + public String toString() { + return "WxMassSendResult [errcode=" + errcode + ", errmsg=" + errmsg + ", msg_id=" + msg_id + "]"; + } + } diff --git a/src/main/java/chanjarster/weixin/bean/result/WxMassMaterialUploadResult.java b/src/main/java/chanjarster/weixin/bean/result/WxMassUploadResult.java similarity index 73% rename from src/main/java/chanjarster/weixin/bean/result/WxMassMaterialUploadResult.java rename to src/main/java/chanjarster/weixin/bean/result/WxMassUploadResult.java index 5adbd118..503061f7 100644 --- a/src/main/java/chanjarster/weixin/bean/result/WxMassMaterialUploadResult.java +++ b/src/main/java/chanjarster/weixin/bean/result/WxMassUploadResult.java @@ -3,11 +3,14 @@ package chanjarster.weixin.bean.result; import chanjarster.weixin.util.json.WxGsonBuilder; /** - * 上传群发所用的素材的结果 + *
+ * 上传群发用的素材的结果
+ * 视频和图文消息需要在群发前上传素材
+ * 
* @author chanjarster * */ -public class WxMassMaterialUploadResult { +public class WxMassUploadResult { protected String type; protected String media_id; @@ -37,8 +40,8 @@ public class WxMassMaterialUploadResult { this.created_at = created_at; } - public static WxMassMaterialUploadResult fromJson(String json) { - return WxGsonBuilder.create().fromJson(json, WxMassMaterialUploadResult.class); + public static WxMassUploadResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMassUploadResult.class); } @Override diff --git a/src/main/java/chanjarster/weixin/util/json/WxGsonBuilder.java b/src/main/java/chanjarster/weixin/util/json/WxGsonBuilder.java index 76e97941..ddad36b7 100644 --- a/src/main/java/chanjarster/weixin/util/json/WxGsonBuilder.java +++ b/src/main/java/chanjarster/weixin/util/json/WxGsonBuilder.java @@ -1,8 +1,9 @@ package chanjarster.weixin.util.json; import chanjarster.weixin.bean.WxCustomMessage; -import chanjarster.weixin.bean.WxMassMessage; +import chanjarster.weixin.bean.WxMassGroupMessage; import chanjarster.weixin.bean.WxMassNews; +import chanjarster.weixin.bean.WxMassOpenIdsMessage; import chanjarster.weixin.bean.WxMenu; import com.google.gson.Gson; @@ -17,7 +18,8 @@ public class WxGsonBuilder { INSTANCE.registerTypeAdapter(WxCustomMessage.class, new WxCustomMessageGsonAdapter()); INSTANCE.registerTypeAdapter(WxMenu.class, new WxMenuGsonAdapter()); INSTANCE.registerTypeAdapter(WxMassNews.class, new WxMassNewsGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMassMessage.class, new WxMassMessageGsonAdapter()); + INSTANCE.registerTypeAdapter(WxMassGroupMessage.class, new WxMassMessageGsonAdapter()); + INSTANCE.registerTypeAdapter(WxMassOpenIdsMessage.class, new WxMassOpenIdsMessageGsonAdapter()); } public static Gson create() { diff --git a/src/main/java/chanjarster/weixin/util/json/WxMassMessageGsonAdapter.java b/src/main/java/chanjarster/weixin/util/json/WxMassMessageGsonAdapter.java index db1efae8..dd8632bb 100644 --- a/src/main/java/chanjarster/weixin/util/json/WxMassMessageGsonAdapter.java +++ b/src/main/java/chanjarster/weixin/util/json/WxMassMessageGsonAdapter.java @@ -11,12 +11,10 @@ package chanjarster.weixin.util.json; import java.lang.reflect.Type; import chanjarster.weixin.api.WxConsts; -import chanjarster.weixin.bean.WxMassMessage; +import chanjarster.weixin.bean.WxMassGroupMessage; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; @@ -25,47 +23,39 @@ import com.google.gson.JsonSerializer; * @author qianjia * */ -public class WxMassMessageGsonAdapter implements JsonSerializer { +public class WxMassMessageGsonAdapter implements JsonSerializer { - public JsonElement serialize(WxMassMessage message, Type typeOfSrc, JsonSerializationContext context) { + public JsonElement serialize(WxMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) { JsonObject messageJson = new JsonObject(); - if (message.getTouser().size() > 0) { - JsonArray sub = new JsonArray(); - for(String openId : message.getTouser()) { - sub.add(new JsonPrimitive(openId)); - } - messageJson.add("touser", sub); - } else { - JsonObject sub = new JsonObject(); - sub.addProperty("group_id", message.getGroup_id()); - messageJson.add("filter", sub); - } + JsonObject filter = new JsonObject(); + filter.addProperty("group_id", message.getGroup_id()); + messageJson.add("filter", filter); if (WxConsts.MASS_MSG_NEWS.equals(message.getMsgtype())) { JsonObject sub = new JsonObject(); sub.addProperty("media_id", message.getMedia_id()); - messageJson.add("mpnews", sub); + messageJson.add(WxConsts.MASS_MSG_NEWS, sub); } if (WxConsts.MASS_MSG_TEXT.equals(message.getMsgtype())) { JsonObject sub = new JsonObject(); sub.addProperty("content", message.getContent()); - messageJson.add("text", sub); + messageJson.add(WxConsts.MASS_MSG_TEXT, sub); } if (WxConsts.MASS_MSG_VOICE.equals(message.getMsgtype())) { JsonObject sub = new JsonObject(); sub.addProperty("media_id", message.getMedia_id()); - messageJson.add("voice", sub); + messageJson.add(WxConsts.MASS_MSG_VOICE, sub); } if (WxConsts.MASS_MSG_IMAGE.equals(message.getMsgtype())) { JsonObject sub = new JsonObject(); sub.addProperty("media_id", message.getMedia_id()); - messageJson.add("image", sub); + messageJson.add(WxConsts.MASS_MSG_IMAGE, sub); } if (WxConsts.MASS_MSG_VIDEO.equals(message.getMsgtype())) { JsonObject sub = new JsonObject(); sub.addProperty("media_id", message.getMedia_id()); - messageJson.add("mpvideo", sub); + messageJson.add(WxConsts.MASS_MSG_VIDEO, sub); } messageJson.addProperty("msgtype", message.getMsgtype()); return messageJson; diff --git a/src/main/java/chanjarster/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java b/src/main/java/chanjarster/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java new file mode 100644 index 00000000..a4311d58 --- /dev/null +++ b/src/main/java/chanjarster/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java @@ -0,0 +1,68 @@ +/* + * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. + * + * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended + * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction + * arose from modification of the original source, or other redistribution of this source + * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. + */ +package chanjarster.weixin.util.json; + +import java.lang.reflect.Type; + +import chanjarster.weixin.api.WxConsts; +import chanjarster.weixin.bean.WxMassOpenIdsMessage; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * + * @author qianjia + * + */ +public class WxMassOpenIdsMessageGsonAdapter implements JsonSerializer { + + public JsonElement serialize(WxMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) { + JsonObject messageJson = new JsonObject(); + + JsonArray touser = new JsonArray(); + for (String openId : message.getTouser()) { + touser.add(new JsonPrimitive(openId)); + } + messageJson.add("touser", touser); + + if (WxConsts.MASS_MSG_NEWS.equals(message.getMsgtype())) { + JsonObject sub = new JsonObject(); + sub.addProperty("media_id", message.getMedia_id()); + messageJson.add(WxConsts.MASS_MSG_NEWS, sub); + } + if (WxConsts.MASS_MSG_TEXT.equals(message.getMsgtype())) { + JsonObject sub = new JsonObject(); + sub.addProperty("content", message.getContent()); + messageJson.add(WxConsts.MASS_MSG_TEXT, sub); + } + if (WxConsts.MASS_MSG_VOICE.equals(message.getMsgtype())) { + JsonObject sub = new JsonObject(); + sub.addProperty("media_id", message.getMedia_id()); + messageJson.add(WxConsts.MASS_MSG_VOICE, sub); + } + if (WxConsts.MASS_MSG_IMAGE.equals(message.getMsgtype())) { + JsonObject sub = new JsonObject(); + sub.addProperty("media_id", message.getMedia_id()); + messageJson.add(WxConsts.MASS_MSG_IMAGE, sub); + } + if (WxConsts.MASS_MSG_VIDEO.equals(message.getMsgtype())) { + JsonObject sub = new JsonObject(); + sub.addProperty("media_id", message.getMedia_id()); + messageJson.add(WxConsts.MASS_MSG_VIDEO, sub); + } + messageJson.addProperty("msgtype", message.getMsgtype()); + return messageJson; + } + +} diff --git a/src/test/java/chanjarster/weixin/api/ApiTestModule.java b/src/test/java/chanjarster/weixin/api/ApiTestModule.java index 19f255a3..ef95f193 100644 --- a/src/test/java/chanjarster/weixin/api/ApiTestModule.java +++ b/src/test/java/chanjarster/weixin/api/ApiTestModule.java @@ -3,8 +3,10 @@ package chanjarster.weixin.api; import java.io.InputStream; import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; -import chanjarster.weixin.api.WxBaseAPITest.WxXmlConfigStorage; import chanjarster.weixin.util.xml.XmlTransformer; import com.google.inject.Binder; @@ -26,4 +28,24 @@ public class ApiTestModule implements Module { } } + @XmlRootElement(name = "xml") + @XmlAccessorType(XmlAccessType.FIELD) + public static class WxXmlConfigStorage extends WxInMemoryConfigStorage { + + protected String openId; + + public String getOpenId() { + return openId; + } + public void setOpenId(String openId) { + this.openId = openId; + } + @Override + public String toString() { + return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken + + ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; + } + + } + } diff --git a/src/test/java/chanjarster/weixin/api/WxBaseAPITest.java b/src/test/java/chanjarster/weixin/api/WxBaseAPITest.java index 6fcb1f7a..3002f2dd 100644 --- a/src/test/java/chanjarster/weixin/api/WxBaseAPITest.java +++ b/src/test/java/chanjarster/weixin/api/WxBaseAPITest.java @@ -1,113 +1,42 @@ package chanjarster.weixin.api; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlRootElement; - import org.apache.commons.lang3.StringUtils; import org.testng.Assert; -import org.testng.annotations.DataProvider; import org.testng.annotations.Guice; import org.testng.annotations.Test; -import chanjarster.weixin.bean.result.WxMediaUploadResult; import chanjarster.weixin.exception.WxErrorException; import com.google.inject.Inject; -@Test(groups="baseAPI") +/** + * 基础API测试 + * @author chanjarster + * + */ +@Test(groups = "baseAPI") @Guice(modules = ApiTestModule.class) public class WxBaseAPITest { @Inject protected WxServiceImpl wxService; - private List media_ids = new ArrayList(); - - @Test public void testRefreshAccessToken() throws WxErrorException { WxConfigStorage configStorage = wxService.wxConfigStorage; String before = configStorage.getAccessToken(); wxService.refreshAccessToken(); - + String after = configStorage.getAccessToken(); - + Assert.assertNotEquals(before, after); Assert.assertTrue(StringUtils.isNotBlank(after)); } - - @Test(dependsOnMethods = { "testRefreshAccessToken" }, dataProvider="uploadMedia", enabled = true) - public void testUploadMedia(String mediaType, String fileType, String fileName) throws WxErrorException, IOException { - InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName); - WxMediaUploadResult res = wxService.uploadMedia(mediaType, fileType, inputStream); - Assert.assertNotNull(res.getType()); - Assert.assertNotNull(res.getCreated_at()); - Assert.assertTrue(res.getMedia_id() != null || res.getThumb_media_id() != null); - - if (res.getMedia_id() != null) { - media_ids.add(res.getMedia_id()); - } - if (res.getThumb_media_id() != null) { - media_ids.add(res.getThumb_media_id()); - } - } - - @DataProvider - public Object[][] uploadMedia() { - return new Object[][] { - new Object[] { WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, "mm.jpeg" }, - new Object[] { WxConsts.MEDIA_VOICE, WxConsts.FILE_MP3, "mm.mp3" }, - new Object[] { WxConsts.MEDIA_VIDEO, WxConsts.FILE_MP4, "mm.mp4" }, - new Object[] { WxConsts.MEDIA_THUMB, WxConsts.FILE_JPG, "mm.jpeg" } - }; - } - - @Test(dependsOnMethods = { "testUploadMedia" }, dataProvider="downloadMedia") - public void testDownloadMedia(String media_id) throws WxErrorException { - wxService.downloadMedia(media_id); - } - - @DataProvider - public Object[][] downloadMedia() { - Object[][] params = new Object[this.media_ids.size()][]; - for (int i = 0; i < this.media_ids.size(); i++) { - params[i] = new Object[] { this.media_ids.get(i) }; - } - return params; - } - - @Test(enabled = true) + public void testCheckSignature() throws WxErrorException { String timestamp = "23234235423246"; String nonce = "y7didfkcmvnbd90sdofjkiefhsd"; String signature = "77b6651628dfb9a64bfb0d3432ee053ac566a459"; Assert.assertTrue(wxService.checkSignature(timestamp, nonce, signature)); } - - @XmlRootElement(name = "xml") - @XmlAccessorType(XmlAccessType.FIELD) - public static class WxXmlConfigStorage extends WxInMemoryConfigStorage { - - protected String openId; - - public String getOpenId() { - return openId; - } - public void setOpenId(String openId) { - this.openId = openId; - } - @Override - public String toString() { - return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken - + ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; - } - - } - - + } diff --git a/src/test/java/chanjarster/weixin/api/WxCustomMessageAPITest.java b/src/test/java/chanjarster/weixin/api/WxCustomMessageAPITest.java index b2a99d52..ce99bdd6 100644 --- a/src/test/java/chanjarster/weixin/api/WxCustomMessageAPITest.java +++ b/src/test/java/chanjarster/weixin/api/WxCustomMessageAPITest.java @@ -3,20 +3,24 @@ package chanjarster.weixin.api; import org.testng.annotations.Guice; import org.testng.annotations.Test; -import com.google.inject.Inject; - -import chanjarster.weixin.api.WxBaseAPITest.WxXmlConfigStorage; +import chanjarster.weixin.api.ApiTestModule.WxXmlConfigStorage; import chanjarster.weixin.bean.WxCustomMessage; import chanjarster.weixin.exception.WxErrorException; -@Test(dependsOnGroups="baseAPI") +import com.google.inject.Inject; + +/*** + * 测试发送客服消息 + * @author chanjarster + * + */ +@Test(groups="customMessageAPI", dependsOnGroups = "baseAPI") @Guice(modules = ApiTestModule.class) public class WxCustomMessageAPITest { @Inject protected WxServiceImpl wxService; - - @Test(enabled = true) + public void testSendCustomMessage() throws WxErrorException { WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; WxCustomMessage message = new WxCustomMessage(); @@ -26,5 +30,5 @@ public class WxCustomMessageAPITest { wxService.sendCustomMessage(message); } - + } diff --git a/src/test/java/chanjarster/weixin/api/WxMassMessageAPITest.java b/src/test/java/chanjarster/weixin/api/WxMassMessageAPITest.java new file mode 100644 index 00000000..7ffbdbf7 --- /dev/null +++ b/src/test/java/chanjarster/weixin/api/WxMassMessageAPITest.java @@ -0,0 +1,142 @@ +package chanjarster.weixin.api; + +import java.io.IOException; +import java.io.InputStream; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import chanjarster.weixin.api.ApiTestModule.WxXmlConfigStorage; +import chanjarster.weixin.bean.WxMassNews; +import chanjarster.weixin.bean.WxMassNews.WxMassNewsArticle; +import chanjarster.weixin.bean.WxMassOpenIdsMessage; +import chanjarster.weixin.bean.WxMassVideo; +import chanjarster.weixin.bean.result.WxMassSendResult; +import chanjarster.weixin.bean.result.WxMassUploadResult; +import chanjarster.weixin.bean.result.WxMediaUploadResult; +import chanjarster.weixin.exception.WxErrorException; + +import com.google.inject.Inject; + +/** + * 测试群发消息 + * @author chanjarster + * + */ +@Test(groups = "massAPI", dependsOnGroups = { "baseAPI", "mediaAPI"}) +@Guice(modules = ApiTestModule.class) +public class WxMassMessageAPITest { + + @Inject + protected WxServiceImpl wxService; + + @Test(enabled = false) + public void testSendMassTextByOpenIds() throws WxErrorException { + // 发送群发消息 + WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; + WxMassOpenIdsMessage massMessage = new WxMassOpenIdsMessage(); + massMessage.setMsgtype(WxConsts.MASS_MSG_TEXT); + massMessage.setContent("测试群发消息\n欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World"); + massMessage.getTouser().add(configProvider.getOpenId()); + + WxMassSendResult massResult = wxService.sendMassMessageByOpenIds(massMessage); + Assert.assertNotNull(massResult); + Assert.assertNotNull(massResult.getMsg_id()); + } + + @Test(enabled = true, dataProvider="massMessages") + public void testSendMassByOpenIds(String massMsgType, String mediaId) throws WxErrorException, IOException { + // 发送群发消息 + WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; + WxMassOpenIdsMessage massMessage = new WxMassOpenIdsMessage(); + massMessage.setMsgtype(massMsgType); + massMessage.setMedia_id(mediaId); + massMessage.getTouser().add(configProvider.getOpenId()); + + WxMassSendResult massResult = wxService.sendMassMessageByOpenIds(massMessage); + Assert.assertNotNull(massResult); + Assert.assertNotNull(massResult.getMsg_id()); + } + + @DataProvider + public Object[][] massMessages() throws WxErrorException, IOException { + Object[][] messages = new Object[4][]; + /* + * 视频素材 + */ + { + // 上传视频到媒体库 + InputStream inputStream = ClassLoader.getSystemResourceAsStream("mm.mp4"); + WxMediaUploadResult uploadMediaRes = wxService.uploadMedia(WxConsts.MEDIA_VIDEO, WxConsts.FILE_MP4, inputStream); + Assert.assertNotNull(uploadMediaRes); + Assert.assertNotNull(uploadMediaRes.getMedia_id()); + + // 把视频变成可被群发的媒体 + WxMassVideo video = new WxMassVideo(); + video.setTitle("测试标题"); + video.setDescription("测试描述"); + video.setMedia_id(uploadMediaRes.getMedia_id()); + WxMassUploadResult uploadResult = wxService.uploadMassVideo(video); + Assert.assertNotNull(uploadResult); + Assert.assertNotNull(uploadResult.getMedia_id()); + messages[0] = new Object[] { WxConsts.MASS_MSG_VIDEO, uploadResult.getMedia_id() }; + } + /** + * 图片素材 + */ + { + InputStream inputStream = ClassLoader.getSystemResourceAsStream("mm.jpeg"); + WxMediaUploadResult uploadMediaRes = wxService.uploadMedia(WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, inputStream); + Assert.assertNotNull(uploadMediaRes); + Assert.assertNotNull(uploadMediaRes.getMedia_id()); + messages[1] = new Object[] { WxConsts.MASS_MSG_IMAGE, uploadMediaRes.getMedia_id() }; + } + /** + * 语音素材 + */ + { + InputStream inputStream = ClassLoader.getSystemResourceAsStream("mm.mp3"); + WxMediaUploadResult uploadMediaRes = wxService.uploadMedia(WxConsts.MEDIA_VOICE, WxConsts.FILE_MP3, inputStream); + Assert.assertNotNull(uploadMediaRes); + Assert.assertNotNull(uploadMediaRes.getMedia_id()); + messages[2] = new Object[] { WxConsts.MASS_MSG_VOICE, uploadMediaRes.getMedia_id() }; + } + /** + * 图文素材 + */ + { + // 上传照片到媒体库 + InputStream inputStream = ClassLoader.getSystemResourceAsStream("mm.jpeg"); + WxMediaUploadResult uploadMediaRes = wxService.uploadMedia(WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, inputStream); + Assert.assertNotNull(uploadMediaRes); + Assert.assertNotNull(uploadMediaRes.getMedia_id()); + + // 上传图文消息 + WxMassNews news = new WxMassNews(); + WxMassNewsArticle article1 = new WxMassNewsArticle(); + article1.setTitle("标题1"); + article1.setContent("内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1"); + article1.setThumb_media_id(uploadMediaRes.getMedia_id()); + news.addArticle(article1); + + WxMassNewsArticle article2 = new WxMassNewsArticle(); + article2.setTitle("标题2"); + article2.setContent("内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2"); + article2.setThumb_media_id(uploadMediaRes.getMedia_id()); + article2.setShow_cover_pic(true); + article2.setAuthor("作者2"); + article2.setContent_source_url("www.baidu.com"); + article2.setDigest("摘要2"); + news.addArticle(article2); + + WxMassUploadResult massUploadResult = wxService.uploadMassNews(news); + Assert.assertNotNull(massUploadResult); + Assert.assertNotNull(uploadMediaRes.getMedia_id()); + messages[3] = new Object[] { WxConsts.MASS_MSG_VIDEO, massUploadResult.getMedia_id() }; + } + return messages; + } + +} diff --git a/src/test/java/chanjarster/weixin/api/WxMediaAPITest.java b/src/test/java/chanjarster/weixin/api/WxMediaAPITest.java new file mode 100644 index 00000000..009ffbff --- /dev/null +++ b/src/test/java/chanjarster/weixin/api/WxMediaAPITest.java @@ -0,0 +1,72 @@ +package chanjarster.weixin.api; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import chanjarster.weixin.bean.result.WxMediaUploadResult; +import chanjarster.weixin.exception.WxErrorException; + +import com.google.inject.Inject; + +/** + * 测试多媒体文件上传下载 + * @author chanjarster + * + */ +@Test(groups="mediaAPI", dependsOnGroups="baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxMediaAPITest { + + @Inject + protected WxServiceImpl wxService; + + private List media_ids = new ArrayList(); + + @Test(dataProvider="uploadMedia", enabled = true) + public void testUploadMedia(String mediaType, String fileType, String fileName) throws WxErrorException, IOException { + InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName); + WxMediaUploadResult res = wxService.uploadMedia(mediaType, fileType, inputStream); + Assert.assertNotNull(res.getType()); + Assert.assertNotNull(res.getCreated_at()); + Assert.assertTrue(res.getMedia_id() != null || res.getThumb_media_id() != null); + + if (res.getMedia_id() != null) { + media_ids.add(res.getMedia_id()); + } + if (res.getThumb_media_id() != null) { + media_ids.add(res.getThumb_media_id()); + } + } + + @DataProvider + public Object[][] uploadMedia() { + return new Object[][] { + new Object[] { WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, "mm.jpeg" }, + new Object[] { WxConsts.MEDIA_VOICE, WxConsts.FILE_MP3, "mm.mp3" }, + new Object[] { WxConsts.MEDIA_VIDEO, WxConsts.FILE_MP4, "mm.mp4" }, + new Object[] { WxConsts.MEDIA_THUMB, WxConsts.FILE_JPG, "mm.jpeg" } + }; + } + + @Test(dependsOnMethods = { "testUploadMedia" }, dataProvider="downloadMedia") + public void testDownloadMedia(String media_id) throws WxErrorException { + wxService.downloadMedia(media_id); + } + + @DataProvider + public Object[][] downloadMedia() { + Object[][] params = new Object[this.media_ids.size()][]; + for (int i = 0; i < this.media_ids.size(); i++) { + params[i] = new Object[] { this.media_ids.get(i) }; + } + return params; + } + +} diff --git a/src/test/java/chanjarster/weixin/api/WxMenuAPITest.java b/src/test/java/chanjarster/weixin/api/WxMenuAPITest.java index 66f060a7..9ca7acef 100644 --- a/src/test/java/chanjarster/weixin/api/WxMenuAPITest.java +++ b/src/test/java/chanjarster/weixin/api/WxMenuAPITest.java @@ -13,6 +13,11 @@ import chanjarster.weixin.bean.WxMenu; import chanjarster.weixin.bean.WxMenu.WxMenuButton; import chanjarster.weixin.exception.WxErrorException; +/** + * 测试菜单 + * @author chanjarster + * + */ @Test(dependsOnGroups="baseAPI") @Guice(modules = ApiTestModule.class) public class WxMenuAPITest { diff --git a/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java b/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java index 39d65498..944486db 100644 --- a/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java +++ b/src/test/java/chanjarster/weixin/api/WxMessageRouterTest.java @@ -9,6 +9,11 @@ import org.testng.annotations.Test; import chanjarster.weixin.bean.WxXmlMessage; +/** + * 测试消息路由器 + * @author chanjarster + * + */ @Test public class WxMessageRouterTest { diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml index 6691adb1..e74f1ad1 100644 --- a/src/test/resources/testng.xml +++ b/src/test/resources/testng.xml @@ -7,10 +7,13 @@ + + + - +