用c#开发微信 (13) 微统计 - 阅读分享统计系统 3 UI设计及后台处理
            时间:2015-06-05 10:06:36  
            收藏:0  
            阅读:333
        
        
        
微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读、分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问、好友分享消息访问等。本系统实现了手机网页阅读、分享与来源统计及手机网页在朋友圈的传播路径分析。
本系统使用最传统的三层架构。本文是微统计的第三篇,主要介绍如下内容:
1. 为页面HighCharts画图控件提供数据
2. 接收分享记录信息并保存到数据库
3. 访问记录统计图
4. 阅读统计界面
5. 处理文字请求
前端开发框架使用Bootstrap,没有注明前台的页面表示前台不用显示任何内容
1. 为页面HighCharts画图控件提供数据 Data.aspx
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;
    }
}
2. 接收分享记录信息并保存到数据库 Share.aspx
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();
    }
}
当发送朋友或朋友圈时保存分享数据。
3. 访问记录统计图 StatisticsPage.aspx
前台:
<%@ 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();
}
通过业务逻辑层获取数据。
4. 阅读统计界面 WeixinPageIndex.aspx
前台:
<%@ 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;
    }
}
保存访问者记录,识别访问者和分享者。
5. 处理文字请求
在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带上,为了方便识别访问者和分享者。
所有界面如下:
未完待续!!!
用c#开发微信 系列汇总
            评论(0)
        
        
        
        