在线用户统计与命令模式

>>获取该文章的源码

  我阅读过几个论坛的在线用户统计代码,发现其中有两个问题,一个是需要借助数据库,另外一个是“锁”的粒度比较强!在线用户统计并不要求十分的精确(在这篇文章里,我不会讨论如何侦测到浏览器的关闭动作,而是讨论如何提高代码性能),那么借助数据库来完成这样的功能就显得很夸张!更重要的是对数据库进行读写操作(I/O操作),是要消耗性能的,而且还要在数据表里产生一条记录。为了一个不精确的功能需求消耗如此多的资源,的确不划算!另外一个办法是直接使用DataSet和ASP.NET缓存的方式来做统计,类似这样的代码我看过几个,自己也写过一个。但这样做也存在很大问题,最严重的地方还是“锁”的问题。在更新DataSet时需要通过lock关键字来将其锁定,但如果用户数量很大时,DataSet被加锁的次数过于频繁,所造成的坏结果千奇百怪。所以我不得不寻找一种更为有效的方法。

  在进行讨论之前,有必要先做一个介绍。在线用户信息应该包括:

SessionID  用户名称 最后活动时间 最后请求地址(Url地址)

还可以包括IP地址或其他更详细的信息。这是在线用户信息的数据结构,在线用户统计的算法是:
1. 将在线用户信息插入到集合,如果集合中已经存在相同用户名称的数据项,则更新该数据项;
2. 根据最后活动时间倒排序;
排序步骤虽然可以在列表显示在线用户信息的时候再做,但是那样会花费一点时间,不如在插入数据项以后马上排序,需要显示的时候直接显示。OnlineUser数据结构代码如下:


  1 None.gifusing System;
  2None.gifusing System.Collections.Generic;
  3None.gif
  4None.gifnamespace Net.AfritXia.Web.OnlineStat
  5ExpandedBlockStart.gifContractedBlock.gifdot.gif{
  6ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
  7InBlock.gif    /// 在线用户类
  8ExpandedSubBlockEnd.gif    /// </summary>

  9InBlock.gif    public class OnlineUser
 10ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 11InBlock.gif        // 用户 ID
 12InBlock.gif        private int m_uniqueID;
 13InBlock.gif        // 名称
 14InBlock.gif        private string m_userName;
 15InBlock.gif        // 最后活动时间
 16InBlock.gif        private DateTime m_lastActiveTime;
 17InBlock.gif        // 最后请求地址
 18InBlock.gif        private string m_lastRequestURL;
 19InBlock.gif        // SessionID
 20InBlock.gif        private string m_sessionID;
 21InBlock.gif        // IP 地址
 22InBlock.gif        private string m_clientIP;
 23InBlock.gif
 24ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
 25ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 26InBlock.gif        /// 类默认构造器
 27ExpandedSubBlockEnd.gif        /// </summary>

 28InBlock.gif        public OnlineUser()
 29ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 30ExpandedSubBlockEnd.gif        }

 31InBlock.gif
 32ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 33InBlock.gif        /// 类参数构造器
 34InBlock.gif        /// </summary>
 35InBlock.gif        /// <param name="uniqueID">用户 ID</param>
 36ExpandedSubBlockEnd.gif        /// <param name="userName">用户名称</param>

 37InBlock.gif        public OnlineUser(int uniqueID, string userName)
 38ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 39InBlock.gif            this.UniqueID = uniqueID;
 40InBlock.gif            this.UserName = userName;
 41ExpandedSubBlockEnd.gif        }

 42ExpandedSubBlockEnd.gif        #endregion

 43InBlock.gif
 44ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 45InBlock.gif        /// 设置或获取用户 ID
 46ExpandedSubBlockEnd.gif        /// </summary>

 47InBlock.gif        public int UniqueID
 48ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 49InBlock.gif            set
 50ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 51InBlock.gif                this.m_uniqueID = value;
 52ExpandedSubBlockEnd.gif            }

 53InBlock.gif
 54InBlock.gif            get
 55ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 56InBlock.gif                return this.m_uniqueID;
 57ExpandedSubBlockEnd.gif            }

 58ExpandedSubBlockEnd.gif        }

 59InBlock.gif
 60ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 61InBlock.gif        /// 设置或获取用户昵称
 62ExpandedSubBlockEnd.gif        /// </summary>

 63InBlock.gif        public string UserName
 64ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 65InBlock.gif            set
 66ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 67InBlock.gif                this.m_userName = value;
 68ExpandedSubBlockEnd.gif            }

 69InBlock.gif
 70InBlock.gif            get
 71ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 72InBlock.gif                return this.m_userName;
 73ExpandedSubBlockEnd.gif            }

 74ExpandedSubBlockEnd.gif        }

 75InBlock.gif
 76ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 77InBlock.gif        /// 最后活动时间
 78ExpandedSubBlockEnd.gif        /// </summary>

 79InBlock.gif        public DateTime ActiveTime
 80ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 81InBlock.gif            set
 82ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 83InBlock.gif                this.m_lastActiveTime = value;
 84ExpandedSubBlockEnd.gif            }

 85InBlock.gif
 86InBlock.gif            get
 87ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 88InBlock.gif                return this.m_lastActiveTime;
 89ExpandedSubBlockEnd.gif            }

 90ExpandedSubBlockEnd.gif        }

 91InBlock.gif
 92ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 93InBlock.gif        /// 最后请求地址
 94ExpandedSubBlockEnd.gif        /// </summary>

 95InBlock.gif        public string RequestURL
 96ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 97InBlock.gif            set
 98ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 99InBlock.gif                this.m_lastRequestURL = value;
100ExpandedSubBlockEnd.gif            }

101InBlock.gif
102InBlock.gif            get
103ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
104InBlock.gif                return this.m_lastRequestURL;
105ExpandedSubBlockEnd.gif            }

106ExpandedSubBlockEnd.gif        }

107InBlock.gif
108ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
109InBlock.gif        /// 设置或获取 SessionID
110ExpandedSubBlockEnd.gif        /// </summary>

111InBlock.gif        public string SessionID
112ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
113InBlock.gif            set
114ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
115InBlock.gif                this.m_sessionID = value;
116ExpandedSubBlockEnd.gif            }

117InBlock.gif
118InBlock.gif            get
119ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
120InBlock.gif                return this.m_sessionID;
121ExpandedSubBlockEnd.gif            }

122ExpandedSubBlockEnd.gif        }

123InBlock.gif
124ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
125InBlock.gif        /// 设置或获取 IP 地址
126ExpandedSubBlockEnd.gif        /// </summary>

127InBlock.gif        public string ClientIP
128ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
129InBlock.gif            set
130ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
131InBlock.gif                this.m_clientIP = value;
132ExpandedSubBlockEnd.gif            }

133InBlock.gif
134InBlock.gif            get
135ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
136InBlock.gif                return this.m_clientIP;
137ExpandedSubBlockEnd.gif            }

138ExpandedSubBlockEnd.gif        }

139ExpandedSubBlockEnd.gif    }

140ExpandedBlockEnd.gif}

   对于在线用户列表数据集,我们只用一个List<OnlineUser>对象来表示就可以了,不过我现在是把它封装在OnlineUserRecorder类里。代码如下:

 1 None.gifusing System;
 2None.gifusing System.Collections.Generic;
 3None.gifusing System.Threading;
 4None.gif
 5None.gifnamespace Net.AfritXia.Web.OnlineStat
 6ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 7ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
 8InBlock.gif    /// 在线用户记录器
 9ExpandedSubBlockEnd.gif    /// </summary>

10InBlock.gif    public class OnlineUserRecorder
11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
12InBlock.gif        // 在线用户列表
13InBlock.gif        private List<OnlineUser> m_onlineUserList = new List<OnlineUser>();
14InBlock.gif
15ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
16InBlock.gif        /// 保存在线用户
17InBlock.gif        /// </summary>
18ExpandedSubBlockEnd.gif        /// <param name="onlineUser">在线用户信息</param>

19InBlock.gif        public void Persist(OnlineUser onlineUser)
20ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
21InBlock.gif            if (onlineUser == null)
22InBlock.gif                return;
23InBlock.gif
24InBlock.gif            lock (typeof(OnlineUserRecorder))
25ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
26InBlock.gif                // 添加在线用户到集合
27InBlock.gif                this.m_onlineUserList.Add(onlineUser);
28InBlock.gif     // 按最后活动时间排序
29InBlock.gif                this.m_onlineUserList.Sort(CompareByActiveTime);
30ExpandedSubBlockEnd.gif            }

31ExpandedSubBlockEnd.gif        }

32InBlock.gif
33ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
34InBlock.gif        /// 比较两个用户的活动时间
35InBlock.gif        /// </summary>
36InBlock.gif        /// <param name="x"></param>
37InBlock.gif        /// <param name="y"></param>
38ExpandedSubBlockEnd.gif        /// <returns></returns>

39InBlock.gif        private static int CompareByActiveTime(OnlineUser x, OnlineUser y)
40ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
41InBlock.gif            if (x == null)
42InBlock.gif                throw new NullReferenceException("X 值为空 ( X Is Null )");
43InBlock.gif
44InBlock.gif            if (y == null)
45InBlock.gif                throw new NullReferenceException("Y 值为空 ( Y Is Null )");
46InBlock.gif
47InBlock.gif            if (x.LastActiveTime > y.LastActiveTime)
48InBlock.gif                return -1;
49InBlock.gif
50InBlock.gif            if (x.LastActiveTime < y.LastActiveTime)
51InBlock.gif                return +1;
52InBlock.gif
53InBlock.gif            return 0;
54ExpandedSubBlockEnd.gif        }

55ExpandedSubBlockEnd.gif    }

56ExpandedBlockEnd.gif}

   先不要管OnlineUserReader的代码是否正确,这还远远不是最终的代码。注意排序使用的是简化版的策略模式,这个并不主要。关键问题是在于lock代码段!代码执行到lock关键字时,需要判断锁定状态,如果正处于锁定状态,那么就会在此等待,直到被锁定的资源释放掉。想象一下,如果有两个用户先后请求一个页面,这个页面要需要执行这部分代码,那么就会有一个用户的请求先被处理,而另外一个用户只能原地等待。而恰好此时又来第三个用户,他也请求了这个页面,那么他也得等一会儿……第四个用户来了、第五个用户也来了……这样,用户就排起了长队。呵呵,等到第一个用户释放了被锁定的资源以后会发生什么情况呢?第二、三、四、五这几个用户会争夺资源!谁想抢到了,谁就先执行。也就是说第二个请求网页的用户,可能要等到最后才被执行。假如,第一个用户在执行代码的时候发生异常不能再继续执行,他也没有释放被锁定的资源,那么其他用户将无限期的等待下去……这就是死锁。

  比如你去一个裁缝店,叫那里的小裁缝给你做套西服。你先选定一块布料,然后小裁缝会用皮尺量出你的身高、腰围、臂长、腿长等数据。再然后呢?小裁缝马上扯下一块布裁裁剪剪,开启缝纫机来个现场制作对么?如果这时候又来了一个顾客怎么办?这位新来的顾客就一直等着?等到你要的西服都做好了,才去招呼这位新来的顾客吗?也许这位新来的顾客等一会儿就摔门走人了。对于这个裁缝店算是丢掉了一笔买卖。但事实并不是这样子的!小裁缝是将你的身高、腰围等信息记录在一个收据单上,你选择什么样的布料也记录在这单子上,最后会告诉你几天以后来取就可以了。如果这个时候又来了一个顾客,小裁缝也一样记录这位新顾客的身高腰围等信息,并告诉他几天以后来取……

  这就是小裁缝的智慧,即免除了顾客的等待时间,又为自己争取到了顾客量。你只需要选定一块布料,并且把自己的身高、腰围等信息留下就可以了。至于这个小裁缝是什么时候开工,手工过程是什么样的,你无需知道了。你只会记得到日子取回自己的西服。这就是命令模式!

  命令模式的特点是:

命令的发出者和命令的执行者可能不是同一个人(不是同一个类或代码段,甚至不是在同一台服务器上); 命令的发出者发出命令给执行者以后会立即返回,或者说发出命令的时间和执行命令的时间可能会有很大间隔,是不同步的; 命令的发出者不知道也不想知道执行者的执行过程;


  你就是命令的发出者,小裁缝就是命令的执行者,那张记录你身高、腰围等信息的收据单,就是命令对象!

   对于统计在线用户,我们事将用户信息翻译成命令对象并记录到一个队列中。并不马上更新在下用户数据集合,而是延迟一段时间在更新。将用户更新信息积攒起来,等到一定时间后批量处理。这样就免除了对在线用户数据集的频繁加锁!更具体的说明如下:
1. 创建两个命令队列,cmdQueueA和cmdQueueX。cmdQueueA专门负责接收并存储新的命令对象;cmdQueueX专门负责存储即将执行的命令对象;

 

t0.jpg


2. 在一定时间间隔之后,交换两个命令队列!系统(在下图中用红色曲线表示)将根据cmdQueueX中的命令对象,更新在线用户数据集合onlineUserList;在更新onlineUserList的时候需要加锁,所以使用两个命令队列可以保证命令的接收效率,也避免了对同一队列同时进行入出队操作;

 

t1.jpg



最终代码,首先是OnlineUserRecorder类,该类属于“控制器”:


  1 None.gifusing System;
  2None.gifusing System.Collections.Generic;
  3None.gifusing System.Threading;
  4None.gif
  5None.gifnamespace Net.AfritXia.Web.OnlineStat
  6ExpandedBlockStart.gifContractedBlock.gifdot.gif{
  7ExpandedSubBlockStart.gifContractedSubBlock.gif /**//// <summary>
  8InBlock.gif /// 在线用户记录器
  9ExpandedSubBlockEnd.gif /// </summary>

 10InBlock.gif    public class OnlineUserRecorder
 11ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 12InBlock.gif        // 在线用户数据库
 13InBlock.gif        private OnlineUserDB m_db = null;
 14InBlock.gif        // 命令队列A, 用于接收命令
 15InBlock.gif        private Queue<OnlineUserCmdBase> m_cmdQueueA = null;
 16InBlock.gif        // 命令队列X, 用于执行命令
 17InBlock.gif        private Queue<OnlineUserCmdBase> m_cmdQueueX = null;
 18InBlock.gif        // 繁忙标志
 19InBlock.gif        private bool m_isBusy = false;
 20InBlock.gif        // 上次统计时间
 21InBlock.gif        private DateTime m_lastStatisticTime = new DateTime(0);
 22InBlock.gif        // 用户超时分钟数
 23InBlock.gif        private int m_userTimeOutMinute = 20;
 24InBlock.gif        // 统计时间间隔
 25InBlock.gif        private int m_statisticEventInterval = 60;
 26InBlock.gif        // 命令队列长度
 27InBlock.gif        private int m_cmdQueueLength = 256;
 28InBlock.gif
 29ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
 30ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 31InBlock.gif        /// 类默认构造器
 32ExpandedSubBlockEnd.gif        /// </summary>

 33InBlock.gif        internal OnlineUserRecorder()
 34ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 35InBlock.gif            this.m_db = new OnlineUserDB();
 36InBlock.gif
 37InBlock.gif            // 初始化命令队列
 38InBlock.gif            this.m_cmdQueueA = new Queue<OnlineUserCmdBase>();
 39InBlock.gif            this.m_cmdQueueX = new Queue<OnlineUserCmdBase>();
 40ExpandedSubBlockEnd.gif        }

 41ExpandedSubBlockEnd.gif        #endregion

 42InBlock.gif
 43ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 44InBlock.gif        /// 设置或获取用户超时分钟数
 45ExpandedSubBlockEnd.gif        /// </summary>

 46InBlock.gif        internal int UserTimeOutMinute
 47ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 48InBlock.gif            set
 49ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 50InBlock.gif                this.m_userTimeOutMinute = value;
 51ExpandedSubBlockEnd.gif            }

 52InBlock.gif
 53InBlock.gif            get
 54ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 55InBlock.gif                return this.m_userTimeOutMinute;
 56ExpandedSubBlockEnd.gif            }

 57ExpandedSubBlockEnd.gif        }

 58InBlock.gif
 59ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 60InBlock.gif        /// 设置或获取统计时间间隔(单位秒)
 61ExpandedSubBlockEnd.gif        /// </summary>

 62InBlock.gif        internal int StatisticEventInterval
 63ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 64InBlock.gif            set
 65ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 66InBlock.gif                this.m_statisticEventInterval = value;
 67ExpandedSubBlockEnd.gif            }

 68InBlock.gif
 69InBlock.gif            get
 70ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 71InBlock.gif                return this.m_statisticEventInterval;
 72ExpandedSubBlockEnd.gif            }

 73ExpandedSubBlockEnd.gif        }

 74InBlock.gif
 75ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 76InBlock.gif        /// 设置或获取命令队列长度
 77ExpandedSubBlockEnd.gif        /// </summary>

 78InBlock.gif        public int CmdQueueLength
 79ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 80InBlock.gif            set
 81ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 82InBlock.gif                this.m_cmdQueueLength = value;
 83ExpandedSubBlockEnd.gif            }

 84InBlock.gif
 85InBlock.gif            get
 86ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 87InBlock.gif                return this.m_cmdQueueLength;
 88ExpandedSubBlockEnd.gif            }

 89ExpandedSubBlockEnd.gif        }

 90InBlock.gif
 91ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 92InBlock.gif        /// 保存在线用户信息
 93InBlock.gif        /// </summary>
 94ExpandedSubBlockEnd.gif        /// <param name="onlineUser"></param>

 95InBlock.gif        public void Persist(OnlineUser onlineUser)
 96ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 97InBlock.gif            // 创建删除命令
 98InBlock.gif            OnlineUserDeleteCmd delCmd = new OnlineUserDeleteCmd(this.m_db, onlineUser);
 99InBlock.gif            // 创建插入命令
100InBlock.gif            OnlineUserInsertCmd insCmd = new OnlineUserInsertCmd(this.m_db, onlineUser);
101InBlock.gif
102InBlock.gif            if (this.m_cmdQueueA.Count > this.CmdQueueLength)
103InBlock.gif                return;
104InBlock.gif
105InBlock.gif            // 将命令添加到队列
106InBlock.gif            this.m_cmdQueueA.Enqueue(delCmd);
107InBlock.gif            this.m_cmdQueueA.Enqueue(insCmd);
108InBlock.gif
109InBlock.gif            // 处理命令队列
110InBlock.gif            this.BeginProcessCmdQueue();
111ExpandedSubBlockEnd.gif        }

112InBlock.gif
113ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
114InBlock.gif        /// 删除在线用户信息
115InBlock.gif        /// </summary>
116ExpandedSubBlockEnd.gif        /// <param name="onlineUser"></param>

117InBlock.gif        public void Delete(OnlineUser onlineUser)
118ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
119InBlock.gif            // 创建删除命令
120InBlock.gif            OnlineUserDeleteCmd delCmd = new OnlineUserDeleteCmd(this.m_db, onlineUser);
121InBlock.gif
122InBlock.gif            // 将命令添加到队列
123InBlock.gif            this.m_cmdQueueA.Enqueue(delCmd);
124InBlock.gif
125InBlock.gif            // 处理命令队列
126InBlock.gif            this.BeginProcessCmdQueue();
127ExpandedSubBlockEnd.gif        }

128InBlock.gif
129ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
130InBlock.gif        /// 获取在线用户列表
131InBlock.gif        /// </summary>
132ExpandedSubBlockEnd.gif        /// <returns></returns>

133InBlock.gif        public IList<OnlineUser> GetUserList()
134ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
135InBlock.gif            return this.m_db.Select();
136ExpandedSubBlockEnd.gif        }

137InBlock.gif
138ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
139InBlock.gif        /// 获取在线用户数量
140InBlock.gif        /// </summary>
141ExpandedSubBlockEnd.gif        /// <returns></returns>

142InBlock.gif        public int GetUserCount()
143ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
144InBlock.gif            return this.m_db.Count();
145ExpandedSubBlockEnd.gif        }

146InBlock.gif
147ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
148InBlock.gif        /// 异步方式处理命令队列
149ExpandedSubBlockEnd.gif        /// </summary>

150InBlock.gif        private void BeginProcessCmdQueue()
151ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
152InBlock.gif            if (this.m_isBusy)
153InBlock.gif                return;
154InBlock.gif
155InBlock.gif            // 未到可以统计的时间
156InBlock.gif            if (DateTime.Now - this.m_lastStatisticTime < TimeSpan.FromSeconds(this.StatisticEventInterval))
157InBlock.gif                return;
158InBlock.gif
159InBlock.gif            Thread t = null;
160InBlock.gif
161InBlock.gif            t = new Thread(new ThreadStart(this.ProcessCmdQueue));
162InBlock.gif            t.Start();
163ExpandedSubBlockEnd.gif        }

164InBlock.gif
165ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
166InBlock.gif        /// 处理命令队列
167ExpandedSubBlockEnd.gif        /// </summary>

168InBlock.gif        private void ProcessCmdQueue()
169ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
170InBlock.gif            lock (this)
171ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
172InBlock.gif                if (this.m_isBusy)
173InBlock.gif                    return;
174InBlock.gif
175InBlock.gif                // 未到可以统计的时间
176InBlock.gif                if (DateTime.Now - this.m_lastStatisticTime < TimeSpan.FromSeconds(this.StatisticEventInterval))
177InBlock.gif                    return;
178InBlock.gif
179InBlock.gif                this.m_isBusy = true;
180InBlock.gif
181InBlock.gif                // 声明临时队列, 用于交换
182InBlock.gif                Queue<OnlineUserCmdBase> tempQ = null;
183InBlock.gif
184InBlock.gif                // 交换两个命令队列
185InBlock.gif                tempQ = this.m_cmdQueueA;
186InBlock.gif                this.m_cmdQueueA = this.m_cmdQueueX;
187InBlock.gif                this.m_cmdQueueX = tempQ;
188InBlock.gif                tempQ = null;
189InBlock.gif
190InBlock.gif                while (this.m_cmdQueueX.Count > 0)
191ExpandedSubBlockStart.gifContractedSubBlock.gif                dot.gif{
192InBlock.gif                    // 获取命令
193InBlock.gif                    OnlineUserCmdBase cmd = this.m_cmdQueueX.Peek();
194InBlock.gif
195InBlock.gif                    if (cmd == null)
196InBlock.gif                        break;
197InBlock.gif
198InBlock.gif                    // 执行命令
199InBlock.gif                    cmd.Execute();
200InBlock.gif
201InBlock.gif                    // 从队列中移除命令
202InBlock.gif                    this.m_cmdQueueX.Dequeue();
203ExpandedSubBlockEnd.gif                }

204InBlock.gif
205InBlock.gif    // 清除超时用户
206InBlock.gif    this.m_db.ClearTimeOut(this.UserTimeOutMinute);
207InBlock.gif    // 排序
208InBlock.gif    this.m_db.Sort();
209InBlock.gif
210InBlock.gif                this.m_lastStatisticTime = DateTime.Now;
211InBlock.gif                this.m_isBusy = false;
212ExpandedSubBlockEnd.gif            }

213ExpandedSubBlockEnd.gif        }

214ExpandedSubBlockEnd.gif    }

215ExpandedBlockEnd.gif}

  

命令对象,包括OnlineUserCmdBase命令基础类、OnlineUserInsertCmd插入命令、OnlineUserDeleteCmd删除命令。

 1 None.gifusing System;
 2None.gif
 3None.gifnamespace Net.AfritXia.Web.OnlineStat
 4ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
 6InBlock.gif    /// 在线用户命令基础类
 7ExpandedSubBlockEnd.gif    /// </summary>

 8InBlock.gif    internal abstract class OnlineUserCmdBase
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
10InBlock.gif        // 当前用户对象
11InBlock.gif        private OnlineUser m_currUser = null;
12InBlock.gif        // 在线用户数据库
13InBlock.gif        private OnlineUserDB m_db = null;
14InBlock.gif
15ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
16ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
17InBlock.gif        /// 类默认构造器
18ExpandedSubBlockEnd.gif        /// </summary>

19InBlock.gif        public OnlineUserCmdBase()
20ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
21ExpandedSubBlockEnd.gif        }

22InBlock.gif
23ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
24InBlock.gif        /// 类参数构造器
25InBlock.gif        /// </summary>
26InBlock.gif        /// <param name="db">在线用户数据库</param>
27ExpandedSubBlockEnd.gif        /// <param name="currUser">当前用户</param>

28InBlock.gif        public OnlineUserCmdBase(OnlineUserDB db, OnlineUser currUser)
29ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
30InBlock.gif            this.OnlineUserDB = db;
31InBlock.gif            this.CurrentUser = currUser;
32ExpandedSubBlockEnd.gif        }

33ExpandedSubBlockEnd.gif        #endregion

34InBlock.gif
35ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
36InBlock.gif        /// 设置或获取当前用户
37ExpandedSubBlockEnd.gif        /// </summary>

38InBlock.gif        public OnlineUser CurrentUser
39ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
40InBlock.gif            set
41ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
42InBlock.gif                this.m_currUser = value;
43ExpandedSubBlockEnd.gif            }

44InBlock.gif
45InBlock.gif            get
46ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
47InBlock.gif                return this.m_currUser;
48ExpandedSubBlockEnd.gif            }

49ExpandedSubBlockEnd.gif        }

50InBlock.gif
51ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
52InBlock.gif        /// 设置或获取在线用户数据库
53ExpandedSubBlockEnd.gif        /// </summary>

54InBlock.gif        public OnlineUserDB OnlineUserDB
55ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
56InBlock.gif            set
57ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
58InBlock.gif                this.m_db = value;
59ExpandedSubBlockEnd.gif            }

60InBlock.gif
61InBlock.gif            get
62ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
63InBlock.gif                return this.m_db;
64ExpandedSubBlockEnd.gif            }

65ExpandedSubBlockEnd.gif        }

66InBlock.gif
67ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
68InBlock.gif        /// 执行命令
69ExpandedSubBlockEnd.gif        /// </summary>

70InBlock.gif        public abstract void Execute();
71ExpandedSubBlockEnd.gif    }

72ExpandedBlockEnd.gif}

  

 1 None.gifusing System;
 2None.gif
 3None.gifnamespace Net.AfritXia.Web.OnlineStat
 4ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
 6InBlock.gif    /// 插入命令
 7ExpandedSubBlockEnd.gif    /// </summary>

 8InBlock.gif    internal class OnlineUserInsertCmd : OnlineUserCmdBase
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
10ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
11ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
12InBlock.gif        /// 类默认构造器
13ExpandedSubBlockEnd.gif        /// </summary>

14InBlock.gif        public OnlineUserInsertCmd()
15InBlock.gif            : base()
16ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
17ExpandedSubBlockEnd.gif        }

18InBlock.gif
19ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
20InBlock.gif        /// 类参数构造器
21InBlock.gif        /// </summary>
22InBlock.gif        /// <param name="db">在线用户数据库</param>
23ExpandedSubBlockEnd.gif        /// <param name="currUser">当前插入的新用户</param>

24InBlock.gif        public OnlineUserInsertCmd(OnlineUserDB db, OnlineUser currUser)
25InBlock.gif            : base(db, currUser)
26ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
27ExpandedSubBlockEnd.gif        }

28ExpandedSubBlockEnd.gif        #endregion

29InBlock.gif
30ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
31InBlock.gif        /// 执行命令
32ExpandedSubBlockEnd.gif        /// </summary>

33InBlock.gif        public override void Execute()
34ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
35InBlock.gif            this.OnlineUserDB.Insert(this.CurrentUser);
36ExpandedSubBlockEnd.gif        }

37ExpandedSubBlockEnd.gif    }

38ExpandedBlockEnd.gif}


 1 None.gifusing System;
 2None.gif
 3None.gifnamespace Net.AfritXia.Web.OnlineStat
 4ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 5ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
 6InBlock.gif    /// 删除命令
 7ExpandedSubBlockEnd.gif    /// </summary>
 8InBlock.gif    internal class OnlineUserDeleteCmd : OnlineUserCmdBase
 9ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
10ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
11ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
12InBlock.gif        /// 类默认构造器
13ExpandedSubBlockEnd.gif        /// </summary>
14InBlock.gif        public OnlineUserDeleteCmd()
15InBlock.gif            : base()
16ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
17ExpandedSubBlockEnd.gif        }
18InBlock.gif
19ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
20InBlock.gif        /// 类参数构造器
21InBlock.gif        /// </summary>
22InBlock.gif        /// <param name="db">在线用户数据库</param>
23ExpandedSubBlockEnd.gif        /// <param name="currUser">当前被删除的用户</param>
24InBlock.gif        public OnlineUserDeleteCmd(OnlineUserDB db, OnlineUser currUser)
25InBlock.gif            : base(db, currUser)
26ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
27ExpandedSubBlockEnd.gif        }
28ExpandedSubBlockEnd.gif        #endregion
29InBlock.gif
30ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
31InBlock.gif        /// 执行命令
32ExpandedSubBlockEnd.gif        /// </summary>
33InBlock.gif        public override void Execute()
34ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
35InBlock.gif            this.OnlineUserDB.Delete(this.CurrentUser);
36ExpandedSubBlockEnd.gif        }
37ExpandedSubBlockEnd.gif    }
38ExpandedBlockEnd.gif}


最后,OnlineUserList 被封装到OnlineUserDB类中,OnlineUserDB也属于“控制器”代码如下:

 

  1 None.gifusing System;
  2None.gifusing System.Collections.Generic;
  3None.gif
  4None.gifnamespace Net.AfritXia.Web.OnlineStat
  5ExpandedBlockStart.gifContractedBlock.gifdot.gif{
  6ExpandedSubBlockStart.gifContractedSubBlock.gif    /**//// <summary>
  7InBlock.gif    /// 在线用户数据库
  8ExpandedSubBlockEnd.gif    /// </summary>

  9InBlock.gif    internal class OnlineUserDB
 10ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 11InBlock.gif        // 在线用户集合
 12InBlock.gif        private List<OnlineUser> m_onlineUserList = null;
 13InBlock.gif
 14ContractedSubBlock.gifExpandedSubBlockStart.gif        类构造器#region 类构造器
 15ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 16InBlock.gif        /// 类默认构造器
 17ExpandedSubBlockEnd.gif        /// </summary>

 18InBlock.gif        public OnlineUserDB()
 19ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 20InBlock.gif            this.m_onlineUserList = new List<OnlineUser>();
 21ExpandedSubBlockEnd.gif        }

 22ExpandedSubBlockEnd.gif        #endregion

 23InBlock.gif
 24ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 25InBlock.gif        /// 插入新用户
 26InBlock.gif        /// </summary>
 27ExpandedSubBlockEnd.gif        /// <param name="newUser"></param>

 28InBlock.gif        public void Insert(OnlineUser newUser)
 29ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 30InBlock.gif            lock (this)
 31ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 32InBlock.gif                this.m_onlineUserList.Add(newUser);
 33ExpandedSubBlockEnd.gif            }

 34ExpandedSubBlockEnd.gif        }

 35InBlock.gif
 36ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 37InBlock.gif        /// 删除用户
 38InBlock.gif        /// </summary>
 39ExpandedSubBlockEnd.gif        /// <param name="delUser"></param>

 40InBlock.gif        public void Delete(OnlineUser delUser)
 41ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 42InBlock.gif            lock (this)
 43ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 44InBlock.gif                this.m_onlineUserList.RemoveAll((new PredicateDelete(delUser)).Predicate);
 45ExpandedSubBlockEnd.gif            }

 46ExpandedSubBlockEnd.gif        }

 47InBlock.gif
 48ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 49InBlock.gif        /// 清除超时用户
 50InBlock.gif        /// </summary>
 51ExpandedSubBlockEnd.gif        /// <param name="timeOutMinute">超时分钟数</param>

 52InBlock.gif        public void ClearTimeOut(int timeOutMinute)
 53ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 54InBlock.gif            lock (this)
 55ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
 56InBlock.gif                this.m_onlineUserList.RemoveAll((new PredicateTimeOut(timeOutMinute)).Predicate);
 57ExpandedSubBlockEnd.gif            }

 58ExpandedSubBlockEnd.gif        }

 59InBlock.gif
 60ExpandedSubBlockStart.gifContractedSubBlock.gif  /**//// <summary>
 61InBlock.gif  /// 排序在线用户列表
 62ExpandedSubBlockEnd.gif  /// </summary>

 63InBlock.gif  public void Sort()
 64ExpandedSubBlockStart.gifContractedSubBlock.gif  dot.gif{
 65InBlock.gif   // 按活动时间进行排序
 66InBlock.gif   this.m_onlineUserList.Sort(CompareByActiveTime);
 67ExpandedSubBlockEnd.gif  }

 68InBlock.gif
 69ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 70InBlock.gif        /// 获取所有用户
 71InBlock.gif        /// </summary>
 72ExpandedSubBlockEnd.gif        /// <returns></returns>

 73InBlock.gif        public IList<OnlineUser> Select()
 74ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 75InBlock.gif            return this.m_onlineUserList.ToArray();
 76ExpandedSubBlockEnd.gif        }

 77InBlock.gif
 78ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 79InBlock.gif        /// 获取在线用户数量
 80InBlock.gif        /// </summary>
 81ExpandedSubBlockEnd.gif        /// <returns></returns>

 82InBlock.gif        public int Count()
 83ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 84InBlock.gif            return this.m_onlineUserList.Count;
 85ExpandedSubBlockEnd.gif        }

 86InBlock.gif
 87ContractedSubBlock.gifExpandedSubBlockStart.gif        用户删除条件断言#region 用户删除条件断言
 88InBlock.gif        private class PredicateDelete
 89ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 90InBlock.gif            // 被删除的用户
 91InBlock.gif            private OnlineUser m_delUser = null;
 92InBlock.gif            // 是否为空条件
 93InBlock.gif            private bool m_isNullCondation = true;
 94InBlock.gif
 95ContractedSubBlock.gifExpandedSubBlockStart.gif            类构造器#region 类构造器
 96ExpandedSubBlockStart.gifContractedSubBlock.gif            /**//// <summary>
 97InBlock.gif            /// 类参数构造器
 98InBlock.gif            /// </summary>
 99ExpandedSubBlockEnd.gif            /// <param name="delUser"></param>

100InBlock.gif            public PredicateDelete(OnlineUser delUser)
101ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
102InBlock.gif                this.m_delUser = delUser;
103InBlock.gif
104InBlock.gif                if (this.m_delUser == null)
105InBlock.gif                    return;
106InBlock.gif
107InBlock.gif                // 用户 ID
108InBlock.gif                this.m_isNullCondation &= this.m_delUser.UniqueID <= 0;
109InBlock.gif                // 名称
110InBlock.gif                this.m_isNullCondation &= String.IsNullOrEmpty(this.m_delUser.UserName);
111InBlock.gif                // SessionID
112InBlock.gif                this.m_isNullCondation &= String.IsNullOrEmpty(this.m_delUser.SessionID);
113ExpandedSubBlockEnd.gif            }

114ExpandedSubBlockEnd.gif            #endregion

115InBlock.gif
116ExpandedSubBlockStart.gifContractedSubBlock.gif            /**//// <summary>
117InBlock.gif            /// 判断用户 ID 是否等于指定值
118InBlock.gif            /// </summary>
119InBlock.gif            /// <param name="user"></param>
120ExpandedSubBlockEnd.gif            /// <returns></returns>

121InBlock.gif            public bool Predicate(OnlineUser user)
122ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
123InBlock.gif                if (this.m_isNullCondation)
124InBlock.gif                    return false;
125InBlock.gif
126InBlock.gif                if (user == null)
127InBlock.gif                    return false;
128InBlock.gif
129InBlock.gif                // 用户 ID 相同, ID > 0
130InBlock.gif                if (user.UniqueID > 0 && user.UniqueID == this.m_delUser.UniqueID)
131InBlock.gif                    return true;
132InBlock.gif
133InBlock.gif                // 用户名称相同, 并且不是空字符串
134InBlock.gif                if (!String.IsNullOrEmpty(user.UserName) && user.UserName == this.m_delUser.UserName)
135InBlock.gif                    return true;
136InBlock.gif
137InBlock.gif                // SessionID 相同, 并且不是空字符串
138InBlock.gif                if (user.SessionID == this.m_delUser.SessionID)
139InBlock.gif                    return true;
140InBlock.gif
141InBlock.gif                return false;
142ExpandedSubBlockEnd.gif            }

143ExpandedSubBlockEnd.gif        }

144ExpandedSubBlockEnd.gif        #endregion

145InBlock.gif
146ContractedSubBlock.gifExpandedSubBlockStart.gif        用户超时条件断言#region 用户超时条件断言
147InBlock.gif        private class PredicateTimeOut
148ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
149InBlock.gif            // 超时分钟数
150InBlock.gif            private int m_timeOutMinute;
151InBlock.gif
152ContractedSubBlock.gifExpandedSubBlockStart.gif            类构造器#region 类构造器
153ExpandedSubBlockStart.gifContractedSubBlock.gif            /**//// <summary>
154InBlock.gif            /// 类参数构造器
155InBlock.gif            /// </summary>
156ExpandedSubBlockEnd.gif            /// <param name="minute">超时分钟数</param>

157InBlock.gif            public PredicateTimeOut(int minute)
158ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
159InBlock.gif                this.m_timeOutMinute = minute;
160ExpandedSubBlockEnd.gif            }

161ExpandedSubBlockEnd.gif            #endregion

162InBlock.gif
163ExpandedSubBlockStart.gifContractedSubBlock.gif            /**//// <summary>
164InBlock.gif            /// 判断用户活动时间是否小于指定值
165InBlock.gif            /// </summary>
166InBlock.gif            /// <param name="user"></param>
167ExpandedSubBlockEnd.gif            /// <returns></returns>

168InBlock.gif            public bool Predicate(OnlineUser user)
169ExpandedSubBlockStart.gifContractedSubBlock.gif            dot.gif{
170InBlock.gif                if (user == null)
171InBlock.gif                    return false;
172InBlock.gif
173InBlock.gif                return user.LastActiveTime < DateTime.Now.AddMinutes(-this.m_timeOutMinute);
174ExpandedSubBlockEnd.gif            }

175ExpandedSubBlockEnd.gif        }

176ExpandedSubBlockEnd.gif        #endregion

177InBlock.gif
178ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
179InBlock.gif        /// 比较两个用户的活动时间
180InBlock.gif        /// </summary>
181InBlock.gif        /// <param name="x"></param>
182InBlock.gif        /// <param name="y"></param>
183ExpandedSubBlockEnd.gif        /// <returns></returns>

184InBlock.gif        private static int CompareByActiveTime(OnlineUser x, OnlineUser y)
185ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
186InBlock.gif            if (x == null)
187InBlock.gif                throw new NullReferenceException("X 值为空 ( X Is Null )");
188InBlock.gif
189InBlock.gif            if (y == null)
190InBlock.gif                throw new NullReferenceException("Y 值为空 ( Y Is Null )");
191InBlock.gif
192InBlock.gif            if (x.LastActiveTime > y.LastActiveTime)
193InBlock.gif                return -1;
194InBlock.gif
195InBlock.gif            if (x.LastActiveTime < y.LastActiveTime)
196InBlock.gif                return +1;
197InBlock.gif
198InBlock.gif            return 0;
199ExpandedSubBlockEnd.gif        }

200ExpandedSubBlockEnd.gif    }

201ExpandedBlockEnd.gif}

 关于本文更详细的代码,请参见代码包WebUI项目中的DefaultLayout.master.cs文件。

 

转载于:https://www.cnblogs.com/afritxia2008/archive/2008/06/27/1231070.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值