diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java
new file mode 100644
index 00000000..bfa038f1
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaAnalysisService.java
@@ -0,0 +1,145 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaSummaryTrend;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitPage;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitTrend;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 小程序数据分析相关接口
+ * 文档:https://mp.weixin.qq.com/debug/wxadoc/dev/api/analysis.html
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+public interface WxMaAnalysisService {
+ String GET_DAILY_SUMMARY_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailysummarytrend";
+ String GET_DAILY_VISIT_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyvisittrend";
+ String GET_WEEKLY_VISIT_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidweeklyvisittrend";
+ String GET_MONTHLY_VISIT_TREND_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidmonthlyvisittrend";
+ String GET_VISIT_DISTRIBUTION_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitdistribution";
+ String GET_DAILY_RETAIN_INFO_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiddailyretaininfo";
+ String GET_WEEKLY_RETAIN_INFO_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidweeklyretaininfo";
+ String GET_MONTHLY_RETAIN_INFO_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidmonthlyretaininfo";
+ String GET_VISIT_PAGE_URL = "https://api.weixin.qq.com/datacube/getweanalysisappidvisitpage";
+ String GET_USER_PORTRAIT_URL = "https://api.weixin.qq.com/datacube/getweanalysisappiduserportrait";
+
+ /**
+ * 查询概况趋势
+ * 温馨提示:小程序接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
+ *
+ * @param beginDate 开始日期
+ * @param endDate 结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+ * @return 概况趋势
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ List getDailySummaryTrend(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取日访问趋势
+ * 温馨提示:小程序接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
+ *
+ * @param beginDate 开始日期
+ * @param endDate 结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+ * @return 日访问趋势
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ List getDailyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取周访问趋势
+ * 限定查询一个自然周的数据,时间必须按照自然周的方式输入: 如:20170306(周一), 20170312(周日)
+ *
+ * @param beginDate 开始日期,为周一日期
+ * @param endDate 结束日期,为周日日期,限定查询一周数据
+ * @return 周访问趋势(每项数据都是一个自然周汇总)
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ List getWeeklyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取月访问趋势
+ * 限定查询一个自然月的数据,时间必须按照自然月的方式输入: 如:20170201(月初), 20170228(月末)
+ *
+ * @param beginDate 开始日期,为自然月第一天
+ * @param endDate 结束日期,为自然月最后一天,限定查询一个月数据
+ * @return 月访问趋势(每项数据都是一个自然月汇总)
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ List getMonthlyVisitTrend(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取访问分布
+ * (此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样)
+ *
+ * @param beginDate 开始日期,为周一日期
+ * @param endDate 结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+ * @return 访问分布
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ WxMaVisitDistribution getVisitDistribution(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 日留存
+ * (此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样)
+ *
+ * @param beginDate 开始日期,为周一日期
+ * @param endDate 结束日期,限定查询 1 天数据,endDate 允许设置的最大值为昨日
+ * @return 日留存
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ WxMaRetainInfo getDailyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 周留存
+ * 限定查询一个自然周的数据,时间必须按照自然周的方式输入: 如:20170306(周一), 20170312(周日)
+ *
+ * @param beginDate 开始日期,为周一日期
+ * @param endDate 结束日期,为周日日期,限定查询一周数据
+ * @return 周留存
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ WxMaRetainInfo getWeeklyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 月留存
+ * 限定查询一个自然月的数据,时间必须按照自然月的方式输入: 如:20170201(月初), 20170228(月末)
+ *
+ * @param beginDate 开始日期,为自然月第一天
+ * @param endDate 结束日期,为自然月最后一天,限定查询一个月数据
+ * @return 月留存
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ WxMaRetainInfo getMonthlyRetainInfo(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取访问页面数据
+ * 温馨提示:此接口目前只能查询一天的数据,即 beginDate 和 endDate 一样
+ *
+ * @param beginDate 开始日期
+ * @param endDate 结束日期,限定查询1天数据,end_date允许设置的最大值为昨日
+ * @return 访问页面数据
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ List getVisitPage(Date beginDate, Date endDate) throws WxErrorException;
+
+ /**
+ * 获取小程序新增或活跃用户的画像分布数据
+ * 时间范围支持昨天、最近7天、最近30天。
+ * 其中,新增用户数为时间范围内首次访问小程序的去重用户数,
+ * 活跃用户数为时间范围内访问过小程序的去重用户数。
+ * 画像属性包括用户年龄、性别、省份、城市、终端类型、机型。
+ *
+ * @param beginDate 开始日期
+ * @param endDate 结束日期,开始日期与结束日期相差的天数限定为0/6/29,分别表示查询最近1/7/30天数据,end_date允许设置的最大值为昨日
+ * @return 小程序新增或活跃用户的画像分布数据
+ * @throws WxErrorException 获取失败时抛出,具体错误码请看文档
+ */
+ WxMaUserPortrait getUserPortrait(Date beginDate, Date endDate) throws WxErrorException;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index 9ba17f2b..cec42242 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -135,6 +135,13 @@ public interface WxMaService {
*/
WxMaTemplateService getTemplateService();
+ /**
+ * 数据分析相关查询服务
+ *
+ * @return WxMaAnalysisService
+ */
+ WxMaAnalysisService getAnalysisService();
+
/**
* 返回代码操作相关的 API
*
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java
new file mode 100644
index 00000000..a9247fc3
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java
@@ -0,0 +1,126 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaAnalysisService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaSummaryTrend;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitPage;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitTrend;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.lang.reflect.Type;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaAnalysisServiceImpl implements WxMaAnalysisService {
+ private static final JsonParser JSON_PARSER = new JsonParser();
+ private WxMaService wxMaService;
+
+ public WxMaAnalysisServiceImpl(WxMaService wxMaService) {
+ this.wxMaService = wxMaService;
+ }
+
+ @Override
+ public List getDailySummaryTrend(Date beginDate, Date endDate) throws WxErrorException {
+ return getAnalysisResultAsList(GET_DAILY_SUMMARY_TREND_URL, beginDate, endDate,
+ new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public List getDailyVisitTrend(Date beginDate, Date endDate) throws WxErrorException {
+ return getAnalysisResultAsList(GET_DAILY_VISIT_TREND_URL, beginDate, endDate,
+ new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public List getWeeklyVisitTrend(Date beginDate, Date endDate) throws WxErrorException {
+ return getAnalysisResultAsList(GET_WEEKLY_VISIT_TREND_URL, beginDate, endDate,
+ new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public List getMonthlyVisitTrend(Date beginDate, Date endDate) throws WxErrorException {
+ return getAnalysisResultAsList(GET_MONTHLY_VISIT_TREND_URL, beginDate, endDate,
+ new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public WxMaVisitDistribution getVisitDistribution(Date beginDate, Date endDate) throws WxErrorException {
+ String responseContent = this.wxMaService.post(GET_VISIT_DISTRIBUTION_URL, toJson(beginDate, endDate));
+ return WxMaVisitDistribution.fromJson(responseContent);
+ }
+
+ @Override
+ public WxMaRetainInfo getDailyRetainInfo(Date beginDate, Date endDate) throws WxErrorException {
+ return getRetainInfo(beginDate, endDate, GET_DAILY_RETAIN_INFO_URL);
+ }
+
+ @Override
+ public WxMaRetainInfo getWeeklyRetainInfo(Date beginDate, Date endDate) throws WxErrorException {
+ return getRetainInfo(beginDate, endDate, GET_WEEKLY_RETAIN_INFO_URL);
+ }
+
+ @Override
+ public WxMaRetainInfo getMonthlyRetainInfo(Date beginDate, Date endDate) throws WxErrorException {
+ return getRetainInfo(beginDate, endDate, GET_MONTHLY_RETAIN_INFO_URL);
+ }
+
+ @Override
+ public List getVisitPage(Date beginDate, Date endDate) throws WxErrorException {
+ return getAnalysisResultAsList(GET_VISIT_PAGE_URL, beginDate, endDate,
+ new TypeToken>() {
+ }.getType());
+ }
+
+ @Override
+ public WxMaUserPortrait getUserPortrait(Date beginDate, Date endDate) throws WxErrorException {
+ String responseContent = this.wxMaService.post(GET_USER_PORTRAIT_URL, toJson(beginDate, endDate));
+ return WxMaUserPortrait.fromJson(responseContent);
+ }
+
+ private WxMaRetainInfo getRetainInfo(Date beginDate, Date endDate, String url) throws WxErrorException {
+ String responseContent = this.wxMaService.post(url, toJson(beginDate, endDate));
+ return WxMaRetainInfo.fromJson(responseContent);
+ }
+
+ /**
+ * 获取数据分析结果并返回 List,returnType 类型
+ *
+ * @param url 链接
+ * @param returnType 返回的类型
+ * @param 返回的类型
+ * @return List 类型的数据
+ */
+ private List getAnalysisResultAsList(String url, Date beginDate, Date endDate, Type returnType) throws WxErrorException {
+ String responseContent = this.wxMaService.post(url, toJson(beginDate, endDate));
+ JsonObject response = JSON_PARSER.parse(responseContent).getAsJsonObject();
+ boolean hasList = response.has("list");
+ if (hasList) {
+ return WxMaGsonBuilder.create().fromJson(response.getAsJsonArray("list"), returnType);
+ } else {
+ return null;
+ }
+ }
+
+ private static String toJson(Date beginDate, Date endDate) {
+ JsonObject param = new JsonObject();
+ param.addProperty("begin_date", DateFormatUtils.format(beginDate, "yyyyMMdd"));
+ param.addProperty("end_date", DateFormatUtils.format(endDate, "yyyyMMdd"));
+ return param.toString();
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index a4d9d515..014ca1c9 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -1,5 +1,6 @@
package cn.binarywang.wx.miniapp.api.impl;
+import cn.binarywang.wx.miniapp.api.WxMaAnalysisService;
import cn.binarywang.wx.miniapp.api.WxMaCodeService;
import cn.binarywang.wx.miniapp.api.WxMaMediaService;
import cn.binarywang.wx.miniapp.api.WxMaMsgService;
@@ -52,6 +53,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttpCharming
+ * @since 2018-04-28 14:41
+ */
+@Data
+public class WxMaRetainInfo implements Serializable {
+ private static final long serialVersionUID = 8986403173656848413L;
+ /**
+ * 日留存:日期,yyyyMMdd 格式,如 20170313
+ * 周留存:时间,如"20170306-20170312"
+ * 月留存:时间,如"201702"
+ */
+ @SerializedName(value = "refDate", alternate = "ref_date")
+ private String refDate;
+ /**
+ * 新增用户留存
+ * - key:
+ * - 日留存:标识,0开始,0表示当天,1表示1天后,依此类推,key取值分别是:0,1,2,3,4,5,6,7,14,30
+ * - 周留存:标识,0开始,0表示当周,1表示1周后,依此类推,key 取值分别是:0,1,2,3,4
+ * - 月留存:标识,0开始,0表示当月,1表示1月后,key取值分别是:0,1
+ * - value: key对应日期的新增用户数/活跃用户数(key=0时)或留存用户数(k>0时)
+ */
+ private Map visitUvNew;
+ /**
+ * 活跃用户留存
+ */
+ private Map visitUv;
+
+ public static WxMaRetainInfo fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaRetainInfo.class);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaSummaryTrend.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaSummaryTrend.java
new file mode 100644
index 00000000..f4ceb0ad
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaSummaryTrend.java
@@ -0,0 +1,37 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 小程序概况趋势
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Data
+public class WxMaSummaryTrend implements Serializable {
+ private static final long serialVersionUID = 1379688517709317935L;
+ /**
+ * 日期,yyyyMMdd 格式,如 20170313
+ */
+ @SerializedName(value = "refDate", alternate = "ref_date")
+ private String refDate;
+ /**
+ * 累计用户数
+ */
+ @SerializedName(value = "visitTotal", alternate = "visit_total")
+ private Long visitTotal;
+ /**
+ * 转发次数
+ */
+ @SerializedName(value = "sharePv", alternate = "share_pv")
+ private Long sharePv;
+ /**
+ * 转发人数
+ */
+ @SerializedName(value = "shareUv", alternate = "share_uv")
+ private Long shareUv;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java
new file mode 100644
index 00000000..5e116490
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java
@@ -0,0 +1,68 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 用户画像
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Data
+public class WxMaUserPortrait implements Serializable {
+ private static final long serialVersionUID = 5653571047669243178L;
+ /**
+ * 时间范围,如: "20170611-20170617"
+ */
+ private String refDate;
+ /**
+ * 新用户
+ */
+ private Item visitUvNew;
+ /**
+ * 活跃用户
+ */
+ private Item visitUv;
+
+ public static WxMaUserPortrait fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaUserPortrait.class);
+ }
+
+ @Data
+ public static class Item {
+ /**
+ * key: 省份,如北京、广东等
+ * value: 活跃用户数或新用户数
+ */
+ private Map province;
+ /**
+ * key: 城市,如北京、广州等
+ * value: 活跃用户数或新用户数
+ */
+ private Map city;
+ /**
+ * key: 性别,包括男、女、未知
+ * value: 活跃用户数或新用户数
+ */
+ private Map genders;
+ /**
+ * key: 终端类型,包括iPhone, android,其他
+ * value: 活跃用户数或新用户数
+ */
+ private Map platforms;
+ /**
+ * key: 机型,如苹果iPhone6, OPPO R9等
+ * value: 活跃用户数或新用户数
+ */
+ private Map devices;
+ /**
+ * key: 年龄,包括17岁以下、18-24岁等区间
+ * value: 活跃用户数或新用户数
+ */
+ private Map ages;
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java
new file mode 100644
index 00000000..1655eec2
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java
@@ -0,0 +1,83 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 访问分布
+ * 访问来源:(index="access_source_session_cnt")
+ * 1:小程序历史列表
+ * 2:搜索
+ * 3:会话
+ * 4:二维码
+ * 5:公众号主页
+ * 6:聊天顶部
+ * 7:系统桌面
+ * 8:小程序主页
+ * 9:附近的小程序
+ * 10:其他
+ * 11:模板消息
+ * 12:客服消息
+ * 13: 公众号菜单
+ * 14: APP分享
+ * 15: 支付完成页
+ * 16: 长按识别二维码
+ * 17: 相册选取二维码
+ * 18: 公众号文章
+ * 19:钱包
+ * 20:卡包
+ * 21:小程序内卡券
+ * 22:其他小程序
+ * 23:其他小程序返回
+ * 24:卡券适用门店列表
+ * 25:搜索框快捷入口
+ * 26:小程序客服消息
+ * 27:公众号下发
+ * 访问时长:(index="access_staytime_info")
+ * 1: 0-2s
+ * 2: 3-5s
+ * 3: 6-10s
+ * 4: 11-20s
+ * 5: 20-30s
+ * 6: 30-50s
+ * 7: 50-100s
+ * 8: > 100s
+ * 平均访问深度:(index="access_depth_info")
+ * 1: 1页
+ * 2: 2页
+ * 3: 3页
+ * 4: 4页
+ * 5: 5页
+ * 6: 6-10页
+ * 7: >10页
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Data
+public class WxMaVisitDistribution implements Serializable {
+ private static final long serialVersionUID = 5404250039495926632L;
+ /**
+ * 日期,yyyyMMdd 格式,如 20170313
+ */
+ @SerializedName(value = "refDate", alternate = "ref_date")
+ private String refDate;
+ /**
+ * key: 分布类型
+ * - access_source_session_cnt 访问来源分布
+ * - access_staytime_info 访问时长分布
+ * - access_depth_info 访问深度的分布
+ * value: 场景 ID 下的值
+ * - key: 场景 ID
+ * - value: 场景下的值
+ */
+ private Map> list;
+
+ public static WxMaVisitDistribution fromJson(String json) {
+ return WxMaGsonBuilder.create().fromJson(json, WxMaVisitDistribution.class);
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitPage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitPage.java
new file mode 100644
index 00000000..705cf6b4
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitPage.java
@@ -0,0 +1,55 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Data
+public class WxMaVisitPage implements Serializable {
+ private static final long serialVersionUID = -7006334774516877372L;
+ /**
+ * 页面路径
+ */
+ @SerializedName(value = "pagePath", alternate = "page_path")
+ private String pagePath;
+ /**
+ * 访问次数
+ */
+ @SerializedName(value = "pageVisitPv", alternate = "page_visit_pv")
+ private Long pageVisitPv;
+ /**
+ * 访问人数
+ */
+ @SerializedName(value = "pageVisitUv", alternate = "page_visit_uv")
+ private Long pageVisitUv;
+ /**
+ * 次均停留时长
+ */
+ @SerializedName(value = "pageStayTimePv", alternate = "page_staytime_pv")
+ private Float pageStayTimePv;
+ /**
+ * 进入页次数
+ */
+ @SerializedName(value = "entryPagePv", alternate = "entrypage_pv")
+ private Long entryPagePv;
+ /**
+ * 退出页次数
+ */
+ @SerializedName(value = "exitPagePv", alternate = "exitpage_pv")
+ private Long exitPagePv;
+ /**
+ * 转发次数
+ */
+ @SerializedName(value = "pageSharePv", alternate = "page_share_pv")
+ private Long pageSharePv;
+ /**
+ * 转发人数
+ */
+ @SerializedName(value = "pageShareUv", alternate = "page_share_uv")
+ private Long pageShareUv;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitTrend.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitTrend.java
new file mode 100644
index 00000000..72223ef6
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitTrend.java
@@ -0,0 +1,59 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 访问趋势
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Data
+public class WxMaVisitTrend implements Serializable {
+ private static final long serialVersionUID = 1379688517709317935L;
+ /**
+ * 日留存:日期,yyyyMMdd 格式,如 20170313
+ * 周留存:时间,如"20170306-20170312"
+ * 月留存:时间,如"201702"
+ */
+ @SerializedName(value = "refDate", alternate = "ref_date")
+ private String refDate;
+ /**
+ * 打开次数
+ */
+ @SerializedName(value = "sessionCnt", alternate = "session_cnt")
+ private Long sessionCnt;
+ /**
+ * 访问次数
+ */
+ @SerializedName(value = "visitPv", alternate = "visit_pv")
+ private Long visitPv;
+ /**
+ * 访问人数
+ */
+ @SerializedName(value = "visitUv", alternate = "visit_uv")
+ private Long visitUv;
+ /**
+ * 新用户数
+ */
+ @SerializedName(value = "visitUvNew", alternate = "visit_uv_new")
+ private Long visitUvNew;
+ /**
+ * 人均停留时长 (浮点型,单位:秒)
+ */
+ @SerializedName(value = "stayTimeUv", alternate = "stay_time_uv")
+ private Float stayTimeUv;
+ /**
+ * 人均停留时长 (浮点型,单位:秒)
+ */
+ @SerializedName(value = "stayTimeSession", alternate = "stay_time_session")
+ private Float stayTimeSession;
+ /**
+ * 人均停留时长 (浮点型,单位:秒)
+ */
+ @SerializedName(value = "visitDepth", alternate = "visit_depth")
+ private Float visitDepth;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/package-info.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/package-info.java
new file mode 100644
index 00000000..e4e68c78
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 数据分析
+ *
+ * @author Charming
+ * @since 2018-04-28
+ */
+package cn.binarywang.wx.miniapp.bean.analysis;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
index a11e8e6c..21f48d3f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java
@@ -1,6 +1,9 @@
package cn.binarywang.wx.miniapp.util.json;
import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
import com.google.gson.Gson;
@@ -17,6 +20,9 @@ public class WxMaGsonBuilder {
INSTANCE.registerTypeAdapter(WxMaTemplateMessage.class, new WxMaTemplateMessageGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaCodeCommitRequest.class, new WxMaCodeCommitRequestGsonAdapter());
INSTANCE.registerTypeAdapter(WxMaCodeVersionDistribution.class, new WxMaCodeVersionDistributionGsonAdapter());
+ INSTANCE.registerTypeAdapter(WxMaVisitDistribution.class, new WxMaVisitDistributionGsonAdapter());
+ INSTANCE.registerTypeAdapter(WxMaRetainInfo.class, new WxMaRetainInfoGsonAdapter());
+ INSTANCE.registerTypeAdapter(WxMaUserPortrait.class, new WxMaUserPortraitGsonAdapter());
}
public static Gson create() {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java
new file mode 100644
index 00000000..a51972b4
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java
@@ -0,0 +1,52 @@
+package cn.binarywang.wx.miniapp.util.json;
+
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaRetainInfoGsonAdapter implements JsonDeserializer {
+ @Override
+ public WxMaRetainInfo deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ if (json == null) {
+ return null;
+ }
+
+ WxMaRetainInfo retainInfo = new WxMaRetainInfo();
+ JsonObject object = json.getAsJsonObject();
+ String refDate = GsonHelper.getString(object, "ref_date");
+ retainInfo.setRefDate(refDate);
+ retainInfo.setVisitUvNew(getAsMap(object, "visit_uv_new"));
+ retainInfo.setVisitUv(getAsMap(object, "visit_uv"));
+ return retainInfo;
+ }
+
+ private Map getAsMap(JsonObject object, String memberName) {
+ JsonArray array = object.getAsJsonArray(memberName);
+ if (array != null && array.size() > 0) {
+ Map map = new LinkedHashMap<>(array.size());
+ for (JsonElement element : array) {
+ JsonObject elementObject = element.getAsJsonObject();
+ Integer key = GsonHelper.getInteger(elementObject, "key");
+ if (key != null) {
+ Integer value = GsonHelper.getInteger(elementObject, "value");
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+ return null;
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java
new file mode 100644
index 00000000..b8a7c448
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java
@@ -0,0 +1,67 @@
+package cn.binarywang.wx.miniapp.util.json;
+
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaUserPortraitGsonAdapter implements JsonDeserializer {
+ @Override
+ public WxMaUserPortrait deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ if (json == null) {
+ return null;
+ }
+
+ WxMaUserPortrait portrait = new WxMaUserPortrait();
+ JsonObject object = json.getAsJsonObject();
+ String refDate = GsonHelper.getString(object, "ref_date");
+ portrait.setRefDate(refDate);
+ portrait.setVisitUvNew(getPortraitItem(object.getAsJsonObject("visit_uv_new")));
+ portrait.setVisitUv(getPortraitItem(object.getAsJsonObject("visit_uv")));
+ return portrait;
+ }
+
+ private WxMaUserPortrait.Item getPortraitItem(JsonObject object) {
+ if (object == null) {
+ return null;
+ }
+ WxMaUserPortrait.Item item = new WxMaUserPortrait.Item();
+ item.setProvince(getAsMap(object, "province"));
+ item.setCity(getAsMap(object, "city"));
+ item.setGenders(getAsMap(object, "genders"));
+ item.setPlatforms(getAsMap(object, "platforms"));
+ item.setDevices(getAsMap(object, "devices"));
+ item.setAges(getAsMap(object, "ages"));
+ return item;
+ }
+
+ private Map getAsMap(JsonObject object, String memberName) {
+ JsonArray array = object.getAsJsonArray(memberName);
+ if (array != null && array.size() > 0) {
+ Map map = new LinkedHashMap<>(array.size());
+ for (JsonElement element : array) {
+ JsonObject elementObject = element.getAsJsonObject();
+ String name = GsonHelper.getString(elementObject, "name");
+ if (StringUtils.isNotBlank(name)) {
+ Long value = GsonHelper.getLong(elementObject, "value");
+ map.put(name, value);
+ }
+ }
+ return map;
+ }
+ return null;
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java
new file mode 100644
index 00000000..45bdbac0
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java
@@ -0,0 +1,67 @@
+package cn.binarywang.wx.miniapp.util.json;
+
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+
+import java.lang.reflect.Type;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaVisitDistributionGsonAdapter implements JsonDeserializer {
+ @Override
+ public WxMaVisitDistribution deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ if (json == null) {
+ return null;
+ }
+
+ WxMaVisitDistribution distribution = new WxMaVisitDistribution();
+ JsonObject object = json.getAsJsonObject();
+ String refDate = GsonHelper.getString(object, "ref_date");
+ distribution.setRefDate(refDate);
+
+ boolean hasList = object.has("list");
+ if (!hasList) {
+ return distribution;
+ }
+
+ JsonArray listArray = object.getAsJsonArray("list");
+ Map> list = new Hashtable<>(listArray.size());
+ for (JsonElement indexElement : listArray) {
+ JsonObject indexObject = indexElement.getAsJsonObject();
+ String index = GsonHelper.getString(indexObject, "index");
+ if (index == null) {
+ continue;
+ }
+
+ Map itemList = new LinkedHashMap<>();
+ JsonArray itemArray = indexObject.getAsJsonArray("item_list");
+ if (itemArray == null || itemArray.size() <= 0) {
+ list.put(index, itemList);
+ continue;
+ }
+
+ for (JsonElement itemElement : itemArray) {
+ JsonObject itemObject = itemElement.getAsJsonObject();
+ Integer key = GsonHelper.getInteger(itemObject, "key");
+ Integer value = GsonHelper.getInteger(itemObject, "value");
+ if (key != null) {
+ itemList.put(key, value);
+ }
+ }
+ list.put(index, itemList);
+ }
+ distribution.setList(list);
+ return distribution;
+ }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImplTest.java
new file mode 100644
index 00000000..9c1c2f00
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImplTest.java
@@ -0,0 +1,155 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaAnalysisService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaSummaryTrend;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitPage;
+import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitTrend;
+import cn.binarywang.wx.miniapp.test.ApiTestModule;
+import com.google.inject.Inject;
+import org.apache.commons.lang3.time.DateFormatUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import static org.testng.Assert.*;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxMaAnalysisServiceImplTest {
+ @Inject
+ private WxMaService wxMaService;
+
+ @Test
+ public void testGetDailySummaryTrend() throws Exception {
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ List trends = service.getDailySummaryTrend(twoDaysAgo, twoDaysAgo);
+ assertEquals(1, trends.size());
+ System.out.println(trends);
+ }
+
+ @Test
+ public void testGetDailyVisitTrend() throws Exception {
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ List trends = service.getDailyVisitTrend(twoDaysAgo, twoDaysAgo);
+ assertEquals(1, trends.size());
+ System.out.println(trends);
+ }
+
+ @Test
+ public void testGetWeeklyVisitTrend() throws Exception {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
+ Date now = new Date();
+ Date lastSunday = calendar.getTime();
+ if (DateUtils.isSameDay(lastSunday, now)) {
+ lastSunday = DateUtils.addDays(lastSunday, -7);
+ }
+ Date lastMonday = DateUtils.addDays(lastSunday, -6);
+
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ List trends = service.getWeeklyVisitTrend(lastMonday, lastSunday);
+ assertEquals(1, trends.size());
+ System.out.println(trends);
+ }
+
+ @Test
+ public void testGetMonthlyVisitTrend() throws Exception {
+ Date now = new Date();
+ Date firstDayOfThisMonth = DateUtils.setDays(now, 1);
+ Date lastDayOfLastMonth = DateUtils.addDays(firstDayOfThisMonth, -1);
+ Date firstDayOfLastMonth = DateUtils.addMonths(firstDayOfThisMonth, -1);
+
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ List trends = service.getMonthlyVisitTrend(firstDayOfLastMonth, lastDayOfLastMonth);
+ assertEquals(1, trends.size());
+ System.out.println(trends);
+ }
+
+ @Test
+ public void testGetVisitDistribution() throws Exception {
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ WxMaVisitDistribution distribution = service.getVisitDistribution(twoDaysAgo, twoDaysAgo);
+ assertNotNull(distribution);
+ String date = DateFormatUtils.format(twoDaysAgo, "yyyyMMdd");
+ assertEquals(date, distribution.getRefDate());
+ assertTrue(distribution.getList().containsKey("access_source_session_cnt"));
+ assertTrue(distribution.getList().containsKey("access_staytime_info"));
+ assertTrue(distribution.getList().containsKey("access_depth_info"));
+ System.out.println(distribution);
+ }
+
+ @Test
+ public void testGetDailyRetainInfo() throws Exception {
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ WxMaRetainInfo retainInfo = service.getDailyRetainInfo(twoDaysAgo, twoDaysAgo);
+ assertNotNull(retainInfo);
+ System.out.println(retainInfo);
+ }
+
+ @Test
+ public void testGetWeeklyRetainInfo() throws Exception {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
+ Date now = new Date();
+ Date lastSunday = calendar.getTime();
+ if (DateUtils.isSameDay(lastSunday, now)) {
+ lastSunday = DateUtils.addDays(lastSunday, -7);
+ }
+ Date lastMonday = DateUtils.addDays(lastSunday, -6);
+
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ WxMaRetainInfo retainInfo = service.getWeeklyRetainInfo(lastMonday, lastSunday);
+ assertNotNull(retainInfo);
+ System.out.println(retainInfo);
+ }
+
+ @Test
+ public void testGetMonthlyRetainInfo() throws Exception {
+ Date now = new Date();
+ Date firstDayOfThisMonth = DateUtils.setDays(now, 1);
+ Date lastDayOfLastMonth = DateUtils.addDays(firstDayOfThisMonth, -1);
+ Date firstDayOfLastMonth = DateUtils.addMonths(firstDayOfThisMonth, -1);
+
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ WxMaRetainInfo retainInfo = service.getMonthlyRetainInfo(firstDayOfLastMonth, lastDayOfLastMonth);
+ assertNotNull(retainInfo);
+ System.out.println(retainInfo);
+ }
+
+ @Test
+ public void testGetVisitPage() throws Exception {
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ List visitPages = service.getVisitPage(twoDaysAgo, twoDaysAgo);
+ assertNotNull(visitPages);
+ System.out.println(visitPages);
+ System.out.println(visitPages.get(0).getPagePath());
+ System.out.println(visitPages.get(0).getPageVisitPv());
+ }
+
+ @Test
+ public void testGetUserPortrait() throws Exception {
+ Date twoDaysAgo = DateUtils.addDays(new Date(), -2);
+ Date eightDaysAgo = DateUtils.addDays(new Date(), -8);
+
+ final WxMaAnalysisService service = wxMaService.getAnalysisService();
+ WxMaUserPortrait portrait = service.getUserPortrait(eightDaysAgo, twoDaysAgo);
+ assertNotNull(portrait);
+ System.out.println(portrait);
+ }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfoTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfoTest.java
new file mode 100644
index 00000000..c2fd925e
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfoTest.java
@@ -0,0 +1,22 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaRetainInfoTest {
+ @Test
+ public void testFromJson() throws Exception {
+ String json = "{\"ref_date\":\"20170313\",\"visit_uv_new\":[{\"key\":0,\"value\":5464}],\"visit_uv\":[{\"key\":0,\"value\":55500}]}\n";
+ WxMaRetainInfo retainInfo = WxMaRetainInfo.fromJson(json);
+ assertNotNull(retainInfo);
+ assertEquals("20170313", retainInfo.getRefDate());
+ assertTrue(retainInfo.getVisitUv().containsKey(0));
+ assertTrue(retainInfo.getVisitUvNew().containsKey(0));
+ System.out.println(retainInfo);
+ }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortraitTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortraitTest.java
new file mode 100644
index 00000000..87239bb5
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortraitTest.java
@@ -0,0 +1,19 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaUserPortraitTest {
+ @Test
+ public void testFromJson() throws Exception {
+ String json = "{\"ref_date\":\"20170611\",\"visit_uv_new\":{\"province\":[{\"id\":31,\"name\":\"广东省\",\"value\":215}],\"city\":[{\"id\":3102,\"name\":\"广州\",\"value\":78}],\"genders\":[{\"id\":1,\"name\":\"男\",\"value\":2146}],\"platforms\":[{\"id\":1,\"name\":\"iPhone\",\"value\":27642}],\"devices\":[{\"name\":\"OPPO R9\",\"value\":61}],\"ages\":[{\"id\":1,\"name\":\"17岁以下\",\"value\":151}]},\"visit_uv\":{\"province\":[{\"id\":31,\"name\":\"广东省\",\"value\":1341}],\"city\":[{\"id\":3102,\"name\":\"广州\",\"value\":234}],\"genders\":[{\"id\":1,\"name\":\"男\",\"value\":14534}],\"platforms\":[{\"id\":1,\"name\":\"iPhone\",\"value\":21750}],\"devices\":[{\"name\":\"OPPO R9\",\"value\":617}],\"ages\":[{\"id\":1,\"name\":\"17岁以下\",\"value\":3156}]}}\n";
+ WxMaUserPortrait portrait = WxMaUserPortrait.fromJson(json);
+ System.out.println(portrait);
+ assertNotNull(portrait);
+ }
+}
diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistributionTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistributionTest.java
new file mode 100644
index 00000000..2b01b3aa
--- /dev/null
+++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistributionTest.java
@@ -0,0 +1,23 @@
+package cn.binarywang.wx.miniapp.bean.analysis;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+/**
+ * @author Charming
+ * @since 2018-04-28
+ */
+public class WxMaVisitDistributionTest {
+ @Test
+ public void testFromJson() throws Exception {
+ String json = "{\"ref_date\":\"20170313\",\"list\":[{\"index\":\"access_source_session_cnt\",\"item_list\":[{\"key\":10,\"value\":5},{\"key\":8,\"value\":687},{\"key\":7,\"value\":10740},{\"key\":6,\"value\":1961},{\"key\":5,\"value\":677},{\"key\":4,\"value\":653},{\"key\":3,\"value\":1120},{\"key\":2,\"value\":10243},{\"key\":1,\"value\":116578}]},{\"index\":\"access_staytime_info\",\"item_list\":[{\"key\":8,\"value\":16329},{\"key\":7,\"value\":19322},{\"key\":6,\"value\":21832},{\"key\":5,\"value\":19539},{\"key\":4,\"value\":29670},{\"key\":3,\"value\":19667},{\"key\":2,\"value\":11794},{\"key\":1,\"value\":4511}]},{\"index\":\"access_depth_info\",\"item_list\":[{\"key\":5,\"value\":217},{\"key\":4,\"value\":3259},{\"key\":3,\"value\":32445},{\"key\":2,\"value\":63542},{\"key\":1,\"value\":43201}]}]}\n";
+ WxMaVisitDistribution distribution = WxMaVisitDistribution.fromJson(json);
+ assertNotNull(distribution);
+ assertEquals("20170313", distribution.getRefDate());
+ assertTrue(distribution.getList().containsKey("access_source_session_cnt"));
+ assertTrue(distribution.getList().containsKey("access_staytime_info"));
+ assertTrue(distribution.getList().containsKey("access_depth_info"));
+ System.out.println(distribution);
+ }
+}