#define DEBUG // 用于调试
/*
* OnlineUserService.cs @Microsoft Visual Studio 2005 <.NET Framework 2.0>
* AfritXia
* 11.21/2005
*
* 在线用户列表服务
*
* 实现原理是在缓存中建立两张数据表:OnlineUserTb 数据表和 SessionInfoTb 数据表
*
* 这两个表的表格结构分别是:
*
* +-------------------------------------+
* | OnlineUserTb |
* +----+---------------+----------------+
* | PK | NickName | System.String |
* | +---------------+----------------+
* | PK | CurrSessionID | System.String |
* | +---------------+----------------+
* | | ActiveTime | System.Int64 |
* | +---------------+----------------+
* | | RequestURL | System.String |
* +----+---------------+----------------+
*
* +-----------------------------------+
* | SessionInfoTb |
* +----+-------------+----------------+
* | PK | SessionID | System.String |
* | +-------------+----------------+
* | | NickName | System.String |
* | +-------------+----------------+
* | | ActiveTime | System.Int64 |
* | +-------------+----------------+
* | | RequestURL | System.String |
* +----+-------------+----------------+
*
* SessionInfoTb 数据表的主要目的是用来维护 OnlineUserTb 数据表
*
* OnlineUserTb 用来对外提供数据
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Threading;
using System.Web;
using Bincess.Classes.Users;
namespace Bincess.WebForum
{
/// <summary>
/// OnlineUserService 在线用户列表服务
/// </summary>
public class OnlineUserService
{
// OnlineUserService 单列对象
private static OnlineUserService g_theInstance = null;
// 在线用户信息集合
private DataSet m_onlineUserDS = null;
// OnlineUser 数据表
private const string TB_ONLINE_USER = "OnlineUser";
// SessionInfo 数据表
private const string TB_SESSION_INFO = "SessionInfo";
// 用户昵称
private const string COL_NICK_NAME = "NickName";
// 当前 SessionID
private const string COL_CURR_SESSION_ID = "CurrSessionID";
// 用户最后活动时间
private const string COL_ACTIVE_TIME = "ActiveTime";
// 用户最后活动页 URL
private const string COL_REQUEST_URL = "RequestURL";
// Session 编号
private const string COL_SESSION_ID = "SessionID";
// 访问者数量
private int m_visitorCount = 0;
// 超时分钟设置
private int m_timeOutMinutes = 20;
// 工作模式
private WorkMode m_inWorkMode = WorkMode.MultiThreading;
#region 类 OnlineUserService 构造器
/// <summary>
/// 类 OnlineUserService 默认构造器
/// </summary>
private OnlineUserService()
{
}
#endregion
/// <summary>
/// 获取 OnlineUserService 类实例
/// </summary>
public static OnlineUserService Instance
{
get
{
if (g_theInstance != null)
return g_theInstance;
lock (typeof(OnlineUserService))
{
if (g_theInstance == null)
{
OnlineUserService theInstance = new OnlineUserService();
// 初始化在线用户数据表
theInstance.InitOnlineUserDS();
g_theInstance = theInstance;
}
}
return g_theInstance;
}
}
#region InitOnlineUserDS 初始化在线用户数据集
/// <summary>
/// 初始化在线用户数据集
/// </summary>
private void InitOnlineUserDS()
{
this.m_onlineUserDS = new DataSet("OnlineUserDS");
// 在线用户数据表
DataTable onlineUserTb = new DataTable(TB_ONLINE_USER);
// 为 OnlineUser 数据表添加列
// 添加数据列 NickName
onlineUserTb.Columns.Add(new DataColumn(COL_NICK_NAME, typeof(System.String)));
// 添加数据列 CurrSessionID
onlineUserTb.Columns.Add(new DataColumn(COL_CURR_SESSION_ID, typeof(System.String)));
// 添加数据列 ActiveTime
onlineUserTb.Columns.Add(new DataColumn(COL_ACTIVE_TIME, typeof(System.Int64)));
// 添加数据列 RequestURL
onlineUserTb.Columns.Add(new DataColumn(COL_REQUEST_URL, typeof(System.String)));
// 设置数据列 CurrSessionID 为非重复的列
onlineUserTb.Columns[COL_CURR_SESSION_ID].Unique = true;
// 设置主键列为 NickName, CurrSessionID
onlineUserTb.PrimaryKey = new DataColumn[] {
onlineUserTb.Columns[COL_NICK_NAME],
onlineUserTb.Columns[COL_CURR_SESSION_ID]
};
// 将数据表添加到在线用户信息集合
this.m_onlineUserDS.Tables.Add(onlineUserTb);
// Session 信息数据表
DataTable sessionInfoTb = new DataTable(TB_SESSION_INFO);
// 为 SessionInfo 数据表添加列
// 添加数据列 SessionID
sessionInfoTb.Columns.Add(new DataColumn(COL_SESSION_ID, typeof(System.String)));
// 添加数据列 NickName
sessionInfoTb.Columns.Add(new DataColumn(COL_NICK_NAME, typeof(System.String)));
// 添加数据列 ActiveTime
sessionInfoTb.Columns.Add(new DataColumn(COL_ACTIVE_TIME, typeof(System.Int64)));
// 添加数据列 RequestURL
sessionInfoTb.Columns.Add(new DataColumn(COL_REQUEST_URL, typeof(System.String)));
// 设置主键为 SessionID
sessionInfoTb.PrimaryKey = new DataColumn[] {
sessionInfoTb.Columns[COL_SESSION_ID]
};
// 将数据表添加到在线用户信息集合
this.m_onlineUserDS.Tables.Add(sessionInfoTb);
// 为 SessionInfoTb 数据表添加事件
sessionInfoTb.RowDeleted += new DataRowChangeEventHandler(SessionInfoTb_RowDeleted);
sessionInfoTb.RowChanged += new DataRowChangeEventHandler(SessionInfoTb_RowChanged);
}
/// <summary>
/// SessionInfoTb 数据表修改事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SessionInfoTb_RowChanged(object sender, DataRowChangeEventArgs e)
{
if (e.Row.RowState != DataRowState.Added && e.Row.RowState != DataRowState.Modified)
return;
// 获取 SessionID
string sessionID = e.Row[COL_SESSION_ID] as string;
// 获取用户昵称
string nickName = e.Row[COL_NICK_NAME] as string;
// 获取最后活动时间
long activeTimeTicks = (long)e.Row[COL_ACTIVE_TIME];
// 获取最后请求地址
string requestURL = e.Row[COL_REQUEST_URL] as string;
// 获取 OnlineUserTb 数据表
DataTable onlineUserTb = this.m_onlineUserDS.Tables[TB_ONLINE_USER];
// 获取满足条件的数据行
// ( nickName != "" && COL_NICK_NAME == nickName ) || COL_CURR_SESSION_ID == sessionID
DataRow[] rows = onlineUserTb.Select(
String.Format("( {0} = '{1}' AND '{1}' <> '' ) OR {2} = '{3}'",
COL_NICK_NAME, nickName, COL_CURR_SESSION_ID, sessionID));
if (rows == null || rows.Length <= 0)
{
DataRow row = onlineUserTb.NewRow();
// 设置在线用户昵称
row[COL_NICK_NAME] = nickName;
// 当前 SessionID
row[COL_CURR_SESSION_ID] = sessionID;
// 最后活动时间
row[COL_ACTIVE_TIME] = activeTimeTicks;
// 最后的请求地址
row[COL_REQUEST_URL] = requestURL;
lock (onlineUserTb)
{
// 添加并保存新的数据行
onlineUserTb.Rows.Add(row);
onlineUserTb.AcceptChanges();
// 访问者数量加 1
this.m_visitorCount += 1;
}
}
else
{
if (rows.Length >= 2)
{
for (int i = rows.Length - 1; i > 0; i--)
rows[i].Delete();
// 访问者数量减 1
this.m_visitorCount -= rows.Length - 1;
}
lock (rows[0])
{
// 更新在线用户昵称
rows[0][COL_NICK_NAME] = nickName;
// 当前 SessionID
rows[0][COL_CURR_SESSION_ID] = sessionID;
// 最后活动时间
rows[0][COL_ACTIVE_TIME] = activeTimeTicks;
// 最后的请求地址
rows[0][COL_REQUEST_URL] = requestURL;
// 保存修改
rows[0].AcceptChanges();
}
}
}
/// <summary>
/// SessionInfoTb 数据表删除事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SessionInfoTb_RowDeleted(object sender, DataRowChangeEventArgs e)
{
if (e.Row.RowState != DataRowState.Deleted)
return;
// 获取 SessionID
string sessionID = e.Row[COL_SESSION_ID, DataRowVersion.Original] as string;
// 获取用户昵称
string nickName = e.Row[COL_NICK_NAME, DataRowVersion.Original] as string;
// 获取在线用户信息数据表
DataTable onlineUserTb = this.m_onlineUserDS.Tables[TB_ONLINE_USER];
lock (onlineUserTb)
{
DataRow[] rows = onlineUserTb.Select(
String.Format("( {0} = '{1}' AND '{1}' <> '' ) OR {2} = '{3}'",
COL_NICK_NAME, nickName, COL_CURR_SESSION_ID, sessionID));
// 删除数据行
foreach (DataRow r in rows)
r.Delete();
// 减少访问者数量
this.m_visitorCount -= rows.Length;
}
}
#endregion
#if DEBUG
/*
* 在 DEBUG 标识被开启时,OnlineUserTb 和 SessionInfoTb 数据表可以被外界所访问。
*
* 而对外开放 OnlineUserTb 和 SessionInfoTb 这两个数据表的主要目的是用于跟踪和调试此程序文件。
*
* 所以,在使用正式版的程序时,请关闭 DEBUG 标志
*
* 若获取在线用户信息,请使用 GetOnlineUsers 函数
*
*/
/// <summary>
/// 获取 OnlineUserTb 数据表
/// </summary>
public DataTable OnlineUserTb
{
get
{
return this.m_onlineUserDS.Tables[TB_ONLINE_USER];
}
}
/// <summary>
/// 获取 SessionInfoTb 数据表
/// </summary>
public DataTable SessionInfoTb
{
get
{
return this.m_onlineUserDS.Tables[TB_SESSION_INFO];
}
}
#endif
/// <summary>
/// 设置或获取超时分钟数,最小值为 1 分钟
/// </summary>
public int TimeOutMinutes
{
set
{
this.m_timeOutMinutes = (value <= 0 ? 1 : value);
}
get
{
return this.m_timeOutMinutes;
}
}
/// <summary>
/// 设置或获取工作状态
/// </summary>
public WorkMode InWorkMode
{
set
{
this.m_inWorkMode = value;
}
get
{
return this.m_inWorkMode;
}
}
/// <summary>
/// 保存在线用户信息
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <param name="onlineUser">在线用户信息</param>
public void Persist(string sessionID, OnlineUser onlineUser)
{
if (this.m_inWorkMode == WorkMode.MultiThreading)
{
// 建立保存工作线程对象
PersistWorkThread workThread = new PersistWorkThread(sessionID, onlineUser);
Thread t = new Thread(new ThreadStart(workThread.WorkStart));
t.Start();
}
else
{
// 建立保存动作的代理对象
MyDelegatePersist @delegate = new MyDelegatePersist(this.Async_Persist);
// 启用异步调用
@delegate.BeginInvoke(sessionID, onlineUser, null, null);
}
}
/// <summary>
/// 保存在线用户信息,异步调用
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <param name="onlineUser">在线用户信息</param>
private void Async_Persist(string sessionID, OnlineUser onlineUser)
{
if (sessionID == null || sessionID == "" || onlineUser == null)
return;
// 获取 SessionInfoTb 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
if (!this.HasSessionIDInSessionInfoTb(sessionID))
{
// 添加在线用户信息
this.Append(sessionID, onlineUser);
}
else
{
// 更新在线用户信息
this.Update(sessionID, onlineUser);
}
// 删除超时在线用户信息
this.DeleteTimeOut();
}
/// <summary>
/// 保存在线用户信息
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <param name="onlineUser">在线用户信息</param>
private void Append(string sessionID, OnlineUser onlineUser)
{
if (sessionID == null || sessionID == "" || onlineUser == null)
return;
// 获取 SessionInfo 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
// 建立新数据行
DataRow row = sessionInfoTb.NewRow();
// 设置 SessionID
row[COL_SESSION_ID] = sessionID;
// 用户昵称
row[COL_NICK_NAME] = (onlineUser.NickName == null ? "" : onlineUser.NickName);
// 最后活动时间
row[COL_ACTIVE_TIME] = onlineUser.ActiveTime.Ticks;
// 最后请求地址
row[COL_REQUEST_URL] = onlineUser.RequestURL;
lock (sessionInfoTb)
{
/*
* if (this.InWorkMode == WorkMode.MultiThreading)
* Thread.Sleep(24);
*
* 当工作模式为 MultiThreading 多线程模式时,可以令当前线程先休眠……
*
* 让 CPU 可以空闲出来处理其他肭螅纯梢蕴岣咔肭笏俣取?
*
* 但要特别注意的是,线程陷入过长时间的休眠,会造成程序的不稳定,也会增加内存的使用率。
*
* 最终 .NET 可能会警告 System.OutOfMemoryException 异常!
*
* 所以,休眠时间应当根据具体硬件环境进行微调……
*
*/
// 添加数据行并保存修改
sessionInfoTb.Rows.Add(row);
sessionInfoTb.AcceptChanges();
}
}
/// <summary>
/// 删除超时在线用户信息
/// </summary>
private void DeleteTimeOut()
{
// 获取 SessionInfoTb 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
lock (sessionInfoTb)
{
// 获取 SessionInfoTb 数据表中的超时行
DataRow[] rows = sessionInfoTb.Select(
String.Format("{0} <= {1}", COL_ACTIVE_TIME, this.TimeOutLimit));
// 删除数据行
foreach (DataRow r in rows)
r.Delete();
}
}
/// <summary>
/// 更新在线用户信息
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <param name="onlineUser">在线用户信息</param>
private void Update(string sessionID, OnlineUser onlineUser)
{
if (sessionID == null || onlineUser == null)
return;
// 获取 SessionInfoTb 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
DataRow[] rows = sessionInfoTb.Select(String.Format("{0} >= {1} AND {2} = '{3}'",
COL_ACTIVE_TIME, this.TimeOutLimit, COL_SESSION_ID, sessionID));
if (rows == null || rows.Length <= 0)
return;
lock (rows[0])
{
/*
* if (this.InWorkMode == WorkMode.MultiThreading)
* Thread.Sleep(24);
*
*/
// 设置用户昵称
rows[0][COL_NICK_NAME] = (onlineUser.NickName == null ? "" : onlineUser.NickName);
// 最后活动时间
rows[0][COL_ACTIVE_TIME] = DateTime.Now.Ticks;
// 最后活动页
rows[0][COL_REQUEST_URL] = onlineUser.RequestURL;
// 保存修改
rows[0].AcceptChanges();
}
}
/// <summary>
/// 判断在 OnlineUserTb 数据表中是否存在指定的用户昵称
/// </summary>
/// <param name="nickName">用户昵称</param>
/// <returns></returns>
private bool HasNickNameInOnlineUserTb(string nickName)
{
if (nickName == null)
return false;
// 获取在线用户数据表
DataTable onlineUserTb = this.m_onlineUserDS.Tables[TB_ONLINE_USER];
// 统计用户昵称数量
object count = onlineUserTb.Compute(
String.Format("COUNT({0})", COL_NICK_NAME),
String.Format("{0} = '{1}'", COL_NICK_NAME, nickName));
return Convert.ToInt32(count) >= 1;
}
/// <summary>
/// 判断在 OnlineUserTb 数据表中是否存在指定的 CurrSessionID
/// </summary>
/// <param name="currSessionID"></param>
/// <returns></returns>
private bool HasCurrSessionIDInOnlineUserTb(string currSessionID)
{
if (currSessionID == null)
return false;
// 获取在线用户数据表
DataTable onlineUserTb = this.m_onlineUserDS.Tables[TB_ONLINE_USER];
// 统计用户昵称数量
object count = onlineUserTb.Compute(
String.Format("COUNT({0})", COL_CURR_SESSION_ID),
String.Format("{0} = '{1}'", COL_CURR_SESSION_ID, currSessionID));
return Convert.ToInt32(count) >= 1;
}
/// <summary>
/// 判断在 SessionInfoTb 数据表中是否存在指定的用户昵称
/// </summary>
/// <param name="nickName">用户昵称</param>
/// <returns></returns>
private bool HasNickNameInSessionInfoTb(string nickName)
{
if (nickName == null)
return false;
// 获取 SessionInfo 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
// 统计用户昵称数量
object count = sessionInfoTb.Compute(
String.Format("COUNT({0})", COL_NICK_NAME),
String.Format("{0} = '{1}'", COL_NICK_NAME, nickName));
return Convert.ToInt32(count) >= 1;
}
/// <summary>
/// 判断在 SessionInfoTb 数据表中是否存在指定的 SessionID
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <returns></returns>
private bool HasSessionIDInSessionInfoTb(string sessionID)
{
if (sessionID == null)
return false;
// 获取 SessionInfo 数据表
DataTable sessionInfoTb = this.m_onlineUserDS.Tables[TB_SESSION_INFO];
// 统计用户昵称数量
object count = sessionInfoTb.Compute(
String.Format("COUNT({0})", COL_SESSION_ID),
String.Format("{0} = '{1}'", COL_SESSION_ID, sessionID));
return Convert.ToInt32(count) >= 1;
}
/// <summary>
/// 获取在线用户信息
/// </summary>
/// <returns>在线用户集合</returns>
public IList<OnlineUser> GetOnlineUsers()
{
IList<OnlineUser> onlineUsers = new List<OnlineUser>();
// 获取 OnlineUserTb 数据表
DataTable onlineUserTb = this.m_onlineUserDS.Tables[TB_ONLINE_USER];
// 获取所有数据行,并排序
DataRow[] rows = onlineUserTb.Select(null, String.Format("{0} DESC", COL_ACTIVE_TIME));
foreach (DataRow r in rows)
{
OnlineUser onlineUser = PutObjectProperty(new OnlineUser(), r);
onlineUsers.Add(onlineUser);
}
return onlineUsers;
}
/// <summary>
/// 获取在线用户数量
/// </summary>
public int VisitorCount
{
get
{
return this.m_visitorCount;
}
}
/// <summary>
/// 获取超时时限
/// </summary>
private long TimeOutLimit
{
get
{
// 设置超时时间限制
DateTime limit = DateTime.Now - TimeSpan.FromMinutes(this.TimeOutMinutes);
return limit.Ticks;
}
}
#region PutObjectProperty 设置对象实例
/// <summary>
/// 从数据源中读取数据,并设置到对象实例
/// </summary>
/// <param name="onlineUser">在线用户对象</param>
/// <param name="row">数据源</param>
/// <returns></returns>
private OnlineUser PutObjectProperty(OnlineUser onlineUser, DataRow row)
{
if (onlineUser == null || row == null)
return onlineUser;
// 设置用户昵称
onlineUser.NickName = row[COL_NICK_NAME] as string;
// 最后活动时间
onlineUser.ActiveTime = new DateTime((long)row[COL_ACTIVE_TIME]);
// 最后活动页
onlineUser.RequestURL = row[COL_REQUEST_URL] as string;
if (onlineUser.NickName == "")
onlineUser.NickName = null;
return onlineUser;
}
#endregion
#region OnlineUser 在线用户类
/// <summary>
/// OnlineUser 在线用户类
/// </summary>
public class OnlineUser
{
// 用户昵称
private string m_nickName;
// 最后活动时间
private DateTime m_activeTime;
// 最后请求地址
private string m_requestURL;
#region 类 OnlineUser 构造器
/// <summary>
/// 类 OnlineUser 默认构造器
/// </summary>
public OnlineUser()
{
}
/// <summary>
/// 类 OnlineUser 参数构造器
/// </summary>
/// <param name="nickName">用户昵称</param>
public OnlineUser(string nickName)
{
this.NickName = nickName;
}
#endregion
/// <summary>
/// 设置或获取用户昵称
/// </summary>
public string NickName
{
set
{
this.m_nickName = value;
}
get
{
return this.m_nickName;
}
}
/// <summary>
/// 最后活动时间
/// </summary>
public DateTime ActiveTime
{
set
{
this.m_activeTime = value;
}
get
{
return this.m_activeTime;
}
}
/// <summary>
/// 最后请求地址
/// </summary>
public string RequestURL
{
set
{
this.m_requestURL = value;
}
get
{
return this.m_requestURL;
}
}
}
#endregion
/// <summary>
/// 工作模式
/// </summary>
public enum WorkMode
{
Asynchronous, MultiThreading
};
// 保存在线用户信息代理函数
private delegate void MyDelegatePersist(string sessionID, OnlineUser onlineUser);
// 删除在线用户信息代理函数
private delegate void MyDelegateDelete(string sessionID);
/// <summary>
/// 保存工作线程
/// </summary>
private class PersistWorkThread
{
// SessionID
private string m_sessionID = null;
// 在线用户信息
private OnlineUser m_onlineUser = null;
#region 类 PersistWorkThread 构造器
/// <summary>
/// 类 PersistWorkThread 参数构造器
/// </summary>
/// <param name="sessionID">SessionID</param>
/// <param name="onlineUser">在线用户信息</param>
public PersistWorkThread(string sessionID, OnlineUser onlineUser)
{
// 设置 SessionID
this.m_sessionID = sessionID;
// 设置在线用户信息
this.m_onlineUser = onlineUser;
}
#endregion
/// <summary>
/// 启动工作线程
/// </summary>
public void WorkStart()
{
OnlineUserService.Instance.Async_Persist(this.m_sessionID, this.m_onlineUser);
}
}
}
}