用c#开发微信 (13) 微统计 - 阅读分享统计系统 3 UI设计及后台处理

时间:2023-03-08 23:37:11
用c#开发微信 (13) 微统计 - 阅读分享统计系统 3 UI设计及后台处理

 

微信平台自带的统计功能太简单,有时我们需要统计有哪些微信个人用户阅读、分享了微信公众号的手机网页,以及微信个人用户访问手机网页的来源:朋友圈分享访问、好友分享消息访问等。本系统实现了手机网页阅读、分享与来源统计及手机网页在朋友圈的传播路径分析。

本系统使用最传统的三层架构。本文是微统计的第三篇,主要介绍如下内容:

 

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#开发微信 (13) 微统计 - 阅读分享统计系统 3 UI设计及后台处理

未完待续!!!

 

 

用c#开发微信 系列汇总