diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..4898b587
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+ass
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+*~
+
+# eclipse ignore
+*.settings/*
+/.project
+/.classpath
+/.tomcatplugin
+
+# maven ignore
+target/*
+
+# other ignore
+*.log
+*.tmp
+Thumbs.db
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..7210f2e8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,189 @@
+
+
+ 4.0.0
+ com.foxinmy.weixin
+ 0.0.1-SNAPSHOT
+ weixin4j-sdk
+ weixin4j-sdk
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.7
+ 1.7
+
+ 3.0
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.6
+
+ ${project.build.sourceEncoding}
+
+
+
+
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ commons-codec
+ commons-codec
+ ${commons.codec.version}
+
+
+ commons-httpclient
+ commons-httpclient
+ ${commons.httpclient.version}
+
+
+ commons-logging
+ commons-logging
+
+
+ commons-codec
+ commons-codec
+
+
+
+
+ com.alibaba
+ fastjson
+ ${fastjson.version}
+
+
+ dom4j
+ dom4j
+ ${dom4j.version}
+
+
+ jaxen
+ jaxen
+ ${jaxen.version}
+
+
+
+ ch.qos.logback
+ logback-core
+ ${logback.version}
+
+
+ ch.qos.logback
+ logback-classic
+ ${logback.version}
+
+
+ logback-core
+ ch.qos.logback
+
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${jcl.over.version}
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.slf4j
+ log4j-over-slf4j
+ ${log4j.over.version}
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.jsoup
+ jsoup
+ 1.7.3
+
+
+
+
+
+ releases
+ Internal Releases
+ http://42.62.64.62:8081/nexus/content/repositories/releases/
+
+
+
+ snapshots
+ Internal Snapshots
+ http://42.62.64.62:8081/nexus/content/repositories/snapshots/
+
+
+
+
+
+
+ true
+
+
+ true
+
+ public
+ Public Repositories
+ http://42.62.64.62:8081/nexus/content/groups/public/
+
+
+
+
+
+ true
+
+
+ true
+
+ public
+ Public Repositories
+ http://42.62.64.62:8081/nexus/content/groups/public/
+
+
+
+ 4.8.2
+ 1.6.1
+ 1.0.9
+ 1.7.6
+ 1.7.6
+ 1.4.7
+ 3.1
+ 1.9
+ 1.1.9
+ 1.1.4
+ UTF-8
+
+
+
+ default
+
+ true
+
+
+ true
+
+
+
+
diff --git a/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java b/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java
new file mode 100644
index 00000000..17a2b0b0
--- /dev/null
+++ b/src/main/java/com/foxinmy/weixin4j/WeixinProxy.java
@@ -0,0 +1,912 @@
+package com.foxinmy.weixin4j;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.io.SAXReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.foxinmy.weixin4j.exception.WeixinException;
+import com.foxinmy.weixin4j.http.HttpRequest;
+import com.foxinmy.weixin4j.http.Response;
+import com.foxinmy.weixin4j.model.Button;
+import com.foxinmy.weixin4j.model.Following;
+import com.foxinmy.weixin4j.model.Group;
+import com.foxinmy.weixin4j.model.MpArticle;
+import com.foxinmy.weixin4j.model.QRParameter;
+import com.foxinmy.weixin4j.model.Token;
+import com.foxinmy.weixin4j.model.User;
+import com.foxinmy.weixin4j.model.UserToken;
+import com.foxinmy.weixin4j.msg.BaseMessage;
+import com.foxinmy.weixin4j.msg.notify.BaseNotify;
+import com.foxinmy.weixin4j.type.EventType;
+import com.foxinmy.weixin4j.type.MediaType;
+import com.foxinmy.weixin4j.type.MessageType;
+import com.foxinmy.weixin4j.util.WeixinConfig;
+import com.foxinmy.weixin4j.util.WeixinUtil;
+import com.foxinmy.weixin4j.xml.XStream;
+
+/**
+ * 微信服务实现
+ *
+ * @className WeixinServiceImpl
+ * @author jy.hu
+ * @date 2014年3月23日
+ * @since JDK 1.7
+ * @see api文档
+ */
+public class WeixinProxy {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private HttpRequest request = new HttpRequest();
+
+ /**
+ * 验证微信签名
+ *
+ * @param echostr
+ * 随机字符串
+ * @param timestamp
+ * 时间戳
+ * @param nonce
+ * 随机数
+ * @param signature
+ * 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数
+ * @return
+ * 开发者通过检验signature对请求进行相关校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效
+ * ,成为开发者成功,否则接入失败
+ * @see 接入指南
+ */
+ public String signature(String echostr, String timestamp, String nonce,
+ String signature) {
+ String app_token = WeixinConfig.getValue("app_token");
+ if (WeixinUtil.isBlank(app_token)) {
+ log.error("signature fail : token is null!");
+ return null;
+ }
+ if (WeixinUtil.isBlank(echostr) || WeixinUtil.isBlank(timestamp)
+ || WeixinUtil.isBlank(nonce)) {
+ log.error("signature fail : invalid parameter!");
+ return null;
+ }
+ String _signature = null;
+ try {
+ String[] a = { app_token, timestamp, nonce };
+ Arrays.sort(a);
+ StringBuilder sb = new StringBuilder(3);
+ for (String str : a) {
+ sb.append(str);
+ }
+ _signature = DigestUtils.sha1Hex(sb.toString());
+ } catch (Exception e) {
+ log.error("signature error", e);
+ }
+ if (signature.equals(_signature)) {
+ return echostr;
+ } else {
+ log.error("signature fail : invalid signature!");
+ return null;
+ }
+ }
+
+ /**
+ * xml消息转换为消息对象
+ *
+ * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
+ *
+ *
+ * @param xml
+ * 消息字符串
+ * @return 消息对象
+ * @throws DocumentException
+ * @see 验证消息的合法性
+ * @see 普通消息
+ * @see 事件触发
+ * @see com.foxinmy.weixin4j.type.MessageType
+ * @see com.feican.weixin.msg.BaeMessage
+ * @see com.foxinmy.weixin4j.msg.TextMessage
+ * @see com.foxinmy.weixin4j.msg.in.ImageMessage
+ * @see com.foxinmy.weixin4j.msg.in.VoiceMessage
+ * @see com.foxinmy.weixin4j.msg.in.VideoMessage
+ * @see com.foxinmy.weixin4j.msg.in.LocationMessage
+ * @see com.foxinmy.weixin4j.msg.in.LinkMessage
+ * @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
+ * @see com.foxinmy.weixin4j.msg.event.ScanEventMessage
+ * @see com.foxinmy.weixin4j.msg.event.LocationEventMessage
+ * @see com.foxinmy.weixin4j.msg.event.MenuEventMessage
+ */
+ public BaseMessage xml2msg(String xmlStr) throws DocumentException {
+ if (WeixinUtil.isBlank(xmlStr))
+ return null;
+ Document doc = DocumentHelper.parseText(xmlStr);
+ String type = doc.selectSingleNode("/xml/MsgType").getStringValue();
+ if (WeixinUtil.isBlank(type)) {
+ return null;
+ }
+ XStream xstream = new XStream();
+ MessageType messageType = MessageType.valueOf(type.toLowerCase());
+ Class extends BaseMessage> messageClass = messageType
+ .getMessageClass();
+ if (messageType == MessageType.event) {
+ type = doc.selectSingleNode("/xml/Event").getStringValue();
+ messageClass = EventType.valueOf(type.toLowerCase())
+ .getEventClass();
+ }
+ xstream.alias("xml", messageClass);
+ xstream.ignoreUnknownElements();
+ xstream.autodetectAnnotations(true);
+ xstream.processAnnotations(messageClass);
+ return xstream.fromXML(doc.asXML(), messageClass);
+ }
+
+ /**
+ * xml消息转换为消息对象
+ *
+ * @param inputStream
+ * 带消息字符串的输入流
+ * @return 消息对象
+ * @throws DocumentException
+ * @see {@link com.foxinmy.weixin4j.WeixinProxy#xml2msg(String)}
+ */
+ public BaseMessage xml2msg(InputStream inputStream)
+ throws DocumentException {
+ SAXReader reader = new SAXReader();
+ Document doc = reader.read(inputStream);
+ return xml2msg(doc.asXML());
+ }
+
+ /**
+ * 获取token
+ *
+ * 正常情况下返回{"access_token":"ACCESS_TOKEN","expires_in":7200},否则抛出异常.
+ *
+ *
+ * @return token对象
+ * @throws WeixinException
+ * @see 获取token说明
+ * @see com.foxinmy.weixin4j.model.Token
+ */
+ public Token getToken() throws WeixinException {
+ XStream xstream = new XStream();
+ xstream.autodetectAnnotations(true);
+ xstream.processAnnotations(Token.class);
+ String appOpenId = WeixinConfig.getValue("app_openId");
+ String token_path = WeixinConfig.getValue("token_path");
+ File token_file = new File(String.format("%s/%s_token.xml", token_path,
+ appOpenId));
+ Token token = null;
+ Calendar ca = Calendar.getInstance();
+ long now_time = ca.getTimeInMillis();
+ try {
+ if (token_file.exists()) {
+ token = (Token) xstream.fromXML(token_file);
+
+ long expise_time = token.getTime()
+ + (token.getExpires_in() * 1000) - 5;
+ if (expise_time > now_time) {
+ return token;
+ }
+ } else {
+ try {
+ token_file.createNewFile();
+ } catch (IOException e) {
+ token_file.getParentFile().mkdirs();
+ }
+ }
+ token_path = WeixinConfig.getValue("api_token_uri");
+ Response response = request.get(token_path);
+ token = response.getAsObject(Token.class);
+ token.setTime(now_time);
+ token.setOpenid(appOpenId);
+ xstream.toXML(token, new FileOutputStream(token_file));
+ } catch (IOException e) {
+ log.error("获取token出错", e);
+ }
+ return token;
+ }
+
+ /**
+ * 获取token
+ *
+ * @param code
+ * 用户授权后返回的code
+ * @return token对象
+ * @throws WeixinException
+ * @see 获取用户token
+ * @see com.foxinmy.weixin4j.model.UserToken
+ */
+ public UserToken getAccessToken(String code) throws WeixinException {
+ String user_token_uri = WeixinConfig.getValue("sns_user_token_uri");
+ Response response = request.get(String.format(user_token_uri, code));
+ // 暂时不保存用户token
+ return response.getAsObject(UserToken.class);
+ }
+
+ /**
+ * 获取用户信息
+ *
+ * @param token
+ * 授权票据
+ * @return 用户对象
+ * @throws WeixinException
+ * @see 拉取用户信息
+ * @see com.foxinmy.weixin4j.model.User
+ * @see com.foxinmy.weixin4j.model.UserToken
+ * {@link com.foxinmy.weixin4j.WeixinProxy#getAccessToken(String)}
+ */
+ public User getUser(UserToken token) throws WeixinException {
+ String user_info_uri = WeixinConfig.getValue("sns_user_info_uri");
+ Response response = request.get(String.format(user_info_uri,
+ token.getAccess_token(), token.getOpenid()));
+ return response.getAsObject(User.class);
+ }
+
+ /**
+ * 获取用户信息
+ *
+ * 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的,对于不同公众号,
+ * 同一用户的openid不同),公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间
+ *
+ *
+ * @param openId
+ * 用户对应的ID
+ * @return 用户对象
+ * @throws WeixinException
+ * @see 获取用户信息
+ * @see com.foxinmy.weixin4j.model.User
+ */
+ public User getUser(String openId) throws WeixinException {
+ String user_info_uri = WeixinConfig.getValue("api_user_info_uri");
+ Token token = getToken();
+ Response response = request.get(String.format(user_info_uri,
+ token.getAccess_token(), openId));
+ return response.getAsObject(User.class);
+ }
+
+ /**
+ * 生成带参数的二维码
+ *
+ * @param parameter
+ * @return byte数据包
+ * @throws WeixinException
+ * @see {@link com.foxinmy.weixin4j.WeixinProxy#getQR(QRParameter)}
+ */
+ public byte[] getQRData(QRParameter parameter) throws WeixinException {
+ Token token = getToken();
+ String qr_uri = WeixinConfig.getValue("qr_ticket_uri");
+ Response response = null;
+ try {
+ response = request.post(String.format(qr_uri,
+ token.getAccess_token()),
+ new StringRequestEntity(parameter.toJson(),
+ "application/json", "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ String ticket = response.getAsJson().getString("ticket");
+ qr_uri = WeixinConfig.getValue("qr_image_uri");
+ response = request.get(String.format(qr_uri, ticket));
+
+ return response.getBody();
+ }
+
+ /**
+ * 生成带参数的二维码
+ *
+ * 二维码分为临时跟永久两种,扫描时触发推送带参数事件
+ *
+ *
+ * @param parameter
+ * 二维码参数
+ * @return 硬盘存储的文件对象
+ * @throws WeixinException
+ * @see 二维码
+ * @see com.foxinmy.weixin4j.model.QRParameter
+ */
+ public File getQR(QRParameter parameter) throws WeixinException {
+ String qr_path = WeixinConfig.getValue("qr_path");
+ String filename = String
+ .format("%s_%d_%d.jpg", parameter.getQrType().name(),
+ parameter.getScene_id(), parameter.getExpire_seconds());
+ File file = new File(qr_path + File.separator + filename);
+ if (file.exists()) {
+ return file;
+ }
+ FileOutputStream out = null;
+ try {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ }
+ out = new FileOutputStream(file);
+ byte[] b = getQRData(parameter);
+ out.write(b);
+ } catch (IOException e) {
+
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+
+ }
+ }
+ return file;
+ }
+
+ /**
+ * 上传媒体文件
+ *
+ * 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789},
+ * 否则抛出异常.
+ *
+ *
+ * @param file
+ * 文件对象
+ * @param mediaType
+ * 媒体类型
+ * @return 上传到微信服务器返回的媒体标识
+ * @throws WeixinException
+ * @see 上传下载说明
+ * @see com.foxinmy.weixin4j.type.MediaType
+ */
+ public String uploadMedia(File file, MediaType mediaType)
+ throws WeixinException {
+ byte[] b = null;
+ ByteArrayOutputStream out = null;
+ FileInputStream in = null;
+ try {
+ b = new byte[4096];
+ out = new ByteArrayOutputStream();
+ in = new FileInputStream(file);
+ int len = -1;
+ while ((len = in.read(b)) != -1) {
+ out.write(b, 0, len);
+ }
+ b = out.toByteArray();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ }
+ }
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return uploadMedia(file.getName(), b, mediaType);
+ }
+
+ /**
+ * 上传媒体文件
+ *
+ * @param bytes
+ * 媒体数据包
+ * @param mediaType
+ * 媒体类型
+ * @return 上传到微信服务器返回的媒体标识
+ * @throws WeixinException
+ * @see {@link com.foxinmy.weixin4j.WeixinProxy#uploadMedia(File, MediaType)}
+ */
+ public String uploadMedia(String fileName, byte[] bytes, MediaType mediaType)
+ throws WeixinException {
+ Token token = getToken();
+ String file_upload_uri = WeixinConfig.getValue("file_upload_uri");
+ Response response = null;
+
+ response = request.post(String.format(file_upload_uri,
+ token.getAccess_token(), mediaType.name()), new FilePart(
+ "media", new ByteArrayPartSource(fileName, bytes), null,
+ "UTF-8"));
+
+ return response.getAsJson().getString("media_id");
+ }
+
+ /**
+ * 下载媒体文件
+ *
+ * 正常情况下返回表头如Content-Type: image/jpeg,否则抛出异常.
+ *
+ *
+ * @param mediaId
+ * 存储在微信服务器上的媒体标识
+ * @param mediaType
+ * 媒体类型
+ * @return 写入硬盘后的文件对象
+ * @throws WeixinException
+ * @see 上传下载说明
+ * @see com.foxinmy.weixin4j.type.MediaType
+ */
+ public File downloadMedia(String mediaId, MediaType mediaType)
+ throws WeixinException {
+ String media_path = WeixinConfig.getValue("media_path");
+ String filename = mediaId + mediaType.getFormatType();
+ File file = new File(media_path + File.separator + filename);
+ if (file.exists()) {
+ return file;
+ }
+ FileOutputStream out = null;
+ try {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ }
+ out = new FileOutputStream(file);
+ byte[] b = downloadMediaData(mediaId, mediaType);
+ out.write(b);
+ } catch (IOException e) {
+
+ } finally {
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+
+ }
+ }
+ return file;
+ }
+
+ /**
+ * 下载媒体文件
+ *
+ * @param mediaId
+ * @param mediaType
+ * @return 二进制数据包
+ * @throws WeixinException
+ * @see {@link com.foxinmy.weixin4j.WeixinProxy#downloadMedia(String, MediaType)}
+ */
+ public byte[] downloadMediaData(String mediaId, MediaType mediaType)
+ throws WeixinException {
+ Token token = getToken();
+ String file_download_uri = WeixinConfig.getValue("file_download_uri");
+ Response response = request.get(String.format(file_download_uri,
+ token.getAccess_token(), mediaId));
+ return response.getBody();
+ }
+
+ /**
+ * 发送客服消息(在48小时内不限制发送次数)
+ *
+ * @param notify
+ * 客服消息对象
+ * @throws WeixinException
+ * @see 发送客服消息
+ */
+ public void sendNotify(BaseNotify notify) throws WeixinException {
+ String custom_notify_uri = WeixinConfig.getValue("custom_notify_uri");
+ Token token = getToken();
+ try {
+ request.post(String.format(custom_notify_uri,
+ token.getAccess_token(), notify.getTouser()),
+ new StringRequestEntity(notify.toJson(),
+ "application/json", "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 创建分组
+ *
+ * @param name
+ * 组名称
+ * @return group对象
+ * @throws WeixinException
+ * @see 创建分组
+ * @see com.foxinmy.weixin4j.model.Group
+ * @see com.foxinmy.weixin4j.model.Group#toCreateJson()
+ */
+ public Group createGroup(String name) throws WeixinException {
+ String group_create_uri = WeixinConfig.getValue("group_create_uri");
+ Token token = getToken();
+ Response response = null;
+ Group group = new Group(name);
+ try {
+ response = request.post(String.format(group_create_uri,
+ token.getAccess_token()),
+ new StringRequestEntity(group.toCreateJson(),
+ "application/json", "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ JSONObject obj = response.getAsJson();
+ return JSON.parseObject(obj.getString("group"), Group.class);
+ }
+
+ /**
+ * 查询所有分组
+ *
+ * @return 组集合
+ * @throws WeixinException
+ * @see 查询所有分组
+ * @see com.foxinmy.weixin4j.model.Group
+ */
+ public List getGroups() throws WeixinException {
+ String group_get_uri = WeixinConfig.getValue("group_get_uri");
+ Token token = getToken();
+ Response response = request.get(String.format(group_get_uri,
+ token.getAccess_token()));
+ JSONObject obj = response.getAsJson();
+ return JSON.parseArray(obj.getString("groups"), Group.class);
+ }
+
+ /**
+ * 查询用户所在分组
+ *
+ * @param openId
+ * 用户对应的ID
+ * @return 组ID
+ * @throws WeixinException
+ * @see 查询用户所在分组
+ * @see com.foxinmy.weixin4j.model.Group
+ */
+ public int findGroupByOpenId(String openId) throws WeixinException {
+ String group_getid_uri = WeixinConfig.getValue("group_getid_uri");
+ Token token = getToken();
+ Response response = null;
+ try {
+ response = request.post(
+ String.format(group_getid_uri, token.getAccess_token()),
+ new StringRequestEntity(String.format(
+ "{\"openid\":\"%s\"}", openId), "application/json",
+ "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ return response.getAsJson().getIntValue("groupid");
+ }
+
+ /**
+ * 修改分组名
+ *
+ * @param groupId
+ * 组ID
+ * @param name
+ * 组名称
+ * @throws WeixinException
+ * @see 修改分组名
+ * @see com.foxinmy.weixin4j.model.Group
+ * @see com.foxinmy.weixin4j.model.Group#toModifyJson()
+ */
+ public void modifyGroup(int groupId, String name) throws WeixinException {
+ String group_modify_uri = WeixinConfig.getValue("group_modify_uri");
+ Token token = getToken();
+ Group group = new Group(groupId, name);
+ try {
+ request.post(String.format(group_modify_uri,
+ token.getAccess_token()),
+ new StringRequestEntity(group.toModifyJson(),
+ "application/json", "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 移动分组
+ *
+ * @param openId
+ * 用户对应的ID
+ * @param groupId
+ * 组ID
+ * @throws WeixinException
+ * @see 移动分组
+ * @see com.foxinmy.weixin4j.model.Group
+ */
+ public void moveGroup(String openId, int groupId) throws WeixinException {
+ String group_move_uri = WeixinConfig.getValue("group_move_uri");
+ Token token = getToken();
+ try {
+ request.post(
+ String.format(group_move_uri, token.getAccess_token()),
+ new StringRequestEntity(String.format(
+ "{\"openid\":\"%s\",\"to_groupid\":%d}", openId,
+ groupId), "application/json", "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 获取用户一定数量(10000)的关注者列表
+ *
+ * @param nextOpenId
+ * 下一次拉取数据的openid
+ * @return 关注信息
+ * @throws WeixinException
+ * @see 获取关注者列表
+ * @see com.foxinmy.weixin4j.model.Following
+ */
+ public Following getFollowing(String nextOpenId) throws WeixinException {
+ String fllowing_uri = WeixinConfig.getValue("following_uri");
+ Token token = getToken();
+ Response response = request.get(String.format(fllowing_uri,
+ token.getAccess_token(), nextOpenId == null ? "" : nextOpenId));
+
+ Following following = response.getAsObject(Following.class);
+
+ if (following.getCount() > 0) {
+ List openIds = JSON.parseArray(following.getDataJson()
+ .getString("openid"), String.class);
+ List userList = new ArrayList();
+ for (String openId : openIds) {
+ userList.add(getUser(openId));
+ }
+ following.setUserList(userList);
+ }
+ return following;
+ }
+
+ /**
+ * 获取用户全部的关注者列表
+ *
+ * 当公众号关注者数量超过10000时,可通过填写next_openid的值,从而多次拉取列表的方式来满足需求,
+ * 将上一次调用得到的返回中的next_openid值,作为下一次调用中的next_openid值
+ *
+ *
+ * @return 用户对象集合
+ * @throws WeixinException
+ * @see 获取关注者列表
+ * @see com.foxinmy.weixin4j.model.Following
+ * @see com.foxinmy.weixin4j.WeixinProxy#getFollowing(String)
+ */
+ public List getAllFollowing() throws WeixinException {
+ List userList = new ArrayList();
+ String nextOpenId = null;
+ Following f = null;
+ for (;;) {
+ f = getFollowing(nextOpenId);
+ if (f.getCount() == 0) {
+ break;
+ }
+ userList.addAll(f.getUserList());
+ nextOpenId = f.getNextOpenId();
+ }
+ return userList;
+ }
+
+ /**
+ * 自定义菜单
+ *
+ * @param btnList
+ * @throws WeixinException
+ * @see 创建自定义菜单
+ * @see com.foxinmy.weixin4j.model.Button
+ */
+ public void createMenu(List