• 一步步开发自己的博客 番外篇(7、异步记录日志 和 文章阅读量统计)


    前言 

    离前一篇《一步步开发自己的博客  .NET版(6、手机端的兼容)》都个把月了。

    当时写完第六篇,很多人问“剧终”了?你还有好多实现没有讲解分析呢。我说没呢,后期还会有第二版、第三版...至于还没有分析到,后期补上。你看,我今天不就来了么。

    前段时间写代码,手都写的酸痛酸痛的,歇息了几天,好多了。然后,又捣鼓了一下webapi。这也是个没有接触过的知识。跌跌撞撞的整了点东西出来。有兴趣写移动端的同学可以联系我,大家一起学习。API文档和测试地址:http://haojima.net/SwaggerUI 

    其他的就不多说了,进入今天的主题,异步记录日志和文章阅读量统计。

         

    异步记录日志

    我们常用日志记录,无非就是,数据库记录和文本日志记录。而今天我要说的是,文本日志记录。

    最简单的文本记录: File.WriteAllText(path,messg); 使用静态类File的WriteAllText 如果文件存在则覆盖,传入文件路径和消息内容。ok,完事。

    当然,我们不能每次都覆盖上一次的记录。 File.AppendAllText(path,messg); 那么我们可是在原有内容追加。这里,我们不用关系文件流是否关闭,使用静态类File的这两个方法都会自动帮我们关闭。

    如果,我们是使用的winfrom单线程。那么,基本的日志记录就这个两个方法 完全可以搞定。

    但是,如果是web程序就不一样了,天生的多线程。多个线程同时访问一个文件,肯定是会报错的。不信你试试。

    那我们怎么解决这个问题?有人会说,加锁呗。锁肯定是要加,不过要看怎么加了。如果加到写文件内容的时候肯定是不合适的。因为写文件要打开文件流,比较耗时。我们可以先把要写的日志,统一存内存,然后单线程从内存取数据,写到文本。当然,写内存也可能会多线程并发,这个时候,我们就可以把锁加到写内存的地方。这里大家就不用担心了,写内存的速度是非常快的,和直接写文件那差的可不是一两个档次的问题了。

    我们刚才说存内存,怎么存?当然是存集合了。有个数据类型 Queue 为什么要用它。因为它是队列,有个特点:先进先出。我们取数据的时候就是去的最早存进去的数据了。

    使用:存数据 Queue myQ = new Queue(); myQ.Enqueue("The");//入队    取数据 var t = myQ.Dequeue(); 直接在取值的时候就把值在队列中移除了。这样正好免了我手动移除。

    那么,很简单。我们记日志的时候就先把日志往 Queue 里存,然后单独开个进程取值存值写文件里。ok,完事。

    刚才说了,我们要加锁。是的,要加锁。因为 Queue 并不是线程安全数据。我们在写数据和读数据的时候都要加锁。

    static object myLock= new object();
    ...
    lock (myLock)
         logQueue.Enqueue(logmede);//
    ...
    lock (myLock)
         var m = logQueue.Dequeue();//

    我之前在网上查资料说不能多线程同时写入队列,经测试其实是不能同时读和写队列。所以在Dequeue取的时候也要锁定同一个对象

    思路大体就是这样了。当然,我们还可以扩展很多的东西。如:定时删除指定过期日志、分文件大小存储日志、自动增加的日志文件命名...等等。

    写到这里,估计又有大把大把的人要来批判我了。又在造轮子。日志框架那么多,干嘛还要自己写。浪费时间.....等。

    没错,我确实是在造轮子。我不想解释太多了。累...   大神请略过....

    下面给出,我的具体实现代码。分为四个文件 LogMode 包含文件名、日志内容 LogHelper 存队列、写文件 LogConfig 读取相关配置 LogSave 外部直接调用

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    
    namespace CommonLib.HiLog
    {
        /// <summary>
        /// 日志模型
        /// </summary>
        internal class LogModel
        {      
            #region logFileName
            private string _logFileName;
    
            /// <summary>
            /// 日志文件名字
            /// </summary>
            public string logFileName
            {
                get { return _logFileName + "_" + DateTime.Now.ToString("yyyyMMdd"); }
                set { _logFileName = value; }
            }
            #endregion
    
            #region logMessg
            private string _logMessg;
    
            /// <summary>
            /// 日志内容
            /// </summary>
            public string logMessg
            {
                get
                {
                    return "----begin-------" + DateTime.Now.ToString() + "----Queue.Count:" + LogHelper.LogQueue.Count + "-----------------------------------
    
    "
                        + _logMessg
                        + "
    
    ----end----------" + DateTime.Now.ToString() + "----Queue.Count:" + LogHelper.LogQueue.Count + "-----------------------------------"
                        + "
    
    
    ";
                }
                set { _logMessg = value; }
            }
            #endregion
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace CommonLib.HiLog
    {
        /// <summary>
        /// 日志操作辅助类
        /// zhaopeiym@163.com
        /// 创建20150104 修改20151003
        /// </summary>
        internal class LogHelper
        {
            /// <summary>
            /// 消息队列
            /// </summary>
            private static Queue<LogModel> logQueue = new Queue<LogModel>();
            /// <summary>
            /// 消息队列 对外只读
            /// </summary>
            public static Queue<LogModel> LogQueue
            {
                get { return LogHelper.logQueue; }
            }
    
            /// <summary>
            /// 标志锁
            /// </summary>
            static string myLock = "true";
    
            /// <summary>
            /// 写入日志文件(异步单线程 记录日志)
            /// </summary>
            /// <param name="logmede"></param>
            public static void logWrite(LogModel logmede)
            {
                // 这里需要锁上 不然会出现:源数组长度不足。请检查 srcIndex 和长度以及数组的下限。异常   
                //网上有资料说 http://blog.csdn.net/greatbody/article/details/26135057  不能多线程同时写入队列
                //其实  不仅仅 不能同时写入队列 也不能同时读和写如队列  所以  在Dequeue 取的时候也要锁定一个对象
                lock (myLock)
                    logQueue.Enqueue(logmede);
                logStartWrite();
            }
    
            /// <summary>
            /// 文件编码格式
            /// </summary>
            public static Encoding encoding = Encoding.Default;
    
            /// <summary>
            /// 是否开始自动记录日志
            /// </summary>
            private static bool isStart = false;
    
            /// <summary>
            /// 用来 标识 最好一次 检测是否 需要 清理 日志文件 时间
            /// </summary>
            private static DateTime time = DateTime.MinValue;
            /// <summary>
            /// 每个日志文件夹 对应的文件下标
            /// </summary>
            private static Dictionary<string, int> logFileNum = new Dictionary<string, int>();
            /// <summary>
            /// 开始把队列消息写入文件
            /// </summary>
            private static void logStartWrite()
            {
                if (isStart)
                    return;
                isStart = true;
                Task.Run(() =>
                {
                    while (true)
                    {
                        if (LogHelper.logQueue.Count >= 1)
                        {
                            LogModel m = null;
                            lock (myLock)
                                m = LogHelper.logQueue.Dequeue();
                            if (m == null)
                                continue;
    
                            if (string.IsNullOrEmpty(LogConfig.logFilePath))
                                throw new Exception("请先初始化日志保存路径LogModel._logFilePath");
    
                            TestingInvalid();
    
                            if (!Directory.Exists(LogConfig.logFilePath + m.logFileName + @""))
                                Directory.CreateDirectory(LogConfig.logFilePath + m.logFileName + @"");
    
                            // int i = m.logFileNum;
                            if (!logFileNum.Keys.Contains(m.logFileName))
                                logFileNum.Add(m.logFileName, 0);
                            //部分 日志 文件路径
                            string SectionfileFullName = LogConfig.logFilePath + m.logFileName + @"" + m.logFileName + "_" + logFileNum[m.logFileName].ToString("000") + ".txt";
                            //最新的写了内容的 部分 日志文件路径
                            string TopSectionfileFullName = SectionfileFullName;
                            // 需要实时更新的 最新日志文件 路径
                            string LogfileFullNqme = LogConfig.logFilePath + m.logFileName + @"" + m.logFileName + ".txt";
    
                            FileInfo file = new FileInfo(SectionfileFullName);
                            while (file.Exists && file.Length >= LogConfig.SectionlogFileSize)
                            {
                                TopSectionfileFullName = SectionfileFullName;
                                logFileNum[m.logFileName]++;
                                SectionfileFullName = LogConfig.logFilePath + m.logFileName + @"" + m.logFileName + "_" + logFileNum[m.logFileName].ToString("000") + ".txt";
                                file = new FileInfo(SectionfileFullName);
                            }
    
                            try
                            {
                                if (!file.Exists)//如果不存在 这个文件 就说明需要 创建新的部分日志文件了
                                {
                                    //因为SectionfileFullName路径的文件不存在    所以创建
                                    File.WriteAllText(SectionfileFullName, m.logMessg, encoding);
    
                                    FileInfo Logfile = new FileInfo(LogfileFullNqme);
                                    if (Logfile.Exists && Logfile.Length >= LogConfig.FileSize)
                                        //先清空  然后加上 上一个部分文件的内容
                                        File.WriteAllText(LogfileFullNqme, File.ReadAllText(TopSectionfileFullName, encoding), encoding);//如果存在则覆盖                           
                                }
                                else
                                    File.AppendAllText(SectionfileFullName, m.logMessg, encoding);//累加
    
                                //追加这次内容 到动态更新的日志文件
                                File.AppendAllText(LogfileFullNqme, m.logMessg, encoding);
                            }
                            catch (Exception ex)
                            {
                                throw ex;
                            }
    
                        }
                        else
                        {
                            isStart = false;//标记下次可执行
                            break;//跳出循环
                        }
                    }
                });
            }
    
            /// <summary>
            /// 检测 并删除 之前之外的 日志文件
            /// </summary>
            public static void TestingInvalid()
            {
                #region 检测 并删除 之前之外的 日志文件
                if (time.AddMinutes(LogConfig.TestingInterval) <= DateTime.Now)// 时间内 检测一次
                {
                    try
                    {
                        time = DateTime.Now;
                        List<string> keyNames = new List<string>();
                        foreach (var logFileName in logFileNum.Keys)
                        {
                            CreatePath(LogConfig.logFilePath + logFileName + @"");
                            DirectoryInfo dir = new DirectoryInfo(LogConfig.logFilePath + logFileName + @"");
                            if (dir.CreationTime.AddMinutes(LogConfig.DelInterval) <= DateTime.Now)//删除 设定时间 之前的日志
                                foreach (var fileInfo in dir.GetFiles())
                                {
                                    if (fileInfo.LastWriteTime.AddMinutes(LogConfig.DelInterval) <= DateTime.Now)//最后修改时间算起
                                        File.Delete(fileInfo.FullName);
    
                                }
                            if (dir.GetFiles().Length == 0)
                                keyNames.Add(logFileName);//临时存储没有日志文件的文件夹
                        }
                        foreach (var key in keyNames)//删除没有日志文件的文件夹
                        {
                            logFileNum.Remove(key);
                            Directory.Delete(LogConfig.logFilePath + key + @"", false);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogSave.ErrLogSave("手动捕获[检测并删除日志出错!]", ex, "记录日志出错");
                    }                
                }
                #endregion
            }
    
            #region  创建路径
            /// <summary>
            /// 创建路径
            /// </summary>
            /// <param name="paht"></param>
            /// <returns></returns>
            public static bool CreatePath(string paht)
            {
                if (!Directory.Exists(paht))
                {
                    Directory.CreateDirectory(paht);
                    return true;
                }
                return false;
            }
            #endregion
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    
    namespace CommonLib.HiLog
    {
        /// <summary>
        /// 日志相关配置    
        /// </summary>
        public static class LogConfig
        {
            #region 辅助方法
            /// <summary>
            /// GetAppSettings
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public static string GetAppSettings(string key)
            {
                if (ConfigurationManager.AppSettings.AllKeys.Contains(key))
                    return ConfigurationManager.AppSettings[key].ToString();
                return string.Empty;
            }
    
            /// <summary>
            /// 计算字符串 转 计算结果
            /// </summary>
            /// <param name="v"></param>
            /// <returns></returns>
            public static string toCompute(this string v)
            {
                return new DataTable().Compute(v, "").ToString();
            }
            #endregion
    
            #region 静态属性和字段
    
            #region logFilePath 路径
            /// <summary>
            /// 日志要存的路径 默认路径:网站根目录 + Log 文件夹
            /// 在程序第一次启动是设置
            /// </summary>       
            private static string _logFilePath;
    
            /// <summary>
            /// 日志要存的路径 默认路径:网站根目录 + Log 文件夹
            /// 在程序第一次启动是设置
            /// </summary>   
            public static string logFilePath
            {
                get
                {
                    if (string.IsNullOrEmpty(_logFilePath))
                    {
                        try
                        {
                            _logFilePath = HttpContext.Current.Server.MapPath("~/");
                        }
                        catch (Exception)
                        {
                            try
                            {
                                _logFilePath = System.Windows.Forms.Application.StartupPath + @"";
                            }
                            catch (Exception)
                            {
                                throw new Exception("请先初始化要保存的路径:LogModel._logFilePath");
                            }
                        }
                    }
                    return _logFilePath;
                }
    
                set
                {
                    _logFilePath = value;
                }
            }
    
            #endregion
    
            #region 检测间隔时间(分钟)
            private static int _TestingInterval;
            /// <summary>
            /// 检测间隔时间(分钟) 默认:一天
            /// 配置:appSettings->Log_TestingInterval 单位:秒
            /// </summary>
            public static int TestingInterval
            {
                get
                {
                    if (_TestingInterval <= 0)
                    {
                        var Log_TestingInterval = GetAppSettings("Log_TestingInterval");
                        if (string.IsNullOrEmpty(Log_TestingInterval))
                            _TestingInterval = 1 * 60 * 24;
                        else
                            _TestingInterval = Convert.ToInt32(Log_TestingInterval.toCompute());
                    }
                    return _TestingInterval;
                }
            }
            #endregion
    
            #region 删除 N分钟(最后修改时间)之前的的日志
            private static int _DelInterval;
            /// <summary>
            /// 删除 N分钟(最后修改时间)之前的的日志 默认:15天
            /// 配置:appSettings->Log_DelInterval 单位:秒
            /// </summary>
            public static int DelInterval
            {
                get
                {
                    if (_DelInterval <= 0)
                    {
                        var Log_DelInterval = GetAppSettings("Log_DelInterval");
                        if (string.IsNullOrEmpty(Log_DelInterval))
                            _DelInterval = 1 * 60 * 24 * 15;
                        else
                            _DelInterval = Convert.ToInt32(Log_DelInterval.toCompute());
                    }
                    return _DelInterval;
                }
            }
            #endregion
    
            #region 部分日志文件大小(Byte)
            private static int _SectionlogFileSize;
            /// <summary>
            /// 部分日志文件大小(Byte) 默认:1024Byte * 1024 * 1 = 1MB
            /// 配置:appSettings->Log_SectionlogFileSize 单位:Byte
            /// </summary>
            public static int SectionlogFileSize
            {
                get
                {
                    if (_SectionlogFileSize <= 0)
                    {
                        var Log_SectionlogFileSize = GetAppSettings("Log_SectionlogFileSize");
                        if (string.IsNullOrEmpty(Log_SectionlogFileSize))
                            _SectionlogFileSize = 1024 * 1024 * 1;
                        else
                            _SectionlogFileSize = Convert.ToInt32(Log_SectionlogFileSize.toCompute());
                    }
                    return _SectionlogFileSize;
                }
            }
            #endregion
    
            #region 变动文件大小(Byte)
            private static int _FileSize;
            /// <summary>
            /// 变动文件大小(Byte) 默认:1024 * 1024 * 4 = 4M
            /// 配置:appSettings->Log_FileSize 单位:Byte
            /// </summary>
            public static int FileSize
            {
                get
                {
                    if (_FileSize <= 0)
                    {
                        var Log_FileSize = GetAppSettings("Log_FileSize");
                        if (string.IsNullOrEmpty(Log_FileSize))
                            _FileSize = 1024 * 1024 * 4;
                        else
                            _FileSize = Convert.ToInt32(Log_FileSize.toCompute());
                    }
                    return _FileSize;
                }
            }
            #endregion
    
            #endregion
        }
    }
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    
    namespace CommonLib.HiLog
    {
        /// <summary>
        /// 异步单线程
        /// </summary>
        public class LogSave
        {
            /// <summary>
            /// 获得Exception 的详细信息
            /// </summary>
            /// <param name="ex"></param>
            /// <returns></returns>
            public static string GetExceptionInfo(Exception ex)
            {
                StringBuilder str = new StringBuilder();
                str.Append("错误信息:" + ex.Message);
                str.Append("
    错误源:" + ex.Source);
                str.Append("
    异常方法:" + ex.TargetSite);
                str.Append("
    堆栈信息:" + ex.StackTrace);
                return str.ToString();
            }
    
            /// <summary>
            /// 系统 自动 捕捉异常
            /// 保存异常详细信息 
            /// 包括: 浏览器  浏览器版本 操作系统 页面  Exception
            /// </summary>
            /// <param name="ex"></param>
            /// <param name="fileName">文件名 默认:SysErr</param>
            public static void SysErrLogSave(Exception ex, string fileName = null)
            {
                StringBuilder str = new StringBuilder();
                string ip = "";
                if (HttpContext.Current.Request.ServerVariables.Get("HTTP_X_FORWARDED_FOR") != null)
                    ip = HttpContext.Current.Request.ServerVariables.Get("HTTP_X_FORWARDED_FOR").ToString().Trim();
                else
                    ip = HttpContext.Current.Request.ServerVariables.Get("Remote_Addr").ToString().Trim();
                str.Append("Ip:" + ip);
                str.Append("
    浏览器:" + HttpContext.Current.Request.Browser.Browser.ToString());
                str.Append("
    浏览器版本:" + HttpContext.Current.Request.Browser.MajorVersion.ToString());
                str.Append("
    操作系统:" + HttpContext.Current.Request.Browser.Platform.ToString());
                str.Append("
    页面:" + HttpContext.Current.Request.Url.ToString());
                str.Append("
    " + GetExceptionInfo(ex));
                LogHelper.logWrite(new LogModel()
                {
                    logFileName = "SysErr" + fileName ?? string.Empty,
                    logMessg = str.ToString()
                });
            }
    
            /// <summary>
            /// 异常日志记录
            /// </summary>
            /// <param name="strmes"></param>
            /// <param name="ex"></param>
            public static void ErrLogSave(string strmes, Exception ex, string fileName = null)
            {
                StringBuilder str = new StringBuilder();
                str.Append(strmes);
                if (ex != null)
                    str.Append("
    " + GetExceptionInfo(ex));
                LogHelper.logWrite(new LogModel()
                {
                    logFileName = fileName ?? "Err",
                    logMessg = str.ToString()
                });
            }
    
            /// <summary>
            /// 警告日志记录
            /// </summary>
            /// <param name="str"></param>
            public static void WarnLogSave(string str, string fileName = null)
            {
                if (str != null && !string.IsNullOrEmpty(str.Trim()))
                    LogHelper.logWrite(new LogModel()
                    {
                        logFileName = fileName ?? "Warn",
                        logMessg = str
                    });
            }
    
            /// <summary>
            /// 追踪日志记录
            /// </summary>
            /// <param name="str"></param>
            public static void TrackLogSave(string str, string fileName = null)
            {
                if (str != null && !string.IsNullOrEmpty(str.Trim()))
                    LogHelper.logWrite(new LogModel()
                    {
                        logFileName = fileName ?? "Track",
                        logMessg = str
                    });
            }
    
            /// <summary>
            /// 追踪日志记录
            /// </summary>
            /// <param name="str"></param>
            public static void TrackLogSave(string str)
            {
                if (!string.IsNullOrEmpty(str.Trim()))
                    LogHelper.logWrite(new LogModel()
                    {
                        logFileName = "SqlTrack",
                        logMessg = str
                    });
            }
        }
    }
    View Code

    写好之后,下次我在别的项目里面就直接引用。

    如果你使用的是EF,那么我再告诉你一个小秘密。 DbContext 中的 Database.Log 可以直接记录所有EF执行的sql语句和参数。

    使用如: dbContext.Database.Log = LogSave.TrackLogSave;  而LogSave.TrackLogSave我们在上面已经封装过。

    效果图1  效果图2 

    文章阅读量统计

    我在一开始就琢磨着怎么统计阅读量。之前也在http://www.cnblogs.com/zhaopei/p/4744846.html的最后提出了这个疑问。

    遗憾的是,并没有谁告诉我更好的解决方案。

    好吧,靠人不如靠己。还是自己瞎折腾吧。

    但是,实现方式还是使用的我自己的提出的“如果实在是找不到好的解决方案,我打算用 IP+系统版本+浏览器版本号+.... 作为“联合主键”,如果“主键”24小时内重复两次以上,则不统计,如果cookie存在也不统计。

    1、我们在每次浏览器访问的时候都种下cookie,并设置过期时间为24小时。下次,浏览器访问的时候。我们检测如果存在我们种下的cookie。则直接忽略。

    2、如果没有带上我们的cookie。我们就先组合“联合主键”。然后检测24小时内的记录有没有这个“联合主键”。如果有,则忽略,否则在原有阅读量的基础上加一,然后存入“联合主键”。

    这里的"联合主键"有个小技巧。大家肯定都发现了,这个主键有点长。存数据库有点浪费空间(我数据库本来就只有50M),然后查询检索应该也会慢些吧(并不清楚)。我们想想,其实我们要的不是这么长一串东东。其实,我们只要得到这串东西代表的唯一性就可以了。那么我们可以用到md5,咱不管你是1G、2G还是高清或是无码。统统给你返回一定长度字符串(我取的是16位小写)。

    随着数据的增加,这个统计阅读量的表数据,肯定是所有表中最大的。然而,我们统计阅读量是在,点击访问文章的时候,然后在统计阅读量这个环节卡太久,给人的感觉就是这个页面访问太慢,体验不好。

    然而,我们每次统计都需要检测数据库里面是否存在,且数据量还不小。那我们只有再开个进程来做统计。

    具体实现代码:

    #region 判断是否阅读过 如果没有 这在BlogReadInfo 插入一条标识信息
    private bool IsRead(Blogs.ModelDB.Blogs blogobj, string md5)
    {
        if (blogobj.BlogReadInfo.Where(t => t.MD5 == md5 && t.LastTime.AddHours(24) > DateTime.Now).Count() > 0)
            return true;
        else
        {
            //BLL.
            blogobj.BlogReadInfo.Add(new Blogs.ModelDB.BlogReadInfo()
            {
                MD5 = md5,
                IsDel = false,
                BlogsId = blogobj.Id,
                CreateTime = DateTime.Now,
                UpTime = DateTime.Now,
                LastTime = DateTime.Now
            });
            return false;
        }
    }
    #endregion
    #region 统计阅读量 异步调用方法
    delegate void SaveReadDelegate(ModelDB.Blogs blogobj, string md5);
    private void SaveReadNum(ModelDB.Blogs blogobj, string md5)
    {
        LogSave.TrackLogSave(GetUserDistinguish(Request, false), "ReadBlogLog");
        var isup = true;
        BLL.BlogsBLL blogbll = new BLL.BlogsBLL();
        var blogtemp = blogbll.GetList(t => t.Id == blogobj.Id, isAsNoTracking: false).FirstOrDefault();
        if (blogtemp.BlogReadNum == null)
            blogtemp.BlogReadNum = 1;
        else if (!IsRead(blogtemp, md5))
            blogtemp.BlogReadNum++;
        else
            isup = false;
        if (isup)
            BLL.BlogCommentSetBLL.StaticSave();
    }
    #region 获取客户端标识(伪)
    /// <summary>
    ///  获取客户端标识 用来判断是否已经阅读过此文章
    /// </summary>
    /// <param name="requestt"></param>
    /// <param name="IsMD5">是否已经md5加密</param>
    /// <returns></returns>
    private string GetUserDistinguish(HttpRequestBase requestt, bool IsMD5 = true)
    {
        //request
        StringBuilder str = new StringBuilder();
        string ip = "";
        if (requestt.ServerVariables.AllKeys.Contains("HTTP_X_FORWARDED_FOR") && requestt.ServerVariables.Get("HTTP_X_FORWARDED_FOR") != null)
            ip = requestt.ServerVariables.Get("HTTP_X_FORWARDED_FOR").ToString().Trim();
        else
            ip = requestt.ServerVariables.Get("Remote_Addr").ToString().Trim();
        str.Append("Ip:" + ip);
        str.Append("
    浏览器:" + requestt.Browser.Browser.ToString());
        str.Append("
    浏览器版本:" + requestt.Browser.MajorVersion.ToString());
        str.Append("
    操作系统:" + requestt.Browser.Platform.ToString());
        str.Append("
    页面:" + requestt.Url.ToString());
        //str.Append("客户端IP:" + requestt.UserHostAddress);
        str.Append("
    用户信息:" + User);
        str.Append("
    浏览器标识:" + requestt.Browser.Id);
        str.Append("
    浏览器版本号:" + requestt.Browser.Version);
        str.Append("
    浏览器是不是测试版本:" + requestt.Browser.Beta);
        //str.Append("<br/>浏览器的分辨率(像素):" + Request["width"].ToString() + "*" + Request["height"].ToString());//1280/1024                        
        str.Append("
    是不是win16系统:" + requestt.Browser.Win16);
        str.Append("
    是不是win32系统:" + requestt.Browser.Win32);
        if (IsMD5)
            return str.ToString().GetMd5_16();
        else
            return str.ToString();
    }
    #endregion

    (当然,这个方式统计也不一定准。请求头信息改改就被伪造了。)

    然后我们通过委托从线程池抓去线程异步调用

    //........................异步调用....................
    new SaveReadDelegate(SaveReadNum).BeginInvoke(blogobj, GetUserDistinguish(Request), null, null);

    ok,统计完事。 

    如果您对本篇文章感兴趣,那就麻烦您点个赞,您的鼓励将是我的动力。      

    当然您还可以加入QQ群:469075305讨论。

    如果您有更好的处理方式,希望不要吝啬赐教。

    一步步开发自己的博客 .NET版系列:http://www.cnblogs.com/zhaopei/tag/Hi-Blogs/

    本文链接:http://www.cnblogs.com/zhaopei/p/4887573.html 

    开源地址:http://git.oschina.net/zhaopeiym/Hi-Blogs

  • 相关阅读:
    DBCC Page查看表和索引数据
    sp_configure
    学习
    OpenRowSet, OpenDataSource
    sp output
    SQL Server中使用CLR调用.NET方法
    SQL Server 2005五个有用的动态管理对象
    SQL显示执行语句信息
    接规则,每三个一组编一个号
    C# winform 与 flash as 的交互通讯
  • 原文地址:https://www.cnblogs.com/zhaopei/p/Hi-Blog_LogRecord_and_ReadingQuantityStatistics.html
Copyright © 2020-2023  润新知