View Javadoc
1   package com.foxinmy.weixin4j.mp.api;
2   
3   import java.util.HashMap;
4   import java.util.List;
5   import java.util.Map;
6   
7   import com.alibaba.fastjson.JSON;
8   import com.alibaba.fastjson.JSONObject;
9   import com.foxinmy.weixin4j.exception.WeixinException;
10  import com.foxinmy.weixin4j.http.weixin.ApiResult;
11  import com.foxinmy.weixin4j.http.weixin.WeixinResponse;
12  import com.foxinmy.weixin4j.model.Token;
13  import com.foxinmy.weixin4j.token.TokenManager;
14  import com.foxinmy.weixin4j.tuple.MassTuple;
15  import com.foxinmy.weixin4j.tuple.MpArticle;
16  import com.foxinmy.weixin4j.tuple.MpNews;
17  import com.foxinmy.weixin4j.tuple.Tuple;
18  import com.foxinmy.weixin4j.util.StringUtil;
19  
20  /**
21   * 群发相关API
22   *
23   * @className MassApi
24   * @author jinyu(foxinmy@gmail.com)
25   * @date 2014年9月25日
26   * @since JDK 1.6
27   */
28  public class MassApi extends MpApi {
29  
30      private final TokenManager tokenManager;
31  
32      public MassApi(TokenManager tokenManager) {
33          this.tokenManager = tokenManager;
34      }
35  
36      /**
37       * 上传图文消息,一个图文消息支持1到10条图文</br>
38       * <font color=
39       * "red">具备微信支付权限的公众号,在使用高级群发接口上传、群发图文消息类型时,可使用&lta&gt标签加入外链</font>
40       *
41       * @param articles
42       *            图片消息
43       * @return 媒体ID
44       * @throws WeixinException
45       * @see <a href=
46       *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">上传图文素材</a>
47       * @see com.foxinmy.weixin4j.tuple.MpArticle
48       */
49      public String uploadArticle(List<MpArticle> articles) throws WeixinException {
50          String article_upload_uri = getRequestUri("article_upload_uri");
51          Token token = tokenManager.getCache();
52          JSONObject obj = new JSONObject();
53          obj.put("articles", articles);
54          WeixinResponse response = weixinExecutor.post(String.format(article_upload_uri, token.getAccessToken()),
55                  obj.toJSONString());
56  
57          return response.getAsJson().getString("media_id");
58      }
59  
60      /**
61       * 分组群发
62       * <p>
63       * 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
64       * 如消息有时会进行审核、服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
65       * </p>
66       *
67       * @param tuple
68       *            消息元件
69       * @param isToAll
70       *            用于设定是否向全部用户发送,值为true或false,选择true该消息群发给所有用户,
71       *            选择false可根据group_id发送给指定群组的用户
72       * @param groupId
73       *            分组ID
74       * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中
75       * @throws WeixinException
76       * @see com.foxinmy.weixin4j.mp.model.Group
77       * @see com.foxinmy.weixin4j.tuple.Text
78       * @see com.foxinmy.weixin4j.tuple.Image
79       * @see com.foxinmy.weixin4j.tuple.Voice
80       * @see com.foxinmy.weixin4j.tuple.MpVideo
81       * @see com.foxinmy.weixin4j.tuple.MpNews
82       * @see com.foxinmy.weixin4j.tuple.Card
83       * @see com.foxinmy.weixin4j.tuple.MassTuple
84       * @see {@link GroupApi#getGroups()}
85       * @see <a href=
86       *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a>
87       */
88      @Deprecated
89      public String[] massByGroupId(MassTuple tuple, boolean isToAll, int groupId) throws WeixinException {
90          if (tuple instanceof MpNews) {
91              MpNews _news = (MpNews) tuple;
92              List<MpArticle> _articles = _news.getArticles();
93              if (StringUtil.isBlank(_news.getMediaId())) {
94                  if (_articles.isEmpty()) {
95                      throw new WeixinException("mass fail:mediaId or articles is required");
96                  }
97                  tuple = new MpNews(uploadArticle(_articles));
98              }
99          }
100         String msgtype = tuple.getMessageType();
101         JSONObject obj = new JSONObject();
102         JSONObject item = new JSONObject();
103         item.put("is_to_all", isToAll);
104         if (!isToAll) {
105             item.put("group_id", groupId);
106         }
107         obj.put("filter", item);
108         obj.put(msgtype, JSON.toJSON(tuple));
109         obj.put("msgtype", msgtype);
110         String mass_group_uri = getRequestUri("mass_group_uri");
111         Token token = tokenManager.getCache();
112         WeixinResponse response = weixinExecutor.post(String.format(mass_group_uri, token.getAccessToken()),
113                 obj.toJSONString());
114 
115         obj = response.getAsJson();
116         return new String[] { obj.getString("msg_id"), obj.getString("msg_data_id") };
117     }
118 
119     /**
120      * 分组ID群发图文消息
121      *
122      * @param articles
123      *            图文列表
124      * @param groupId
125      *            分组ID
126      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现。
127      * @see <a href=
128      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a>
129      * @see {@link #massByGroupId(Tuple,int)}
130      * @see com.foxinmy.weixin4j.tuple.MpArticle
131      * @throws WeixinException
132      */
133     @Deprecated
134     public String[] massArticleByGroupId(List<MpArticle> articles, int groupId) throws WeixinException {
135         String mediaId = uploadArticle(articles);
136         return massByGroupId(new MpNews(mediaId), false, groupId);
137     }
138 
139     /**
140      * 群发消息
141      * <p>
142      * 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
143      * 如消息有时会进行审核、服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
144      * </p>
145      *
146      * @param tuple
147      *            消息元件
148      * @param filter
149      *            过滤条件
150      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中
151      * @throws WeixinException
152      * @see Tag
153      * @see com.foxinmy.weixin4j.tuple.Text
154      * @see com.foxinmy.weixin4j.tuple.Image
155      * @see com.foxinmy.weixin4j.tuple.Voice
156      * @see com.foxinmy.weixin4j.tuple.MpVideo
157      * @see com.foxinmy.weixin4j.tuple.MpNews
158      * @see com.foxinmy.weixin4j.tuple.Card
159      * @see com.foxinmy.weixin4j.tuple.MassTuple
160      */
161     private String[] mass(MassTuple tuple, JSONObject filter) throws WeixinException {
162         if (tuple instanceof MpNews) {
163             MpNews _news = (MpNews) tuple;
164             List<MpArticle> _articles = _news.getArticles();
165             if (StringUtil.isBlank(_news.getMediaId())) {
166                 if (_articles.isEmpty()) {
167                     throw new WeixinException("mass fail:mediaId or articles is required");
168                 }
169                 tuple = new MpNews(uploadArticle(_articles));
170             }
171             if (!filter.containsKey("send_ignore_reprint")) {
172                 filter.put("send_ignore_reprint", 0);
173             }
174         }
175         String msgtype = tuple.getMessageType();
176         JSONObject obj = new JSONObject();
177         obj.putAll(filter);
178         obj.put(msgtype, JSON.toJSON(tuple));
179         obj.put("msgtype", msgtype);
180         String mass_uri = filter.containsKey("touser") ? getRequestUri("mass_openid_uri") : getRequestUri("mass_group_uri");
181         Token token = tokenManager.getCache();
182         WeixinResponse response = weixinExecutor.post(String.format(mass_uri, token.getAccessToken()),
183                 obj.toJSONString());
184 
185         obj = response.getAsJson();
186         return new String[] { obj.getString("msg_id"), obj.getString("msg_data_id") };
187     }
188 
189     /**
190      * 群发消息给所有粉丝
191      *
192      * @param tuple
193      *            消息元件
194      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中
195      * @throws WeixinException
196      * @see <a href=
197      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
198      */
199     public String[] massToAll(MassTuple tuple) throws WeixinException {
200         String filter = String.format("{\"filter\":{\"is_to_all\":true}}");
201         return mass(tuple, JSON.parseObject(filter));
202     }
203 
204     /**
205      * 标签群发消息
206      *
207      * @param tuple
208      *            消息元件
209      * @param tagId
210      *            标签ID
211      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中
212      * @throws WeixinException
213      * @see Tag
214      * @see {@link TagApi#listTags()}
215      * @see #mass(MassTuple, JSONObject, boolean)
216      * @see <a href=
217      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
218      */
219     public String[] massByTagId(MassTuple tuple, int tagId) throws WeixinException {
220         String filter = String.format("{\"filter\":{\"is_to_all\":false,\"tag_id\":%d}}", tagId);
221         return mass(tuple, JSON.parseObject(filter));
222     }
223 
224     /**
225      * 标签群发图文消息
226      *
227      * @param articles
228      *            图文列表
229      * @param tagId
230      *            标签ID
231      * @param ignoreReprint
232      *            图文消息被判定为转载时,是否继续群发
233      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现。
234      * @see <a href=
235      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
236      * @see {@link #massByTagId(Tuple,int)}
237      * @see com.foxinmy.weixin4j.tuple.MpArticle
238      * @throws WeixinException
239      */
240     public String[] massArticleByTagId(List<MpArticle> articles, int tagId, boolean ignoreReprint)
241             throws WeixinException {
242         String mediaId = uploadArticle(articles);
243         String text = String.format("{\"filter\":{\"is_to_all\":false,\"tag_id\":%d}}", tagId);
244         JSONObject filter = JSON.parseObject(text);
245         filter.put("send_ignore_reprint", ignoreReprint ? 1 : 0);
246         return mass(new MpNews(mediaId), filter);
247     }
248 
249     /**
250      * openId群发消息
251      *
252      * @param tuple
253      *            消息元件
254      * @param openIds
255      *            openId列表
256      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中
257      * @throws WeixinException
258      * @see <a href=
259      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a>
260      * @see {@link UserApi#getUser(String)}
261      * @see #mass(MassTuple, JSONObject, boolean)
262      */
263     public String[] massByOpenIds(MassTuple tuple, String... openIds) throws WeixinException {
264         JSONObject filter = new JSONObject();
265         filter.put("touser", openIds);
266         return mass(tuple, filter);
267     }
268 
269     /**
270      * openid群发图文消息
271      *
272      * @param articles
273      *            图文列表
274      * @param ignoreReprint
275      *            图文消息被判定为转载时,是否继续群发
276      * @param openIds
277      *            openId列表
278      * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID,该字段只有在群发图文消息时,才会出现,可以用于在图文分析数据接口中.
279      * @see <a href=
280      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a>
281      * @see {@link #massByOpenIds(Tuple,String...)}
282      * @see com.foxinmy.weixin4j.tuple.MpArticle
283      * @throws WeixinException
284      */
285     public String[] massArticleByOpenIds(List<MpArticle> articles, boolean ignoreReprint, String... openIds)
286             throws WeixinException {
287         String mediaId = uploadArticle(articles);
288         JSONObject filter = new JSONObject();
289         filter.put("touser", openIds);
290         filter.put("send_ignore_reprint", ignoreReprint ? 1 : 0);
291         return mass(new MpNews(mediaId), filter);
292     }
293 
294     /**
295      * 删除群发消息
296      *
297      * @param msgid
298      *            发送出去的消息ID
299      * @throws WeixinException
300      * @see <a href=
301      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">删除群发</a>
302      * @see #deleteMassNews(String, int)
303      */
304     public ApiResult deleteMassNews(String msgid) throws WeixinException {
305         return deleteMassNews(msgid, 0);
306     }
307 
308     /**
309      * 删除群发消息
310      * <p>
311      * 请注意,只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片
312      * </p>
313      *
314      * @param msgid
315      *            发送出去的消息ID
316      * @param articleIndex
317      *            要删除的文章在图文消息中的位置,第一篇编号为1,该字段不填或填0会删除全部文章
318      * @throws WeixinException
319      * @see <a href=
320      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">删除群发</a>
321      * @see {@link #massByTagId(Tuple, int)}
322      * @see {@link #massByOpenIds(Tuple, String...)
323      *
324      */
325     public ApiResult deleteMassNews(String msgid, int articleIndex) throws WeixinException {
326         JSONObject obj = new JSONObject();
327         obj.put("msgid", msgid);
328         if (articleIndex > 0)
329             obj.put("article_idx", articleIndex);
330         String mass_delete_uri = getRequestUri("mass_delete_uri");
331         Token token = tokenManager.getCache();
332         WeixinResponse response = weixinExecutor.post(String.format(mass_delete_uri, token.getAccessToken()),
333                 obj.toJSONString());
334 
335         return response.getAsResult();
336     }
337 
338     /**
339      * 预览群发消息</br>
340      * 开发者可通过该接口发送消息给指定用户,在手机端查看消息的样式和排版
341      *
342      * @param toUser
343      *            接收用户的openID
344      * @param toWxName
345      *            接收用户的微信号 towxname和touser同时赋值时,以towxname优先
346      * @param tuple
347      *            消息元件
348      * @return 处理结果
349      * @throws WeixinException
350      * @see com.foxinmy.weixin4j.tuple.MassTuple
351      * @see <a href=
352      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">预览群发消息</a>
353      */
354     public ApiResult previewMassNews(String toUser, String toWxName, MassTuple tuple) throws WeixinException {
355         String msgtype = tuple.getMessageType();
356         JSONObject obj = new JSONObject();
357         if(toUser != null){
358             obj.put("touser", toUser);
359         }
360         if(toWxName != null){
361             obj.put("towxname", toWxName);
362         }
363         obj.put(msgtype, JSON.toJSON(tuple));
364         obj.put("msgtype", msgtype);
365         String mass_preview_uri = getRequestUri("mass_preview_uri");
366         Token token = tokenManager.getCache();
367         WeixinResponse response = weixinExecutor.post(String.format(mass_preview_uri, token.getAccessToken()),
368                 obj.toJSONString());
369 
370         return response.getAsResult();
371     }
372 
373     /**
374      * 查询群发发送状态
375      *
376      * @param msgId
377      *            消息ID
378      * @return 消息发送状态,如sendsuccess:发送成功、sendfail:发送失败
379      * @throws WeixinException
380      * @see <a href=
381      *      "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">查询群发状态</a>
382      */
383     public String getMassNewStatus(String msgId) throws WeixinException {
384         JSONObject obj = new JSONObject();
385         obj.put("msg_id", msgId);
386         String mass_get_uri = getRequestUri("mass_get_uri");
387         Token token = tokenManager.getCache();
388         WeixinResponse response = weixinExecutor.post(String.format(mass_get_uri, token.getAccessToken()),
389                 obj.toJSONString());
390 
391         String status = response.getAsJson().getString("msg_status");
392         String desc = massStatusMap.get(status);
393         return String.format("%s:%s", status, desc);
394     }
395 
396     private final static Map<String, String> massStatusMap;
397     static {
398         massStatusMap = new HashMap<String, String>();
399         massStatusMap.put("sendsuccess", "发送成功");
400         massStatusMap.put("send_success", "发送成功");
401         massStatusMap.put("success", "发送成功");
402         massStatusMap.put("send success", "发送成功");
403         massStatusMap.put("sendfail", "发送失败");
404         massStatusMap.put("send_fail", "发送失败");
405         massStatusMap.put("fail", "发送失败");
406         massStatusMap.put("send fail", "发送失败");
407         massStatusMap.put("err(10001)", "涉嫌广告");
408         massStatusMap.put("err(20001)", "涉嫌政治");
409         massStatusMap.put("err(20004)", "涉嫌社会");
410         massStatusMap.put("err(20006)", "涉嫌违法犯罪");
411         massStatusMap.put("err(20008)", "涉嫌欺诈");
412         massStatusMap.put("err(20013)", "涉嫌版权");
413         massStatusMap.put("err(22000)", "涉嫌互推(互相宣传)");
414         massStatusMap.put("err(21000)", "涉嫌其他");
415     }
416 }