diff --git "a/NewLife.Cube/Entity/\347\224\250\346\210\267\345\234\250\347\272\277.Biz.cs" "b/NewLife.Cube/Entity/\347\224\250\346\210\267\345\234\250\347\272\277.Biz.cs" index cf427eef..650fe6e8 100644 --- "a/NewLife.Cube/Entity/\347\224\250\346\210\267\345\234\250\347\272\277.Biz.cs" +++ "b/NewLife.Cube/Entity/\347\224\250\346\210\267\345\234\250\347\272\277.Biz.cs" @@ -1,115 +1,112 @@ -using System; -using System.Collections.Generic; -using NewLife.Data; +using NewLife.Data; using XCode; using XCode.Configuration; -using XCode.Membership; -namespace NewLife.Cube.Entity +namespace NewLife.Cube.Entity; + +/// 用户在线 +public partial class UserOnline : Entity { - /// 用户在线 - public partial class UserOnline : Entity + #region 对象操作 + static UserOnline() { - #region 对象操作 - static UserOnline() - { - // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx - var df = Meta.Factory.AdditionalFields; - df.Add(__.Times); - //df.Add(__.OnlineTime); - - var sc = Meta.SingleCache; - if (sc.Expire < 20 * 60) sc.Expire = 20 * 60; - sc.FindSlaveKeyMethod = k => Find(__.SessionID, k); - sc.GetSlaveKeyMethod = e => e.SessionID; - - // 过滤器 UserModule、TimeModule、IPModule - Meta.Modules.Add(); - Meta.Modules.Add(); - } + // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx + var df = Meta.Factory.AdditionalFields; + df.Add(__.Times); + //df.Add(__.OnlineTime); + + var sc = Meta.SingleCache; + if (sc.Expire < 20 * 60) sc.Expire = 20 * 60; + sc.FindSlaveKeyMethod = k => Find(__.SessionID, k); + sc.GetSlaveKeyMethod = e => e.SessionID; + + // 过滤器 UserModule、TimeModule、IPModule + Meta.Modules.Add(); + Meta.Modules.Add(); + } - /// 验证并修补数据,通过抛出异常的方式提示验证失败。 - /// 是否插入 - public override void Valid(Boolean isNew) - { - // 如果没有脏数据,则不需要进行任何处理 - if (!HasDirty) return; + /// 验证并修补数据,通过抛出异常的方式提示验证失败。 + /// 是否插入 + public override void Valid(Boolean isNew) + { + // 如果没有脏数据,则不需要进行任何处理 + if (!HasDirty) return; - // 截取长度 - CutField(_.Status, _.Page, _.Platform, _.OS, _.Device, _.Brower, _.NetType); + // 截取长度 + CutField(_.Status, _.Page, _.Platform, _.OS, _.Device, _.Brower, _.NetType); - // 建议先调用基类方法,基类方法会做一些统一处理 - base.Valid(isNew); - } + // 建议先调用基类方法,基类方法会做一些统一处理 + base.Valid(isNew); + } - void CutField(params FieldItem[] fields) + void CutField(params FieldItem[] fields) + { + foreach (var field in fields) { - foreach (var field in fields) + var len = field.Length; + if (len > 0 && field.Type == typeof(String)) { - var len = field.Length; - if (len > 0 && field.Type == typeof(String)) + var str = this[field.Name] as String; + if (!str.IsNullOrEmpty() && str.Length > len) { - var str = this[field.Name] as String; - if (!str.IsNullOrEmpty() && str.Length > len) - { - str = str[..len]; - SetItem(field.Name, str); - } + str = str[..len]; + SetItem(field.Name, str); } } } - #endregion + } + #endregion - #region 扩展属性 - #endregion + #region 扩展属性 + #endregion - #region 扩展查询 - /// 根据编号查找 - /// 编号 - /// 实体对象 - public static UserOnline FindByID(Int32 id) - { - if (id <= 0) return null; + #region 扩展查询 + /// 根据编号查找 + /// 编号 + /// 实体对象 + public static UserOnline FindByID(Int32 id) + { + if (id <= 0) return null; - //// 实体缓存 - //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id); + //// 实体缓存 + //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.ID == id); - // 单对象缓存 - return Meta.SingleCache[id]; + // 单对象缓存 + return Meta.SingleCache[id]; - //return Find(_.ID == id); - } - - /// 根据用户查找 - /// 用户 - /// 实体列表 - public static IList FindAllByUserID(Int32 userId) - { - //// 实体缓存 - //if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.UserID == userId); + //return Find(_.ID == id); + } - return FindAll(_.UserID == userId); - } + /// 根据用户查找 + /// 用户 + /// 实体列表 + public static IList FindAllByUserID(Int32 userId) + { + //// 实体缓存 + //if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.UserID == userId); - /// 根据会话查找 - /// 会话 - /// 是否从缓存查询 - /// 实体列表 - public static UserOnline FindBySessionID(String sessionId, Boolean cache = true) - { - if (sessionId.IsNullOrEmpty()) return null; + return FindAll(_.UserID == userId); + } - if (cache) - { - //// 实体缓存 - //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.SessionID.EqualIgnoreCase(sessionId)); + /// 根据会话查找 + /// 会话 + /// 是否从缓存查询 + /// 实体列表 + public static UserOnline FindBySessionID(String sessionId, Boolean cache = true) + { + if (sessionId.IsNullOrEmpty()) return null; - return Meta.SingleCache.GetItemWithSlaveKey(sessionId) as UserOnline; - } + if (cache) + { + //// 实体缓存 + //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.SessionID.EqualIgnoreCase(sessionId)); - return Find(_.SessionID == sessionId); + return Meta.SingleCache.GetItemWithSlaveKey(sessionId) as UserOnline; } + return Find(_.SessionID == sessionId); + } + /// 根据会话查找 /// 会话 /// 实体列表 @@ -122,52 +119,51 @@ public static IList FindAllBySessionID(String sessionId) return FindAll(_.SessionID == sessionId); } - #endregion - - #region 高级查询 - /// 高级查询 - /// 用户 - /// 会话。Web的SessionID或Server的会话编号 - /// 创建时间开始 - /// 创建时间结束 - /// 关键字 - /// 分页参数信息。可携带统计和数据权限扩展查询等信息 - /// 实体列表 - public static IList Search(Int32 userId, String sessionId, DateTime start, DateTime end, String key, PageParameter page) - { - var exp = new WhereExpression(); + #endregion + + #region 高级查询 + /// 高级查询 + /// 用户 + /// 会话。Web的SessionID或Server的会话编号 + /// 创建时间开始 + /// 创建时间结束 + /// 关键字 + /// 分页参数信息。可携带统计和数据权限扩展查询等信息 + /// 实体列表 + public static IList Search(Int32 userId, String sessionId, DateTime start, DateTime end, String key, PageParameter page) + { + var exp = new WhereExpression(); - if (userId >= 0) exp &= _.UserID == userId; - if (!sessionId.IsNullOrEmpty()) exp &= _.SessionID == sessionId; - exp &= _.CreateTime.Between(start, end); - if (!key.IsNullOrEmpty()) exp &= _.Name.Contains(key) | _.SessionID.Contains(key) | _.Page.Contains(key) | _.Status.Contains(key) | _.CreateIP.Contains(key) | _.UpdateIP.Contains(key); + if (userId >= 0) exp &= _.UserID == userId; + if (!sessionId.IsNullOrEmpty()) exp &= _.SessionID == sessionId; + exp &= _.CreateTime.Between(start, end); + if (!key.IsNullOrEmpty()) exp &= _.Name.Contains(key) | _.SessionID.Contains(key) | _.Page.Contains(key) | _.Status.Contains(key) | _.CreateIP.Contains(key) | _.UpdateIP.Contains(key); - return FindAll(exp, page); - } + return FindAll(exp, page); + } - //// Select Count(ID) as ID,SessionID From UserOnline Where CreateTime>'2020-01-24 00:00:00' Group By SessionID Order By ID Desc limit 20 - //private static readonly FieldCache _SessionIDCache = new FieldCache(nameof(SessionID)) - //{ - // //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty - //}; - - ///// 获取会话列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择 - ///// - //public static IDictionary GetSessionIDList() => _SessionIDCache.FindAllName(); - #endregion - - #region 业务操作 - /// - /// 设置错误 - /// - /// - public void SetError(String error) - { - Status = error; - LastError = DateTime.Now; + //// Select Count(ID) as ID,SessionID From UserOnline Where CreateTime>'2020-01-24 00:00:00' Group By SessionID Order By ID Desc limit 20 + //private static readonly FieldCache _SessionIDCache = new FieldCache(nameof(SessionID)) + //{ + // //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty + //}; + + ///// 获取会话列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择 + ///// + //public static IDictionary GetSessionIDList() => _SessionIDCache.FindAllName(); + #endregion + + #region 业务操作 + /// + /// 设置错误 + /// + /// + public void SetError(String error) + { + Status = error; + LastError = DateTime.Now; - SaveAsync(3_000); - } - #endregion + SaveAsync(3_000); } + #endregion } \ No newline at end of file diff --git a/NewLife.Cube/WebMiddleware/RunTimeMiddleware.cs b/NewLife.Cube/WebMiddleware/RunTimeMiddleware.cs index 8a763260..6b7a85e4 100644 --- a/NewLife.Cube/WebMiddleware/RunTimeMiddleware.cs +++ b/NewLife.Cube/WebMiddleware/RunTimeMiddleware.cs @@ -22,7 +22,7 @@ public class RunTimeMiddleware private readonly UserService _userService; /// 会话提供者 - static readonly SessionProvider _sessionProvider = new SessionProvider(); + static readonly SessionProvider _sessionProvider = new(); /// 实例化 /// @@ -48,8 +48,8 @@ public async Task Invoke(HttpContext ctx) var ip = ctx.GetUserHost(); ManageProvider.UserHost = ip; - //// 创建Session集合 - //var token = CreateSession(ctx); + // 创建Session集合 + var (token, session) = CreateSession(ctx); var inf = new RunTimeInfo(); ctx.Items[nameof(RunTimeInfo)] = inf; @@ -65,14 +65,14 @@ public async Task Invoke(HttpContext ctx) // 设计时收集执行的SQL语句 if (SysConfig.Current.Develop) { - inf.Sqls = new List(); + inf.Sqls = []; DAL.LocalFilter = s => inf.Sqls.Add(s); } // 日志控制,精确标注Web类型线程 WriteLogEventArgs.Current.IsWeb = true; - UserOnline olt = null; + var online = session["Online"] as UserOnline; var user = ManageProvider.User; try { @@ -85,16 +85,17 @@ public async Task Invoke(HttpContext ctx) var deviceId = FillDeviceId(ctx); //var sessionId = token?.MD5_16() ?? ip; var sessionId = deviceId; - olt = _userService.SetWebStatus(sessionId, deviceId, p, userAgent, ua, user, ip); + online = _userService.SetWebStatus(online, sessionId, deviceId, p, userAgent, ua, user, ip); //FillDeviceId(ctx, olt); - ctx.Items["Cube_Online"] = olt; + session["Online"] = online; + ctx.Items["Cube_Online"] = online; } await _next.Invoke(ctx); } catch (Exception ex) { var uri = ctx.Request.GetRawUrl(); - olt?.SetError(ex.Message); + online?.SetError(ex.Message); XTrace.Log.Error("[{0}]的错误[{1}] {2}", uri, ip, ctx.TraceIdentifier); @@ -132,8 +133,7 @@ public async Task Invoke(HttpContext ctx) /// public static String GetInfo(HttpContext ctx) { - var rtinf = ctx.Items[nameof(RunTimeInfo)] as RunTimeInfo; - if (rtinf == null) return null; + if (ctx.Items[nameof(RunTimeInfo)] is not RunTimeInfo rtinf) return null; var inf = String.Format(DbRunTimeFormat, DAL.QueryTimes - rtinf.QueryTimes, @@ -150,29 +150,30 @@ public static String GetInfo(HttpContext ctx) return inf; } - //private static String CreateSession(HttpContext ctx) - //{ - // // 准备Session - // var ss = ctx.Session; - // if (ss != null) - // { - // //var token = Request.Cookies["Token"]; - // var token = ss.GetString("Cube_Token"); - // if (token.IsNullOrEmpty()) - // { - // token = Rand.NextString(16); - // //Response.Cookies.Append("Token", token, new CookieOptions { }); - // ss.SetString("Cube_Token", token); - // } - - // //Session = _sessionProvider.GetSession(ss.Id); - // ctx.Items["Session"] = _sessionProvider.GetSession(token); - - // return token; - // } - - // return null; - //} + private static (String, IDictionary) CreateSession(HttpContext ctx) + { + // 准备Session + var ss = ctx.Session; + if (ss != null) + { + //var token = Request.Cookies["Token"]; + var token = ss.GetString("Cube_Token"); + if (token.IsNullOrEmpty()) + { + token = Rand.NextString(16); + //Response.Cookies.Append("Token", token, new CookieOptions { }); + ss.SetString("Cube_Token", token); + } + + //Session = _sessionProvider.GetSession(ss.Id); + var session = _sessionProvider.GetSession(token); + ctx.Items["Session"] = session; + + return (token, session); + } + + return (null, null); + } /// 忽略的后缀 public static String[] ExcludeSuffixes { get; set; } = new[] { diff --git a/NewLife.CubeNC/Services/UserService.cs b/NewLife.CubeNC/Services/UserService.cs index fb9ccc6f..962ef922 100644 --- a/NewLife.CubeNC/Services/UserService.cs +++ b/NewLife.CubeNC/Services/UserService.cs @@ -44,6 +44,7 @@ private void StartTimer() #region 用户在线 /// 设置会话状态 + /// /// /// /// @@ -53,54 +54,58 @@ private void StartTimer() /// /// /// - public UserOnline SetStatus(String sessionId, String deviceId, String page, String status, UserAgentParser userAgent, Int32 userid = 0, String name = null, String ip = null) + public UserOnline SetStatus(UserOnline online, String sessionId, String deviceId, String page, String status, UserAgentParser userAgent, Int32 userid = 0, String name = null, String ip = null) { + if (online != null && online.SessionID != sessionId) online = null; + // LastError 设计缺陷,非空设计导致无法在插入中忽略 - var entity = UserOnline.GetOrAdd(sessionId, UserOnline.FindBySessionID, k => new UserOnline + online ??= UserOnline.GetOrAdd(sessionId, UserOnline.FindBySessionID, k => new UserOnline { SessionID = k, LastError = new DateTime(1970, 1, 2),//MSSql不能使用1973年之前的日期 CreateIP = ip, CreateTime = DateTime.Now }); - //var entity = FindBySessionID(sessionid) ?? new UserOnline(); - //entity.SessionID = sessionid; - entity.DeviceId = deviceId; - entity.Page = page; + //var online = FindBySessionID(sessionid) ?? new UserOnline(); + //online.SessionID = sessionid; + online.DeviceId = deviceId; + online.Page = page; if (userAgent != null) { - entity.Platform = userAgent.Platform; - entity.OS = userAgent.OSorCPU; + online.Platform = userAgent.Platform; + online.OS = userAgent.OSorCPU; if (userAgent.Device.IsNullOrEmpty() || !userAgent.DeviceBuild.IsNullOrEmpty() && userAgent.DeviceBuild.Contains(userAgent.Device)) - entity.Device = userAgent.DeviceBuild; + online.Device = userAgent.DeviceBuild; else - entity.Device = userAgent.Device; - entity.Brower = userAgent.Brower; - entity.NetType = userAgent.NetType; + online.Device = userAgent.Device; + online.Brower = userAgent.Brower; + online.NetType = userAgent.NetType; } - if (!status.IsNullOrEmpty() || entity.LastError.AddMinutes(3) < DateTime.Now) entity.Status = status; + if (!status.IsNullOrEmpty() || online.LastError.AddMinutes(3) < DateTime.Now) online.Status = status; - entity.Times++; - if (userid > 0) entity.UserID = userid; - if (!name.IsNullOrEmpty()) entity.Name = name; + online.Times++; + if (userid > 0) online.UserID = userid; + if (!name.IsNullOrEmpty()) online.Name = name; - entity.Address = ip.IPToAddress(); + online.Address = ip.IPToAddress(); // 累加在线时间 - entity.UpdateTime = DateTime.Now; - entity.UpdateIP = ip; - entity.OnlineTime = (Int32)(entity.UpdateTime - entity.CreateTime).TotalSeconds; - entity.TraceId = DefaultSpan.Current?.TraceId; - entity.SaveAsync(5_000); + online.UpdateTime = DateTime.Now; + online.UpdateIP = ip; + online.OnlineTime = (Int32)(online.UpdateTime - online.CreateTime).TotalSeconds; + online.TraceId = DefaultSpan.Current?.TraceId; + online.SaveAsync(5_000); - Interlocked.Increment(ref _onlines); + if (_onlines == 0 || online.Times <= 1) + Interlocked.Increment(ref _onlines); - return entity; + return online; } /// 设置网页会话状态 + /// /// /// /// @@ -109,12 +114,12 @@ public UserOnline SetStatus(String sessionId, String deviceId, String page, Stri /// /// /// - public UserOnline SetWebStatus(String sessionId, String deviceId, String page, String status, UserAgentParser userAgent, IUser user, String ip) + public UserOnline SetWebStatus(UserOnline online, String sessionId, String deviceId, String page, String status, UserAgentParser userAgent, IUser user, String ip) { // 网页使用一个定时器来清理过期 StartTimer(); - if (user == null) return SetStatus(sessionId, deviceId, page, status, userAgent, 0, null, ip); + if (user == null) return SetStatus(online, sessionId, deviceId, page, status, userAgent, 0, null, ip); // 根据IP修正用户城市 if (user is User user2 && (user2.AreaId == 0 || user2.AreaId % 10000 == 0)) @@ -134,7 +139,7 @@ public UserOnline SetWebStatus(String sessionId, String deviceId, String page, S } } - return SetStatus(sessionId, deviceId, page, status, userAgent, user.ID, user + "", ip); + return SetStatus(online, sessionId, deviceId, page, status, userAgent, user.ID, user + "", ip); } /// 删除过期,指定过期时间 @@ -143,7 +148,7 @@ public UserOnline SetWebStatus(String sessionId, String deviceId, String page, S public IList ClearExpire(Int32 secTimeout = 20 * 60) { // 无在线则不执行 - if (_onlines == 0) return new List(); + if (_onlines == 0) return []; using var span = _tracer?.NewSpan("ClearExpireOnline"); @@ -162,19 +167,23 @@ public IList ClearExpire(Int32 secTimeout = 20 * 60) // 修正在线数 var total = UserOnline.Meta.Count; - _onlines = total - list.Count; // 设置统计 UserStat stat = null; - if (set.EnableUserStat) + if (total != _onlines || list.Count > 0) { - stat = UserStat.GetOrAdd(DateTime.Today); - if (stat != null) + if (set.EnableUserStat) { - if (total > stat.MaxOnline) stat.MaxOnline = total; + stat = UserStat.GetOrAdd(DateTime.Today); + if (stat != null) + { + if (total > stat.MaxOnline) stat.MaxOnline = total; + } } } + _onlines = total - list.Count; + // 设置离线 foreach (var item in list) { diff --git a/NewLife.CubeNC/WebMiddleware/RunTimeMiddleware.cs b/NewLife.CubeNC/WebMiddleware/RunTimeMiddleware.cs index 78c24f69..533e37e2 100644 --- a/NewLife.CubeNC/WebMiddleware/RunTimeMiddleware.cs +++ b/NewLife.CubeNC/WebMiddleware/RunTimeMiddleware.cs @@ -22,7 +22,7 @@ public class RunTimeMiddleware private readonly UserService _userService; /// 会话提供者 - static readonly SessionProvider _sessionProvider = new SessionProvider(); + static readonly SessionProvider _sessionProvider = new(); /// 实例化 /// @@ -50,7 +50,7 @@ public async Task Invoke(HttpContext ctx) ManageProvider.UserHost = ip; // 创建Session集合 - var token = CreateSession(ctx); + var (token, session) = CreateSession(ctx); var inf = new RunTimeInfo(); ctx.Items[nameof(RunTimeInfo)] = inf; @@ -66,14 +66,14 @@ public async Task Invoke(HttpContext ctx) // 设计时收集执行的SQL语句 if (SysConfig.Current.Develop) { - inf.Sqls = new List(); + inf.Sqls = []; DAL.LocalFilter = s => inf.Sqls.Add(s); } // 日志控制,精确标注Web类型线程 WriteLogEventArgs.Current.IsWeb = true; - UserOnline olt = null; + var online = session["Online"] as UserOnline; var user = ManageProvider.User; try { @@ -86,16 +86,17 @@ public async Task Invoke(HttpContext ctx) var deviceId = FillDeviceId(ctx); //var sessionId = token?.MD5_16() ?? ip; var sessionId = deviceId; - olt = _userService.SetWebStatus(sessionId, deviceId, p, userAgent, ua, user, ip); + online = _userService.SetWebStatus(online, sessionId, deviceId, p, userAgent, ua, user, ip); //FillDeviceId(ctx, olt); - ctx.Items["Cube_Online"] = olt; + session["Online"] = online; + ctx.Items["Cube_Online"] = online; } await _next.Invoke(ctx); } catch (Exception ex) { var uri = ctx.Request.GetRawUrl(); - olt?.SetError(ex.Message); + online?.SetError(ex.Message); XTrace.Log.Error("[{0}]的错误[{1}] {2}", uri, ip, ctx.TraceIdentifier); @@ -133,8 +134,7 @@ public async Task Invoke(HttpContext ctx) /// public static String GetInfo(HttpContext ctx) { - var rtinf = ctx.Items[nameof(RunTimeInfo)] as RunTimeInfo; - if (rtinf == null) return null; + if (ctx.Items[nameof(RunTimeInfo)] is not RunTimeInfo rtinf) return null; var inf = String.Format(DbRunTimeFormat, DAL.QueryTimes - rtinf.QueryTimes, @@ -151,7 +151,7 @@ public static String GetInfo(HttpContext ctx) return inf; } - private static String CreateSession(HttpContext ctx) + private static (String, IDictionary) CreateSession(HttpContext ctx) { // 准备Session var ss = ctx.Session; @@ -167,12 +167,13 @@ private static String CreateSession(HttpContext ctx) } //Session = _sessionProvider.GetSession(ss.Id); - ctx.Items["Session"] = _sessionProvider.GetSession(token); + var session = _sessionProvider.GetSession(token); + ctx.Items["Session"] = session; - return token; + return (token, session); } - return null; + return (null, null); } /// 忽略的后缀