😎调整在线用户列表为缓存模式(去掉存库模式)

This commit is contained in:
zuohuaijun 2025-08-15 14:30:23 +08:00
parent 511be92199
commit 12c8e240bc
28 changed files with 246 additions and 691 deletions

View File

@ -28,6 +28,5 @@ public class Startup : AppStartup
/// <param name="componentContext"></param>
public void LoadAppComponent(object application, IWebHostEnvironment env, ComponentContext componentContext)
{
WebApplication webApplication = application as WebApplication;
}
}

View File

@ -27,10 +27,10 @@
<PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
<PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" Aliases="BouncyCastleV2" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.3" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.108" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.108" />
<PackageReference Include="Furion.Pure" Version="4.9.7.108" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.4" />
<PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.109" />
<PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.109" />
<PackageReference Include="Furion.Pure" Version="4.9.7.109" />
<PackageReference Include="Hardware.Info" Version="101.0.1.1" />
<PackageReference Include="Hashids.net" Version="1.7.0" />
<PackageReference Include="IPTools.China" Version="1.6.0" />
@ -52,7 +52,7 @@
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.11.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.13.0" />
<PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.247" />
<PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.248" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.199" />
<PackageReference Include="SSH.NET" Version="2025.0.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.7" />

View File

@ -1,81 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 系统在线用户表
/// </summary>
[SugarTable(null, "系统在线用户表")]
[SysTable]
public partial class SysOnlineUser : EntityTenantId
{
/// <summary>
/// 连接Id
/// </summary>
[SugarColumn(ColumnDescription = "连接Id")]
public string? ConnectionId { get; set; }
/// <summary>
/// 用户Id
/// </summary>
[SugarColumn(ColumnDescription = "用户Id")]
public long UserId { get; set; }
/// <summary>
/// 账号
/// </summary>
[SugarColumn(ColumnDescription = "账号", Length = 32)]
[Required, MaxLength(32)]
public virtual string UserName { get; set; }
/// <summary>
/// 真实姓名
/// </summary>
[SugarColumn(ColumnDescription = "真实姓名", Length = 32)]
[MaxLength(32)]
public string? RealName { get; set; }
/// <summary>
/// 连接时间
/// </summary>
[SugarColumn(ColumnDescription = "连接时间")]
public DateTime? Time { get; set; }
/// <summary>
/// 连接IP
/// </summary>
[SugarColumn(ColumnDescription = "连接IP", Length = 256)]
[MaxLength(256)]
public string? Ip { get; set; }
/// <summary>
/// 浏览器
/// </summary>
[SugarColumn(ColumnDescription = "浏览器", Length = 128)]
[MaxLength(128)]
public string? Browser { get; set; }
/// <summary>
/// 操作系统
/// </summary>
[SugarColumn(ColumnDescription = "操作系统", Length = 128)]
[MaxLength(128)]
public string? Os { get; set; }
/// <summary>
/// 登录模式
/// </summary>
[SugarColumn(ColumnDescription = "登录模式", DefaultValue = "1")]
public LoginModeEnum LoginMode { get; set; } = LoginModeEnum.PC;
/// <summary>
/// 登录设备
/// </summary>
[SugarColumn(ColumnDescription = "登录设备", Length = 256)]
[MaxLength(256)]
public string? Device { get; set; }
}

View File

@ -12,5 +12,5 @@ public class OnlineUserList
public bool Online { get; set; }
public List<SysOnlineUser> UserList { get; set; }
public List<OnlineUser> UserList { get; set; }
}

View File

@ -17,15 +17,12 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
{
private const string GROUP_ONLINE = "GROUP_ONLINE_"; // 租户分组前缀
private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
private readonly SysMessageService _sysMessageService;
private readonly IHubContext<OnlineUserHub, IOnlineUserHub> _onlineUserHubContext;
public OnlineUserHub(SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
SysMessageService sysMessageService,
public OnlineUserHub(SysMessageService sysMessageService,
IHubContext<OnlineUserHub, IOnlineUserHub> onlineUserHubContext)
{
_sysOnlineUerRep = sysOnlineUerRep;
_sysMessageService = sysMessageService;
_onlineUserHubContext = onlineUserHubContext;
}
@ -48,7 +45,7 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
var device = httpContext.GetClientDeviceInfo().Trim();
var ipAddress = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
var user = new SysOnlineUser
var user = new OnlineUser
{
ConnectionId = Context.ConnectionId,
UserId = userId,
@ -62,15 +59,14 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
LoginMode = loginMode,
Device = device
};
await _sysOnlineUerRep.InsertAsync(user);
SysCacheService.HashAdd(CacheConst.KeyUserOnline, user.UserId + Context.ConnectionId + loginMode, user);
SysCacheService.HashAdd(CacheConst.KeyUserOnline, Context.ConnectionId, user);
// 以租户Id进行分组
var groupName = $"{GROUP_ONLINE}{user.TenantId}";
await _onlineUserHubContext.Groups.AddToGroupAsync(Context.ConnectionId, groupName);
var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true)
.Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync();
// 更新在线用户列表
var userList = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline).Where(u => u.Value.TenantId == tenantId).Select(u => u.Value).ToList();
await _onlineUserHubContext.Clients.Groups(groupName).OnlineUserList(new OnlineUserList
{
RealName = user.RealName,
@ -88,14 +84,13 @@ public class OnlineUserHub : Hub<IOnlineUserHub>
{
if (string.IsNullOrEmpty(Context.ConnectionId)) return;
var user = await _sysOnlineUerRep.AsQueryable().Filter("", true).FirstAsync(u => u.ConnectionId == Context.ConnectionId);
var user = SysCacheService.HashGetOne<OnlineUser>(CacheConst.KeyUserOnline, Context.ConnectionId);
if (user == null) return;
await _sysOnlineUerRep.DeleteByIdAsync(user.Id);
SysCacheService.HashDel<SysOnlineUser>(CacheConst.KeyUserOnline, user.UserId + Context.ConnectionId + user.LoginMode);
SysCacheService.HashDel<OnlineUser>(CacheConst.KeyUserOnline, Context.ConnectionId);
// 通知当前组用户变动
var userList = await _sysOnlineUerRep.AsQueryable().Filter("", true).Where(u => u.TenantId == user.TenantId).Take(10).ToListAsync();
// 更新在线用户列表
var userList = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline).Where(u => u.Value.TenantId == user.TenantId).Select(u => u.Value).ToList();
await _onlineUserHubContext.Clients.Groups($"{GROUP_ONLINE}{user.TenantId}").OnlineUserList(new OnlineUserList
{
RealName = user.RealName,

View File

@ -1,32 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core;
/// <summary>
/// 清理在线用户作业任务(每隔 60秒 执行)
/// </summary>
[JobDetail("job_onlineUser", Description = "清理在线用户", GroupName = "default", Concurrent = false)]
[PeriodSeconds(60, TriggerId = "trigger_onlineUser", Description = "清理在线用户", RunOnStart = true)]
public class OnlineUserJob(IServiceScopeFactory serviceScopeFactory) : IJob
{
private readonly IServiceScopeFactory _serviceScopeFactory = serviceScopeFactory;
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
using var serviceScope = _serviceScopeFactory.CreateScope();
var sysOnlineUserService = serviceScope.ServiceProvider.GetRequiredService<SysOnlineUserService>();
await sysOnlineUserService.ClearOnline();
var originColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Green;
var message = $"【定时任务】清理系统在线用户 {DateTime.Now}";
Console.WriteLine(message);
Log.Information(message);
Console.ForegroundColor = originColor;
}
}

View File

@ -36,8 +36,7 @@ public class StartHostedService(IServiceScopeFactory serviceScopeFactory) : IHos
Log.Information(message);
// 清理在线用户
var db = serviceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>().CopyNew();
await db.Deleteable<SysOnlineUser>().ExecuteCommandAsync(cancellationToken);
serviceScope.ServiceProvider.GetRequiredService<SysCacheService>().Remove(CacheConst.KeyUserOnline);
message = $"【启动任务】清理系统在线用户 {DateTime.Now}";
Console.WriteLine(message);
Log.Information(message);

View File

@ -103,7 +103,7 @@ public class SysCacheService : IDynamicApiController, ISingleton
{
var key = Key(cacheName, obs);
// 使用分布式锁
using (_cacheProvider.Cache.AcquireLock($@"lock:AdGetAsync:{cacheName}", 1000))
using (_cacheProvider.Cache.AcquireLock($@"lock:AdGetAsync:{cacheName}", expiry != null ? (int)expiry.Value.TotalSeconds * 1000 : 1000))
{
var value = Get<T>(key);
value ??= await ((dynamic)del).DynamicInvokeAsync(obs);

View File

@ -43,7 +43,7 @@ public class SysMessageService : IDynamicApiController, ITransient
[DisplayName("发送消息给某人")]
public async Task SendUser(MessageInput input)
{
var hashKey = SysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
var hashKey = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline);
var sendUser = hashKey.Where(u => u.Value.UserId == input.SendUserId).Select(u => u.Value).FirstOrDefault();
var receiveUsers = hashKey.Where(u => input.ReceiveUserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
foreach (var receiveUser in receiveUsers)

View File

@ -0,0 +1,68 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 在线用户
/// </summary>
public class OnlineUser
{
/// <summary>
/// 连接Id
/// </summary>
public string ConnectionId { get; set; }
/// <summary>
/// 租户Id
/// </summary>
public long TenantId { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 账号
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 真实姓名
/// </summary>
public string RealName { get; set; }
/// <summary>
/// 上线时间
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// 登录IP
/// </summary>
public string Ip { get; set; }
/// <summary>
/// 浏览器
/// </summary>
public string Browser { get; set; }
/// <summary>
/// 操作系统
/// </summary>
public string Os { get; set; }
/// <summary>
/// 登录模式
/// </summary>
public LoginModeEnum LoginMode { get; set; } = LoginModeEnum.PC;
/// <summary>
/// 登录设备
/// </summary>
public string Device { get; set; }
}

View File

@ -14,30 +14,28 @@ namespace Admin.NET.Core.Service;
[ApiDescriptionSettings(Order = 300, Description = "在线用户")]
public class SysOnlineUserService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysOnlineUser> _sysOnlineUerRep;
private readonly UserManager _userManager;
private readonly SysConfigService _sysConfigService;
private readonly IHubContext<OnlineUserHub, IOnlineUserHub> _onlineUserHubContext;
public SysOnlineUserService(SqlSugarRepository<SysOnlineUser> sysOnlineUerRep,
public SysOnlineUserService(UserManager userManager,
SysConfigService sysConfigService,
IHubContext<OnlineUserHub, IOnlineUserHub> onlineUserHubContext)
{
_userManager = userManager;
_sysConfigService = sysConfigService;
_onlineUserHubContext = onlineUserHubContext;
_sysOnlineUerRep = sysOnlineUerRep;
}
/// <summary>
/// 获取在线用户分页列表 🔖
/// 获取在线用户列表 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取在线用户分页列表")]
public async Task<SqlSugarPagedList<SysOnlineUser>> Page(PageOnlineUserInput input)
[DisplayName("获取在线用户列表")]
public async Task<List<OnlineUser>> GetOnlineUserList()
{
return await _sysOnlineUerRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.UserName), u => u.UserName.Contains(input.UserName))
.WhereIF(!string.IsNullOrWhiteSpace(input.RealName), u => u.RealName.Contains(input.RealName))
.ToPagedListAsync(input.Page, input.PageSize);
var userList = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline).Where(u => u.Value.TenantId == _userManager.TenantId).Select(u => u.Value).ToList();
return await Task.FromResult(userList);
}
/// <summary>
@ -47,10 +45,24 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
/// <returns></returns>
[NonValidation]
[DisplayName("强制下线")]
public async Task ForceOffline(SysOnlineUser user)
public async Task ForceOffline(OnlineUser user)
{
await _onlineUserHubContext.Clients.Client(user.ConnectionId ?? "").ForceOffline("强制下线");
await _sysOnlineUerRep.DeleteAsync(user);
}
/// <summary>
/// 通过用户Id踢掉在线用户
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[NonAction]
public async Task ForceOfflineByUserId(long userId)
{
var users = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline).Where(u => u.Value.UserId == userId).Select(u => u.Value).ToList();
foreach (var user in users)
{
await ForceOffline(user);
}
}
/// <summary>
@ -62,7 +74,9 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
[NonAction]
public async Task PublicNotice(SysNotice notice, List<long> userIds)
{
var userList = await _sysOnlineUerRep.GetListAsync(u => userIds.Contains(u.UserId));
var userList = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline)
.Where(u => userIds.Contains(u.Value.UserId))
.Select(u => u.Value).ToList();
if (userList.Count == 0) return;
foreach (var item in userList)
@ -80,61 +94,15 @@ public class SysOnlineUserService : IDynamicApiController, ITransient
[NonAction]
public async Task SingleLogin(long userId, LoginModeEnum loginMode)
{
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysSingleLogin))
if (!await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysSingleLogin)) return;
var users = SysCacheService.HashGetAll<OnlineUser>(CacheConst.KeyUserOnline).Where(u => u.Value.UserId == userId).Select(u => u.Value).ToList();
foreach (var user in users)
{
var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId);
foreach (var user in users)
if (user.LoginMode == loginMode)
{
if (loginMode == user.LoginMode)
{
await ForceOffline(user);
}
await ForceOffline(user);
}
}
}
/// <summary>
/// 通过用户ID踢掉在线用户
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[NonAction]
public async Task ForceOfflineByUserId(long userId)
{
var users = await _sysOnlineUerRep.GetListAsync(u => u.UserId == userId);
foreach (var user in users)
{
await ForceOffline(user);
}
}
/// <summary>
/// 清理在线用户(开启单设备登录时只留相同账号最后登录的)
/// </summary>
/// <returns></returns>
[HttpPost]
[DisplayName("清理在线用户")]
public async Task ClearOnline()
{
if (!await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysSingleLogin)) return;
// 相同账号最后登录的用户Id集合
var onlineUsers = await _sysOnlineUerRep.AsQueryable().GroupBy(u => u.UserId)
.Select(u => new
{
UserId = u.UserId,
Count = SqlFunc.AggregateCount(u.UserId),
Id = SqlFunc.AggregateMax(u.Id)
})
.ToListAsync();
if (onlineUsers.Count < 1) return;
// 无效登录用户集合
var onlineUserIds = onlineUsers.Select(u => u.Id).ToList();
var offlineUsers = await _sysOnlineUerRep.AsQueryable().Where(u => !onlineUserIds.Contains(u.Id)).ToListAsync();
foreach (var user in offlineUsers)
{
await ForceOffline(user);
}
}
}

View File

@ -196,7 +196,7 @@ public class SysReportConfigService : IDynamicApiController, ITransient
{
summaryInfo[summaryFieldName] = dataTable.AsEnumerable().Sum(row =>
{
decimal.TryParse(row[summaryFieldName] + "", out decimal summaryValue);
_ = decimal.TryParse(row[summaryFieldName] + "", out decimal summaryValue);
return summaryValue;
});
}
@ -218,9 +218,9 @@ public class SysReportConfigService : IDynamicApiController, ITransient
var dataSourceDetailList = await _sysReportDataSourceService.GetDataSourceListIncludeDetail();
var dataSourceDetail = dataSourceDetailList.FirstOrDefault(u => u.Id == dataSource) ?? throw Oops.Bah(ErrorCodeEnum.C1002);
ISqlSugarClient dbClient = GetDbClient(dataSourceDetail);
SqlSugarScopeProvider dbClient = GetDbClient(dataSourceDetail);
var newExecParams = BuildInParamsHandle(dbClient, sqlScript, execParams);
var newExecParams = BuildInParamsHandle(sqlScript, execParams);
var parameters = newExecParams.Select(u => new SugarParameter(u.Key, u.Value)).ToList();
if (isSelectQuery)
@ -242,9 +242,9 @@ public class SysReportConfigService : IDynamicApiController, ITransient
/// </summary>
/// <param name="dataSourceDetail"></param>
/// <returns></returns>
private ISqlSugarClient GetDbClient(SysReportDataSourceDetail dataSourceDetail)
private SqlSugarScopeProvider GetDbClient(SysReportDataSourceDetail dataSourceDetail)
{
ISqlSugarClient dbClient = null;
SqlSugarScopeProvider dbClient = null;
if (dataSourceDetail.IsBuildIn)
{
// 获取内置数据库和租户的连接
@ -287,7 +287,7 @@ public class SysReportConfigService : IDynamicApiController, ITransient
/// <summary>
/// 内置参数处理
/// </summary>
private Dictionary<string, object> BuildInParamsHandle(ISqlSugarClient dbClient, string sqlScript, Dictionary<string, object> execParams)
private Dictionary<string, object> BuildInParamsHandle(string sqlScript, Dictionary<string, object> execParams)
{
var newExecParams = new Dictionary<string, object>(execParams);
@ -380,7 +380,7 @@ public class SysReportConfigService : IDynamicApiController, ITransient
int colIndex = fields.FindIndex(f => f.FieldName == field.FieldName) + 1;
decimal sum = data.Sum(r =>
{
decimal.TryParse(r[field.FieldName]?.ToString(), out decimal val);
_ = decimal.TryParse(r[field.FieldName]?.ToString(), out decimal val);
return val;
});
worksheet.Cells[currentRow, colIndex].Value = sum;

View File

@ -320,7 +320,7 @@ public class SysRoleService : IDynamicApiController, ITransient
[DisplayName("设置角色状态")]
public async Task<int> SetStatus(RoleInput input)
{
if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005);
if (!Enum.IsDefined(input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005);
return await _sysRoleRep.AsUpdateable()
.SetColumns(u => u.Status == input.Status)

View File

@ -264,7 +264,7 @@ public class SysUserService : IDynamicApiController, ITransient
var user = await _sysUserRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009);
user.ValidateIsSuperAdminAccountType(ErrorCodeEnum.D1015);
if (!Enum.IsDefined(typeof(StatusEnum), input.Status))
if (!Enum.IsDefined(input.Status))
throw Oops.Oh(ErrorCodeEnum.D3005);
if (input.Status != StatusEnum.Enable)

View File

@ -521,9 +521,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient
[DisplayName("微信退款申请)")]
public async Task Refund(RefundRequestInput input)
{
var vechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber);
if (vechatPay == null)
throw Oops.Bah("没有相应支付记录");
var vechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber) ?? throw Oops.Bah("没有相应支付记录");
var request = new CreateRefundDomesticRefundRequest()
{

View File

@ -6,6 +6,7 @@
using NewLife.IO;
using NewLife.Reflection;
using SixLabors.ImageSharp.Processing;
using System.Text.Json;
namespace Admin.NET.Core;
@ -133,7 +134,7 @@ public static class SqlSugarExtension
Expression binaryExpresioFilter;
if (Enum.IsDefined(typeof(FilterLogicEnum), filter.Logic))
if (Enum.IsDefined(filter.Logic))
{
if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
binaryExpresioFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
@ -151,7 +152,7 @@ public static class SqlSugarExtension
return queryable;
}
private static Expression CombineFilter(FilterLogicEnum filterLogic, Expression bExpresionBase, Expression bExpresion)
private static BinaryExpression CombineFilter(FilterLogicEnum filterLogic, Expression bExpresionBase, Expression bExpresion)
{
return filterLogic switch
{
@ -177,7 +178,7 @@ public static class SqlSugarExtension
{
Expression bExpresionFilter;
if (Enum.IsDefined(typeof(FilterLogicEnum), filter.Logic))
if (Enum.IsDefined(filter.Logic))
{
if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic");
bExpresionFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
@ -296,7 +297,7 @@ public static class SqlSugarExtension
return (MemberExpression)propertyExpression;
}
private static Expression AddSearchPropertyByKeyword<T>(Expression propertyExpr, string keyword, FilterOperatorEnum operatorSearch = FilterOperatorEnum.Contains)
private static MethodCallExpression AddSearchPropertyByKeyword<T>(Expression propertyExpr, string keyword, FilterOperatorEnum operatorSearch = FilterOperatorEnum.Contains)
{
if (propertyExpr is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo property)
throw new ArgumentException("propertyExpr must be a property expression.", nameof(propertyExpr));
@ -493,7 +494,7 @@ public static class SqlSugarExtension
ArgumentNullException.ThrowIfNull(queryable);
// 获取实体映射信息
var entityInfo = queryable.Context.EntityMaintenance.GetEntityInfo(typeof(T));
var entityInfo = queryable.Context.EntityMaintenance.GetEntityInfo<T>();
if (entityInfo?.Columns == null || entityInfo.Columns.Count == 0) return queryable.ToSqlString();
// 构建需要替换的字段名映射(只处理实际有差异的字段)
@ -572,7 +573,7 @@ public static class SqlSugarExtension
.Select(p => (p, p.GetCustomAttribute<BindTextAbbrAttribute>()!))
.ToDictionary(t => t.p.Name, t => (t.p, t.Item2));
return props.Any() ? props : null;
return props.Count != 0 ? props : null;
});
// 无绑定属性或当前属性不匹配时提前结束
@ -631,7 +632,7 @@ public static class SqlSugarExtension
.Select(p => (p, p.GetCustomAttribute<BindSerialAttribute>()!))
.ToDictionary(t => t.p.Name, t => (t.p, t.Item2));
return props.Any() ? props : null;
return props.Count != 0 ? props : null;
});
// 无绑定属性或当前属性不匹配时提前结束

View File

@ -84,18 +84,18 @@ public class IdCardHelper
long n = 0;
if (long.TryParse(idNumber, out n) == false || n < Math.Pow(10, 14))
{
return false;//数字验证
return false; // 数字验证
}
string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
if (address.IndexOf(idNumber.Remove(2)) == -1)
{
return false;//省份验证
return false; // 省份验证
}
string birth = idNumber.Substring(6, 6).Insert(4, "-").Insert(2, "-");
DateTime time = new DateTime();
if (DateTime.TryParse(birth, out time) == false)
{
return false;//生日验证
return false; // 生日验证
}
return true;
}
@ -131,7 +131,7 @@ public class IdCardHelper
string strSex = "";
if (idNumber.Length == 18) strSex = idNumber.Substring(14, 3);
//性别代码为偶数是女性奇数为男性
// 性别代码为偶数是女性奇数为男性
strSex = int.Parse(strSex) % 2 == 0 ? "女" : "男";
return strSex;
}
@ -220,7 +220,7 @@ public class IdCardHelper
DateTime birthDate = DateTime.Parse(birthDay);
DateTime nowDateTime = DateTime.Now;
int _age = nowDateTime.Year - birthDate.Year;
//再考虑月、天的因素
// 再考虑月、天的因素
if (nowDateTime.Month < birthDate.Month || (nowDateTime.Month == birthDate.Month && nowDateTime.Day < birthDate.Day))
{
_age--;

View File

@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
"lastBuildTime": "2025.08.13",
"lastBuildTime": "2025.08.15",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@ -79,8 +79,8 @@
"vue-router": "^4.5.1",
"vue-signature-pad": "^3.0.2",
"vue3-tree-org": "^4.2.2",
"vxe-pc-ui": "^4.8.14",
"vxe-table": "^4.15.7",
"vxe-pc-ui": "^4.8.18",
"vxe-table": "^4.15.8",
"xe-utils": "^3.7.8",
"xlsx-js-style": "^1.2.0"
},
@ -97,7 +97,7 @@
"@vitejs/plugin-vue-jsx": "^5.0.1",
"@vue/compiler-sfc": "^3.5.18",
"cli-progress": "^3.12.0",
"code-inspector-plugin": "^1.0.5",
"code-inspector-plugin": "^1.1.1",
"colors": "^1.4.0",
"dotenv": "^17.2.1",
"eslint": "^9.33.0",

View File

@ -17,9 +17,8 @@ import { Configuration } from '../configuration';
// Some imports not used depending on template conditions
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';
import { AdminNETResultSqlSugarPagedListSysOnlineUser } from '../models';
import { PageOnlineUserInput } from '../models';
import { SysOnlineUser } from '../models';
import { AdminNETResultListOnlineUser } from '../models';
import { OnlineUser } from '../models';
/**
* SysOnlineUserApi - axios parameter creator
* @export
@ -29,11 +28,11 @@ export const SysOnlineUserApiAxiosParamCreator = function (configuration?: Confi
/**
*
* @summary 线 🔖
* @param {SysOnlineUser} [body]
* @param {OnlineUser} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysOnlineUserForceOfflinePost: async (body?: SysOnlineUser, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
apiSysOnlineUserForceOfflinePost: async (body?: OnlineUser, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysOnlineUser/forceOffline`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
@ -76,19 +75,19 @@ export const SysOnlineUserApiAxiosParamCreator = function (configuration?: Confi
},
/**
*
* @summary 线
* @summary 线 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysOnlineUserOnlinePost: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysOnlineUser/online`;
apiSysOnlineUserOnlineUserListGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysOnlineUser/onlineUserList`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
@ -112,54 +111,6 @@ export const SysOnlineUserApiAxiosParamCreator = function (configuration?: Confi
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
};
},
/**
*
* @summary 线 🔖
* @param {PageOnlineUserInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
apiSysOnlineUserPagePost: async (body?: PageOnlineUserInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/sysOnlineUser/page`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, 'https://example.com');
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication Bearer required
// http bearer authentication required
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
}
localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
const query = new URLSearchParams(localVarUrlObj.search);
for (const key in localVarQueryParameter) {
query.set(key, localVarQueryParameter[key]);
}
for (const key in options.params) {
query.set(key, options.params[key]);
}
localVarUrlObj.search = (new URLSearchParams(query)).toString();
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
localVarRequestOptions.data = needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
return {
url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
options: localVarRequestOptions,
@ -177,11 +128,11 @@ export const SysOnlineUserApiFp = function(configuration?: Configuration) {
/**
*
* @summary 线 🔖
* @param {SysOnlineUser} [body]
* @param {OnlineUser} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserForceOfflinePost(body?: SysOnlineUser, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
async apiSysOnlineUserForceOfflinePost(body?: OnlineUser, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
const localVarAxiosArgs = await SysOnlineUserApiAxiosParamCreator(configuration).apiSysOnlineUserForceOfflinePost(body, options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
@ -190,26 +141,12 @@ export const SysOnlineUserApiFp = function(configuration?: Configuration) {
},
/**
*
* @summary 线
* @summary 线 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserOnlinePost(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
const localVarAxiosArgs = await SysOnlineUserApiAxiosParamCreator(configuration).apiSysOnlineUserOnlinePost(options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
};
},
/**
*
* @summary 线 🔖
* @param {PageOnlineUserInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserPagePost(body?: PageOnlineUserInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultSqlSugarPagedListSysOnlineUser>>> {
const localVarAxiosArgs = await SysOnlineUserApiAxiosParamCreator(configuration).apiSysOnlineUserPagePost(body, options);
async apiSysOnlineUserOnlineUserListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminNETResultListOnlineUser>>> {
const localVarAxiosArgs = await SysOnlineUserApiAxiosParamCreator(configuration).apiSysOnlineUserOnlineUserListGet(options);
return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
return axios.request(axiosRequestArgs);
@ -227,31 +164,21 @@ export const SysOnlineUserApiFactory = function (configuration?: Configuration,
/**
*
* @summary 线 🔖
* @param {SysOnlineUser} [body]
* @param {OnlineUser} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserForceOfflinePost(body?: SysOnlineUser, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
async apiSysOnlineUserForceOfflinePost(body?: OnlineUser, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysOnlineUserApiFp(configuration).apiSysOnlineUserForceOfflinePost(body, options).then((request) => request(axios, basePath));
},
/**
*
* @summary 线
* @summary 线 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserOnlinePost(options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
return SysOnlineUserApiFp(configuration).apiSysOnlineUserOnlinePost(options).then((request) => request(axios, basePath));
},
/**
*
* @summary 线 🔖
* @param {PageOnlineUserInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async apiSysOnlineUserPagePost(body?: PageOnlineUserInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultSqlSugarPagedListSysOnlineUser>> {
return SysOnlineUserApiFp(configuration).apiSysOnlineUserPagePost(body, options).then((request) => request(axios, basePath));
async apiSysOnlineUserOnlineUserListGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminNETResultListOnlineUser>> {
return SysOnlineUserApiFp(configuration).apiSysOnlineUserOnlineUserListGet(options).then((request) => request(axios, basePath));
},
};
};
@ -266,33 +193,22 @@ export class SysOnlineUserApi extends BaseAPI {
/**
*
* @summary 线 🔖
* @param {SysOnlineUser} [body]
* @param {OnlineUser} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysOnlineUserApi
*/
public async apiSysOnlineUserForceOfflinePost(body?: SysOnlineUser, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
public async apiSysOnlineUserForceOfflinePost(body?: OnlineUser, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysOnlineUserApiFp(this.configuration).apiSysOnlineUserForceOfflinePost(body, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 线
* @summary 线 🔖
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysOnlineUserApi
*/
public async apiSysOnlineUserOnlinePost(options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
return SysOnlineUserApiFp(this.configuration).apiSysOnlineUserOnlinePost(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary 线 🔖
* @param {PageOnlineUserInput} [body]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof SysOnlineUserApi
*/
public async apiSysOnlineUserPagePost(body?: PageOnlineUserInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultSqlSugarPagedListSysOnlineUser>> {
return SysOnlineUserApiFp(this.configuration).apiSysOnlineUserPagePost(body, options).then((request) => request(this.axios, this.basePath));
public async apiSysOnlineUserOnlineUserListGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminNETResultListOnlineUser>> {
return SysOnlineUserApiFp(this.configuration).apiSysOnlineUserOnlineUserListGet(options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -12,20 +12,20 @@
* Do not edit the class manually.
*/
import { SqlSugarPagedListSysOnlineUser } from './sql-sugar-paged-list-sys-online-user';
import { OnlineUser } from './online-user';
/**
*
*
* @export
* @interface AdminNETResultSqlSugarPagedListSysOnlineUser
* @interface AdminNETResultListOnlineUser
*/
export interface AdminNETResultSqlSugarPagedListSysOnlineUser {
export interface AdminNETResultListOnlineUser {
/**
*
*
* @type {number}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
* @memberof AdminNETResultListOnlineUser
*/
code?: number;
@ -33,7 +33,7 @@ export interface AdminNETResultSqlSugarPagedListSysOnlineUser {
* successwarningerror
*
* @type {string}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
* @memberof AdminNETResultListOnlineUser
*/
type?: string | null;
@ -41,21 +41,23 @@ export interface AdminNETResultSqlSugarPagedListSysOnlineUser {
*
*
* @type {string}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
* @memberof AdminNETResultListOnlineUser
*/
message?: string | null;
/**
* @type {SqlSugarPagedListSysOnlineUser}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
*
*
* @type {Array<OnlineUser>}
* @memberof AdminNETResultListOnlineUser
*/
result?: SqlSugarPagedListSysOnlineUser;
result?: Array<OnlineUser> | null;
/**
*
*
* @type {any}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
* @memberof AdminNETResultListOnlineUser
*/
extras?: any | null;
@ -63,7 +65,7 @@ export interface AdminNETResultSqlSugarPagedListSysOnlineUser {
*
*
* @type {Date}
* @memberof AdminNETResultSqlSugarPagedListSysOnlineUser
* @memberof AdminNETResultListOnlineUser
*/
time?: Date;
}

View File

@ -64,6 +64,7 @@ export * from './admin-netresult-list-list-string';
export * from './admin-netresult-list-log-vis-output';
export * from './admin-netresult-list-menu-output';
export * from './admin-netresult-list-nu-get-package';
export * from './admin-netresult-list-online-user';
export * from './admin-netresult-list-pos-output';
export * from './admin-netresult-list-report-data-source-output';
export * from './admin-netresult-list-role-output';
@ -121,7 +122,6 @@ export * from './admin-netresult-sql-sugar-paged-list-sys-log-op';
export * from './admin-netresult-sql-sugar-paged-list-sys-log-vis';
export * from './admin-netresult-sql-sugar-paged-list-sys-notice';
export * from './admin-netresult-sql-sugar-paged-list-sys-notice-user';
export * from './admin-netresult-sql-sugar-paged-list-sys-online-user';
export * from './admin-netresult-sql-sugar-paged-list-sys-plugin';
export * from './admin-netresult-sql-sugar-paged-list-sys-print';
export * from './admin-netresult-sql-sugar-paged-list-sys-region';
@ -350,6 +350,7 @@ export * from './nu-get-package';
export * from './number-format-info';
export * from './oauth-user-input';
export * from './oauth-user-output';
export * from './online-user';
export * from './open-access-output';
export * from './order-by-type';
export * from './page-code-gen-input';
@ -365,7 +366,6 @@ export * from './page-ldap-input';
export * from './page-log-input';
export * from './page-msg-log-input';
export * from './page-notice-input';
export * from './page-online-user-input';
export * from './page-op-log-input';
export * from './page-open-access-input';
export * from './page-plugin-input';
@ -455,7 +455,6 @@ export * from './sql-sugar-paged-list-sys-log-op';
export * from './sql-sugar-paged-list-sys-log-vis';
export * from './sql-sugar-paged-list-sys-notice';
export * from './sql-sugar-paged-list-sys-notice-user';
export * from './sql-sugar-paged-list-sys-online-user';
export * from './sql-sugar-paged-list-sys-plugin';
export * from './sql-sugar-paged-list-sys-print';
export * from './sql-sugar-paged-list-sys-region';
@ -502,7 +501,6 @@ export * from './sys-menu-meta';
export * from './sys-notice';
export * from './sys-notice-user';
export * from './sys-oauth-user';
export * from './sys-online-user';
export * from './sys-org';
export * from './sys-plugin';
export * from './sys-pos-import-body';

View File

@ -14,42 +14,34 @@
import { LoginModeEnum } from './login-mode-enum';
/**
* 线
* 线
*
* @export
* @interface SysOnlineUser
* @interface OnlineUser
*/
export interface SysOnlineUser {
/**
* Id
*
* @type {number}
* @memberof SysOnlineUser
*/
id?: number;
/**
* Id
*
* @type {number}
* @memberof SysOnlineUser
*/
tenantId?: number | null;
export interface OnlineUser {
/**
* Id
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
connectionId?: string | null;
/**
* Id
*
* @type {number}
* @memberof OnlineUser
*/
tenantId?: number;
/**
* Id
*
* @type {number}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
userId?: number;
@ -57,31 +49,31 @@ export interface SysOnlineUser {
*
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
userName: string;
userName?: string | null;
/**
*
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
realName?: string | null;
/**
*
* 线
*
* @type {Date}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
time?: Date | null;
time?: Date;
/**
* IP
* IP
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
ip?: string | null;
@ -89,7 +81,7 @@ export interface SysOnlineUser {
*
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
browser?: string | null;
@ -97,13 +89,13 @@ export interface SysOnlineUser {
*
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
os?: string | null;
/**
* @type {LoginModeEnum}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
loginMode?: LoginModeEnum;
@ -111,7 +103,7 @@ export interface SysOnlineUser {
*
*
* @type {string}
* @memberof SysOnlineUser
* @memberof OnlineUser
*/
device?: string | null;
}

View File

@ -1,100 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Admin.NET
* .NET <br/><u><b><font color='FF0000'> 👮</font></b></u>
*
* OpenAPI spec version: 1.0.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { Filter } from './filter';
import { Search } from './search';
/**
*
*
* @export
* @interface PageOnlineUserInput
*/
export interface PageOnlineUserInput {
/**
* @type {Search}
* @memberof PageOnlineUserInput
*/
search?: Search;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
keyword?: string | null;
/**
* @type {Filter}
* @memberof PageOnlineUserInput
*/
filter?: Filter;
/**
*
*
* @type {number}
* @memberof PageOnlineUserInput
*/
page?: number;
/**
*
*
* @type {number}
* @memberof PageOnlineUserInput
*/
pageSize?: number;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
field?: string | null;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
order?: string | null;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
descStr?: string | null;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
userName?: string | null;
/**
*
*
* @type {string}
* @memberof PageOnlineUserInput
*/
realName?: string | null;
}

View File

@ -1,79 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Admin.NET
* .NET <br/><u><b><font color='FF0000'> 👮</font></b></u>
*
* OpenAPI spec version: 1.0.0
*
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
import { SysOnlineUser } from './sys-online-user';
/**
*
*
* @export
* @interface SqlSugarPagedListSysOnlineUser
*/
export interface SqlSugarPagedListSysOnlineUser {
/**
*
*
* @type {number}
* @memberof SqlSugarPagedListSysOnlineUser
*/
page?: number;
/**
*
*
* @type {number}
* @memberof SqlSugarPagedListSysOnlineUser
*/
pageSize?: number;
/**
*
*
* @type {number}
* @memberof SqlSugarPagedListSysOnlineUser
*/
total?: number;
/**
*
*
* @type {number}
* @memberof SqlSugarPagedListSysOnlineUser
*/
totalPages?: number;
/**
*
*
* @type {Array<SysOnlineUser>}
* @memberof SqlSugarPagedListSysOnlineUser
*/
items?: Array<SysOnlineUser> | null;
/**
*
*
* @type {boolean}
* @memberof SqlSugarPagedListSysOnlineUser
*/
hasPrevPage?: boolean;
/**
*
*
* @type {boolean}
* @memberof SqlSugarPagedListSysOnlineUser
*/
hasNextPage?: boolean;
}

View File

@ -47,12 +47,15 @@
<script lang="ts" setup name="sendMessage">
import { reactive, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { useUserInfo } from '/@/stores/userInfo';
import { storeToRefs } from 'pinia';
import Editor from '/@/components/editor/index.vue';
import { useI18n } from 'vue-i18n';
import Editor from '/@/components/editor/index.vue';
import { signalR } from '../signalR';
const i18n = useI18n();
const stores = useUserInfo();
const { userInfos } = storeToRefs(stores);
const props = defineProps({
@ -85,6 +88,7 @@ const submit = () => {
// Hub
signalR.send('SendMessageToUser', state.ruleForm);
ElMessage.success(i18n.t('message.list.operationSuccessful'));
});
};

View File

@ -1,43 +1,15 @@
<template>
<div class="sys-onlineUser-container">
<el-drawer v-model="state.isVisible" size="35%">
<el-drawer v-model="state.isVisible" size="50%">
<template #header>
<div style="color: #fff">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-UserFilled /> </el-icon>
<span> {{ $t('message.list.onlineUserList') }} </span>
</div>
</template>
<el-card shadow="hover" :body-style="{ padding: '5px 5px 0 5px', display: 'flex', width: '100%', height: '100%', alignItems: 'start' }">
<el-form :model="state.queryParams" ref="queryForm" :show-message="false" :inlineMessage="true" label-width="auto" style="flex: 1 1 0%">
<el-row :gutter="10">
<el-col class="mb5" :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item :label="$t('message.list.account')" prop="userName">
<el-input v-model="state.queryParams.userName" :placeholder="$t('message.list.account')" clearable @keyup.enter.native="handleQuery(true)" />
</el-form-item>
</el-col>
<el-col class="mb5" :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item :label="$t('message.list.realName')" prop="realName">
<el-input v-model="state.queryParams.realName" :placeholder="$t('message.list.realName')" clearable @keyup.enter.native="handleQuery(true)" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider style="height: calc(100% - 5px); margin: 0 10px" direction="vertical" />
<el-row>
<el-col>
<el-button-group>
<el-button type="primary" icon="ele-Search" @click="handleQuery(true)" :loading="options.loading"> {{ $t('message.list.query') }} </el-button>
<el-button icon="ele-Refresh" @click="resetQuery" :loading="options.loading"> {{ $t('message.list.reset') }} </el-button>
</el-button-group>
</el-col>
</el-row>
</el-card>
<el-card class="full-table" shadow="hover" style="margin-top: 5px">
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options" v-on="gridEvents">
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options">
<template #toolbar_buttons> </template>
<template #toolbar_tools> </template>
<template #empty>
@ -62,21 +34,19 @@
<!-- 在线用户 -->
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { ElMessageBox, ElNotification } from 'element-plus';
import { VxeGridInstance, VxeGridListeners, VxeGridPropTypes } from 'vxe-table';
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
import { VxeGridInstance } from 'vxe-table';
import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook';
import { useThemeConfig } from '/@/stores/themeConfig';
import { storeToRefs } from 'pinia';
import { Local } from '/@/utils/storage';
import { useI18n } from 'vue-i18n';
import { throttle } from 'lodash-es';
import { signalR } from './signalR';
import SendMessage from '/@/views/system/onlineUser/component/sendMessage.vue';
import { getAPI, clearAccessTokens } from '/@/utils/axios-utils';
import { SysOnlineUserApi, SysAuthApi } from '/@/api-services/system/api';
import { SysOnlineUser, PageOnlineUserInput } from '/@/api-services/system/models';
import { SysAuthApi, SysOnlineUserApi } from '/@/api-services/system/api';
import { OnlineUser } from '/@/api-services/system/models';
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -85,25 +55,14 @@ const xGrid = ref<VxeGridInstance>();
const sendMessageRef = ref<InstanceType<typeof SendMessage>>();
const state = reactive({
isVisible: false,
queryParams: {
userName: undefined,
realName: undefined,
},
localPageParam: {
pageSize: 50 as number,
defaultSort: { field: 'orderNo', order: 'asc', descStr: 'desc' },
},
onlineUserList: [] as Array<SysOnlineUser>, // 线
lastUserState: {
online: false,
realName: '',
}, //
}, //
});
//
const localPageParamKey = 'localPageParam:sysOnlineUser';
//
const options = useVxeTable<SysOnlineUser>(
const options = useVxeTable<OnlineUser>(
{
id: 'sysOnlineUser',
name: t('message.list.onlineUserList'),
@ -112,48 +71,43 @@ const options = useVxeTable<SysOnlineUser>(
{ type: 'seq', title: t('message.list.seq'), width: 50, fixed: 'left' },
{ field: 'userName', title: t('message.list.account'), minWidth: 110, showOverflow: 'tooltip' },
{ field: 'realName', title: t('message.list.realName'), minWidth: 110, showOverflow: 'tooltip' },
{ field: 'time', title: t('message.list.loginTime'), minWidth: 120, showOverflow: 'tooltip' },
{ field: 'ip', title: t('message.list.ipAddress'), minWidth: 100, showOverflow: 'tooltip' },
{ field: 'browser', title: t('message.list.browser'), minWidth: 160, showOverflow: 'tooltip' },
// { field: 'connectionId', title: 'Id', minWidth: 160, showOverflow: 'tooltip', sortable: true },
{ field: 'time', title: t('message.list.loginTime'), minWidth: 120, showOverflow: 'tooltip' },
{ field: 'buttons', title: t('message.list.operation'), fixed: 'right', width: 100, showOverflow: true, slots: { default: 'row_buttons' } },
],
},
// vxeGrid()vxe-table
{
//
proxyConfig: { autoLoad: true, ajax: { query: ({ page, sort }) => handleQueryApi(page, sort) } },
//
sortConfig: { defaultSort: Local.get(localPageParamKey)?.defaultSort || state.localPageParam.defaultSort },
proxyConfig: { enabled: false },
//
pagerConfig: { pageSize: Local.get(localPageParamKey)?.pageSize || state.localPageParam.pageSize },
pagerConfig: { enabled: false },
//
toolbarConfig: { export: true },
toolbarConfig: { enabled: false },
}
);
//
onMounted(async () => {
state.localPageParam = Local.get(localPageParamKey) || state.localPageParam;
// 线
signalR.off('OnlineUserList');
// 线
signalR.on('OnlineUserList', async (data: any) => {
state.onlineUserList = data.userList;
state.lastUserState = {
online: data.online,
realName: data.realName,
};
options.data = data.userList;
// 线线
if (themeConfig.value.onlineNotice) notificationThrottle();
// //
// await handleQuery();
console.log('dddddd');
// 线
if (!themeConfig.value.onlineNotice) return;
ElNotification({
title: '提示',
message: `${data.online ? `${data.realName}】上线了` : `${data.realName}】离开了`}`,
type: `${data.online ? 'info' : 'error'}`,
position: 'bottom-right',
});
});
// 线
signalR.off('ForceOffline');
// 线
signalR.on('ForceOffline', async (data: any) => {
// console.log('线', data);
await signalR.stop();
await getAPI(SysAuthApi).apiSysAuthLogoutPost();
@ -161,61 +115,13 @@ onMounted(async () => {
});
});
//
const notificationThrottle = throttle(
function () {
ElNotification({
title: '提示',
message: `${state.lastUserState.online ? `${state.lastUserState.realName}】上线了` : `${state.lastUserState.realName}】离开了`}`,
type: `${state.lastUserState.online ? 'info' : 'error'}`,
position: 'bottom-right',
});
},
3000,
{
leading: true,
trailing: false,
}
);
//
const openDrawer = async () => {
state.isVisible = true;
await handleQuery();
};
// api
const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, sort: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams) => {
const params = Object.assign(state.queryParams, { page: page.currentPage, pageSize: page.pageSize, field: sort.field, order: sort.order, descStr: 'desc' }) as PageOnlineUserInput;
return getAPI(SysOnlineUserApi).apiSysOnlineUserPagePost(params);
};
//
const handleQuery = async (reset = false) => {
options.loading = true;
reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query');
options.loading = false;
};
//
const resetQuery = async () => {
state.queryParams.userName = undefined;
state.queryParams.realName = undefined;
await handleQuery();
};
//
const gridEvents: VxeGridListeners<SysOnlineUser> = {
// pager-config
async pageChange({ pageSize }) {
state.localPageParam.pageSize = pageSize;
Local.set(localPageParamKey, state.localPageParam);
},
//
async sortChange({ field, order }) {
state.localPageParam.defaultSort = { field: field, order: order!, descStr: 'desc' };
Local.set(localPageParamKey, state.localPageParam);
},
// 线
var res = await getAPI(SysOnlineUserApi).apiSysOnlineUserOnlineUserListGet();
options.data = res.data.result ?? [];
};
//
@ -231,8 +137,9 @@ const forceOffline = async (row: any) => {
type: 'warning',
})
.then(async () => {
//
await signalR.send('ForceOffline', { connectionId: row.connectionId }).catch(function (err: any) {
console.log(err);
ElMessage.error(err);
});
})
.catch(() => {});

View File

@ -54,7 +54,7 @@ connection.on('ReceiveMessage', (message: any) => {
type: message.messageType.toString().toLowerCase(),
position: 'top-right',
dangerouslyUseHTMLString: true,
duration: 0,
duration: 5000,
});
});

View File

@ -169,7 +169,7 @@ const handleEdit = async (row: any) => {
//
const handleDelete = (row: any) => {
ElMessageBox.confirm(i18n.t('message.list.confirmDeletePosition', { name: row.name }), i18n.t('message.list.hint'), {
ElMessageBox.confirm(i18n.t('message.list.confirmDelete', { name: row.name }), i18n.t('message.list.hint'), {
confirmButtonText: i18n.t('message.list.confirmButtonText'),
cancelButtonText: i18n.t('message.list.cancelButtonText'),
type: 'warning',