diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/DataApi.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/DataApi.java
new file mode 100644
index 00000000..73ede162
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/DataApi.java
@@ -0,0 +1,133 @@
+package com.foxinmy.weixin4j.mp.api;
+
+import java.util.Calendar;
+import java.util.Date;
+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.Response;
+import com.foxinmy.weixin4j.model.Token;
+import com.foxinmy.weixin4j.mp.type.DatacubeType;
+import com.foxinmy.weixin4j.token.TokenHolder;
+import com.foxinmy.weixin4j.util.DateUtil;
+
+/**
+ * 数据分析API
+ *
+ * 1、接口侧的公众号数据的数据库中仅存储了2014年12月1日之后的数据,将查询不到在此之前的日期,即使有查到,也是不可信的脏数据;
+ * 2、请开发者在调用接口获取数据后,将数据保存在自身数据库中,即加快下次用户的访问速度,也降低了微信侧接口调用的不必要损耗。
+ *
+ *
+ * @className DataApi
+ * @author jy
+ * @date 2015年1月7日
+ * @since JDK 1.7
+ * @see
+ */
+public class DataApi extends MpApi {
+ private final TokenHolder tokenHolder;
+
+ public DataApi(TokenHolder tokenHolder) {
+ this.tokenHolder = tokenHolder;
+ }
+
+ /**
+ * 数据统计
+ *
+ * @param datacubeType
+ * 统计类型
+ * @param beginDate
+ * 开始日期
+ * @param offset
+ * 增量 表示向前几天 比如 offset=1 则查询 beginDate的后一天之间的数据
+ * @return
+ * @throws WeixinException
+ */
+ public List> datacube(DatacubeType datacubeType, Date beginDate,
+ int offset) throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.setTime(beginDate);
+ ca.add(Calendar.DAY_OF_MONTH, offset);
+ return datacube(datacubeType, beginDate, ca.getTime());
+ }
+
+ /**
+ * 数据统计
+ *
+ * @param datacubeType
+ * 统计类型
+ * @param offset
+ * 增量 表示向后几天 比如 offset=1 则查询 beginDate的前一天之间的数据
+ * @param endDate
+ * 截至日期
+ * @return
+ * @throws WeixinException
+ */
+ public List> datacube(DatacubeType datacubeType, int offset, Date endDate)
+ throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.setTime(endDate);
+ ca.add(Calendar.DAY_OF_MONTH, 0 - offset);
+ return datacube(datacubeType, ca.getTime(), endDate);
+ }
+
+ /**
+ * 查询日期跨度为1的统计数据
+ *
+ * @param datacubeType
+ * 统计类型
+ * @param date
+ * 统计日期
+ * @return 统计结果
+ * @throws WeixinException
+ */
+ public List> datacube(DatacubeType datacubeType, Date date)
+ throws WeixinException {
+ return datacube(datacubeType, date, date);
+ }
+
+ /**
+ * 数据统计
+ *
+ * @param datacubeType
+ * 数据统计类型
+ * @param beginDate
+ * 获取数据的起始日期,begin_date和end_date的差值需小于“最大时间跨度”(比如最大时间跨度为1时,
+ * begin_date和end_date的差值只能为0,才能小于1),否则会报错
+ * @param endDate
+ * 获取数据的结束日期,end_date允许设置的最大值为昨日
+ * @see com.foxinmy.weixin4j.mp.datacube.UserSummary
+ * @see com.foxinmy.weixin4j.mp.datacube.ArticleSummary
+ * @see com.foxinmy.weixin4j.mp.datacube.ArticleTotal
+ * @see com.foxinmy.weixin4j.mp.datacube.ArticleDatacubeShare
+ * @see com.foxinmy.weixin4j.mp.datacube.UpstreamMsg
+ * @see com.foxinmy.weixin4j.mp.datacube.UpstreamMsgDist
+ * @see com.foxinmy.weixin4j.mp.datacube.InterfaceSummary
+ * @return 统计结果
+ * @see 用户分析
+ * @see 图文分析
+ * @see 消息分析
+ * @see 接口分析
+ * @throws WeixinException
+ */
+ public List> datacube(DatacubeType datacubeType, Date beginDate,
+ Date endDate) throws WeixinException {
+ String datacube_uri = getRequestUri("datacube_uri");
+ Token token = tokenHolder.getToken();
+ JSONObject obj = new JSONObject();
+ obj.put("begin_date", DateUtil.fortmat2yyyy_MM_dd(beginDate));
+ obj.put("end_date", DateUtil.fortmat2yyyy_MM_dd(endDate));
+ Response response = request.post(String.format(datacube_uri,
+ datacubeType.name().toLowerCase(), token.getAccessToken()), obj
+ .toJSONString());
+
+ return JSON.parseArray(response.getAsJson().getString("list"),
+ datacubeType.getClazz());
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube1.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube1.java
new file mode 100644
index 00000000..5970b579
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube1.java
@@ -0,0 +1,100 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+public class ArticleDatacube1 implements Serializable {
+
+ private static final long serialVersionUID = 4140706754295502971L;
+
+ @JSONField(name = "int_page_read_user")
+ private int intPageReadUser;// 图文页(点击群发图文卡片进入的页面)的阅读人数
+ @JSONField(name = "int_page_read_count")
+ private int intPageReadCount;// 图文页的阅读次数
+ @JSONField(name = "ori_page_read_user")
+ private int oriPageReadUser;// 原文页(点击图文页“阅读原文”进入的页面)的阅读人数,无原文页时此处数据为0
+ @JSONField(name = "ori_page_read_count")
+ private int oriPageReadCount;// 原文页的阅读次数
+ @JSONField(name = "shareUser")
+ private int shareUser; // 分享的人数
+ @JSONField(name = "shareCount")
+ private int shareCount;// 分享的次数
+ @JSONField(name = "add_to_fav_user")
+ private int favUser;// 收藏的人数
+ @JSONField(name = "add_to_fav_count")
+ private int favCount;// 收藏的次数
+
+ public int getIntPageReadUser() {
+ return intPageReadUser;
+ }
+
+ public void setIntPageReadUser(int intPageReadUser) {
+ this.intPageReadUser = intPageReadUser;
+ }
+
+ public int getIntPageReadCount() {
+ return intPageReadCount;
+ }
+
+ public void setIntPageReadCount(int intPageReadCount) {
+ this.intPageReadCount = intPageReadCount;
+ }
+
+ public int getOriPageReadUser() {
+ return oriPageReadUser;
+ }
+
+ public void setOriPageReadUser(int oriPageReadUser) {
+ this.oriPageReadUser = oriPageReadUser;
+ }
+
+ public int getOriPageReadCount() {
+ return oriPageReadCount;
+ }
+
+ public void setOriPageReadCount(int oriPageReadCount) {
+ this.oriPageReadCount = oriPageReadCount;
+ }
+
+ public int getShareUser() {
+ return shareUser;
+ }
+
+ public void setShareUser(int shareUser) {
+ this.shareUser = shareUser;
+ }
+
+ public int getShareCount() {
+ return shareCount;
+ }
+
+ public void setShareCount(int shareCount) {
+ this.shareCount = shareCount;
+ }
+
+ public int getFavUser() {
+ return favUser;
+ }
+
+ public void setFavUser(int favUser) {
+ this.favUser = favUser;
+ }
+
+ public int getFavCount() {
+ return favCount;
+ }
+
+ public void setFavCount(int favCount) {
+ this.favCount = favCount;
+ }
+
+ @Override
+ public String toString() {
+ return " intPageReadUser=" + intPageReadUser + ", intPageReadCount="
+ + intPageReadCount + ", oriPageReadUser=" + oriPageReadUser
+ + ", oriPageReadCount=" + oriPageReadCount + ", share_user="
+ + shareUser + ", share_count=" + shareCount + ", favUser="
+ + favUser + ", favCount=" + favCount;
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube2.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube2.java
new file mode 100644
index 00000000..1571b7a2
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacube2.java
@@ -0,0 +1,37 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.util.Date;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+public class ArticleDatacube2 extends ArticleDatacube1 {
+ private static final long serialVersionUID = -2924534868674264316L;
+
+ @JSONField(name = "stat_date")
+ private Date statDate;
+ @JSONField(name = "target_user")
+ private int targetUser;
+
+ public Date getStatDate() {
+ return statDate;
+ }
+
+ public void setStatDate(Date statDate) {
+ this.statDate = statDate;
+ }
+
+ public int getTargetUser() {
+ return targetUser;
+ }
+
+ public void setTargetUser(int targetUser) {
+ this.targetUser = targetUser;
+ }
+
+ @Override
+ public String toString() {
+ return "statDate=" + statDate + ", targetUser=" + targetUser + ", "
+ + super.toString();
+ }
+
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacubeShare.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacubeShare.java
new file mode 100644
index 00000000..01ea2533
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleDatacubeShare.java
@@ -0,0 +1,84 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.foxinmy.weixin4j.mp.type.ShareSourceType;
+
+/**
+ * 数据统计:图文分享数据
+ * @className ArticleDatacubeShare
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class ArticleDatacubeShare implements Serializable {
+ private static final long serialVersionUID = 3841239305410294553L;
+
+ @JSONField(name = "ref_date")
+ private String refDate; // 数据的日期
+ @JSONField(name = "ref_hour")
+ private int refHour; // 数据的小时,包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时
+ @JSONField(name = "shareUser")
+ private int shareUser; // 分享的人数
+ @JSONField(name = "shareCount")
+ private int shareCount;// 分享的次数
+ @JSONField(name = "share_scene")
+ private int shareScene;// 分享的场景
+
+ public String getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(String refDate) {
+ this.refDate = refDate;
+ }
+
+ public int getRefHour() {
+ return refHour;
+ }
+
+ public void setRefHour(int refHour) {
+ this.refHour = refHour;
+ }
+
+ public int getShareUser() {
+ return shareUser;
+ }
+
+ public void setShareUser(int shareUser) {
+ this.shareUser = shareUser;
+ }
+
+ public int getShareCount() {
+ return shareCount;
+ }
+
+ public void setShareCount(int shareCount) {
+ this.shareCount = shareCount;
+ }
+
+ public ShareSourceType getShareScene() {
+ if (shareScene == 1) {
+ return ShareSourceType.FRIENDFORWARD;
+ } else if (shareScene == 2) {
+ return ShareSourceType.FRIENDSCIRCLE;
+ } else if (shareScene == 3) {
+ return ShareSourceType.TENCENTWEIBO;
+ } else {
+ return ShareSourceType.OTHER;
+ }
+ }
+
+ public void setShareScene(int shareScene) {
+ this.shareScene = shareScene;
+ }
+
+ @Override
+ public String toString() {
+ return "ArticleDatacubeShare [refDate=" + refDate + ", refHour="
+ + refHour + ", shareUser=" + shareUser + ", shareCount="
+ + shareCount + ", shareScene=" + shareScene + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleSummary.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleSummary.java
new file mode 100644
index 00000000..cc2f2155
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleSummary.java
@@ -0,0 +1,65 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 数据统计:图文群发每日数据
+ *
+ * @className ArticleSummary
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class ArticleSummary extends ArticleDatacube1 {
+ private static final long serialVersionUID = 4820605570501368550L;
+ @JSONField(name = "ref_date")
+ private String refDate; // 数据的日期
+ @JSONField(name = "ref_hour")
+ private int refHour; // 数据的小时,包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时
+ /**
+ * 这里的msgid实际上是由msgid(图文消息id)和index(消息次序索引)组成, 例如12003_3,
+ * 其中12003是msgid,即一次群发的id消息的; 3为index,假设该次群发的图文消息共5个文章(因为可能为多图文), 3表示5个中的第3个
+ */
+ private String msgid;
+ private String title;// 图文消息的标题
+
+ public String getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(String refDate) {
+ this.refDate = refDate;
+ }
+
+ public int getRefHour() {
+ return refHour;
+ }
+
+ public void setRefHour(int refHour) {
+ this.refHour = refHour;
+ }
+
+ public String getMsgid() {
+ return msgid;
+ }
+
+ public void setMsgid(String msgid) {
+ this.msgid = msgid;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public String toString() {
+ return "ArticleSummary [refDate=" + refDate + ", refHour=" + refHour
+ + ", msgid=" + msgid + ", title=" + title + ", "
+ + super.toString() + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleTotal.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleTotal.java
new file mode 100644
index 00000000..24da3b36
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/ArticleTotal.java
@@ -0,0 +1,67 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 数据统计:图文群发总数据
+ *
+ * @className ArticleTotal
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class ArticleTotal implements Serializable {
+ private static final long serialVersionUID = -6820948857241500950L;
+
+ @JSONField(name = "ref_date")
+ private String refDate; // 数据的日期
+ /**
+ * 这里的msgid实际上是由msgid(图文消息id)和index(消息次序索引)组成, 例如12003_3,
+ * 其中12003是msgid,即一次群发的id消息的; 3为index,假设该次群发的图文消息共5个文章(因为可能为多图文), 3表示5个中的第3个
+ */
+ private String msgid;
+ private String title;// 图文消息的标题
+ private List details;
+
+ public String getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(String refDate) {
+ this.refDate = refDate;
+ }
+
+ public String getMsgid() {
+ return msgid;
+ }
+
+ public void setMsgid(String msgid) {
+ this.msgid = msgid;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public List getDetails() {
+ return details;
+ }
+
+ public void setDetails(List details) {
+ this.details = details;
+ }
+
+ @Override
+ public String toString() {
+ return "ArticleTotal [refDate=" + refDate + ", msgid=" + msgid
+ + ", title=" + title + ", details=" + details + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/InterfaceSummary.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/InterfaceSummary.java
new file mode 100644
index 00000000..2272f071
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/InterfaceSummary.java
@@ -0,0 +1,89 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 数据统计:接口分析数据
+ *
+ * @className InterfaceSummary
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class InterfaceSummary implements Serializable {
+
+ private static final long serialVersionUID = -8812979112580350988L;
+
+ @JSONField(name = "ref_date")
+ private Date refDate; // 引用的日期
+ @JSONField(name = "ref_hour")
+ private int refHour; // 数据的小时,包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时
+ @JSONField(name = "callback_count")
+ private int callbackCount; // 通过服务器配置地址获得消息后,被动回复用户消息的次数
+ @JSONField(name = "fail_count")
+ private int failCount; // 上述动作的失败次数
+ @JSONField(name = "total_time_cost")
+ private int totalTimeCost;// 总耗时,除以callback_count即为平均耗时
+ @JSONField(name = "max_time_cost")
+ private int maxTimeCost;// 最大耗时
+
+ public Date getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(Date refDate) {
+ this.refDate = refDate;
+ }
+
+ public int getRefHour() {
+ return refHour;
+ }
+
+ public void setRefHour(int refHour) {
+ this.refHour = refHour;
+ }
+
+ public int getCallbackCount() {
+ return callbackCount;
+ }
+
+ public void setCallbackCount(int callbackCount) {
+ this.callbackCount = callbackCount;
+ }
+
+ public int getFailCount() {
+ return failCount;
+ }
+
+ public void setFailCount(int failCount) {
+ this.failCount = failCount;
+ }
+
+ public int getTotalTimeCost() {
+ return totalTimeCost;
+ }
+
+ public void setTotalTimeCost(int totalTimeCost) {
+ this.totalTimeCost = totalTimeCost;
+ }
+
+ public int getMaxTimeCost() {
+ return maxTimeCost;
+ }
+
+ public void setMaxTimeCost(int maxTimeCost) {
+ this.maxTimeCost = maxTimeCost;
+ }
+
+ @Override
+ public String toString() {
+ return "InterfaceSummary [refDate=" + refDate + ", refHour=" + refHour
+ + ", callbackCount=" + callbackCount + ", failCount="
+ + failCount + ", totalTimeCost=" + totalTimeCost
+ + ", maxTimeCost=" + maxTimeCost + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsg.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsg.java
new file mode 100644
index 00000000..44f46817
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsg.java
@@ -0,0 +1,92 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.foxinmy.weixin4j.type.MessageType;
+
+/**
+ * 数据统计:消息发送概况数据
+ *
+ * @className UpstreamMsg
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class UpstreamMsg implements Serializable {
+
+ private static final long serialVersionUID = -2605207523094962029L;
+ @JSONField(name = "ref_date")
+ private Date refDate; // 引用的日期
+ @JSONField(name = "ref_hour")
+ private int refHour; // 数据的小时,包括从000到2300,分别代表的是[000,100)到[2300,2400),即每日的第1小时和最后1小时
+ @JSONField(name = "msg_type")
+ private int msgType; // 消息类型
+ @JSONField(name = "msg_user")
+ private int msgUser; // 上行发送了(向公众号发送了)消息的用户数
+ @JSONField(name = "msg_count")
+ private int msgCount;// 上行发送了消息的消息总数
+
+ public Date getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(Date refDate) {
+ this.refDate = refDate;
+ }
+
+ public int getRefHour() {
+ return refHour;
+ }
+
+ public void setRefHour(int refHour) {
+ this.refHour = refHour;
+ }
+
+ public MessageType getMsgType() {
+ // 1代表文字 2代表图片 3代表语音 4代表视频 6代表第三方应用消息(链接消息)
+ switch (msgType) {
+ case 1:
+ return MessageType.text;
+ case 2:
+ return MessageType.image;
+ case 3:
+ return MessageType.voice;
+ case 4:
+ return MessageType.video;
+ case 6:
+ return MessageType.link;
+ default:
+ return null;
+ }
+ }
+
+ public void setMsgType(int msgType) {
+ this.msgType = msgType;
+ }
+
+ public int getMsgUser() {
+ return msgUser;
+ }
+
+ public void setMsgUser(int msgUser) {
+ this.msgUser = msgUser;
+ }
+
+ public int getMsgCount() {
+ return msgCount;
+ }
+
+ public void setMsgCount(int msgCount) {
+ this.msgCount = msgCount;
+ }
+
+ @Override
+ public String toString() {
+ return "UpstreamMsg [refDate=" + refDate + ", refHour=" + refHour
+ + ", msgType=" + msgType + ", msgUser=" + msgUser
+ + ", msgCount=" + msgCount + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsgDist.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsgDist.java
new file mode 100644
index 00000000..1dbd47f0
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UpstreamMsgDist.java
@@ -0,0 +1,59 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 数据统计:消息发送分布数据
+ *
+ * @className UpstreamMsgDist
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public class UpstreamMsgDist implements Serializable {
+
+ private static final long serialVersionUID = -2605207523094962029L;
+ @JSONField(name = "ref_date")
+ private Date refDate; // 引用的日期
+ @JSONField(name = "msg_user")
+ private int msgUser; // 上行发送了(向公众号发送了)消息的用户数
+ /**
+ * 当日发送消息量分布的区间,0代表 “0”,1代表“1-5”,2代表“6-10”,3代表“10次以上
+ */
+ @JSONField(name = "count_interval")
+ private int countInterval;
+
+ public Date getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(Date refDate) {
+ this.refDate = refDate;
+ }
+
+ public int getMsgUser() {
+ return msgUser;
+ }
+
+ public void setMsgUser(int msgUser) {
+ this.msgUser = msgUser;
+ }
+
+ public int getCountInterval() {
+ return countInterval;
+ }
+
+ public void setCountInterval(int countInterval) {
+ this.countInterval = countInterval;
+ }
+
+ @Override
+ public String toString() {
+ return "UpstreamMsgDist [refDate=" + refDate + ", msgUser=" + msgUser
+ + ", countInterval=" + countInterval + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UserSummary.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UserSummary.java
new file mode 100644
index 00000000..74895043
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/datacube/UserSummary.java
@@ -0,0 +1,91 @@
+package com.foxinmy.weixin4j.mp.datacube;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.foxinmy.weixin4j.mp.type.UserSourceType;
+
+/**
+ * 数据统计:用户增减
+ *
+ * @className UserSummary
+ * @author jy
+ * @date 2015年1月25日
+ * @since JDK 1.7
+ * @see
+ */
+public class UserSummary implements Serializable {
+
+ private static final long serialVersionUID = 5303181828798568052L;
+ @JSONField(name = "ref_date")
+ private Date refDate; // 数据的日期
+ @JSONField(name = "user_source")
+ private int userSource; // 用户的渠道,数值代表的含义如下:0代表其他 30代表扫二维码 17代表名片分享
+ // 35代表搜号码(即微信添加朋友页的搜索) 39代表查询微信公众帐号 43代表图文页右上角菜单
+ @JSONField(name = "new_user")
+ private int newUser; // 新增的用户数量
+ @JSONField(name = "cancel_user")
+ private int cancelUser; // 取消关注的用户数量,new_user减去cancel_user即为净增用户数量
+ @JSONField(name = "cumulate_user")
+ private int cumulateUser; // 总用户量
+
+ public Date getRefDate() {
+ return refDate;
+ }
+
+ public void setRefDate(Date refDate) {
+ this.refDate = refDate;
+ }
+
+ public UserSourceType getUserSource() {
+ if (userSource == 30) {
+ return UserSourceType.QRCODE;
+ } else if (userSource == 17) {
+ return UserSourceType.CARDSHARE;
+ } else if (userSource == 35) {
+ return UserSourceType.SONUMBER;
+ } else if (userSource == 39) {
+ return UserSourceType.SOMPACCOUNT;
+ } else if (userSource == 43) {
+ return UserSourceType.ARTICLEMENU;
+ } else {
+ return UserSourceType.OTHER;
+ }
+ }
+
+ public void setUserSource(int userSource) {
+ this.userSource = userSource;
+ }
+
+ public int getNewUser() {
+ return newUser;
+ }
+
+ public void setNewUser(int newUser) {
+ this.newUser = newUser;
+ }
+
+ public int getCancelUser() {
+ return cancelUser;
+ }
+
+ public void setCancelUser(int cancelUser) {
+ this.cancelUser = cancelUser;
+ }
+
+ public int getCumulateUser() {
+ return cumulateUser;
+ }
+
+ public void setCumulateUser(int cumulateUser) {
+ this.cumulateUser = cumulateUser;
+ }
+
+ @Override
+ public String toString() {
+ return "UserSummary [refDate=" + refDate + ", userSource="
+ + getUserSource() + ", newUser=" + newUser + ", cancelUser="
+ + cancelUser + ", cumulateUser=" + cumulateUser + "]";
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/DatacubeType.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/DatacubeType.java
new file mode 100644
index 00000000..e5be72a3
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/DatacubeType.java
@@ -0,0 +1,110 @@
+package com.foxinmy.weixin4j.mp.type;
+
+import com.foxinmy.weixin4j.mp.datacube.ArticleDatacubeShare;
+import com.foxinmy.weixin4j.mp.datacube.ArticleSummary;
+import com.foxinmy.weixin4j.mp.datacube.ArticleTotal;
+import com.foxinmy.weixin4j.mp.datacube.InterfaceSummary;
+import com.foxinmy.weixin4j.mp.datacube.UpstreamMsg;
+import com.foxinmy.weixin4j.mp.datacube.UpstreamMsgDist;
+import com.foxinmy.weixin4j.mp.datacube.UserSummary;
+
+/**
+ * 数据统计类型
+ *
+ * @className DatacubeType
+ * @author jy
+ * @date 2015年1月25日
+ * @since JDK 1.7
+ * @see
+ */
+public enum DatacubeType {
+ /**
+ * 获取用户增减数据
+ */
+ GETUSERSUMMARY(UserSummary.class),
+ /**
+ * 获取累计用户数据
+ */
+ GETUSERCUMULATE(UserSummary.class),
+ /**
+ * 获取图文群发每日数据
+ */
+ GETARTICLESUMMARY(ArticleSummary.class),
+ /**
+ * 获取图文群发总数据,获取的是某天所有被阅读过的文章(仅包括群发的文章)在当天的阅读次数等数据。
+ */
+ GETARTICLETOTAL(ArticleTotal.class),
+ /**
+ * 获取图文统计数据,获取的是某天群发的文章,从群发日起到接口调用日(但最多统计发表日后7天数据),
+ * 每天的到当天的总等数据。例如某篇文章是12月1日发出的,发出后在1日
+ * 、2日、3日的阅读次数分别为1万,则getarticletotal获取到的数据为
+ * ,距发出到12月1日24时的总阅读量为1万,距发出到12月2日24时的总阅读量为2万,距发出到12月1日24时的总阅读量为3万。
+ */
+ GETUSERREAD(ArticleSummary.class),
+ /**
+ * 获取图文统计分时数据
+ */
+ GETUSERREADHOUR(ArticleSummary.class),
+ /**
+ * 获取图文分享转发数据
+ */
+ GETUSERSHARE(ArticleDatacubeShare.class),
+ /**
+ * 获取图文分享转发分时数据
+ */
+ GETUSERSHAREHOUR(ArticleDatacubeShare.class),
+
+ /**
+ * 获取消息发送概况数据
+ */
+ GETUPSTREAMMSG(UpstreamMsg.class),
+ /**
+ * 获取消息分送分时数据
+ */
+ GETUPSTREAMMSGHOUR(UpstreamMsg.class),
+ /**
+ * 获取消息发送周数据
+ * 关于周数据与月数据,请注意:每个月/周的周期数据的数据标注日期在当月/当周的第一天(当月1日或周一)。在某一月/周过后去调用接口
+ * ,才能获取到该周期的数据
+ * 。比如,在12月1日以(11月1日-11月5日)作为(begin_date和end_date)调用获取月数据接口,可以获取到11月1日的月数据
+ * (即11月的月数据)。
+ */
+ GETUPSTREAMMSGWEEK(UpstreamMsg.class),
+ /**
+ * 获取消息发送月数据
+ */
+ GETUPSTREAMMSGMONTH(UpstreamMsg.class),
+
+ /**
+ * 获取消息发送分布数据
+ */
+ GETUPSTREAMMSGDIST(UpstreamMsgDist.class),
+
+ /**
+ * 获取消息发送分布周数据
+ */
+ GETUPSTREAMMSGDISTWEEK(UpstreamMsgDist.class),
+
+ /**
+ * 获取消息发送分布月数据
+ */
+ GETUPSTREAMMSGDISTMONTH(UpstreamMsgDist.class),
+ /**
+ * 获取接口分析数据
+ */
+ GETINTERFACESUMMARY(InterfaceSummary.class),
+ /**
+ * 获取接口分析分时数据
+ */
+ GETINTERFACESUMMARYHOUR(InterfaceSummary.class);
+
+ private Class> clazz;
+
+ DatacubeType(Class> clazz) {
+ this.clazz = clazz;
+ }
+
+ public Class> getClazz() {
+ return clazz;
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/ShareSourceType.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/ShareSourceType.java
new file mode 100644
index 00000000..e324385b
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/ShareSourceType.java
@@ -0,0 +1,17 @@
+package com.foxinmy.weixin4j.mp.type;
+
+/**
+ * 分享的场景
+ *
+ * @className ShareSourceType
+ * @author jy
+ * @date 2015年1月30日
+ * @since JDK 1.7
+ * @see
+ */
+public enum ShareSourceType {
+ FRIENDFORWARD, // 好友转发
+ FRIENDSCIRCLE, // 朋友圈
+ TENCENTWEIBO, // 腾讯微博
+ OTHER // 其它
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/UserSourceType.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/UserSourceType.java
new file mode 100644
index 00000000..eabc0103
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/UserSourceType.java
@@ -0,0 +1,24 @@
+package com.foxinmy.weixin4j.mp.type;
+
+/**
+ * 用户渠道来源
+ *
+ * @className UserSourceType
+ * @author jy
+ * @date 2015年1月25日
+ * @since JDK 1.7
+ * @see
+ */
+public enum UserSourceType {
+ OTHER("其它"), QRCODE("扫二维码"), CARDSHARE("名片分享"), SONUMBER("搜号码(即微信添加朋友页的搜索)"), SOMPACCOUNT(
+ "查询微信公众帐号"), ARTICLEMENU("图文页右上角菜单");
+ private String desc;
+
+ UserSourceType(String desc) {
+ this.desc = desc;
+ }
+
+ public String getDesc() {
+ return desc;
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/DataApiTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/DataApiTest.java
new file mode 100644
index 00000000..7db1f8f8
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/DataApiTest.java
@@ -0,0 +1,104 @@
+package com.foxinmy.weixin4j.mp.test;
+
+import java.util.Calendar;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.foxinmy.weixin4j.exception.WeixinException;
+import com.foxinmy.weixin4j.mp.api.DataApi;
+import com.foxinmy.weixin4j.mp.datacube.ArticleDatacubeShare;
+import com.foxinmy.weixin4j.mp.datacube.ArticleSummary;
+import com.foxinmy.weixin4j.mp.datacube.ArticleTotal;
+import com.foxinmy.weixin4j.mp.type.DatacubeType;
+
+/**
+ * 数据分析测试
+ *
+ * @className DataApiTest
+ * @author jy
+ * @date 2015年1月25日
+ * @since JDK 1.7
+ * @see
+ */
+@SuppressWarnings("unchecked")
+public class DataApiTest extends TokenTest {
+ private DataApi dataApi;
+
+ @Before
+ public void init() {
+ dataApi = new DataApi(tokenHolder);
+ }
+
+ @Test
+ public void testUserCumulate() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List> userSummaryList = dataApi.datacube(
+ DatacubeType.GETUSERCUMULATE, ca.getTime(), 3);
+ System.err.println(userSummaryList);
+ }
+
+ @Test
+ public void testUserSummary() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List> userSummaryList = dataApi.datacube(DatacubeType.GETUSERSUMMARY,
+ ca.getTime(), 3);
+ System.err.println(userSummaryList);
+ }
+
+ @Test
+ public void testArticleSummary() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List articleSummaryList = (List) dataApi
+ .datacube(DatacubeType.GETARTICLESUMMARY, ca.getTime());
+ System.err.println(articleSummaryList);
+ }
+
+ @Test
+ public void testArticleTotal() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List articleTotalList = (List) dataApi
+ .datacube(DatacubeType.GETARTICLETOTAL, ca.getTime());
+ System.err.println(articleTotalList);
+ }
+
+ @Test
+ public void testUserReadHour() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List articleSummaryList = (List) dataApi
+ .datacube(DatacubeType.GETUSERREADHOUR, ca.getTime());
+ System.err.println(articleSummaryList);
+ }
+
+ @Test
+ public void testUserShare() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -7);
+ List userShareList = (List) dataApi
+ .datacube(DatacubeType.GETUSERSHAREHOUR, ca.getTime());
+ System.err.println(userShareList);
+ }
+
+ @Test
+ public void testDatecube() throws WeixinException {
+ Calendar ca = Calendar.getInstance();
+ ca.add(Calendar.DAY_OF_MONTH, -1);
+ List> datacuteList = dataApi.datacube(DatacubeType.GETUPSTREAMMSG,
+ ca.getTime());
+ System.err.println(datacuteList);
+ //dataApi.datacube(DatacubeType.GETUPSTREAMMSGMONTH, ca.getTime());
+ System.err.println(datacuteList);
+
+ //dataApi.datacube(DatacubeType.GETUPSTREAMMSGDISTMONTH, ca.getTime());
+ System.err.println(datacuteList);
+
+ dataApi.datacube(DatacubeType.GETINTERFACESUMMARYHOUR, ca.getTime());
+ System.err.println(datacuteList);
+ }
+}
diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/PayTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/PayTest.java
new file mode 100644
index 00000000..d5818d4c
--- /dev/null
+++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/PayTest.java
@@ -0,0 +1,175 @@
+package com.foxinmy.weixin4j.mp.test;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.foxinmy.weixin4j.exception.PayException;
+import com.foxinmy.weixin4j.exception.WeixinException;
+import com.foxinmy.weixin4j.http.XmlResult;
+import com.foxinmy.weixin4j.model.WeixinMpAccount;
+import com.foxinmy.weixin4j.mp.WeixinPayProxy;
+import com.foxinmy.weixin4j.mp.payment.PayUtil;
+import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
+import com.foxinmy.weixin4j.mp.payment.v3.Order;
+import com.foxinmy.weixin4j.mp.payment.v3.PayPackageV3;
+import com.foxinmy.weixin4j.mp.payment.v3.PrePay;
+import com.foxinmy.weixin4j.mp.type.IdQuery;
+import com.foxinmy.weixin4j.mp.type.IdType;
+import com.foxinmy.weixin4j.mp.type.TradeType;
+import com.foxinmy.weixin4j.token.FileTokenHolder;
+import com.foxinmy.weixin4j.token.WeixinTokenCreator;
+import com.foxinmy.weixin4j.type.AccountType;
+
+public class PayTest {
+ private final static WeixinPayProxy PAY2;
+ private final static WeixinPayProxy PAY3;
+ private final static WeixinMpAccount ACCOUNT2;
+ private final static WeixinMpAccount ACCOUNT3;
+ static {
+ ACCOUNT2 = new WeixinMpAccount(
+ "appId",
+ "appSecret",
+ "paySignKey",
+ "partnerId", "partnerKey");
+ PAY2 = new WeixinPayProxy(ACCOUNT2, new FileTokenHolder(
+ new WeixinTokenCreator(ACCOUNT2.getId(), ACCOUNT2.getSecret(),
+ AccountType.MP)));
+ ACCOUNT3 = new WeixinMpAccount("appId",
+ "appSecret",
+ "paySignKey", "mchId");
+ PAY3 = new WeixinPayProxy(ACCOUNT3, new FileTokenHolder(
+ new WeixinTokenCreator(ACCOUNT3.getId(), ACCOUNT3.getSecret(),
+ AccountType.MP)));
+ }
+
+ @Test
+ public void orderQueryV2() throws WeixinException {
+ System.err.println(PAY2.orderQueryV2("D14110500021"));
+ }
+
+ @Test
+ public void refundV2() throws WeixinException {
+ File caFile = new File(
+ "/Users/jy/download/1221928801.pfx");
+ IdQuery idQuery = new IdQuery("D15012400026", IdType.TRADENO);
+ System.err.println(PAY2.refundV2(caFile, idQuery, "R000000001", 2d, 2d,
+ "1221928801", "111111", null, null, null));
+ }
+
+ @Test
+ public void refundQueryV2() throws WeixinException {
+ System.err.println(PAY2.refundQueryV2(new IdQuery("D14123000004",
+ IdType.TRADENO)));
+ refundQueryV3();
+ }
+
+ @Test
+ public void downbillV2() throws WeixinException {
+ Calendar c = Calendar.getInstance();
+ c.set(Calendar.YEAR, 2014);
+ c.set(Calendar.MONTH, 11);
+ c.set(Calendar.DAY_OF_MONTH, 22);
+ File file = PAY2.downloadbill(c.getTime(), null);
+ System.err.println(file);
+ }
+
+ @Test
+ public void orderQueryV3() throws WeixinException {
+ Order order = PAY3.orderQueryV3(new IdQuery("T0002", IdType.TRADENO));
+ System.err.println(order);
+ String sign = order.getSign();
+ order.setSign(null);
+ String valiSign = PayUtil.paysignMd5(order, ACCOUNT3.getPaySignKey());
+ System.err
+ .println(String.format("sign=%s,valiSign=%s", sign, valiSign));
+ Assert.assertEquals(valiSign, sign);
+ }
+
+ @Test
+ public void refundQueryV3() throws WeixinException {
+ System.err.println(PAY3.refundQueryV3(new IdQuery("T00015",
+ IdType.TRADENO)));
+ }
+
+ @Test
+ public void downbillV3() throws WeixinException {
+ Calendar c = Calendar.getInstance();
+ System.err.println(c.getTime());
+ c.set(Calendar.YEAR, 2014);
+ c.set(Calendar.MONTH, 9);
+ c.set(Calendar.DAY_OF_MONTH, 22);
+ System.err.println(c.getTime());
+ File file = PAY3.downloadbill(c.getTime(), null);
+ System.err.println(file);
+ }
+
+ @Test
+ public void refundV3() throws WeixinException {
+ File caFile = new File(
+ "/Users/jy/download/10020674.p12");
+ IdQuery idQuery = new IdQuery("T00015", IdType.TRADENO);
+ com.foxinmy.weixin4j.mp.payment.v3.RefundResult result = PAY3.refundV3(
+ caFile, idQuery, "R0002", 1d, 1d, "10020674");
+ System.err.println(result);
+ String sign = result.getSign();
+ result.setSign(null);
+ String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
+ System.err
+ .println(String.format("sign=%s,valiSign=%s", sign, valiSign));
+ Assert.assertEquals(valiSign, sign);
+ }
+
+ @Test
+ public void nativeV3() throws WeixinException {
+ PayPackageV3 payPackageV3 = new PayPackageV3(ACCOUNT3,
+ "oyFLst1bqtuTcxK-ojF8hOGtLQao", "native测试", "T0001", 0.1d,
+ "127.0.0.1", TradeType.NATIVE);
+ payPackageV3.setProductId("0001");
+ payPackageV3.setNotifyUrl("xxxx");
+ PrePay prePay = null;
+ try {
+ prePay = PayUtil.createPrePay(payPackageV3,
+ ACCOUNT3.getPaySignKey());
+ } catch (PayException e) {
+ e.printStackTrace();
+ }
+ System.err.println(prePay);
+ }
+
+ @Test
+ public void closeOrder() throws WeixinException {
+ ApiResult result = PAY3.closeOrder("D111");
+ System.err.println(result);
+ String sign = result.getSign();
+ result.setSign(null);
+ String valiSign = PayUtil.paysignMd5(result, ACCOUNT3.getPaySignKey());
+ System.err
+ .println(String.format("sign=%s,valiSign=%s", sign, valiSign));
+ Assert.assertEquals(valiSign, sign);
+ }
+
+ @Test
+ public void shortUrl() throws WeixinException {
+ String url = "weixin://wxpay/bizpayurl?xxxxxx";
+ String shortUrl = PAY3.getPayShorturl(url);
+ System.err.println(shortUrl);
+ }
+
+ @Test
+ public void interfaceReport() throws WeixinException {
+ String interfaceUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+ int executeTime = 2500;
+ String outTradeNo = null;
+ String ip = "127.0.0.1";
+ Date time = new Date();
+ XmlResult returnXml = new XmlResult("SUCCESS", "");
+ returnXml.setResultCode("SUCCESS");
+ returnXml = PAY3.interfaceReport(interfaceUrl, executeTime, outTradeNo,
+ ip, time, returnXml);
+ System.err.println(returnXml);
+ }
+}