新增评论管理接口

This commit is contained in:
jinyu 2017-05-19 19:06:26 +08:00
parent 66417fdc8b
commit c53e0718ef
17 changed files with 3720 additions and 3192 deletions

View File

@ -783,3 +783,7 @@
+ 新增摇一摇周边接口 + 新增摇一摇周边接口
+ version upgrade to 1.7.4 + version upgrade to 1.7.4
* 2017-05-19
+ weixin4j-mp:新增评论管理接口

View File

@ -719,6 +719,18 @@
<code>45059</code> <code>45059</code>
<text>有粉丝身上的标签数已经超过限制</text> <text>有粉丝身上的标签数已经超过限制</text>
</error> </error>
<error>
<code>45065</code>
<text>相同 clientmsgid 已存在群发记录,返回数据中带有已存在的群发任务的 msgid</text>
</error>
<error>
<code>45066</code>
<text>相同 clientmsgid 重试速度过快请间隔1分钟重试</text>
</error>
<error>
<code>45067</code>
<text>clientmsgid 长度超过限制</text>
</error>
<error> <error>
<code>45157</code> <code>45157</code>
<text>标签名非法,请注意不能和其他标签重名</text> <text>标签名非法,请注意不能和其他标签重名</text>
@ -1655,6 +1667,61 @@
<code>86320</code> <code>86320</code>
<text>不合法的客服类型</text> <text>不合法的客服类型</text>
</error> </error>
<error>
<code>45009</code>
<msg>reach max api daily quota limit</msg>
<text>没有剩余的调用次数</text>
</error>
<error>
<code>88000</code>
<msg>without comment privilege</msg>
<text>没有留言权限</text>
</error>
<error>
<code>88001</code>
<msg>msg_data is not exists</msg>
<text>该图文不存在</text>
</error>
<error>
<code>88002</code>
<msg>the article is limit for safety</msg>
<text>文章存在敏感信息</text>
</error>
<error>
<code>88003</code>
<msg>elected comment upper limit</msg>
<text>精选评论数已达上限</text>
</error>
<error>
<code>88004</code>
<msg>comment was deleted by user</msg>
<text>已被用户删除,无法精选</text>
</error>
<error>
<code>88005</code>
<msg>already reply</msg>
<text>该评论已回复</text>
</error>
<error>
<code>88007</code>
<msg>reply content beyond max len or content len is zero</msg>
<text>回复超过长度限制或为0</text>
</error>
<error>
<code>88008</code>
<msg>comment is not exists</msg>
<text>该评论不存在</text>
</error>
<error>
<code>88009</code>
<msg>reply is not exists</msg>
<text>该回复不存在</text>
</error>
<error>
<code>88010</code>
<msg>count range error. cout <= 0 or count > 50</msg>
<text>获取评论数目不合法</text>
</error>
<error> <error>
<code>90001</code> <code>90001</code>
<text>未认证摇一摇周边</text> <text>未认证摇一摇周边</text>

View File

@ -22,129 +22,186 @@ import com.foxinmy.weixin4j.type.ButtonType;
*/ */
public class Button implements Serializable { public class Button implements Serializable {
private static final long serialVersionUID = -6422234732203854866L; private static final long serialVersionUID = -6422234732203854866L;
/** /**
* 菜单标题不超过16个字节子菜单不超过40个字节 * 菜单标题不超过16个字节子菜单不超过40个字节
*/ */
private String name; private String name;
/** /**
* 菜单类型 </br> <font color="red"> * 菜单类型 </br>
* 公众平台官网上能够设置的菜单类型有viewtextimgphotovideovoice </font> * <font color="red"> 公众平台官网上能够设置的菜单类型有viewtextimgphotovideovoice
* * </font>
* @see com.foxinmy.weixin4j.type.ButtonType *
*/ * @see com.foxinmy.weixin4j.type.ButtonType
private ButtonType type; */
/** private String type;
* 菜单KEY值,根据type的类型而定</p> 通过公众平台设置的自定义菜单</br> <li>text:保存文字 <li> /**
* imgvoice保存媒体ID <li>video保存视频URL <li> * 菜单KEY值,根据type的类型而定
* news保存图文消息媒体ID <li>view保存链接URL * </p>
* <p> * 通过公众平台设置的自定义菜单</br>
* 使用API设置的自定义菜单 * <li>text:保存文字
* </p> <li> * <li>imgvoice保存媒体ID
* clickscancode_pushscancode_waitmsgpic_sysphotopic_photo_or_album * <li>video保存视频URL
* pic_weixinlocation_select保存key <li>view保存链接URL; <li> * <li>news保存图文消息媒体ID
* media_idview_limited保存媒体ID * <li>view保存链接URL
*/ * <p>
private String content; * 使用API设置的自定义菜单
/** * </p>
* 扩展属性比如在公众平台设置菜单时的图文列表 * <li>clickscancode_pushscancode_waitmsgpic_sysphotopic_photo_or_album
*/ * pic_weixinlocation_select保存key
@JSONField(serialize = false, deserialize = false) * <li>view保存链接URL;
private Object extra; * <li>media_idview_limited保存媒体ID
/** */
* 二级菜单数组个数应为1~5个 private String content;
*/ /**
@JSONField(name = "sub_button") * 扩展属性比如在公众平台设置菜单时的图文列表
private List<Button> subs; */
@JSONField(serialize = false, deserialize = false)
private Object extra;
/**
* miniprogram类型必须 小程序的appid仅认证公众号可配置
*/
private String appid;
/**
* miniprogram类型必须 小程序的页面路径
*/
private String pagepath;
protected Button() { /**
this.subs = new ArrayList<Button>(); * 二级菜单数组个数应为1~5个
} */
@JSONField(name = "sub_button")
private List<Button> subs;
/** protected Button() {
* 创建一个具有子菜单的菜单 this.subs = new ArrayList<Button>();
* }
* @param name
* 菜单名
* @param subButtons
* 二级菜单列表
*/
public Button(String name, Button... subButtons) {
this.name = name;
this.subs = new ArrayList<Button>(Arrays.asList(subButtons));
}
/** /**
* 创建一个普通菜单 * 创建一个具有子菜单的菜单
* *
* @param name * @param name
* 菜单名 * 菜单名
* @param content * @param subButtons
* 菜单内容 * 二级菜单列表
* @param type */
* 菜单类型 public Button(String name, Button... subButtons) {
*/ this.name = name;
public Button(String name, String content, ButtonType type) { this.subs = new ArrayList<Button>(Arrays.asList(subButtons));
this.name = name; }
this.content = content;
this.type = type;
this.subs = new ArrayList<Button>();
}
public String getName() { /**
return name; * 创建一个普通菜单
} *
* @param name
* 菜单名
* @param content
* 菜单内容
* @param type
* 菜单类型
*/
public Button(String name, String content, ButtonType type) {
this.name = name;
this.content = content;
this.type = type.name();
this.subs = new ArrayList<Button>();
}
public void setName(String name) { /**
this.name = name; * 小程序菜单
} *
* @param name
* 菜单名
* @param url
* 小程序的url页面
* @param appid
* 小程序的appid
* @param pagepath
* 小程序员的页面路径
*/
public Button(String name, String url, String appid, String pagepath) {
this.name = name;
this.content = url;
this.appid = appid;
this.pagepath = pagepath;
this.type = ButtonType.miniprogram.name();
this.subs = new ArrayList<Button>();
}
public ButtonType getType() { public String getName() {
return type; return name;
} }
public void setType(ButtonType type) { public void setName(String name) {
this.type = type; this.name = name;
} }
public String getContent() { public String getType() {
return content; return type;
} }
public void setContent(String content) { public void setType(String type) {
this.content = content; this.type = type;
} }
public Object getExtra() { public void setType(ButtonType type) {
return extra; this.type = type.name();
} }
/** public String getContent() {
* 扩展只读属性设置无效 return content;
* }
* @param extra
*/
public void setExtra(Object extra) {
this.extra = extra;
}
public List<Button> getSubs() { public void setContent(String content) {
return subs; this.content = content;
} }
public void setSubs(List<Button> subs) { public Object getExtra() {
this.subs = subs; return extra;
} }
public Button pushSub(Button btn) { /**
this.subs.add(btn); * 扩展只读属性设置无效
return this; *
} * @param extra
*/
public void setExtra(Object extra) {
this.extra = extra;
}
@Override public String getAppid() {
public String toString() { return appid;
return "Button [name=" + name + ", type=" + type + ", content=" }
+ content + ", extra=" + extra + ", subs=" + subs + "]";
} public void setAppid(String appid) {
this.appid = appid;
}
public String getPagepath() {
return pagepath;
}
public void setPagepath(String pagepath) {
this.pagepath = pagepath;
}
public List<Button> getSubs() {
return subs;
}
public void setSubs(List<Button> subs) {
this.subs = subs;
}
public Button pushSub(Button btn) {
this.subs.add(btn);
return this;
}
@Override
public String toString() {
return "Button [name=" + name + ", type=" + type + ", content=" + content + ", extra=" + extra + ", appid="
+ appid + ", pagepath=" + pagepath + ", subs=" + subs + "]";
}
} }

View File

@ -8,7 +8,7 @@ import com.alibaba.fastjson.annotation.JSONField;
/** /**
* 卡券对象 * 卡券对象
* <p> * <p>
* <font color="red">可用于群发消息</font> * <font color="red">可用于群发消息客服消息</font>
* </p> * </p>
* *
* @className Card * @className Card
@ -17,33 +17,33 @@ import com.alibaba.fastjson.annotation.JSONField;
* @since JDK 1.6 * @since JDK 1.6
* @see * @see
*/ */
public class Card implements MassTuple { public class Card implements MassTuple, NotifyTuple {
private static final long serialVersionUID = 6119453633595102147L; private static final long serialVersionUID = 6119453633595102147L;
@Override @Override
public String getMessageType() { public String getMessageType() {
return "wxcard"; return "wxcard";
} }
/** /**
* 上传后的微信返回的媒体ID * 上传后的微信返回的媒体ID
*/ */
@JSONField(name = "card_id") @JSONField(name = "card_id")
@XmlElement(name = "CardId") @XmlElement(name = "CardId")
private String cardId; private String cardId;
@JSONCreator @JSONCreator
public Card(@JSONField(name = "cardId") String cardId) { public Card(@JSONField(name = "cardId") String cardId) {
this.cardId = cardId; this.cardId = cardId;
} }
public String getCardId() { public String getCardId() {
return cardId; return cardId;
} }
@Override @Override
public String toString() { public String toString() {
return "Card [cardId=" + cardId + "]"; return "Card [cardId=" + cardId + "]";
} }
} }

View File

@ -14,153 +14,196 @@ import com.alibaba.fastjson.annotation.JSONField;
*/ */
public class MpArticle implements Serializable { public class MpArticle implements Serializable {
private static final long serialVersionUID = 5583479943661639234L; private static final long serialVersionUID = 5583479943661639234L;
/** /**
* 图文消息缩略图的media_id可以在基础支持-上传多媒体文件接口中获得 非空 * 图文消息缩略图的media_id可以在基础支持-上传多媒体文件接口中获得 非空
*/ */
@JSONField(name = "thumb_media_id") @JSONField(name = "thumb_media_id")
private String thumbMediaId; private String thumbMediaId;
/** /**
* 图文消息的封面图片的地址(不一定有请关注thumbMediaId) * 图文消息的封面图片的地址(不一定有请关注thumbMediaId)
*/ */
@JSONField(name = "thumb_url") @JSONField(name = "thumb_url")
private String thumbUrl; private String thumbUrl;
/** /**
* 图文消息的作者 可为空 * 图文消息的作者 可为空
*/ */
private String author; private String author;
/** /**
* 图文消息的标题 非空 * 图文消息的标题 非空
*/ */
private String title; private String title;
/** /**
* 图文页的URL 获取图文消息时群发消息时填写无效 * 图文页的URL 获取图文消息时群发消息时填写无效
*/ */
private String url; private String url;
/** /**
* 在图文消息页面点击阅读原文后的页面 可为空 * 在图文消息页面点击阅读原文后的页面 可为空
*/ */
@JSONField(name = "content_source_url") @JSONField(name = "content_source_url")
private String sourceUrl; private String sourceUrl;
/** /**
* 图文消息页面的内容支持HTML标签 非空 * 图文消息页面的内容支持HTML标签 非空
*/ */
private String content; private String content;
/** /**
* 图文消息的描述 可为空 * 图文消息的描述 可为空
*/ */
private String digest; private String digest;
/** /**
* 是否显示封面1为显示0为不显示 可为空 * 是否显示封面1为显示0为不显示 可为空
*/ */
@JSONField(name = "show_cover_pic") @JSONField(name = "show_cover_pic")
private String showCoverPic; private String showCoverPic;
/**
* 是否打开评论0不打开1打开
*/
@JSONField(name = "need_open_comment")
private String openComment;
/**
* 是否粉丝才可评论0所有人可评论1粉丝才可评论
*/
@JSONField(name = "only_fans_can_comment")
private String onlyFansCanComment;
protected MpArticle() { protected MpArticle() {
} }
/** /**
* @param thumbMediaId * @param thumbMediaId
* 缩略图 * 缩略图
* @param title * @param title
* 标题 * 标题
* @param content * @param content
* 内容 * 内容
*/ */
public MpArticle(String thumbMediaId, String title, String content) { public MpArticle(String thumbMediaId, String title, String content) {
this.thumbMediaId = thumbMediaId; this.thumbMediaId = thumbMediaId;
this.title = title; this.title = title;
this.content = content; this.content = content;
} }
public String getThumbMediaId() { public String getThumbMediaId() {
return thumbMediaId; return thumbMediaId;
} }
public void setThumbMediaId(String thumbMediaId) { public void setThumbMediaId(String thumbMediaId) {
this.thumbMediaId = thumbMediaId; this.thumbMediaId = thumbMediaId;
} }
public String getThumbUrl() { public String getThumbUrl() {
return thumbUrl; return thumbUrl;
} }
public void setThumbUrl(String thumbUrl) { public void setThumbUrl(String thumbUrl) {
this.thumbUrl = thumbUrl; this.thumbUrl = thumbUrl;
} }
public String getAuthor() { public String getAuthor() {
return author; return author;
} }
public void setAuthor(String author) { public void setAuthor(String author) {
this.author = author; this.author = author;
} }
public String getTitle() { public String getTitle() {
return title; return title;
} }
public void setTitle(String title) { public void setTitle(String title) {
this.title = title; this.title = title;
} }
public String getUrl() { public String getUrl() {
return url; return url;
} }
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
public String getSourceUrl() { public String getSourceUrl() {
return sourceUrl; return sourceUrl;
} }
public void setSourceUrl(String sourceUrl) { public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl; this.sourceUrl = sourceUrl;
} }
public String getContent() { public String getContent() {
return content; return content;
} }
public void setContent(String content) { public void setContent(String content) {
this.content = content; this.content = content;
} }
public String getDigest() { public String getDigest() {
return digest; return digest;
} }
public void setDigest(String digest) { public void setDigest(String digest) {
this.digest = digest; this.digest = digest;
} }
public String getShowCoverPic() { public String getShowCoverPic() {
return showCoverPic; return showCoverPic;
} }
public void setShowCoverPic(String showCoverPic) { public void setShowCoverPic(String showCoverPic) {
this.showCoverPic = showCoverPic; this.showCoverPic = showCoverPic;
} }
public void setShowCoverPic(boolean showCoverPic) { public void setShowCoverPic(boolean showCoverPic) {
this.showCoverPic = showCoverPic ? "1" : "0"; this.showCoverPic = showCoverPic ? "1" : "0";
} }
@JSONField(serialize = false) @JSONField(serialize = false)
public boolean getFormatShowCoverPic() { public boolean getFormatShowCoverPic() {
return this.showCoverPic != null && this.showCoverPic.equals("1"); return this.showCoverPic != null && this.showCoverPic.equals("1");
} }
@Override public void setOpenComment(boolean openComment) {
public String toString() { this.openComment = openComment ? "1" : "0";
return "MpArticle [thumbMediaId=" + thumbMediaId + ", thumbUrl=" }
+ thumbUrl + ", author=" + author + ", title=" + title
+ ", sourceUrl=" + sourceUrl + ", content=" + content public String getOpenComment() {
+ ", url=" + url + ", digest=" + digest + ", showCoverPic=" return openComment;
+ showCoverPic + "]"; }
}
public void setOpenComment(String openComment) {
this.openComment = openComment;
}
public String getOnlyFansCanComment() {
return onlyFansCanComment;
}
public void setOnlyFansCanComment(String onlyFansCanComment) {
this.onlyFansCanComment = onlyFansCanComment;
}
@JSONField(serialize = false)
public boolean getFormatOpenComment() {
return this.openComment != null && this.openComment.equals("1");
}
public void setOnlyFansCanComment(boolean onlyFansCanComment) {
this.onlyFansCanComment = onlyFansCanComment ? "1" : "0";
}
@JSONField(serialize = false)
public boolean getFormatOnlyFansCanComment() {
return this.onlyFansCanComment != null && this.onlyFansCanComment.equals("1");
}
@Override
public String toString() {
return "MpArticle [thumbMediaId=" + thumbMediaId + ", thumbUrl=" + thumbUrl + ", author=" + author + ", title="
+ title + ", url=" + url + ", sourceUrl=" + sourceUrl + ", content=" + content + ", digest=" + digest
+ ", showCoverPic=" + showCoverPic + ", openComment=" + openComment + ", onlyFansCanComment="
+ onlyFansCanComment + "]";
}
} }

View File

@ -66,9 +66,5 @@ public enum ButtonType {
/** /**
* 小程序 * 小程序
*/ */
miniprogram, miniprogram;
/**
* 以下类型请勿使用,在公众平台设置的按钮类型,如果尝试使用API方式创建菜单则会出错
*/
popups,text,img,voice,video,news;
} }

View File

@ -267,3 +267,7 @@
+ 新增批量发红包接口 + 新增批量发红包接口
+ version upgrade to 1.7.4 + version upgrade to 1.7.4
* 2017-05-19
+ 新增评论管理接口

View File

@ -39,6 +39,8 @@ weixin4j-mp
* CardApi `卡券API` * CardApi `卡券API`
* CommentApi `评论API`
[如何使用](https://github.com/foxinmy/weixin4j/wiki) [如何使用](https://github.com/foxinmy/weixin4j/wiki)
--------- ---------

View File

@ -0,0 +1,240 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
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.paging.Pageable;
import com.foxinmy.weixin4j.model.paging.Pagedata;
import com.foxinmy.weixin4j.mp.model.ArticleComment;
import com.foxinmy.weixin4j.mp.model.ArticleComment.ArticleCommentType;
import com.foxinmy.weixin4j.token.TokenManager;
/**
* 文章评论API
*
* @className CommentApi
* @author jinyu
* @date May 19, 2017
* @since JDK 1.6
* @see <a href=
* "https://mp.weixin.qq.com/wiki?action=doc&id=mp1494572718_WzHIY&t=0.6758084213658122">图文消息留言管理接口</a>
*/
public class CommentApi extends MpApi {
private final TokenManager tokenManager;
public CommentApi(TokenManager tokenManager) {
this.tokenManager = tokenManager;
}
/**
* 打开/关闭已群发文章评论
*
* @param open
* true为打开false为关闭
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @return 操作结果
* @see {@link MassApi#massByTagId(com.foxinmy.weixin4j.tuple.MassTuple, int)}
* @see {@link MassApi#massByOpenIds(com.foxinmy.weixin4j.tuple.MassTuple, String...)}
* @throws WeixinException
*/
public ApiResult openComment(boolean open, String msgid, int index) throws WeixinException {
String news_comment = open ? getRequestUri("news_comment_open") : getRequestUri("news_comment_close");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", msgid);
obj.put("index", index);
WeixinResponse response = weixinExecutor.post(String.format(news_comment, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult();
}
/**
* 获取评论列表
*
* @param page
* 分页信息
* @param commentType
* 评论类型 为空获取全部类型
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @return 分页数据
* @see ArticleComment
* @see ArticleCommentType
* @see {@link MassApi#massByTagId(com.foxinmy.weixin4j.tuple.MassTuple, int)}
* @see {@link MassApi#massByOpenIds(com.foxinmy.weixin4j.tuple.MassTuple, String...)}
* @throws WeixinException
*/
public Pagedata<ArticleComment> listArticleComments(Pageable page, ArticleCommentType commentType, String msgid,
int index) throws WeixinException {
String news_comment_list = getRequestUri("news_comment_list");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", "msgid");
obj.put("index", index);
obj.put("begin", page.getOffset());
obj.put("count", Math.max(50, page.getPageSize())); // 获取数目>=50会被拒绝
if (commentType != null) {
obj.put("type", commentType.ordinal() + 1);
} else {
obj.put("type", 0);
}
WeixinResponse response = weixinExecutor.post(String.format(news_comment_list, token.getAccessToken()),
obj.toJSONString());
JSONObject result = response.getAsJson();
int total = result.getIntValue("total");
List<ArticleComment> content = JSON.parseArray(result.getString("comment"), ArticleComment.class);
return new Pagedata<ArticleComment>(page, total, content);
}
/**
* 获取评论列表
*
* @param commentType
* 评论类型 为空获取全部类型
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @return 分页数据
* @see #listArticleComments(Pageable, ArticleCommentType, String, int)
* @throws WeixinException
*/
public List<ArticleComment> listAllArticleComments(ArticleCommentType commentType, String msgid, int index)
throws WeixinException {
List<ArticleComment> comments = new ArrayList<ArticleComment>();
Pagedata<ArticleComment> page = null;
Pageable pageable = null;
for (pageable = new Pageable(1, 50);; pageable = pageable.next()) {
page = listArticleComments(pageable, commentType, msgid, index);
if (!page.hasContent()) {
break;
}
comments.addAll(page.getContent());
}
return comments;
}
/**
* 评论标记/取消精选
*
* @param markelect
* true为标记false为取消
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @param commentId
* 用户评论ID
* @return 操作结果
* @see #listArticleComments(Pageable, ArticleCommentType, String, int)
* @throws WeixinException
*/
public ApiResult markelectComment(boolean markelect, String msgid, int index, String commentId)
throws WeixinException {
String news_comment = markelect ? getRequestUri("news_comment_markelect")
: getRequestUri("news_comment_unmarkelect");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", "msgid");
obj.put("index", index);
obj.put("user_comment_id", commentId);
WeixinResponse response = weixinExecutor.post(String.format(news_comment, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult();
}
/**
* 删除评论
*
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @param commentId
* 用户评论ID
* @return 操作结果
* @see #listArticleComments(Pageable, ArticleCommentType, String, int)
* @throws WeixinException
*/
public ApiResult deleteComment(String msgid, int index, String commentId) throws WeixinException {
String news_comment_delete = getRequestUri("news_comment_delete");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", "msgid");
obj.put("index", index);
obj.put("user_comment_id", commentId);
WeixinResponse response = weixinExecutor.post(String.format(news_comment_delete, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult();
}
/**
* 回复评论
*
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @param commentId
* 用户评论ID
* @param content
* 回复内容
* @return 操作结果
* @see #listArticleComments(Pageable, ArticleCommentType, String, int)
* @throws WeixinException
*/
public ApiResult replyComment(String msgid, int index, String commentId, String content) throws WeixinException {
String news_comment_reply = getRequestUri("news_comment_reply_add");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", "msgid");
obj.put("index", index);
obj.put("user_comment_id", commentId);
obj.put("content", content);
WeixinResponse response = weixinExecutor.post(String.format(news_comment_reply, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult();
}
/**
* 删除回复
*
* @param msgid
* 群发返回的msg_data_id
* @param index
* 多图文时用来指定第几篇图文从0开始不带默认操作该msg_data_id的第一篇图文
* @param commentId
* 用户评论ID
* @return 操作结果
* @see #listArticleComments(Pageable, ArticleCommentType, String, int)
* @throws WeixinException
*/
public ApiResult deleteCommentReply(String msgid, int index, String commentId) throws WeixinException {
String news_comment_reply = getRequestUri("news_comment_reply_delete");
Token token = tokenManager.getCache();
JSONObject obj = new JSONObject();
obj.put("msg_data_id", "msgid");
obj.put("index", index);
obj.put("user_comment_id", commentId);
WeixinResponse response = weixinExecutor.post(String.format(news_comment_reply, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult();
}
}

View File

@ -19,7 +19,6 @@ import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.model.SemResult; import com.foxinmy.weixin4j.mp.model.SemResult;
import com.foxinmy.weixin4j.token.TokenManager; import com.foxinmy.weixin4j.token.TokenManager;
import com.foxinmy.weixin4j.tuple.MpArticle; import com.foxinmy.weixin4j.tuple.MpArticle;
import com.foxinmy.weixin4j.type.ButtonType;
/** /**
* 辅助相关API * 辅助相关API
@ -32,214 +31,193 @@ import com.foxinmy.weixin4j.type.ButtonType;
*/ */
public class HelperApi extends MpApi { public class HelperApi extends MpApi {
private final TokenManager tokenManager; private final TokenManager tokenManager;
public HelperApi(TokenManager tokenManager) { public HelperApi(TokenManager tokenManager) {
this.tokenManager = tokenManager; this.tokenManager = tokenManager;
} }
/** /**
* 长链接转短链接 * 长链接转短链接
* *
* @param url * @param url
* 待转换的链接 * 待转换的链接
* @return 短链接 * @return 短链接
* @throws WeixinException * @throws WeixinException
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433600&token=&lang=zh_CN">长链接转短链接</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433600&token=&lang=zh_CN">长链接转短链接</a>
*/ */
public String getShorturl(String url) throws WeixinException { public String getShorturl(String url) throws WeixinException {
String shorturl_uri = getRequestUri("shorturl_uri"); String shorturl_uri = getRequestUri("shorturl_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("action", "long2short"); obj.put("action", "long2short");
obj.put("long_url", url); obj.put("long_url", url);
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(String.format(shorturl_uri, token.getAccessToken()),
String.format(shorturl_uri, token.getAccessToken()), obj.toJSONString());
obj.toJSONString());
return response.getAsJson().getString("short_url"); return response.getAsJson().getString("short_url");
} }
/** /**
* 语义理解 * 语义理解
* *
* @param semQuery * @param semQuery
* 语义理解协议 * 语义理解协议
* @return 语义理解结果 * @return 语义理解结果
* @see com.foxinmy.weixin4j.mp.model.SemQuery * @see com.foxinmy.weixin4j.mp.model.SemQuery
* @see com.foxinmy.weixin4j.mp.model.SemResult * @see com.foxinmy.weixin4j.mp.model.SemResult
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141241&token=&lang=zh_CN">语义理解</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141241&token=&lang=zh_CN">语义理解</a>
* @throws WeixinException * @throws WeixinException
*/ */
public SemResult semantic(SemQuery semQuery) throws WeixinException { public SemResult semantic(SemQuery semQuery) throws WeixinException {
String semantic_uri = getRequestUri("semantic_uri"); String semantic_uri = getRequestUri("semantic_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(String.format(semantic_uri, token.getAccessToken()),
String.format(semantic_uri, token.getAccessToken()), semQuery.toJson());
semQuery.toJson()); return response.getAsObject(new TypeReference<SemResult>() {
return response.getAsObject(new TypeReference<SemResult>() { });
}); }
}
/** /**
* 获取微信服务器IP地址 * 获取微信服务器IP地址
* *
* @return IP地址 * @return IP地址
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140187&token=&lang=zh_CN">获取IP地址</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140187&token=&lang=zh_CN">获取IP地址</a>
* @throws WeixinException * @throws WeixinException
*/ */
public List<String> getWechatServerIp() throws WeixinException { public List<String> getWechatServerIp() throws WeixinException {
String getcallbackip_uri = getRequestUri("getcallbackip_uri"); String getcallbackip_uri = getRequestUri("getcallbackip_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.get(String.format( WeixinResponse response = weixinExecutor.get(String.format(getcallbackip_uri, token.getAccessToken()));
getcallbackip_uri, token.getAccessToken())); return JSON.parseArray(response.getAsJson().getString("ip_list"), String.class);
return JSON.parseArray(response.getAsJson().getString("ip_list"), }
String.class);
}
/** /**
* 获取公众号当前使用的自定义菜单的配置如果公众号是通过API调用设置的菜单则返回菜单的开发配置 * 获取公众号当前使用的自定义菜单的配置如果公众号是通过API调用设置的菜单则返回菜单的开发配置
* 而如果公众号是在公众平台官网通过网站功能发布菜单则本接口返回运营者设置的菜单配置 * 而如果公众号是在公众平台官网通过网站功能发布菜单则本接口返回运营者设置的菜单配置
* *
* @return 菜单配置信息 * @return 菜单配置信息
* @see {@link MenuApi#getMenu()} * @see {@link MenuApi#getMenu()}
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1434698695&token=&lang=zh_CN">获取自定义菜单配置</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1434698695&token=&lang=zh_CN">获取自定义菜单配置</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @see com.foxinmy.weixin4j.mp.model.MenuSetting * @see com.foxinmy.weixin4j.mp.model.MenuSetting
* @see com.foxinmy.weixin4j.tuple.MpArticle * @see com.foxinmy.weixin4j.tuple.MpArticle
* @throws WeixinException * @throws WeixinException
*/ */
public MenuSetting getMenuSetting() throws WeixinException { public MenuSetting getMenuSetting() throws WeixinException {
String menu_get_selfmenu_uri = getRequestUri("menu_get_selfmenu_uri"); String menu_get_selfmenu_uri = getRequestUri("menu_get_selfmenu_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.get(String.format( WeixinResponse response = weixinExecutor.get(String.format(menu_get_selfmenu_uri, token.getAccessToken()));
menu_get_selfmenu_uri, token.getAccessToken())); JSONObject result = response.getAsJson();
JSONObject result = response.getAsJson(); JSONArray buttons = result.getJSONObject("selfmenu_info").getJSONArray("button");
JSONArray buttons = result.getJSONObject("selfmenu_info").getJSONArray( List<Button> buttonList = new ArrayList<Button>(buttons.size());
"button"); JSONObject buttonObj = null;
List<Button> buttonList = new ArrayList<Button>(buttons.size()); for (int i = 0; i < buttons.size(); i++) {
JSONObject buttonObj = null; buttonObj = buttons.getJSONObject(i);
for (int i = 0; i < buttons.size(); i++) { if (buttonObj.containsKey("sub_button")) {
buttonObj = buttons.getJSONObject(i); buttonObj.put("sub_button", buttonObj.getJSONObject("sub_button").getJSONArray("list"));
if (buttonObj.containsKey("sub_button")) { buttonObj.put("type", "popups");
buttonObj.put( }
"sub_button", buttonList.add(JSON.parseObject(buttonObj.toJSONString(), Button.class, ButtonExtraProcessor.global));
buttonObj.getJSONObject("sub_button").getJSONArray( }
"list")); return new MenuSetting(result.getBooleanValue("is_menu_open"), buttonList);
buttonObj.put("type", ButtonType.popups); }
}
buttonList.add(JSON.parseObject(buttonObj.toJSONString(),
Button.class, ButtonExtraProcessor.global));
}
return new MenuSetting(result.getBooleanValue("is_menu_open"),
buttonList);
}
private static final class ButtonExtraProcessor implements ExtraProcessor { private static final class ButtonExtraProcessor implements ExtraProcessor {
private static ButtonExtraProcessor global = new ButtonExtraProcessor(); private static ButtonExtraProcessor global = new ButtonExtraProcessor();
private static final String KEY = "news_info"; private static final String KEY = "news_info";
private ButtonExtraProcessor() { private ButtonExtraProcessor() {
} }
@Override @Override
public void processExtra(Object object, String key, Object value) { public void processExtra(Object object, String key, Object value) {
if (KEY.equalsIgnoreCase(key)) { if (KEY.equalsIgnoreCase(key)) {
JSONArray news = ((JSONObject) value).getJSONArray("list"); JSONArray news = ((JSONObject) value).getJSONArray("list");
List<MpArticle> newsList = new ArrayList<MpArticle>(news.size()); List<MpArticle> newsList = new ArrayList<MpArticle>(news.size());
JSONObject article = null; JSONObject article = null;
for (int i = 0; i < news.size(); i++) { for (int i = 0; i < news.size(); i++) {
article = news.getJSONObject(i); article = news.getJSONObject(i);
article.put("show_cover_pic", article.remove("show_cover")); article.put("show_cover_pic", article.remove("show_cover"));
article.put("thumb_url", article.remove("cover_url")); article.put("thumb_url", article.remove("cover_url"));
article.put("url", article.remove("content_url")); article.put("url", article.remove("content_url"));
article.put("content_source_url", article.put("content_source_url", article.remove("source_url"));
article.remove("source_url")); newsList.add(JSON.toJavaObject(article, MpArticle.class));
newsList.add(JSON.toJavaObject(article, MpArticle.class)); }
} ((Button) object).setExtra(newsList);
((Button) object).setExtra(newsList); } else {
} else { ((Button) object).setContent(String.valueOf(value));
((Button) object).setContent(String.valueOf(value)); }
} }
} };
};
/** /**
* 获取公众号当前使用的自动回复规则包括关注后自动回复消息自动回复60分钟内触发一次关键词自动回复 * 获取公众号当前使用的自动回复规则包括关注后自动回复消息自动回复60分钟内触发一次关键词自动回复
* *
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting * @see com.foxinmy.weixin4j.mp.model.AutoReplySetting
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751299&token=&lang=zh_CN">获取自动回复规则</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751299&token=&lang=zh_CN">获取自动回复规则</a>
* @return 自定义回复配置信息 * @return 自定义回复配置信息
* @throws WeixinException * @throws WeixinException
*/ */
public AutoReplySetting getAutoReplySetting() throws WeixinException { public AutoReplySetting getAutoReplySetting() throws WeixinException {
String autoreply_setting_get_uri = getRequestUri("autoreply_setting_get_uri"); String autoreply_setting_get_uri = getRequestUri("autoreply_setting_get_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.get(String.format( WeixinResponse response = weixinExecutor.get(String.format(autoreply_setting_get_uri, token.getAccessToken()));
autoreply_setting_get_uri, token.getAccessToken()));
JSONObject result = response.getAsJson(); JSONObject result = response.getAsJson();
AutoReplySetting replySetting = JSON.toJavaObject(result, AutoReplySetting replySetting = JSON.toJavaObject(result, AutoReplySetting.class);
AutoReplySetting.class); List<AutoReplySetting.Rule> ruleList = null;
List<AutoReplySetting.Rule> ruleList = null; if (result.containsKey("keyword_autoreply_info")) {
if (result.containsKey("keyword_autoreply_info")) { JSONArray keywordList = result.getJSONObject("keyword_autoreply_info").getJSONArray("list");
JSONArray keywordList = result.getJSONObject( ruleList = new ArrayList<AutoReplySetting.Rule>(keywordList.size());
"keyword_autoreply_info").getJSONArray("list"); JSONObject keywordObj = null;
ruleList = new ArrayList<AutoReplySetting.Rule>(keywordList.size()); JSONArray replyList = null;
JSONObject keywordObj = null; JSONObject replyObj = null;
JSONArray replyList = null; for (int i = 0; i < keywordList.size(); i++) {
JSONObject replyObj = null; keywordObj = keywordList.getJSONObject(i);
for (int i = 0; i < keywordList.size(); i++) { AutoReplySetting.Rule rule = JSON.toJavaObject(keywordObj, AutoReplySetting.Rule.class);
keywordObj = keywordList.getJSONObject(i); replyList = keywordObj.getJSONArray("reply_list_info");
AutoReplySetting.Rule rule = JSON.toJavaObject(keywordObj, List<AutoReplySetting.Entry> entryList = new ArrayList<AutoReplySetting.Entry>(replyList.size());
AutoReplySetting.Rule.class); for (int j = 0; j < replyList.size(); j++) {
replyList = keywordObj.getJSONArray("reply_list_info"); replyObj = replyList.getJSONObject(j);
List<AutoReplySetting.Entry> entryList = new ArrayList<AutoReplySetting.Entry>( if (replyObj.getString("type").equals("news")) {
replyList.size()); entryList.add(JSON.parseObject(replyObj.toJSONString(), AutoReplySetting.Entry.class,
for (int j = 0; j < replyList.size(); j++) { ButtonExtraProcessor.global));
replyObj = replyList.getJSONObject(j); } else {
if (replyObj.getString("type").equals("news")) { entryList.add(JSON.toJavaObject(replyObj, AutoReplySetting.Entry.class));
entryList.add(JSON.parseObject(replyObj.toJSONString(), }
AutoReplySetting.Entry.class, }
ButtonExtraProcessor.global)); rule.setReplyList(entryList);
} else { ruleList.add(rule);
entryList.add(JSON.toJavaObject(replyObj, }
AutoReplySetting.Entry.class)); }
} replySetting.setKeywordReplyList(ruleList);
} return replySetting;
rule.setReplyList(entryList); }
ruleList.add(rule);
}
}
replySetting.setKeywordReplyList(ruleList);
return replySetting;
}
/** /**
* 接口调用次数调用清零公众号调用接口并不是无限制的为了防止公众号的程序错误而引发微信服务器负载异常默认情况下 * 接口调用次数调用清零公众号调用接口并不是无限制的为了防止公众号的程序错误而引发微信服务器负载异常默认情况下
* 每个公众号调用接口都不能超过一定限制 * 每个公众号调用接口都不能超过一定限制 当超过一定限制时调用对应接口会收到{"errcode":45009,"errmsg":"api freq
* 当超过一定限制时调用对应接口会收到{"errcode":45009,"errmsg":"api freq out of limit" * out of limit" }错误返回码。
* }错误返回码 *
* * @param appId
* @param appId * 公众号ID
* 公众号ID * @see <a href=
* @see <a * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592&token=&lang=zh_CN">接口清零</a>
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433744592&token=&lang=zh_CN">接口清零</a> * @return 操作结果
* @return 操作结果 * @throws WeixinException
* @throws WeixinException */
*/ public ApiResult clearQuota(String appId) throws WeixinException {
public ApiResult clearQuota(String appId) throws WeixinException { String clearquota_uri = getRequestUri("clearquota_uri");
String clearquota_uri = getRequestUri("clearquota_uri"); String body = String.format("{\"appid\":\"%s\"}", appId);
String body = String.format("{\"appid\":\"%s\"}", appId); WeixinResponse response = weixinExecutor.post(String.format(clearquota_uri, tokenManager.getAccessToken()),
WeixinResponse response = weixinExecutor.post( body);
String.format(clearquota_uri, tokenManager.getAccessToken()), return response.getAsResult();
body); }
return response.getAsResult();
}
} }

View File

@ -27,374 +27,386 @@ import com.foxinmy.weixin4j.util.StringUtil;
*/ */
public class MassApi extends MpApi { public class MassApi extends MpApi {
private final TokenManager tokenManager; private final TokenManager tokenManager;
public MassApi(TokenManager tokenManager) { public MassApi(TokenManager tokenManager) {
this.tokenManager = tokenManager; this.tokenManager = tokenManager;
} }
/** /**
* 上传图文消息,一个图文消息支持1到10条图文</br> <font * 上传图文消息,一个图文消息支持1到10条图文</br>
* color="red">具备微信支付权限的公众号在使用高级群发接口上传群发图文消息类型时可使用&lta&gt标签加入外链</font> * <font color=
* * "red">具备微信支付权限的公众号在使用高级群发接口上传群发图文消息类型时可使用&lta&gt标签加入外链</font>
* @param articles *
* 图片消息 * @param articles
* @return 媒体ID * 图片消息
* @throws WeixinException * @return 媒体ID
* @see <a * @throws WeixinException
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">上传图文素材</a> * @see <a href=
* @see com.foxinmy.weixin4j.tuple.MpArticle * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">上传图文素材</a>
*/ * @see com.foxinmy.weixin4j.tuple.MpArticle
public String uploadArticle(List<MpArticle> articles) */
throws WeixinException { public String uploadArticle(List<MpArticle> articles) throws WeixinException {
String article_upload_uri = getRequestUri("article_upload_uri"); String article_upload_uri = getRequestUri("article_upload_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("articles", articles); obj.put("articles", articles);
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(String.format(article_upload_uri, token.getAccessToken()),
String.format(article_upload_uri, token.getAccessToken()), obj.toJSONString());
obj.toJSONString());
return response.getAsJson().getString("media_id"); return response.getAsJson().getString("media_id");
} }
/** /**
* 分组群发 * 分组群发
* <p> * <p>
* 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息, * 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
* 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕 * 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
* </p> * </p>
* *
* @param tuple * @param tuple
* 消息元件 * 消息元件
* @param isToAll * @param isToAll
* 用于设定是否向全部用户发送值为true或false选择true该消息群发给所有用户 * 用于设定是否向全部用户发送值为true或false选择true该消息群发给所有用户
* 选择false可根据group_id发送给指定群组的用户 * 选择false可根据group_id发送给指定群组的用户
* @param groupId * @param groupId
* 分组ID * 分组ID
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中 * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中
* @throws WeixinException * @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.Group * @see com.foxinmy.weixin4j.mp.model.Group
* @see com.foxinmy.weixin4j.tuple.Text * @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image * @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice * @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.MpVideo * @see com.foxinmy.weixin4j.tuple.MpVideo
* @see com.foxinmy.weixin4j.tuple.MpNews * @see com.foxinmy.weixin4j.tuple.MpNews
* @see com.foxinmy.weixin4j.tuple.Card * @see com.foxinmy.weixin4j.tuple.Card
* @see com.foxinmy.weixin4j.tuple.MassTuple * @see com.foxinmy.weixin4j.tuple.MassTuple
* @see {@link GroupApi#getGroups()} * @see {@link GroupApi#getGroups()}
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a>
*/ */
@Deprecated @Deprecated
public String[] massByGroupId(MassTuple tuple, boolean isToAll, int groupId) public String[] massByGroupId(MassTuple tuple, boolean isToAll, int groupId) throws WeixinException {
throws WeixinException { if (tuple instanceof MpNews) {
if (tuple instanceof MpNews) { MpNews _news = (MpNews) tuple;
MpNews _news = (MpNews) tuple; List<MpArticle> _articles = _news.getArticles();
List<MpArticle> _articles = _news.getArticles(); if (StringUtil.isBlank(_news.getMediaId())) {
if (StringUtil.isBlank(_news.getMediaId())) { if (_articles.isEmpty()) {
if (_articles.isEmpty()) { throw new WeixinException("mass fail:mediaId or articles is required");
throw new WeixinException( }
"mass fail:mediaId or articles is required"); tuple = new MpNews(uploadArticle(_articles));
} }
tuple = new MpNews(uploadArticle(_articles)); }
} String msgtype = tuple.getMessageType();
} JSONObject obj = new JSONObject();
String msgtype = tuple.getMessageType(); JSONObject item = new JSONObject();
JSONObject obj = new JSONObject(); item.put("is_to_all", isToAll);
JSONObject item = new JSONObject(); if (!isToAll) {
item.put("is_to_all", isToAll); item.put("group_id", groupId);
if (!isToAll) { }
item.put("group_id", groupId); obj.put("filter", item);
} obj.put(msgtype, JSON.toJSON(tuple));
obj.put("filter", item); obj.put("msgtype", msgtype);
obj.put(msgtype, JSON.toJSON(tuple)); String mass_group_uri = getRequestUri("mass_group_uri");
obj.put("msgtype", msgtype); Token token = tokenManager.getCache();
String mass_group_uri = getRequestUri("mass_group_uri"); WeixinResponse response = weixinExecutor.post(String.format(mass_group_uri, token.getAccessToken()),
Token token = tokenManager.getCache(); obj.toJSONString());
WeixinResponse response = weixinExecutor.post(
String.format(mass_group_uri, token.getAccessToken()),
obj.toJSONString());
obj = response.getAsJson(); obj = response.getAsJson();
return new String[] { obj.getString("msg_id"), return new String[] { obj.getString("msg_id"), obj.getString("msg_data_id") };
obj.getString("msg_data_id") }; }
}
/** /**
* 分组ID群发图文消息 * 分组ID群发图文消息
* *
* @param articles * @param articles
* 图文列表 * 图文列表
* @param groupId * @param groupId
* 分组ID * 分组ID
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现 * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现
* @see <a * @see <a href=
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据分组群发</a>
* @see {@link #massByGroupId(Tuple,int)} * @see {@link #massByGroupId(Tuple,int)}
* @see com.foxinmy.weixin4j.tuple.MpArticle * @see com.foxinmy.weixin4j.tuple.MpArticle
* @throws WeixinException * @throws WeixinException
*/ */
@Deprecated @Deprecated
public String[] massArticleByGroupId(List<MpArticle> articles, int groupId) public String[] massArticleByGroupId(List<MpArticle> articles, int groupId) throws WeixinException {
throws WeixinException { String mediaId = uploadArticle(articles);
String mediaId = uploadArticle(articles); return massByGroupId(new MpNews(mediaId), false, groupId);
return massByGroupId(new MpNews(mediaId), false, groupId); }
}
/** /**
* 标签群发 * 群发消息
* <p> * <p>
* 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息, * 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
* 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕 * 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
* </p> * </p>
* *
* @param tuple * @param tuple
* 消息元件 * 消息元件
* @param isToAll * @param filter
* 用于设定是否向全部用户发送值为true或false选择true该消息群发给所有用户 * 过滤条件
* 选择false可根据group_id发送给指定群组的用户 * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中
* @param tagId * @throws WeixinException
* 标签ID * @see Tag
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中 * @see com.foxinmy.weixin4j.tuple.Text
* @throws WeixinException * @see com.foxinmy.weixin4j.tuple.Image
* @see Tag * @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Text * @see com.foxinmy.weixin4j.tuple.MpVideo
* @see com.foxinmy.weixin4j.tuple.Image * @see com.foxinmy.weixin4j.tuple.MpNews
* @see com.foxinmy.weixin4j.tuple.Voice * @see com.foxinmy.weixin4j.tuple.Card
* @see com.foxinmy.weixin4j.tuple.MpVideo * @see com.foxinmy.weixin4j.tuple.MassTuple
* @see com.foxinmy.weixin4j.tuple.MpNews */
* @see com.foxinmy.weixin4j.tuple.Card private String[] mass(MassTuple tuple, JSONObject filter) throws WeixinException {
* @see com.foxinmy.weixin4j.tuple.MassTuple if (tuple instanceof MpNews) {
* @see {@link TagApi#listTags()} MpNews _news = (MpNews) tuple;
* @see <a List<MpArticle> _articles = _news.getArticles();
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a> if (StringUtil.isBlank(_news.getMediaId())) {
*/ if (_articles.isEmpty()) {
public String[] massByTagId(MassTuple tuple, boolean isToAll, int tagId) throw new WeixinException("mass fail:mediaId or articles is required");
throws WeixinException { }
if (tuple instanceof MpNews) { tuple = new MpNews(uploadArticle(_articles));
MpNews _news = (MpNews) tuple; }
List<MpArticle> _articles = _news.getArticles(); if (!filter.containsKey("send_ignore_reprint")) {
if (StringUtil.isBlank(_news.getMediaId())) { filter.put("send_ignore_reprint", 0);
if (_articles.isEmpty()) { }
throw new WeixinException( }
"mass fail:mediaId or articles is required"); String msgtype = tuple.getMessageType();
} JSONObject obj = new JSONObject();
tuple = new MpNews(uploadArticle(_articles)); obj.putAll(filter);
} obj.put(msgtype, JSON.toJSON(tuple));
} obj.put("msgtype", msgtype);
String msgtype = tuple.getMessageType(); String mass_group_uri = getRequestUri("mass_group_uri");
JSONObject obj = new JSONObject(); Token token = tokenManager.getCache();
JSONObject item = new JSONObject(); WeixinResponse response = weixinExecutor.post(String.format(mass_group_uri, token.getAccessToken()),
item.put("is_to_all", isToAll); obj.toJSONString());
if (!isToAll) {
item.put("tag_id", tagId);
}
obj.put("filter", item);
obj.put(msgtype, JSON.toJSON(tuple));
obj.put("msgtype", msgtype);
String mass_group_uri = getRequestUri("mass_group_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(
String.format(mass_group_uri, token.getAccessToken()),
obj.toJSONString());
obj = response.getAsJson(); obj = response.getAsJson();
return new String[] { obj.getString("msg_id"), return new String[] { obj.getString("msg_id"), obj.getString("msg_data_id") };
obj.getString("msg_data_id") }; }
}
/** /**
* 标签群发图文消息 * 群发消息给所有粉丝
* *
* @param articles * @param tuple
* 图文列表 * 消息元件
* @param tagId * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中
* 标签ID * @throws WeixinException
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现 * @see <a href=
* @see <a * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a> */
* @see {@link #massByTagId(Tuple,int)} public String[] massToAll(MassTuple tuple) throws WeixinException {
* @see com.foxinmy.weixin4j.tuple.MpArticle String filter = String.format("{\"filter\":{\"is_to_all\":true}}");
* @throws WeixinException return mass(tuple, JSON.parseObject(filter));
*/ }
public String[] massArticleByTagId(List<MpArticle> articles, int tagId)
throws WeixinException {
String mediaId = uploadArticle(articles);
return massByTagId(new MpNews(mediaId), false, tagId);
}
/** /**
* openId群发 * 标签群发消息
* *
* @param tuple * @param tuple
* 消息元件 * 消息元件
* @param openIds * @param tagId
* openId列表 * 标签ID
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中 * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中
* @throws WeixinException * @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.User * @see Tag
* @see com.foxinmy.weixin4j.tuple.Text * @see {@link TagApi#listTags()}
* @see com.foxinmy.weixin4j.tuple.Image * @see #mass(MassTuple, JSONObject, boolean)
* @see com.foxinmy.weixin4j.tuple.Voice * @see <a href=
* @see com.foxinmy.weixin4j.tuple.MpVideo * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
* @see com.foxinmy.weixin4j.tuple.MpNews */
* @see com.foxinmy.weixin4j.tuple.Card public String[] massByTagId(MassTuple tuple, int tagId) throws WeixinException {
* @see com.foxinmy.weixin4j.tuple.MassTuple String filter = String.format("{\"filter\":{\"is_to_all\":false,\"tag_id\":%d}}", tagId);
* @see <a return mass(tuple, JSON.parseObject(filter));
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a> }
* @see {@link UserApi#getUser(String)}
*/
public String[] massByOpenIds(MassTuple tuple, String... openIds)
throws WeixinException {
if (tuple instanceof MpNews) {
MpNews _news = (MpNews) tuple;
List<MpArticle> _articles = _news.getArticles();
if (StringUtil.isBlank(_news.getMediaId())) {
if (_articles.isEmpty()) {
throw new WeixinException(
"mass fail:mediaId or articles is required");
}
tuple = new MpNews(uploadArticle(_articles));
}
}
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", openIds);
obj.put(msgtype, JSON.toJSON(tuple));
obj.put("msgtype", msgtype);
String mass_openid_uri = getRequestUri("mass_openid_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(
String.format(mass_openid_uri, token.getAccessToken()),
obj.toJSONString());
obj = response.getAsJson(); /**
return new String[] { obj.getString("msg_id"), * 标签群发图文消息
obj.getString("msg_data_id") }; *
} * @param articles
* 图文列表
* @param tagId
* 标签ID
* @param ignoreReprint
* 图文消息被判定为转载时是否继续群发
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据标签群发</a>
* @see {@link #massByTagId(Tuple,int)}
* @see com.foxinmy.weixin4j.tuple.MpArticle
* @throws WeixinException
*/
public String[] massArticleByTagId(List<MpArticle> articles, int tagId, boolean ignoreReprint)
throws WeixinException {
String mediaId = uploadArticle(articles);
String text = String.format("{\"filter\":{\"is_to_all\":false,\"tag_id\":%d}}", tagId);
JSONObject filter = JSON.parseObject(text);
filter.put("send_ignore_reprint", ignoreReprint ? 1 : 0);
return mass(new MpNews(mediaId), filter);
}
/** /**
* 根据openid群发图文消息 * openId群发消息
* *
* @param articles * @param tuple
* 图文列表 * 消息元件
* @param openIds * @param openIds
* openId列表 * openId列表
* @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中. * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中
* @see <a * @throws WeixinException
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a> * @see <a href=
* @see {@link #massByOpenIds(Tuple,String...)} * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a>
* @see com.foxinmy.weixin4j.tuple.MpArticle * @see {@link UserApi#getUser(String)}
* @throws WeixinException * @see #mass(MassTuple, JSONObject, boolean)
*/ */
public String[] massArticleByOpenIds(List<MpArticle> articles, public String[] massByOpenIds(MassTuple tuple, String... openIds) throws WeixinException {
String... openIds) throws WeixinException { JSONObject filter = new JSONObject();
String mediaId = uploadArticle(articles); filter.put("touser", openIds);
return massByOpenIds(new MpNews(mediaId), openIds); return mass(tuple, filter);
} }
/** /**
* 删除群发消息 * openid群发图文消息
* <p> *
* 请注意,只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片 * @param articles
* </p> * 图文列表
* * @param ignoreReprint
* @param msgid * 图文消息被判定为转载时是否继续群发
* 发送出去的消息ID * @param openIds
* @throws WeixinException * openId列表
* @see <a * @return 第一个元素为消息发送任务的ID,第二个元素为消息的数据ID该字段只有在群发图文消息时才会出现,可以用于在图文分析数据接口中.
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">删除群发</a> * @see <a href=
* @see {@link #massByTagId(Tuple, int)} * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">根据openid群发</a>
* @see {@link #massByOpenIds(Tuple, String...) * @see {@link #massByOpenIds(Tuple,String...)}
* @see com.foxinmy.weixin4j.tuple.MpArticle
* @throws WeixinException
*/
public String[] massArticleByOpenIds(List<MpArticle> articles, boolean ignoreReprint, String... openIds)
throws WeixinException {
String mediaId = uploadArticle(articles);
JSONObject filter = new JSONObject();
filter.put("touser", openIds);
filter.put("send_ignore_reprint", ignoreReprint ? 1 : 0);
return mass(new MpNews(mediaId), filter);
}
*/ /**
public ApiResult deleteMassNews(String msgid) throws WeixinException { * 删除群发消息
JSONObject obj = new JSONObject(); *
obj.put("msgid", msgid); * @param msgid
String mass_delete_uri = getRequestUri("mass_delete_uri"); * 发送出去的消息ID
Token token = tokenManager.getCache(); * @throws WeixinException
WeixinResponse response = weixinExecutor.post( * @see <a href=
String.format(mass_delete_uri, token.getAccessToken()), * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">删除群发</a>
obj.toJSONString()); * @see #deleteMassNews(String, int)
*/
public ApiResult deleteMassNews(String msgid) throws WeixinException {
return deleteMassNews(msgid, 0);
}
return response.getAsResult(); /**
} * 删除群发消息
* <p>
* 请注意,只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片
* </p>
*
* @param msgid
* 发送出去的消息ID
* @param articleIndex
* 要删除的文章在图文消息中的位置第一篇编号为1该字段不填或填0会删除全部文章
* @throws WeixinException
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">删除群发</a>
* @see {@link #massByTagId(Tuple, int)}
* @see {@link #massByOpenIds(Tuple, String...)
*
*/
public ApiResult deleteMassNews(String msgid, int articleIndex) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("msgid", msgid);
if (articleIndex > 0)
obj.put("article_idx", articleIndex);
String mass_delete_uri = getRequestUri("mass_delete_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(String.format(mass_delete_uri, token.getAccessToken()),
obj.toJSONString());
/** return response.getAsResult();
* 预览群发消息</br> 开发者可通过该接口发送消息给指定用户在手机端查看消息的样式和排版 }
*
* @param toUser
* 接收用户的openID
* @param toWxName
* 接收用户的微信号 towxname和touser同时赋值时以towxname优先
* @param tuple
* 消息元件
* @return 处理结果
* @throws WeixinException
* @see com.foxinmy.weixin4j.tuple.MassTuple
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">预览群发消息</a>
*/
public ApiResult previewMassNews(String toUser, String toWxName,
MassTuple tuple) throws WeixinException {
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", toUser);
obj.put("towxname", toWxName);
obj.put(msgtype, JSON.toJSON(tuple));
obj.put("msgtype", msgtype);
String mass_preview_uri = getRequestUri("mass_preview_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(
String.format(mass_preview_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsResult(); /**
} * 预览群发消息</br>
* 开发者可通过该接口发送消息给指定用户在手机端查看消息的样式和排版
*
* @param toUser
* 接收用户的openID
* @param toWxName
* 接收用户的微信号 towxname和touser同时赋值时以towxname优先
* @param tuple
* 消息元件
* @return 处理结果
* @throws WeixinException
* @see com.foxinmy.weixin4j.tuple.MassTuple
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">预览群发消息</a>
*/
public ApiResult previewMassNews(String toUser, String toWxName, MassTuple tuple) throws WeixinException {
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", toUser);
obj.put("towxname", toWxName);
obj.put(msgtype, JSON.toJSON(tuple));
obj.put("msgtype", msgtype);
String mass_preview_uri = getRequestUri("mass_preview_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(String.format(mass_preview_uri, token.getAccessToken()),
obj.toJSONString());
/** return response.getAsResult();
* 查询群发发送状态 }
*
* @param msgId
* 消息ID
* @return 消息发送状态,如sendsuccess:发送成功sendfail:发送失败
* @throws WeixinException
* @see <a
* href="https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">查询群发状态</a>
*/
public String getMassNewStatus(String msgId) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("msg_id", msgId);
String mass_get_uri = getRequestUri("mass_get_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(
String.format(mass_get_uri, token.getAccessToken()),
obj.toJSONString());
String status = response.getAsJson().getString("msg_status"); /**
String desc = massStatusMap.get(status); * 查询群发发送状态
return String.format("%s:%s", status, desc); *
} * @param msgId
* 消息ID
* @return 消息发送状态,如sendsuccess:发送成功sendfail:发送失败
* @throws WeixinException
* @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN">查询群发状态</a>
*/
public String getMassNewStatus(String msgId) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("msg_id", msgId);
String mass_get_uri = getRequestUri("mass_get_uri");
Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.post(String.format(mass_get_uri, token.getAccessToken()),
obj.toJSONString());
private final static Map<String, String> massStatusMap; String status = response.getAsJson().getString("msg_status");
static { String desc = massStatusMap.get(status);
massStatusMap = new HashMap<String, String>(); return String.format("%s:%s", status, desc);
massStatusMap.put("sendsuccess", "发送成功"); }
massStatusMap.put("send_success", "发送成功");
massStatusMap.put("success", "发送成功"); private final static Map<String, String> massStatusMap;
massStatusMap.put("send success", "发送成功"); static {
massStatusMap.put("sendfail", "发送失败"); massStatusMap = new HashMap<String, String>();
massStatusMap.put("send_fail", "发送失败"); massStatusMap.put("sendsuccess", "发送成功");
massStatusMap.put("fail", "发送失败"); massStatusMap.put("send_success", "发送成功");
massStatusMap.put("send fail", "发送失败"); massStatusMap.put("success", "发送成功");
massStatusMap.put("err(10001)", "涉嫌广告"); massStatusMap.put("send success", "发送成功");
massStatusMap.put("err(20001)", "涉嫌政治"); massStatusMap.put("sendfail", "发送失败");
massStatusMap.put("err(20004)", "涉嫌社会"); massStatusMap.put("send_fail", "发送失败");
massStatusMap.put("err(20006)", "涉嫌违法犯罪"); massStatusMap.put("fail", "发送失败");
massStatusMap.put("err(20008)", "涉嫌欺诈"); massStatusMap.put("send fail", "发送失败");
massStatusMap.put("err(20013)", "涉嫌版权"); massStatusMap.put("err(10001)", "涉嫌广告");
massStatusMap.put("err(22000)", "涉嫌互推(互相宣传)"); massStatusMap.put("err(20001)", "涉嫌政治");
massStatusMap.put("err(21000)", "涉嫌其他"); massStatusMap.put("err(20004)", "涉嫌社会");
} massStatusMap.put("err(20006)", "涉嫌违法犯罪");
massStatusMap.put("err(20008)", "涉嫌欺诈");
massStatusMap.put("err(20013)", "涉嫌版权");
massStatusMap.put("err(22000)", "涉嫌互推(互相宣传)");
massStatusMap.put("err(21000)", "涉嫌其他");
}
} }

View File

@ -29,216 +29,203 @@ import com.foxinmy.weixin4j.type.ButtonType;
*/ */
public class MenuApi extends MpApi { public class MenuApi extends MpApi {
private final TokenManager tokenManager; private final TokenManager tokenManager;
public MenuApi(TokenManager tokenManager) { public MenuApi(TokenManager tokenManager) {
this.tokenManager = tokenManager; this.tokenManager = tokenManager;
} }
/** /**
* 自定义菜单 * 自定义菜单
* *
* @param buttons * @param buttons
* 菜单列表 * 菜单列表
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013&token=&lang=zh_CN">
* 创建自定义菜单</a> * 创建自定义菜单</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @return 处理结果 * @return 处理结果
*/ */
public ApiResult createMenu(List<Button> buttons) throws WeixinException { public ApiResult createMenu(List<Button> buttons) throws WeixinException {
String menu_create_uri = getRequestUri("menu_create_uri"); String menu_create_uri = getRequestUri("menu_create_uri");
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("button", buttons); obj.put("button", buttons);
return createMenu0(menu_create_uri, obj).getAsResult(); return createMenu0(menu_create_uri, obj).getAsResult();
} }
private WeixinResponse createMenu0(String url, JSONObject data) private WeixinResponse createMenu0(String url, JSONObject data) throws WeixinException {
throws WeixinException { return weixinExecutor.post(String.format(url, tokenManager.getAccessToken()),
return weixinExecutor.post( JSON.toJSONString(data, new NameFilter() {
String.format(url, tokenManager.getAccessToken()), @Override
JSON.toJSONString(data, new NameFilter() { public String process(Object object, String name, Object value) {
@Override if (object instanceof Button && name.equals("content")) {
public String process(Object object, String name, ButtonType buttonType = ButtonType.valueOf(((Button) object).getType());
Object value) { if (buttonType != null) {
if (object instanceof Button && name.equals("content")) { if (ButtonType.view == buttonType || ButtonType.miniprogram == buttonType) {
ButtonType buttonType = ((Button) object).getType(); return "url";
if (buttonType != null) { } else if (ButtonType.media_id == buttonType || ButtonType.view_limited == buttonType) {
if (ButtonType.view == buttonType) { return "media_id";
return "url"; } else {
} else if (ButtonType.media_id == buttonType return "key";
|| ButtonType.view_limited == buttonType) { }
return "media_id"; }
} else { }
return "key"; return name;
} }
} }));
}
return name;
}
}));
} }
/** /**
* 查询菜单 * 查询菜单
* *
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN">
* 查询菜单</a> * 查询菜单</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @return 菜单集合 * @return 菜单集合
*/ */
public List<Button> getMenu() throws WeixinException { public List<Button> getMenu() throws WeixinException {
return buttonsConvertor(getMenu0().getJSONObject("menu")); return buttonsConvertor(getMenu0().getJSONObject("menu"));
} }
private JSONObject getMenu0() throws WeixinException { private JSONObject getMenu0() throws WeixinException {
String menu_get_uri = getRequestUri("menu_get_uri"); String menu_get_uri = getRequestUri("menu_get_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.get(String.format( WeixinResponse response = weixinExecutor.get(String.format(menu_get_uri, token.getAccessToken()));
menu_get_uri, token.getAccessToken())); return response.getAsJson();
return response.getAsJson(); }
}
/** /**
* 查询全部菜单(包含个性化菜单) * 查询全部菜单(包含个性化菜单)
* *
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014&token=&lang=zh_CN">
* 普通菜单</a> * 普通菜单</a>
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN">
* 个性化菜单</a> * 个性化菜单</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @see com.foxinmy.weixin4j.mp.model.Menu * @see com.foxinmy.weixin4j.mp.model.Menu
* @return 菜单集合 * @return 菜单集合
*/ */
public List<Menu> getAllMenu() throws WeixinException { public List<Menu> getAllMenu() throws WeixinException {
JSONObject response = getMenu0(); JSONObject response = getMenu0();
List<Menu> menus = new ArrayList<Menu>(); List<Menu> menus = new ArrayList<Menu>();
// 普通菜单 // 普通菜单
JSONObject menuObj = response.getJSONObject("menu"); JSONObject menuObj = response.getJSONObject("menu");
menus.add(new Menu(menuObj.getString("menuid"), menus.add(new Menu(menuObj.getString("menuid"), buttonsConvertor(menuObj), null));
buttonsConvertor(menuObj), null)); // 个性化菜单
// 个性化菜单 JSONArray menuObjs = response.getJSONArray("conditionalmenu");
JSONArray menuObjs = response.getJSONArray("conditionalmenu"); if (menuObjs != null && !menuObjs.isEmpty()) {
if (menuObjs != null && !menuObjs.isEmpty()) { for (int i = 0; i < menuObjs.size(); i++) {
for (int i = 0; i < menuObjs.size(); i++) { menuObj = menuObjs.getJSONObject(i);
menuObj = menuObjs.getJSONObject(i); menus.add(new Menu(menuObj.getString("menuid"), buttonsConvertor(menuObj),
menus.add(new Menu(menuObj.getString("menuid"), menuObj.getObject("matchrule", MenuMatchRule.class)));
buttonsConvertor(menuObj), menuObj.getObject( }
"matchrule", MenuMatchRule.class))); }
} return menus;
} }
return menus;
}
/** /**
* 删除菜单 * 删除菜单
* *
* @return 处理结果 * @return 处理结果
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015&token=&lang=zh_CN">
* 删除菜单</a> * 删除菜单</a>
* @return 处理结果 * @return 处理结果
*/ */
public ApiResult deleteMenu() throws WeixinException { public ApiResult deleteMenu() throws WeixinException {
String menu_delete_uri = getRequestUri("menu_delete_uri"); String menu_delete_uri = getRequestUri("menu_delete_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
WeixinResponse response = weixinExecutor.get(String.format( WeixinResponse response = weixinExecutor.get(String.format(menu_delete_uri, token.getAccessToken()));
menu_delete_uri, token.getAccessToken()));
return response.getAsResult(); return response.getAsResult();
} }
/** /**
* 创建个性化菜单 * 创建个性化菜单
* *
* @param buttons * @param buttons
* 菜单列表 * 菜单列表
* @param matchRule * @param matchRule
* 匹配规则 至少要有一个匹配信息是不为空 * 匹配规则 至少要有一个匹配信息是不为空
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN">
* 创建个性化菜单</a> * 创建个性化菜单</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @return 菜单ID * @return 菜单ID
*/ */
public String createCustomMenu(List<Button> buttons, MenuMatchRule matchRule) public String createCustomMenu(List<Button> buttons, MenuMatchRule matchRule) throws WeixinException {
throws WeixinException { String menu_create_uri = getRequestUri("menu_custom_create_uri");
String menu_create_uri = getRequestUri("menu_custom_create_uri"); JSONObject obj = new JSONObject();
JSONObject obj = new JSONObject(); obj.put("button", buttons);
obj.put("button", buttons); obj.put("matchrule", matchRule.getRule());
obj.put("matchrule", matchRule.getRule()); return createMenu0(menu_create_uri, obj).getAsJson().getString("menuid");
return createMenu0(menu_create_uri, obj).getAsJson() }
.getString("menuid");
}
/** /**
* 删除个性化菜单 * 删除个性化菜单
* *
* @throws WeixinException * @throws WeixinException
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN">
* 删除个性化菜单</a> * 删除个性化菜单</a>
* @return 处理结果 * @return 处理结果
*/ */
public ApiResult deleteCustomMenu(String menuId) throws WeixinException { public ApiResult deleteCustomMenu(String menuId) throws WeixinException {
String menu_delete_uri = getRequestUri("menu_delete_custom_uri"); String menu_delete_uri = getRequestUri("menu_delete_custom_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("menuid", menuId); obj.put("menuid", menuId);
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(String.format(menu_delete_uri, token.getAccessToken()),
String.format(menu_delete_uri, token.getAccessToken()), obj.toJSONString());
obj.toJSONString());
return response.getAsResult(); return response.getAsResult();
} }
/** /**
* 测试个性化菜单匹配结果 * 测试个性化菜单匹配结果
* *
* @param userId * @param userId
* 可以是粉丝的OpenID也可以是粉丝的微信号 * 可以是粉丝的OpenID也可以是粉丝的微信号
* @return 匹配到的菜单配置 * @return 匹配到的菜单配置
* @see <a href= * @see <a href=
* "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN"> * "https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455782296&token=&lang=zh_CN">
* 测试个性化菜单</a> * 测试个性化菜单</a>
* @see com.foxinmy.weixin4j.model.Button * @see com.foxinmy.weixin4j.model.Button
* @throws WeixinException * @throws WeixinException
*/ */
public List<Button> matchCustomMenu(String userId) throws WeixinException { public List<Button> matchCustomMenu(String userId) throws WeixinException {
String menu_trymatch_uri = getRequestUri("menu_trymatch_uri"); String menu_trymatch_uri = getRequestUri("menu_trymatch_uri");
Token token = tokenManager.getCache(); Token token = tokenManager.getCache();
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
obj.put("user_id", userId); obj.put("user_id", userId);
WeixinResponse response = weixinExecutor.post( WeixinResponse response = weixinExecutor.post(String.format(menu_trymatch_uri, token.getAccessToken()),
String.format(menu_trymatch_uri, token.getAccessToken()), obj.toJSONString());
obj.toJSONString());
return buttonsConvertor(response.getAsJson().getJSONObject("menu")); return buttonsConvertor(response.getAsJson().getJSONObject("menu"));
} }
private final ParseProcess buttonProcess = new ExtraProcessor() { private final ParseProcess buttonProcess = new ExtraProcessor() {
@Override @Override
public void processExtra(Object object, String key, Object value) { public void processExtra(Object object, String key, Object value) {
((Button) object).setContent(String.valueOf(value)); ((Button) object).setContent(String.valueOf(value));
} }
}; };
private List<Button> buttonsConvertor(JSONObject menu) { private List<Button> buttonsConvertor(JSONObject menu) {
JSONArray buttons = menu.getJSONArray("button"); JSONArray buttons = menu.getJSONArray("button");
List<Button> buttonList = new ArrayList<Button>(buttons.size()); List<Button> buttonList = new ArrayList<Button>(buttons.size());
for (int i = 0; i < buttons.size(); i++) { for (int i = 0; i < buttons.size(); i++) {
buttonList.add(JSON.parseObject(buttons.getString(i), Button.class, buttonList.add(JSON.parseObject(buttons.getString(i), Button.class, buttonProcess));
buttonProcess)); }
} return buttonList;
return buttonList; }
}
} }

View File

@ -239,3 +239,22 @@ shake_around_device_update_uri={api_base_url}/shakearound/device/update?access_t
shake_around_device_search_uri={api_base_url}/shakearound/device/search?access_token=%s shake_around_device_search_uri={api_base_url}/shakearound/device/search?access_token=%s
#\u6447\u4e00\u6447\u5468\u8fb9-\u83b7\u53d6\u8bbe\u5907\u548c\u7528\u6237\u4fe1\u606f #\u6447\u4e00\u6447\u5468\u8fb9-\u83b7\u53d6\u8bbe\u5907\u548c\u7528\u6237\u4fe1\u606f
shake_around_user_get_shake_info={api_base_url}/shakearound/user/getshakeinfo?access_token=%s shake_around_user_get_shake_info={api_base_url}/shakearound/user/getshakeinfo?access_token=%s
# \u6253\u5f00\u5df2\u7fa4\u53d1\u6587\u7ae0\u8bc4\u8bba
news_comment_open={api_cgi_url}/comment/open?access_token=%s
# \u5173\u95ed\u5df2\u7fa4\u53d1\u6587\u7ae0\u8bc4\u8bba
news_comment_open={api_cgi_url}/comment/close?access_token=%s
# \u5c06\u8bc4\u8bba\u6807\u8bb0\u7cbe\u9009
news_comment_markelect={api_cgi_url}/comment/markelect?access_token=%s
# \u5c06\u8bc4\u8bba\u53d6\u6d88\u7cbe\u9009
news_comment_unmarkelect={api_cgi_url}/comment/unmarkelect?access_token=%s
# \u5220\u9664\u8bc4\u8bba
news_comment_delete={api_cgi_url}/comment/delete?access_token=%s
# \u56de\u590d\u8bc4\u8bba
news_comment_reply_add={api_cgi_url}/comment/reply/add?access_token=%s
# \u5220\u9664\u56de\u590d
news_comment_reply_delete={api_cgi_url}/comment/reply/delete?access_token=%s
# \u83b7\u53d6\u8bc4\u8bba
news_comment_list={api_cgi_url}/comment/list?access_token=%s

View File

@ -0,0 +1,22 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
/**
* 文章评论
*
* @className ArticleComment
* @author jinyu
* @date May 19, 2017
* @since JDK 1.6
* @see
*/
public class ArticleComment implements Serializable {
private static final long serialVersionUID = -8506024679132313314L;
public enum ArticleCommentType {
GENERAL, // 普通评论
MARKELECT // 精选评论
}
}

View File

@ -29,78 +29,72 @@ import com.foxinmy.weixin4j.tuple.Text;
* @see * @see
*/ */
public class MassTest extends TokenTest { public class MassTest extends TokenTest {
private MassApi massApi; private MassApi massApi;
private MediaApi mediaApi; private MediaApi mediaApi;
@Before @Before
public void init() { public void init() {
this.massApi = new MassApi(tokenManager); this.massApi = new MassApi(tokenManager);
this.mediaApi = new MediaApi(tokenManager); this.mediaApi = new MediaApi(tokenManager);
} }
@Test @Test
public void uploadArticle() throws IOException, WeixinException { public void uploadArticle() throws IOException, WeixinException {
List<MpArticle> articles = new ArrayList<MpArticle>(); List<MpArticle> articles = new ArrayList<MpArticle>();
File file = new File("/tmp/test.jpg"); File file = new File("/tmp/test.jpg");
MediaUploadResult mediaResult = mediaApi.uploadMedia(false, MediaUploadResult mediaResult = mediaApi.uploadMedia(false, new FileInputStream(file), file.getName());
new FileInputStream(file), file.getName()); articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content"));
articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content")); massApi.uploadArticle(articles);
massApi.uploadArticle(articles); }
}
@Test @Test
public void massByGroupId() throws WeixinException { public void massByGroupId() throws WeixinException {
String[] msgId = massApi.massByGroupId(new Image("mediaId"), true, 0); String[] msgId = massApi.massByGroupId(new Image("mediaId"), true, 0);
Assert.assertTrue(msgId[0] != null); Assert.assertTrue(msgId[0] != null);
} }
@Test @Test
public void massByOpenIds() throws WeixinException { public void massByOpenIds() throws WeixinException {
String[] msgId = massApi.massByOpenIds(new Text("HI"), String[] msgId = massApi.massByOpenIds(new Text("HI"), "oyFLst1bqtuTcxK-ojF8hOGtLQao");
"oyFLst1bqtuTcxK-ojF8hOGtLQao"); Assert.assertTrue(msgId[0] != null);
Assert.assertTrue(msgId[0] != null); }
}
@Test @Test
public void massArticleByGroup() throws IOException, WeixinException { public void massArticleByGroup() throws IOException, WeixinException {
List<MpArticle> articles = new ArrayList<MpArticle>(); List<MpArticle> articles = new ArrayList<MpArticle>();
File file = new File("/tmp/test.jpg"); File file = new File("/tmp/test.jpg");
MediaUploadResult mediaResult = mediaApi.uploadMedia(false, MediaUploadResult mediaResult = mediaApi.uploadMedia(false, new FileInputStream(file), file.getName());
new FileInputStream(file), file.getName()); articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content"));
articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content")); String[] massId = massApi.massArticleByGroupId(articles, 0);
String[] massId = massApi.massArticleByGroupId(articles, 0); Assert.assertTrue(massId[0] != null);
Assert.assertTrue(massId[0] != null); }
}
@Test @Test
public void massArticleByOpenIds() throws IOException, WeixinException { public void massArticleByOpenIds() throws IOException, WeixinException {
List<MpArticle> articles = new ArrayList<MpArticle>(); List<MpArticle> articles = new ArrayList<MpArticle>();
File file = new File("/tmp/test.jpg"); File file = new File("/tmp/test.jpg");
MediaUploadResult mediaResult = mediaApi.uploadMedia(false, MediaUploadResult mediaResult = mediaApi.uploadMedia(false, new FileInputStream(file), file.getName());
new FileInputStream(file), file.getName()); articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content"));
articles.add(new MpArticle(mediaResult.getMediaId(), "title", "content")); String[] massId = massApi.massArticleByOpenIds(articles, false, "owGBft_vbBbOaQOmpEUE4xDLeRSU");
String[] massId = massApi.massArticleByOpenIds(articles, Assert.assertTrue(massId[0] != null);
"owGBft_vbBbOaQOmpEUE4xDLeRSU"); }
Assert.assertTrue(massId[0] != null);
}
@Test @Test
public void deleteMass() throws WeixinException { public void deleteMass() throws WeixinException {
ApiResult result = massApi.deleteMassNews("34182"); ApiResult result = massApi.deleteMassNews("34182");
Assert.assertEquals("0", result.getReturnCode()); Assert.assertEquals("0", result.getReturnCode());
} }
@Test @Test
public void previewMass() throws WeixinException { public void previewMass() throws WeixinException {
ApiResult result = massApi.previewMassNews( ApiResult result = massApi.previewMassNews("oyFLst1bqtuTcxK-ojF8hOGtLQao", null, new Text("test"));
"oyFLst1bqtuTcxK-ojF8hOGtLQao", null, new Text("test")); Assert.assertEquals("0", result.getReturnCode());
Assert.assertEquals("0", result.getReturnCode()); }
}
@Test @Test
public void getMassNews() throws WeixinException { public void getMassNews() throws WeixinException {
String status = massApi.getMassNewStatus("82358"); String status = massApi.getMassNewStatus("82358");
System.out.println(status); System.out.println(status);
Assert.assertNotNull(status); Assert.assertNotNull(status);
} }
} }

View File

@ -58,7 +58,7 @@ public class MenuApi extends QyApi {
@Override @Override
public String process(Object object, String name, Object value) { public String process(Object object, String name, Object value) {
if (object instanceof Button && name.equals("content")) { if (object instanceof Button && name.equals("content")) {
ButtonType buttonType = ((Button) object).getType(); ButtonType buttonType = ButtonType.valueOf(((Button) object).getType());
if (buttonType != null) { if (buttonType != null) {
if (ButtonType.view == buttonType) { if (ButtonType.view == buttonType) {
return "url"; return "url";