微信平臺自帶的統計功能太簡單,有時咱們須要統計有哪些微信我的用戶閱讀、分享了微信公衆號的手機網頁,以及微信我的用戶訪問手機網頁的來源:朋友圈分享訪問、好友分享消息訪問等。本系統實現了手機網頁閱讀、分享與來源統計及手機網頁在朋友圈的傳播路徑分析。javascript
本系統使用最傳統的三層架構。本文是微統計的第三篇,主要介紹以下內容:css
1. 爲頁面HighCharts畫圖控件提供數據html
2. 接收分享記錄信息並保存到數據庫前端
3. 訪問記錄統計圖java
4. 閱讀統計界面jquery
5. 處理文字請求ajax
前端開發框架使用Bootstrap,沒有註明前臺的頁面表示前臺不用顯示任何內容數據庫
public partial class Data : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string result = "";
string typeStr = System.Web.HttpContext.Current.Request.QueryString["type"];
if (!string.IsNullOrEmpty(typeStr))
{
switch (typeStr)
{
case "navChart": //頁面訪問圖
result = JsonConvert.SerializeObject(GetPageNavStatistics());
break;
case "shareChart": //頁面分享圖
result = JsonConvert.SerializeObject(GetPageShareStatistics());
break;
}
}
//將HighCharts繪圖所需的數據返回給頁面
HttpResponse response = System.Web.HttpContext.Current.Response;
response.ContentType = "application/json";
response.Write(result);
response.End();
}
/// <summary>
/// 獲取頁面訪問統計信息
/// </summary>
/// <returns></returns>
private ChartData GetPageNavStatistics()
{
//取過去兩天的數據進行統計
DateTime startTime = DateTime.Now.AddDays(-3);
DateTime endTime = DateTime.Now.AddDays(1);
List<PageNavEntity> temp = new PageNavBll().GetPageNavList();
List<decimal> statistics = new List<decimal>();
//HighCharts時間軸的起始時間
ChartData chartData = new ChartData
{
StartYear = startTime.Year,
StartDay = startTime.Day,
StartMonth = startTime.Month
};
//生成按小時統計的數據
while (startTime < endTime)
{
statistics.Add(temp.FindAll(e => e.VisitTime >= startTime && e.VisitTime < startTime.AddHours(1)).Count());
startTime = startTime.AddHours(1);
}
chartData.Statistics = statistics.ToArray();
return chartData;
}
/// <summary>
/// 獲取頁面分享統計信息
/// </summary>
/// <returns></returns>
private ChartData GetPageShareStatistics()
{
//取過去兩天的數據進行統計
DateTime startTime = DateTime.Now.AddDays(-3);
DateTime endTime = DateTime.Now.AddDays(1);
List<PageShareEntity> temp = new PageShareBll().GetPageShareList();
List<decimal> statistics = new List<decimal>();
//HighCharts時間軸的起始時間
ChartData chartData = new ChartData
{
StartYear = startTime.Year,
StartDay = startTime.Day,
StartMonth = startTime.Month
};
//生成按小時統計的數據
while (startTime < endTime)
{
statistics.Add(temp.FindAll(e => e.ShareTime >= startTime && e.ShareTime < startTime.AddHours(1)).Count());
startTime = startTime.AddHours(1);
}
chartData.Statistics = statistics.ToArray();
return chartData;
}
}
public partial class Share : System.Web.UI.Page
{
ILog m_Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
protected void Page_Load(object sender, EventArgs e)
{
string typeStr = Request.QueryString["type"];
m_Log.Info("share type: " + typeStr);
m_Log.Info("share url: " + Request["url"]);
if (!string.IsNullOrEmpty(typeStr))
{
//識別分享類型
ShareType type = ShareType.Unknown;
switch (typeStr)
{
case "timeline":
type = ShareType.Timeline;
break;
case "friend":
type = ShareType.Friend;
break;
}
//構造分享記錄
var pageShare = new PageShareEntity()
{
Id = Guid.NewGuid(),
Url = GetOrigenalUrl(Request["url"]),
ParentShareOpenId = Request["s"],
ShareOpenId = Request["u"],
From = type,
ShareTime = DateTime.Now
};
//保存分享記錄
bool insertShare = new PageShareBll().InsertPageShare(pageShare);
m_Log.Info("insert share: " + insertShare.ToString());
}
}
/// <summary>
/// 獲取不含統計相關參數的頁面地址
/// </summary>
/// <param name="url">網址</param>
/// <returns>不含統計相關參數的頁面地址</returns>
private string GetOrigenalUrl(string url)
{
url = System.Web.HttpUtility.UrlDecode(url);
Uri uri = new Uri(url);
StringBuilder urlBuilder = new StringBuilder();
//獲取不含QueryString的URL
urlBuilder.Append("http://")
.Append(uri.Host)
.Append(uri.AbsolutePath)
.Append("?");
//構造移除統計相關參數的Query
Dictionary<string, string> queryString = uri.Query.Replace("?", "").Split('&').Where(p => !string.IsNullOrEmpty(p)).ToDictionary(p => p.Split('=')[0], p => p.Split('=')[1].Split('#')[0]);
foreach (var key in queryString.Keys)
{
if (key != "s" && key != "u" && key != "from" && key != "code" && key != "state")
{
urlBuilder.Append(key).Append("=").Append(queryString[key]).Append("&");
}
}
return urlBuilder.ToString();
}
}
當發送朋友或朋友圈時保存分享數據。json
前臺:bootstrap
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="StatisticsPage.aspx.cs" Inherits="Statistics.StatisticsPage" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>統計</title>
<%-- Bootstrap --%>
<link href="Css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="Scripts/bootstrap.min.js" type="text/javascript"></script>
<script src="Scripts/jquery-1.9.1.min.js" type="text/javascript"></script>
<%-- HighCharts用於圖表顯示 --%>
<link href="Css/highcharts/charts.css" rel="stylesheet" type="text/css" />
<script src="Scripts/HighCharts/highcharts.js" type="text/javascript"></script>
<script src="Scripts/HighCharts/highcharts-more.js" type="text/javascript"></script>
<script src="Scripts/HighCharts/publiclinecharts.js" type="text/javascript"></script>
</head>
<body>
<div class="container">
<div class="row-fluid">
<div class="span12">
<h3>訪問記錄</h3>
<%-- 訪問記錄統計圖 --%>
<div class="box">
<div class="box-content">
<div class="row" style="margin-top: 30px; ">
<div class="area">
<div id="page-nav-chart">
</div>
</div>
</div>
</div>
</div>
<%-- 訪問記錄列表 --%>
<div class="maincontentinner1" >
<div id="Div12" class="dataTables_wrapper">
<table id="page-nav-table" class="table table-bordered responsive dataTable">
<%-- 訪問記錄列表列名 --%>
<thead>
<tr>
<th>頁面地址
</th>
<th>訪問來源
</th>
<th>訪問者openid
</th>
<th>分享自openid
</th>
<th>訪問時間
</th>
</tr>
</thead>
<tbody id="page-nav-table-body">
<%-- 一行一行生成訪問記錄列表 --%>
<% foreach (Statistics.ViewEntity.PageNavEntity entity in (ViewState["NavList"] as List<Statistics.ViewEntity.PageNavEntity>))
{ %>
<tr class="gradeX odd">
<td>
<%= entity.Url%>
</td>
<td class=" ">
<%= entity.From.ToString()%>
</td>
<td class=" ">
<%= entity.NavOpenId%>
</td>
<td class=" ">
<%= entity.ShareOpenId%>
</td>
<td class=" ">
<%= entity.VisitTime.ToString()%>
</td>
</tr>
<% } %>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
//圖表參數
var pageNavChartOpts = {
getStatisticsUrl: 'Data.aspx?type=navChart', //讀取數據的訪問地址
titletext: "",
ytext: "",
startyear: 0,
startmonth: 0,
startday: 0,
lineinterval: 3600 * 1000, //豎線以1小時爲間隔顯示
pointInterval: 3600 * 1000,//點以1小時爲間隔顯示
countArray: [],
formid: "page-nav-chart", //圖表容器ID
seriesname: "訪問次數",
unit: "次"
};
jQuery(function () {
//使用HighCharts繪製圖表
highcharts.extFunction.PreDrawMethod = function (repJson) {
pageNavChartOpts.startyear = repJson.StartYear;
pageNavChartOpts.startmonth = repJson.StartMonth;
pageNavChartOpts.startday = repJson.StartDay;
highcharts.displayMode = repJson.DisplayMode;
pageNavChartOpts.lineinterval = repJson.LineInterval;
pageNavChartOpts.pointInterval = repJson.PointInterval;
};
highcharts.init(pageNavChartOpts);
});
</script>
</body>
</html>
用HighCharts來圖表顯示數據。
後臺:
protected void Page_Load(object sender, EventArgs e)
{
//傳遞給頁面顯示的記錄列表
ViewState["NavList"] = new PageNavBll().GetPageNavList();
ViewState["ShareList"] = new PageShareBll().GetPageShareList();
}
經過業務邏輯層獲取數據。
前臺:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WeixinPageIndex.aspx.cs" Inherits="Statistics.WeixinPageIndex" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script src="Scripts/jquery-1.9.1.min.js"></script>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
</head>
<body>
<form id="form1" type="post" runat="server">
<div>
<%-- 全部的跳轉頁面,加上訪問者與分享者的OpenId --%>
<a href="WeixinPageSubPage.aspx?u=<%= ViewState["navOpenId"] as string %>&s=<%= ViewState["shareOpenId"] as string %>">WeixinPageSubPage</a>
</div>
</form>
</body>
</html>
<script>
var url = location.href;
alert(url);
wx.config({
debug: true, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
appId: '<%= appID %>', // 必填,公衆號的惟一標識
timestamp: '<%= timestamp %>', // 必填,生成簽名的時間戳
nonceStr: '<%= nonceStr %>', // 必填,生成簽名的隨機串
signature: '<%= signature %>',// 必填,簽名,見附錄1
// 必填,須要使用的JS接口列表,全部JS接口列表見附錄2
jsApiList: [
'onMenuShareAppMessage'
]
});
friendcallback = function (res) {
var shareUrl = "Share.aspx?type=friend&url=" + encodeURIComponent(url) + "&u=" + "<%= ViewState["navOpenId"] as string %>" + "&s=" + "<%= ViewState["shareOpenId"] as string %>";
//AJAX請求
$.ajax({
type: "get",
url: shareUrl,
beforeSend: function () {
},
success: function () {
},
complete: function () {
},
error: function () {
}
});
};
wx.ready(function () {
wx.onMenuShareAppMessage({
title: '用c#開發微信 系列彙總',
desc: '網上開發微信開發的教程不少,但c#相對較少。這裏列出了我全部c#開發微信的文章,方便本身隨時查閱。若是可能,我儘可能附上源碼,這樣就能夠直接發佈運行看效果,更好地理解原理。',
link: url,
imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
trigger: function (res) {
},
success: function (res) {
friendcallback(res);
},
cancel: function (res) {
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
wx.onMenuShareTimeline({
title: '用c#開發微信 系列彙總',
desc: '網上開發微信開發的教程不少,但c#相對較少。這裏列出了我全部c#開發微信的文章,方便本身隨時查閱。若是可能,我儘可能附上源碼,這樣就能夠直接發佈運行看效果,更好地理解原理。',
link: url,
imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
trigger: function (res) {
},
success: function (res) {
friendcallback(res);
},
cancel: function (res) {
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
});
</script>
利用JS-SDK來獲取分享者,並經過Share頁面來保存分享數據。
後臺:
public partial class WeixinPageIndex : System.Web.UI.Page
{
static ILog m_Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public string timestamp = string.Empty;
public string nonceStr = string.Empty;
public string signature = string.Empty;
/// <summary>
/// 從微信公衆平臺獲取的開發者憑據
/// </summary>
public readonly string appID = ConfigurationManager.AppSettings["appID"];
/// <summary>
/// 從微信公衆平臺獲取的開發者憑據
/// </summary>
readonly string appSecret = ConfigurationManager.AppSettings["appSecret"];
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
#region 1. Get wx.config
string ticket = string.Empty;
timestamp = JSSDKHelper.GetTimestamp();
nonceStr = JSSDKHelper.GetNoncestr();
JSSDKHelper jssdkhelper = new JSSDKHelper();
try
{
ticket = JsApiTicketContainer.TryGetTicket(appID, appSecret);
signature = jssdkhelper.GetSignature(ticket, nonceStr, timestamp, Request.Url.AbsoluteUri.ToString());
}
catch (ErrorJsonResultException ex)
{
m_Log.Error("errorcode:" + ex.JsonResult.errcode.ToString() + " errmsg: " + ex.JsonResult.errmsg, ex);
}
#endregion
NameValueCollection parameters = System.Web.HttpContext.Current.Request.Params;
m_Log.Info("URL: " + Request.Url.ToString());
//取得連接中的分享者OpenId
string shareOpenId = parameters["s"];
//獲取訪問者的OpenId
string navOpenId = GetNavOpenId();
if (navOpenId != null)
{
NavStatistics(navOpenId, shareOpenId);
//傳遞給頁面的訪問者OpenId
ViewState["navOpenId"] = navOpenId;
//傳遞給頁面的分享者OpenId
ViewState["shareOpenId"] = shareOpenId;
}
m_Log.Info("timestamp: " + timestamp + " nocestr: " + nonceStr + " singnature: " + signature);
m_Log.Info("nav open id: " + navOpenId + " share open id: " + shareOpenId);
}
}
/// <summary>
/// 記錄頁面訪問
/// </summary>
/// <param name="navOpenId">訪問者微信openid</param>
/// <param name="shareOpenId">當訪問來源爲朋友圈時的分享者微信openid</param>
private void NavStatistics(string navOpenId, string shareOpenId)
{
//獲取訪問來源
NavFrom fromType = GetNavFromType();
//構造訪問記錄
var pageNav = new PageNavEntity()
{
Id = Guid.NewGuid(),
Url = GetOrigenalUrl(),
NavOpenId = navOpenId,
ShareOpenId = navOpenId == shareOpenId ? "" : shareOpenId,
From = fromType,
VisitTime = DateTime.Now
};
//訪問記錄寫入數據庫
new PageNavBll().InsertPageNav(pageNav);
}
/// <summary>
/// 判斷頁面訪問來源類型
/// </summary>
/// <returns></returns>
private static NavFrom GetNavFromType()
{
//網址中的參數集合
NameValueCollection parameters = System.Web.HttpContext.Current.Request.Params;
string fromStr = parameters["from"]; //發送給朋友、分享到朋友圈的連接會含有from參數
m_Log.Info("from: " + fromStr);
NavFrom fromType;
if (!Enum.TryParse<NavFrom>(fromStr, true, out fromType)) //經過判斷from參數,識別頁面訪問是來自於發送給朋友的連接仍是分享到朋友圈的連接
{
//獲取HTTP訪問頭中的User-Agent參數的值
string agent = System.Web.HttpContext.Current.Request.Headers["User-Agent"];
if (agent.Contains(NavFrom.MicroMessenger.ToString())) //判斷頁面是不是在微信內置瀏覽器中打開
fromType = NavFrom.MicroMessenger;
else
fromType = NavFrom.Other;
}
return fromType;
}
/// <summary>
/// 獲取不含統計相關參數的頁面地址
/// </summary>
/// <returns>不含統計相關參數的頁面地址</returns>
private string GetOrigenalUrl()
{
StringBuilder urlBuilder = new StringBuilder();
//獲取不含QueryString的URL
urlBuilder.Append("http://")
.Append(System.Web.HttpContext.Current.Request.Url.Host)
.Append(System.Web.HttpContext.Current.Request.Url.AbsolutePath)
.Append("?");
//構造移除統計相關參數的Query
foreach (var key in System.Web.HttpContext.Current.Request.QueryString.AllKeys)
{
if (key != "s" && key != "u" && key != "from" && key != "code" && key != "state")
{
urlBuilder.Append(key).Append("=").Append(System.Web.HttpContext.Current.Request.QueryString[key]).Append("&");
}
}
return urlBuilder.ToString();
}
/// <summary>
/// 獲取訪問者openId
/// </summary>
private string GetNavOpenId()
{
NameValueCollection parameters = System.Web.HttpContext.Current.Request.Params;
//獲取連接中的openId
string navOpenId = parameters["u"];
#region 若是是從微信瀏覽器瀏覽,獲取真實的微信OpenId
if (!string.IsNullOrEmpty(appID) && !string.IsNullOrEmpty(appSecret))
{
string accessSource = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_USER_AGENT"];
if (accessSource.Contains("MicroMessenger")) //若是是從微信打開頁面
{
string[] cookieKeys = new[] { CookieHelper.COOKIE_NAME };
Dictionary<string, string> realIdCookie = CookieHelper.GetLoginCookies(cookieKeys); //獲取保存在Cookie中的OpenId
//若是Cookie中不存在OpenId,或者連接中的openId與Cookie中的OpenId不一致,連接中的openId爲分享者的OpenId,須要獲取當前用戶的真實OpenId
if (NeedGetReadOpenId(parameters, realIdCookie))
{
if (parameters["code"] == null)
{
// 先去獲取code,並記錄分享者
string snsapi_baseUrl = GoCodeUrl(navOpenId);
if (!string.IsNullOrEmpty(snsapi_baseUrl))
{
CookieHelper.CleanLoginCookie(cookieKeys);
//跳轉到微信網頁受權頁面
System.Web.HttpContext.Current.Response.Redirect(snsapi_baseUrl, true);
System.Web.HttpContext.Current.Response.End();
return null;
}
}
else
{
m_Log.Info("code: " + parameters["code"].ToString());
OAuthAccessTokenResult tokenResult = GetRealOpenId(parameters["code"].ToString());
if (null != tokenResult && !string.IsNullOrEmpty(tokenResult.openid))
{
m_Log.Info("tokenResult.openid: " + tokenResult.openid);
navOpenId = tokenResult.openid;
// 獲取到的當前訪問者的OpenId保存到cookie裏
CookieHelper.CleanLoginCookie(cookieKeys);
realIdCookie[CookieHelper.COOKIE_NAME] = tokenResult.openid;
CookieHelper.WriteLoginCookies(realIdCookie, DateTime.MinValue);
}
}
}
}
}
#endregion
return navOpenId;
}
/// <summary>
/// 若是Cookie中存在OpenId且連接中的openId與Cookie中的OpenId一致
/// 則不須要調用網頁受權接口,連接中的openId即爲當前訪問者的真實OpenId
/// </summary>
/// <param name="parameters"></param>
/// <param name="realIdCookie"></param>
/// <returns></returns>
private bool NeedGetReadOpenId(NameValueCollection parameters, Dictionary<string, string> realIdCookie)
{
string referer = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_REFERER"];
string openId = null;
if (realIdCookie != null)
{
if (realIdCookie.ContainsKey(CookieHelper.COOKIE_NAME))
{
openId = realIdCookie[CookieHelper.COOKIE_NAME];
}
}
m_Log.Info("NeedGetReadOpenId openid: " + openId + " referer: " + referer + " u: " + parameters["u"].ToString());
if (!string.IsNullOrEmpty(referer) && openId == parameters["u"].ToString())
return false;
else
return true;
}
/// <summary>
/// 網頁受權接口第一步
/// 跳轉到獲取code的url
/// </summary>
/// <param name="shareOpenId">當訪問來源爲朋友圈時的分享者微信openid</param>
private string GoCodeUrl(string shareOpenId)
{
string url = System.Web.HttpContext.Current.Request.Url.AbsoluteUri + "&s=" + shareOpenId; //添加分享者OpenId
return OAuthApi.GetAuthorizeUrl(appID, url, "STATE", OAuthScope.snsapi_base);
}
/// <summary>
/// 網頁受權接口第二步
/// 解析code並獲取當前訪問者真正的openId
/// </summary>
/// <param name="parameters">url參數</param>
/// <returns>真正的openId</returns>
private OAuthAccessTokenResult GetRealOpenId(string code)
{
OAuthAccessTokenResult result = new OAuthAccessTokenResult();
try
{
result = OAuthApi.GetAccessToken(appID, appSecret, code);
}
catch (Exception ex)
{
m_Log.Error(ex.Message, ex);
}
return result;
}
}
保存訪問者記錄,識別訪問者和分享者。
在CustomMessageHandler裏處理文字請求,詳細的使用方法可參考《用c#開發微信(2)掃描二維碼,用戶受權後獲取用戶基本信息 (源碼下載)》:
public override IResponseMessageBase OnTextRequest(RequestMessageText requestMessage)
{
var responseMessage = CreateResponseMessage<ResponseMessageNews>();
responseMessage.Articles.Add(new Article()
{
Title = "首頁",
Description = "點擊進入首頁",
PicUrl = "",
Url = System.Configuration.ConfigurationManager.AppSettings["site"] + "/WeixinPageIndex.aspx?u=" + requestMessage.FromUserName
});
return responseMessage;
}
這裏把訪問者的OpenId帶上,爲了方便識別訪問者和分享者。
全部界面以下:
未完待續!!!