页面停留访问计时方案的设计与实现

要求:实现用户对网站网页的访问计时,数据越精确越好,扩展性越方便越好,对服务器的影响越小越好.

适用环境: 在线学习的学习时长的计时,在线考试等的精确计时.

设计思想:

    创建一个js文件,在前台页面只需要引入该js,就可对该页实现访问计时.

具体设计与实现:

1,创建表结构

Create Table PageLogInfo

{

    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Url] [nvarchar](500) NULL,
    [Query] [nvarchar](500) NULL,
    [Entertime] [datetime] NULL,
    [ExitTime] [datetime] NULL,
    [UserName] [varchar](100) NULL,

}


Id设为主键


2,完成js文件

var pageId = 0;

var create_http = function () {
    if (window.XMLHttpRequest)
        return (new XMLHttpRequest());
    var arr_t = new Array(
        "MSXML2.XMLHTTP.4.0 ",
        "MSXML2.XMLHTTP.3.0 ",
        "MSXML2.XMLHTTP.2.6 ",
        "Microsoft.XMLHTTP ",
        "MSXML.XMLHTTP "
    );
    for (var i = 0; i < arr_t.length; i++) {
        try {
            xmlhttp_ver = arr_t[i];
            return new ActiveXObject(arr_t[i]);
        } catch (e) { }
    }
    return (null);
};

window.onload = function () {
    var xmlhttp = create_http();
    if (xmlhttp == null) return;
    xmlhttp.open("Post", "/LogInfo.Log?action=Enter&url="+document.URL);
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            if (xmlhttp.responseText.length > 0)
                pageId = parseInt(xmlhttp.responseText);
            }
        };
    xmlhttp.send(null);

   window.onbeforeunload = function () {
        var xmlhttp = create_http();
        xmlhttp.open("Post", "/LogInfo.Log?action=Exit&Id=" + pageId);
        xmlhttp.send(null);
       
          
    };
};


3,增加httpHandler处理程序:

public class LogHandler:System.Web.IHttpHandler,System.Web.SessionState.IReadOnlySessionState
    {
        public bool IsReusable
        {
            get
            {
                return true;
            }
        }

       
        public void ProcessRequest(HttpContext context)
        {
            HttpRequest Request = context.Request;
            context.Response.Clear();
                
            switch (Request.QueryString["action"])
            {
               case "Enter":
               
                   string url = Request.QueryString["url"];
                   string[] strs = url.Split('?');
                   int id = LogInfo.Create(strs[0], strs.Length > 1 ? strs[1] : null, context.Session.SessionID);
                   context.Response.Write(id);
                   break;
                case "Exit":
                     int id = int.Parse(Request.QueryString["Id"]);
                     LogInfo.UpdatePageLog(id);
                     break;
                                                 
            }
           
            context.Response.End();

 }

4, 完成记录数据的类

public class LogInfo
{
private static LogDataTable logDt = new LogDataTable();
        
        /// <summary>
        /// 进入页面
        /// </summary>
        /// <returns></returns>
        public static int CreatePageLog(string url, string query,string SessionID)
        {
            string commandTxt = @"INSERT INTO PageLogInfo(
                Url, Query, EnterTime, ExitTime, UserName) VALUES(
                @Url,@Query,@EnterTime,@ExitTime,@UserName);SELECT SCOPE_IDENTITY()";
            int ret = int.Parse((数据库访问类).ExecuteScalar(CommandType.Text, commandTxt,
                new SqlParameter("@Url", url),
                new SqlParameter("@Query", query),
                new SqlParameter("@EnterTime", DateTime.Now),
                new SqlParameter("@ExitTime", null),
                new SqlParameter("@UserName", 
当前Session记录的用户名字)
                
               ).ToString());
            logDt.AddRow(HttpContext.Current.Session.SessionID, ret); //
            return ret;
        }

        /// <summary>
        /// 离开页面
        /// </summary>
        /// <param name="id"></param>
        public static void UpdatePageLog(int id)
        {
            string sql = "UPDATE PageLogInfo SET ExitTime=@ExitTime,UserName=@UserName WHERE Id=" + id;
            DBProvider.Instance.GetDBInstance().ExecuteNonQuery(CommandType.Text, sql,
                new SqlParameter("@ExitTime", DateTime.Now),
    new SqlParameter("@UserName",/*当前session记录的用户名*/)
);
            
            logDt.RemoveID(id);
        }

        /// <summary>
        /// 更新没有离开时间的记录
        /// </summary>
        /// <param name="sessionId"></param>
        public static void UpdateCurrentSession(string sessionId)
        {
            string ids = logDt.RemoveSession(sessionId);
            if (ids.Length > 0)
            {
                string sql = "UPDATE PageLogInfo SET ExitTime=@ExitTime,UserName=@UserName WHERE  ExitTime is null And Id in(" + "ids)";
                DBProvider.Instance.GetDBInstance().ExecuteNonQuery(CommandType.Text, sql,
                    new SqlParameter("@ExitTime", DateTime.Now),
                    new SqlParameter("@UserName", LogProvider.Instance.UserName),
                    new SqlParameter("@UserType", LogProvider.Instance.UserType),
                    new SqlParameter("@Name", LogProvider.Instance.Name)
                    );
                   // new SqlParameter("@ids", HttpContext.Current.Session.SessionID));
            }
        }
    }

    /// <summary>
    /// 用来记录Session与id的表
    /// </summary>
    internal class LogDataTable : DataTable
    {
        public LogDataTable()
        {
            this.Columns.Add("SessionID", typeof(string));
            this.Columns.Add("ID", typeof(int));
            
        }

        public void AddRow(string sessionID, int id)
        {
            DataRow row = this.NewRow();
            row["SessionID"] = sessionID;
            row["ID"] = id;
            lock (this)
            {
                this.Rows.Add(row);
            }
        }
        /// <summary>
        /// 如果页面有回发的结束时间,则将该页面的标记移除
        /// </summary>
        /// <param name="id"></param>
        public void RemoveID(int id)
        {
            lock (this)
            {
                DataRow[] dr = this.Select("ID=" + id.ToString());
                for (int i = 0; i < dr.Length; i++)
                    this.Rows.Remove(dr[i]);
            }
        }

        public string RemoveSession(string sessionID)
        {
            string ids = string.Empty;
            lock (this)
            {
                DataRow[] dr = this.Select("SessionID=" + sessionID);
                for (int i = dr.Length - 1; i >= 0; i--)
                {
                    ids += "," + dr[i]["ID"].ToString();
                    this.Rows.Remove(dr[i]);
                }
            }
            if (ids.Length > 0)
                ids = ids.Remove(0, 1);
            return ids;
        }
    }

5,增加Global.asax文件,在Session_End里增加

LogInfo.UpdateCurrentSession(Session.SessionID);

6,在Web.config中,增加对httpHandler的调用

 <httpHandlers>      <add verb="POST" type="......"  path="*.Log"/>    </httpHandlers>

好了,至此,对页面的计时方案完成。

对设计与实现的一些说明:

1,js通过post提交enter参数,获取服务器为页面分配的唯一标记符号。此处是数据库自增主键。 也可以采用其他方式,为页面分配唯一标识码。

2,.Log是我自定义的一种提交文件格式,为了采用httpHandler,需要将此文件后缀增加到iis的aspnet_isapi中。否则httpHandler不会响应。另外,增加该筛选时将验证文件存在的勾务必去掉,否则会报404错误。使用httpHandler的好处在于,几乎可以忽略js中提交的路径问题。

3,存在部分浏览器在某些情况下不能回发的情况,因此需要在session过期时,更新这些session下的页面结束时间。而由于Session只保证了在现存Session中的唯一性,不保证Session的重复性,所以数据库中不能使用SessionID为更新的识别码。

4,由于在一般意义上,前台时间不可信,所以,采用服务器计时。因为不管是在Enter还是Exit情况下,可以考虑稳定网络中传输数据所占用的时间一致,所以该回发的可信程度极高。

5,因为页面存在登陆等情况,所以在记录离开时间时,也更新Session记录的用户名。

扩展与大型应用:

1,用户访问时间可以更精确。

js文件中增加计时器,在onload时启动,页面失去焦点时停止,页面获得焦点时重新启动。在Exit时回发计时器记录的秒数。为了保证数据的可靠性,可以以此秒数与服务器计算的访问时间比较,如果小于服务器记录秒数,则将提交的数据记录,否则以服务器计时为准。

2,处理iframe的情况

文件中增加js判断该页面是否处于iframe中,如果是,则判断父页是否引用了该js文件,如果引用了,则不回发Enter。

3,如果需要计时的页面很多,服务端可以采用缓存技术,来提高数据响应速度,而不需要直接写库。

4,以后哪个页面需要计时了,直接引入js文件,ok了。

5,可以针对特定页面,可以跑一些任务,来实现计时统计。

一些额外说明:

网络计时特别是web计时,由于浏览器、网络或者人为等因素,不可能做到精准,所以只能尽量去做到准确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值