diff --git a/src/backend/NetAdmin/NetAdmin.Application/Extensions/UnitOfWorkManagerExtensions.cs b/src/backend/NetAdmin/NetAdmin.Application/Extensions/UnitOfWorkManagerExtensions.cs new file mode 100644 index 00000000..2d2285db --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Extensions/UnitOfWorkManagerExtensions.cs @@ -0,0 +1,31 @@ +namespace NetAdmin.Application.Extensions; + +/// +/// 工作单元管理器扩展方法 +/// +public static class UnitOfWorkManagerExtensions +{ + /// + /// 事务操作 + /// + public static async Task AtomicOperateAsync(this UnitOfWorkManager me, Func handle) + { + var logger = LogHelper.Get(); + using var unitOfWork = me.Begin(); + var hashCode = unitOfWork.GetHashCode(); + try { + #if DEBUG + logger?.Debug($"{Ln.开始事务}: {hashCode}"); + #endif + await handle().ConfigureAwait(false); + unitOfWork.Commit(); + logger?.Info($"{Ln.事务已提交}: {hashCode}"); + } + catch (Exception ex) { + logger?.Warn(ex); + unitOfWork.Rollback(); + logger?.Warn($"{Ln.事务已回滚}: {hashCode}"); + throw; + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Modules/ICrudModule.cs b/src/backend/NetAdmin/NetAdmin.Application/Modules/ICrudModule.cs new file mode 100644 index 00000000..441b4368 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Modules/ICrudModule.cs @@ -0,0 +1,65 @@ +using NetAdmin.Domain; +using NetAdmin.Domain.Dto.Dependency; + +namespace NetAdmin.Application.Modules; + +/// +/// 增删改查模块接口 +/// +/// 创建请求类型 +/// 创建响应类型 +/// 查询请求类型 +/// 查询响应类型 +/// 删除请求类型 +public interface ICrudModule + where TCreateReq : DataAbstraction, new() + where TCreateRsp : DataAbstraction + where TQueryReq : DataAbstraction, new() + where TQueryRsp : DataAbstraction + where TDelReq : DataAbstraction, new() +{ + /// + /// 批量删除实体 + /// + Task BulkDeleteAsync(BulkReq req); + + /// + /// 实体计数 + /// + Task CountAsync(QueryReq req); + + /// + /// 创建实体 + /// + Task CreateAsync(TCreateReq req); + + /// + /// 删除实体 + /// + Task DeleteAsync(TDelReq req); + + /// + /// 判断实体是否存在 + /// + Task ExistAsync(QueryReq req); + + /// + /// 导出实体 + /// + Task ExportAsync(QueryReq req); + + /// + /// 获取单个实体 + /// + Task GetAsync(TQueryReq req); + + /// + /// 分页查询实体 + /// + Task> PagedQueryAsync(PagedQueryReq req); + + /// + /// 查询实体 + /// + Task> QueryAsync(QueryReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Modules/Tpl/IExampleModule.cs b/src/backend/NetAdmin/NetAdmin.Application/Modules/Tpl/IExampleModule.cs new file mode 100644 index 00000000..fd653293 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Modules/Tpl/IExampleModule.cs @@ -0,0 +1,12 @@ +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Tpl.Example; + +namespace NetAdmin.Application.Modules.Tpl; + +/// +/// 示例模块 +/// +public interface IExampleModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/NetAdmin.Application.csproj b/src/backend/NetAdmin/NetAdmin.Application/NetAdmin.Application.csproj new file mode 100644 index 00000000..c21da26e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/NetAdmin.Application.csproj @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Repositories/BasicRepository.cs b/src/backend/NetAdmin/NetAdmin.Application/Repositories/BasicRepository.cs new file mode 100644 index 00000000..779faee2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Repositories/BasicRepository.cs @@ -0,0 +1,18 @@ +using NetAdmin.Domain.Contexts; +using NetAdmin.Domain.DbMaps.Dependency; + +namespace NetAdmin.Application.Repositories; + +/// +/// 基础仓储 +/// +public sealed class BasicRepository(IFreeSql fSql, UnitOfWorkManager uowManger, ContextUserToken userToken) + : DefaultRepository(fSql, uowManger) + where TEntity : EntityBase // + where TPrimary : IEquatable +{ + /// + /// 当前上下文关联的用户令牌 + /// + public ContextUserToken UserToken => userToken; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/IService.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/IService.cs new file mode 100644 index 00000000..48bc95a2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/IService.cs @@ -0,0 +1,19 @@ +using NetAdmin.Domain.Contexts; + +namespace NetAdmin.Application.Services; + +/// +/// 服务接口 +/// +public interface IService +{ + /// + /// 服务编号 + /// + Guid ServiceId { get; init; } + + /// + /// 上下文用户令牌 + /// + ContextUserToken UserToken { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/RedisService.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/RedisService.cs new file mode 100644 index 00000000..dc7c304b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/RedisService.cs @@ -0,0 +1,43 @@ +using NetAdmin.Application.Repositories; +using NetAdmin.Domain.DbMaps.Dependency; +using StackExchange.Redis; + +namespace NetAdmin.Application.Services; + +/// +/// Redis Service Base +/// +/// +/// Initializes a new instance of the class. +/// Redis Service Base +/// +public abstract class RedisService(BasicRepository rpo) + : RepositoryService(rpo) + where TEntity : EntityBase // + where TPrimary : IEquatable +{ + /// + /// Redis Database + /// + protected IDatabase RedisDatabase { get; } // + = App.GetService() + .GetDatabase(App.GetOptions().Instances.First(x => x.Name == Chars.FLG_REDIS_INSTANCE_DATA_CACHE).Database); + + /// + /// 获取锁 + /// + protected Task GetLockerAsync(string lockerName) + { + return RedisLocker.GetLockerAsync(RedisDatabase, lockerName, TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_EXPIRY) + , Numbers.MAX_LIMIT_RETRY_CNT_REDIS_LOCK, TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_RETRY_DELAY)); + } + + /// + /// 获取锁(仅获取一次) + /// + protected Task GetLockerOnceAsync(string lockerName) + { + return RedisLocker.GetLockerAsync(RedisDatabase, lockerName, TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_EXPIRY), 1 + , TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_RETRY_DELAY)); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/RepositoryService.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/RepositoryService.cs new file mode 100644 index 00000000..7209bc07 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/RepositoryService.cs @@ -0,0 +1,159 @@ +using CsvHelper; +using Microsoft.Net.Http.Headers; +using NetAdmin.Application.Repositories; +using NetAdmin.Domain; +using NetAdmin.Domain.DbMaps.Dependency; +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.Domain.Dto.Dependency; + +namespace NetAdmin.Application.Services; + +/// +/// 仓储服务基类 +/// +/// 实体类型 +/// 主键类型 +/// 日志类型 +public abstract class RepositoryService(BasicRepository rpo) : ServiceBase + where TEntity : EntityBase // + where TPrimary : IEquatable +{ + /// + /// 默认仓储 + /// + protected BasicRepository Rpo => rpo; + + /// + /// 启用级联保存 + /// + protected bool EnableCascadeSave { + get => Rpo.DbContextOptions.EnableCascadeSave; + set => Rpo.DbContextOptions.EnableCascadeSave = value; + } + + /// + /// 导出实体 + /// + protected static async Task ExportAsync( // + Func, ISelectGrouping> selector, QueryReq query, string fileName + , Expression, object>> listExp = null) + where TQuery : DataAbstraction, new() + { + var list = await selector(query).Take(Numbers.MAX_LIMIT_EXPORT).ToListAsync(listExp).ConfigureAwait(false); + return await GetExportFileStreamAsync(fileName, list).ConfigureAwait(false); + } + + /// + /// 导出实体 + /// + protected static async Task ExportAsync( // + Func, ISelect> selector, QueryReq query, string fileName, Expression> listExp = null) + where TQuery : DataAbstraction, new() + { + var select = selector(query) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(Numbers.MAX_LIMIT_EXPORT); + + object list = listExp == null ? await select.ToListAsync().ConfigureAwait(false) : await select.ToListAsync(listExp).ConfigureAwait(false); + + return await GetExportFileStreamAsync(fileName, list).ConfigureAwait(false); + } + + /// + /// 更新实体 + /// + /// 新的值 + /// 包含的属性 + /// 排除的属性 + /// 查询表达式 + /// 查询sql + /// 是否忽略版本锁 + /// 更新行数 + protected Task UpdateAsync( // + TEntity newValue // + , IEnumerable includeFields // + , string[] excludeFields = null // + , Expression> whereExp = null // + , string whereSql = null // + , bool ignoreVersion = false) + { + // 默认匹配主键 + whereExp ??= a => a.Id.Equals(newValue.Id); + var update = BuildUpdate(newValue, includeFields, excludeFields, ignoreVersion).Where(whereExp).Where(whereSql); + return update.ExecuteAffrowsAsync(); + } + + #if DBTYPE_SQLSERVER + /// + /// 更新实体 + /// + /// 新的值 + /// 包含的属性 + /// 排除的属性 + /// 查询表达式 + /// 查询sql + /// 是否忽略版本锁 + /// 更新后的实体列表 + protected Task> UpdateReturnListAsync( // + TEntity newValue // + , IEnumerable includeFields // + , string[] excludeFields = null // + , Expression> whereExp = null // + , string whereSql = null // + , bool ignoreVersion = false) + { + // 默认匹配主键 + whereExp ??= a => a.Id.Equals(newValue.Id); + return BuildUpdate(newValue, includeFields, excludeFields, ignoreVersion).Where(whereExp).Where(whereSql).ExecuteUpdatedAsync(); + } + #endif + + private static async Task GetExportFileStreamAsync(string fileName, object list) + { + var listTyped = list.Adapt>(); + var stream = new MemoryStream(); + var writer = new StreamWriter(stream); + var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); + csv.WriteHeader(); + await csv.NextRecordAsync().ConfigureAwait(false); + + foreach (var item in listTyped) { + csv.WriteRecord(item); + await csv.NextRecordAsync().ConfigureAwait(false); + } + + await csv.FlushAsync().ConfigureAwait(false); + _ = stream.Seek(0, SeekOrigin.Begin); + + App.HttpContext.Response.Headers.ContentDisposition + = new ContentDispositionHeaderValue(Chars.FLG_HTTP_HEADER_VALUE_ATTACHMENT) { + FileNameStar + = $"{fileName}_{DateTime.Now:yyyy.MM.dd-HH.mm.ss}.csv" + }.ToString(); + + return new FileStreamResult(stream, Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_OCTET_STREAM); + } + + private IUpdate BuildUpdate( // + TEntity entity // + , IEnumerable includeFields // + , string[] excludeFields = null // + , bool ignoreVersion = false) + { + var updateExp = includeFields == null + ? Rpo.UpdateDiy.SetSource(entity) + : Rpo.UpdateDiy.SetDto(includeFields!.ToDictionary( + x => x, x => typeof(TEntity).GetProperty(x, BindingFlags.Public | BindingFlags.Instance)!.GetValue(entity))); + if (excludeFields != null) { + updateExp = updateExp.IgnoreColumns(excludeFields); + } + + if (!ignoreVersion && entity is IFieldVersion ver) { + updateExp = updateExp.Where($"{nameof(IFieldVersion.Version)} = @version", new { version = ver.Version }); + } + + return updateExp; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/ServiceBase.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/ServiceBase.cs new file mode 100644 index 00000000..174c0e0e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/ServiceBase.cs @@ -0,0 +1,41 @@ +using NetAdmin.Domain.Contexts; + +namespace NetAdmin.Application.Services; + +/// +public abstract class ServiceBase : ServiceBase +{ + /// + /// Initializes a new instance of the class. + /// + protected ServiceBase() // + { + Logger = App.GetService>(); + } + + /// + /// 日志记录器 + /// + protected ILogger Logger { get; } +} + +/// +/// 服务基类 +/// +public abstract class ServiceBase : IScoped, IService +{ + /// + /// Initializes a new instance of the class. + /// + protected ServiceBase() + { + UserToken = App.GetService(); + ServiceId = Guid.NewGuid(); + } + + /// + public Guid ServiceId { get; init; } + + /// + public ContextUserToken UserToken { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/Dependency/IExampleService.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/Dependency/IExampleService.cs new file mode 100644 index 00000000..670d0f04 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/Dependency/IExampleService.cs @@ -0,0 +1,8 @@ +using NetAdmin.Application.Modules.Tpl; + +namespace NetAdmin.Application.Services.Tpl.Dependency; + +/// +/// 示例服务 +/// +public interface IExampleService : IService, IExampleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/ExampleService.cs b/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/ExampleService.cs new file mode 100644 index 00000000..bf23ecea --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Application/Services/Tpl/ExampleService.cs @@ -0,0 +1,128 @@ +using NetAdmin.Application.Repositories; +using NetAdmin.Application.Services.Tpl.Dependency; +using NetAdmin.Domain.DbMaps.Tpl; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Tpl.Example; + +namespace NetAdmin.Application.Services.Tpl; + +/// +public sealed class ExampleService(BasicRepository rpo) // + : RepositoryService(rpo), IExampleService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateExampleReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.示例导出); + } + + /// + public async Task GetAsync(QueryExampleReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/CacheBase.cs b/src/backend/NetAdmin/NetAdmin.Cache/CacheBase.cs new file mode 100644 index 00000000..c05f2660 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/CacheBase.cs @@ -0,0 +1,16 @@ +using NetAdmin.Application.Services; + +namespace NetAdmin.Cache; + +/// +/// 缓存基类 +/// +public abstract class CacheBase(TCacheContainer cache, TService service) : ICache + where TService : IService +{ + /// + public TCacheContainer Cache => cache; + + /// + public TService Service => service; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/DistributedCache.cs b/src/backend/NetAdmin/NetAdmin.Cache/DistributedCache.cs new file mode 100644 index 00000000..50ce7975 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/DistributedCache.cs @@ -0,0 +1,93 @@ +using System.Runtime.CompilerServices; +using NetAdmin.Application.Services; + +namespace NetAdmin.Cache; + +/// +/// 分布式缓存 +/// +public abstract class DistributedCache(IDistributedCache cache, TService service) : CacheBase(cache, service) + where TService : IService +{ + /// + /// 创建缓存 + /// + /// 缓存键 + /// 创建对象 + /// 绝对过期时间 + /// 滑动过期时间 + /// 缓存对象类型 + /// 缓存对象 + protected Task CreateAsync(string key, T createObj, TimeSpan? absLifeTime = null, TimeSpan? slideLifeTime = null) + { + var cacheWrite = createObj.ToJson(); + + var options = new DistributedCacheEntryOptions(); + if (absLifeTime != null) { + _ = options.SetAbsoluteExpiration(absLifeTime.Value); + } + + if (slideLifeTime != null) { + _ = options.SetSlidingExpiration(slideLifeTime.Value); + } + + return Cache.SetAsync(key, cacheWrite.Hex(), options); + } + + /// + /// 获取缓存 + /// + protected async Task GetAsync(string key) + { + var cacheRead = await Cache.GetStringAsync(key).ConfigureAwait(false); + try { + return cacheRead != null ? cacheRead.ToObject() : default; + } + catch (JsonException) { + return default; + } + } + + /// + /// 获取缓存键 + /// + protected string GetCacheKey(string id = "0", [CallerMemberName] string memberName = null) + { + return $"{GetType().FullName}.{memberName}.{id}"; + } + + /// + /// 获取或创建缓存 + /// + /// 缓存键 + /// 创建函数 + /// 绝对过期时间 + /// 滑动过期时间 + /// 缓存对象类型 + /// 缓存对象 + protected async Task GetOrCreateAsync(string key, Func> createProc, TimeSpan? absLifeTime = null, TimeSpan? slideLifeTime = null) + { + var cacheRead = await GetAsync(key).ConfigureAwait(false); + if (cacheRead is not null && App.HttpContext?.Request.Headers.CacheControl.FirstOrDefault() != Chars.FLG_HTTP_HEADER_VALUE_NO_CACHE) { + return cacheRead; + } + + var obj = await createProc.Invoke().ConfigureAwait(false); + + var cacheWrite = obj?.ToJson(); + if (cacheWrite == null) { + return obj; + } + + await CreateAsync(key, obj, absLifeTime, slideLifeTime).ConfigureAwait(false); + return obj; + } + + /// + /// 删除缓存 + /// + protected Task RemoveAsync(string key) + { + return Cache.RemoveAsync(key); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/ICache.cs b/src/backend/NetAdmin/NetAdmin.Cache/ICache.cs new file mode 100644 index 00000000..092f8efd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/ICache.cs @@ -0,0 +1,20 @@ +using NetAdmin.Application.Services; + +namespace NetAdmin.Cache; + +/// +/// 缓存接口 +/// +public interface ICache + where TService : IService +{ + /// + /// 缓存对象 + /// + TCacheLoad Cache { get; } + + /// + /// 关联的服务 + /// + public TService Service { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/MemoryCache.cs b/src/backend/NetAdmin/NetAdmin.Cache/MemoryCache.cs new file mode 100644 index 00000000..af232fbc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/MemoryCache.cs @@ -0,0 +1,9 @@ +using NetAdmin.Application.Services; + +namespace NetAdmin.Cache; + +/// +/// 内存缓存 +/// +public abstract class MemoryCache(IMemoryCache cache, TService service) : CacheBase(cache, service) + where TService : IService; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/NetAdmin.Cache.csproj b/src/backend/NetAdmin/NetAdmin.Cache/NetAdmin.Cache.csproj new file mode 100644 index 00000000..6138b0be --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/NetAdmin.Cache.csproj @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/Tpl/Dependency/IExampleCache.cs b/src/backend/NetAdmin/NetAdmin.Cache/Tpl/Dependency/IExampleCache.cs new file mode 100644 index 00000000..d887f95a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/Tpl/Dependency/IExampleCache.cs @@ -0,0 +1,9 @@ +using NetAdmin.Application.Modules.Tpl; +using NetAdmin.Application.Services.Tpl.Dependency; + +namespace NetAdmin.Cache.Tpl.Dependency; + +/// +/// 示例缓存 +/// +public interface IExampleCache : ICache, IExampleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Cache/Tpl/ExampleCache.cs b/src/backend/NetAdmin/NetAdmin.Cache/Tpl/ExampleCache.cs new file mode 100644 index 00000000..d2a7b472 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Cache/Tpl/ExampleCache.cs @@ -0,0 +1,65 @@ +using NetAdmin.Application.Services.Tpl.Dependency; +using NetAdmin.Cache.Tpl.Dependency; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Tpl.Example; + +namespace NetAdmin.Cache.Tpl; + +/// +public sealed class ExampleCache(IDistributedCache cache, IExampleService service) + : DistributedCache(cache, service), IScoped, IExampleCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateExampleReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryExampleReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DangerFieldAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DangerFieldAttribute.cs new file mode 100644 index 00000000..63deb7c0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DangerFieldAttribute.cs @@ -0,0 +1,7 @@ +namespace NetAdmin.Domain.Attributes; + +/// +/// 危险字段标记 +/// +[AttributeUsage(AttributeTargets.Property)] +public sealed class DangerFieldAttribute : Attribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/AlipayAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/AlipayAttribute.cs new file mode 100644 index 00000000..47b7dfef --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/AlipayAttribute.cs @@ -0,0 +1,23 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 支付宝验证器(手机或邮箱) +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class AlipayAttribute : ValidationAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public AlipayAttribute() + { + ErrorMessageResourceName = nameof(Ln.支付宝账号); + ErrorMessageResourceType = typeof(Ln); + } + + /// + public override bool IsValid(object value) + { + return new MobileAttribute().IsValid(value) || new EmailAddressAttribute().IsValid(value); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CertificateAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CertificateAttribute.cs new file mode 100644 index 00000000..3c70b01a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CertificateAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 证件号码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class CertificateAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public CertificateAttribute() // + : base(Chars.RGX_CERTIFICATE) + { + ErrorMessageResourceName = nameof(Ln.无效证件号码); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/ChineseNameAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/ChineseNameAttribute.cs new file mode 100644 index 00000000..c1609be0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/ChineseNameAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 中文姓名验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class ChineseNameAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public ChineseNameAttribute() // + : base(Chars.RGXL_CHINESE_NAME) + { + ErrorMessageResourceName = nameof(Ln.中文姓名); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs new file mode 100644 index 00000000..38d40ca5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/CronAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 时间表达式验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class CronAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public CronAttribute() // + : base(Chars.RGXL_CRON) + { + ErrorMessageResourceName = nameof(Ln.时间表达式); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/EmailAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/EmailAttribute.cs new file mode 100644 index 00000000..0573aaa2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/EmailAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 邮箱验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class EmailAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public EmailAttribute() // + : base(Chars.RGXL_EMAIL) + { + ErrorMessageResourceName = nameof(Ln.电子邮箱); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/InviteCodeAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/InviteCodeAttribute.cs new file mode 100644 index 00000000..a55a02ec --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/InviteCodeAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 邀请码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class InviteCodeAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public InviteCodeAttribute() // + : base(Chars.RGX_INVITE_CODE) + { + ErrorMessageResourceName = nameof(Ln.邀请码不正确); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/JsonStringAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/JsonStringAttribute.cs new file mode 100644 index 00000000..f46eb3aa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/JsonStringAttribute.cs @@ -0,0 +1,14 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// JSON文本验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class JsonStringAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + return (value as string).IsJsonString() ? ValidationResult.Success : new ValidationResult(Ln.非JSON字符串, [validationContext.MemberName]); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/MobileAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/MobileAttribute.cs new file mode 100644 index 00000000..0d550f0d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/MobileAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 手机号码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class MobileAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public MobileAttribute() // + : base(Chars.RGX_MOBILE) + { + ErrorMessageResourceName = nameof(Ln.手机号码不正确); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PasswordAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PasswordAttribute.cs new file mode 100644 index 00000000..0ff71759 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PasswordAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 密码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class PasswordAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public PasswordAttribute() // + : base(Chars.RGX_PASSWORD) + { + ErrorMessageResourceName = nameof(Ln._8位以上数字字母组合); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PayPasswordAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PayPasswordAttribute.cs new file mode 100644 index 00000000..f97f1c5c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PayPasswordAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 交易密码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class PayPasswordAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public PayPasswordAttribute() // + : base(Chars.RGX_PAY_PASSWORD) + { + ErrorMessageResourceName = nameof(Ln._6位数字); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PortAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PortAttribute.cs new file mode 100644 index 00000000..36d55df8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/PortAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 端口号验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class PortAttribute : RangeAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public PortAttribute() // + : base(1, ushort.MaxValue) + { + ErrorMessageResourceName = nameof(Ln.无效端口号); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/RegexAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/RegexAttribute.cs new file mode 100644 index 00000000..d00e62c5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/RegexAttribute.cs @@ -0,0 +1,16 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 正则表达式验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +#pragma warning disable DesignedForInheritance +public class RegexAttribute : RegularExpressionAttribute +#pragma warning restore DesignedForInheritance +{ + /// + /// Initializes a new instance of the class. + /// + protected RegexAttribute(string pattern) // + : base(pattern) { } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/TelephoneAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/TelephoneAttribute.cs new file mode 100644 index 00000000..c521e97a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/TelephoneAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 固定电话验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class TelephoneAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public TelephoneAttribute() // + : base(Chars.RGX_TELEPHONE) + { + ErrorMessageResourceName = nameof(Ln.区号电话号码分机号); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/UserNameAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/UserNameAttribute.cs new file mode 100644 index 00000000..ca17b04a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/UserNameAttribute.cs @@ -0,0 +1,34 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 用户名验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class UserNameAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public UserNameAttribute() // + : base(Chars.RGX_USERNAME) + { + ErrorMessageResourceType = typeof(Ln); + } + + /// + public override bool IsValid(object value) + { + if (!base.IsValid(value)) { + ErrorMessageResourceName = nameof(Ln.用户名长度4位以上); + return false; + } + + if (!new MobileAttribute().IsValid(value)) { + return true; + } + + // 不能是手机号码 + ErrorMessageResourceName = nameof(Ln.用户名不能是手机号码); + return false; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/VerifyCodeAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/VerifyCodeAttribute.cs new file mode 100644 index 00000000..07cdf1c1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/DataValidation/VerifyCodeAttribute.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Domain.Attributes.DataValidation; + +/// +/// 验证码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class VerifyCodeAttribute : RegexAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public VerifyCodeAttribute() // + : base(Chars.RGX_VERIFY_CODE) + { + ErrorMessageResourceName = nameof(Ln.验证码不正确); + ErrorMessageResourceType = typeof(Ln); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/IndicatorAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/IndicatorAttribute.cs new file mode 100644 index 00000000..f489eb59 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/IndicatorAttribute.cs @@ -0,0 +1,14 @@ +namespace NetAdmin.Domain.Attributes; + +/// +/// 标记一个枚举的状态指示 +/// +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)] +public sealed class IndicatorAttribute(string indicate) : Attribute +{ + /// + /// 状态指示 + /// + public string Indicate { get; } = indicate; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/ServerTimeAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/ServerTimeAttribute.cs new file mode 100644 index 00000000..e480bfa0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/ServerTimeAttribute.cs @@ -0,0 +1,7 @@ +namespace NetAdmin.Domain.Attributes; + +/// +/// 标记一个字段启用服务器时间 +/// +[AttributeUsage(AttributeTargets.Property)] +public sealed class ServerTimeAttribute : Attribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Attributes/SnowflakeAttribute.cs b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/SnowflakeAttribute.cs new file mode 100644 index 00000000..84578e15 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Attributes/SnowflakeAttribute.cs @@ -0,0 +1,9 @@ +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + +namespace NetAdmin.Domain.Attributes; + +/// +/// 标记一个字段启用雪花编号生成 +/// +[AttributeUsage(AttributeTargets.Property)] +public sealed class SnowflakeAttribute : Attribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Contexts/ContextUserToken.cs b/src/backend/NetAdmin/NetAdmin.Domain/Contexts/ContextUserToken.cs new file mode 100644 index 00000000..6ec5e9cb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Contexts/ContextUserToken.cs @@ -0,0 +1,58 @@ +namespace NetAdmin.Domain.Contexts; + +/// +/// 上下文用户凭据 +/// +public sealed record ContextUserToken : DataAbstraction +{ + /// + /// 部门编号 + /// + /// ReSharper disable once MemberCanBePrivate.Global + public long DeptId { get; init; } + + /// + /// 用户编号 + /// + /// ReSharper disable once MemberCanBePrivate.Global + public long Id { get; init; } + + /// + /// 做授权验证的Token,全局唯一,可以随时重置(强制下线) + /// + /// ReSharper disable once MemberCanBePrivate.Global + public Guid Token { get; init; } + + /// + /// 用户名 + /// + /// ReSharper disable once MemberCanBePrivate.Global + public string UserName { get; init; } + + /// + /// 从HttpContext 创建上下文用户 + /// + public static ContextUserToken Create() + { + var claim = App.User?.FindFirst(nameof(ContextUserToken)); + return claim?.Value.ToObject(); + } + + /// + /// 从 QueryUserRsp 创建上下文用户 + /// + public static ContextUserToken Create(long id, Guid token, string userName, long deptId) + { + return new ContextUserToken { Id = id, Token = token, UserName = userName, DeptId = deptId }; + } + + /// + /// 从 Json Web Token 创建上下文用户 + /// + public static ContextUserToken Create(string jwt) + { + var claim = JWTEncryption.ReadJwtToken(jwt.TrimPrefix($"{Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA} ")) + ?.Claims.FirstOrDefault(x => x.Type == nameof(ContextUserToken)); + return claim?.Value.ToObject(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DataAbstraction.cs b/src/backend/NetAdmin/NetAdmin.Domain/DataAbstraction.cs new file mode 100644 index 00000000..51ec5107 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DataAbstraction.cs @@ -0,0 +1,48 @@ +namespace NetAdmin.Domain; + +/// +/// 数据基类 +/// +public abstract record DataAbstraction +{ + /// + /// 如果数据校验失败,抛出异常 + /// + /// NetAdminValidateException + public void ThrowIfInvalid() + { + var validationResult = this.TryValidate(); + if (!validationResult.IsValid) { + throw new NetAdminValidateException(validationResult.ValidationResults.ToDictionary( // + x => x.MemberNames.First() // + , x => new[] { x.ErrorMessage })); + } + } + + /// + public override string ToString() + { + return this.ToJson(); + } + + /// + /// 截断所有字符串属性 以符合[MaxLength(x)]特性 + /// + public void TruncateStrings() + { + foreach (var property in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.PropertyType == typeof(string))) { + var maxLen = property.GetCustomAttribute(true)?.Length; + if (maxLen is null or 0) { + continue; + } + + var value = property.GetValue(this); + if (value is not string s || s.Length < maxLen) { + continue; + } + + s = s.Sub(0, maxLen.Value); + property.SetValue(this, s); + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/EntityBase.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/EntityBase.cs new file mode 100644 index 00000000..6e517e64 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/EntityBase.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +/// 数据库实体基类 +/// +public abstract record EntityBase : DataAbstraction + where T : IEquatable +{ + /// + /// 唯一编码 + /// + public virtual T Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientIp.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientIp.cs new file mode 100644 index 00000000..26739390 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientIp.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 创建者客户端IP字段接口 +/// +public interface IFieldCreatedClientIp +{ + /// + /// 创建者客户端IP + /// + int? CreatedClientIp { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientUserAgent.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientUserAgent.cs new file mode 100644 index 00000000..8c1b2f40 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedClientUserAgent.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 创建者客户端用户代理字段接口 +/// +public interface IFieldCreatedClientUserAgent +{ + /// + /// 创建者客户端用户代理 + /// + string CreatedUserAgent { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedTime.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedTime.cs new file mode 100644 index 00000000..e2e2336c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedTime.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 创建时间字段接口 +/// +public interface IFieldCreatedTime +{ + /// + /// 创建时间 + /// + DateTime CreatedTime { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedUser.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedUser.cs new file mode 100644 index 00000000..cd6200d4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldCreatedUser.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 创建用户字段接口 +/// +public interface IFieldCreatedUser +{ + /// + /// 创建者编号 + /// + long? CreatedUserId { get; init; } + + /// + /// 创建者用户名 + /// + string CreatedUserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldEnabled.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldEnabled.cs new file mode 100644 index 00000000..a537d5cb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldEnabled.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 启用字段接口 +/// +public interface IFieldEnabled +{ + /// + /// 是否启用 + /// + bool Enabled { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientIp.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientIp.cs new file mode 100644 index 00000000..a301488d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientIp.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 修改客户端IP字段接口 +/// +public interface IFieldModifiedClientIp +{ + /// + /// 客户端IP + /// + int ModifiedClientIp { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientUserAgent.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientUserAgent.cs new file mode 100644 index 00000000..74fb59dc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedClientUserAgent.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 修改客户端用户代理字段接口 +/// +public interface IFieldModifiedClientUserAgent +{ + /// + /// 客户端用户代理 + /// + string ModifiedUserAgent { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedTime.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedTime.cs new file mode 100644 index 00000000..50f55804 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedTime.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 修改时间字段接口 +/// +public interface IFieldModifiedTime +{ + /// + /// 修改时间 + /// + DateTime? ModifiedTime { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedUser.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedUser.cs new file mode 100644 index 00000000..92df41a6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldModifiedUser.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 修改用户字段接口 +/// +public interface IFieldModifiedUser +{ + /// + /// 修改者编号 + /// + long? ModifiedUserId { get; init; } + + /// + /// 修改者用户名 + /// + string ModifiedUserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldOwner.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldOwner.cs new file mode 100644 index 00000000..91b45072 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldOwner.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 拥有者字段接口 +/// +public interface IFieldOwner +{ + /// + /// 拥有者部门编号 + /// + long? OwnerDeptId { get; init; } + + /// + /// 拥有者用户编号 + /// + long? OwnerId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSort.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSort.cs new file mode 100644 index 00000000..620cc0fa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSort.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 排序字段接口 +/// +public interface IFieldSort +{ + /// + /// 排序值,越大越前 + /// + long Sort { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSummary.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSummary.cs new file mode 100644 index 00000000..48353a24 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldSummary.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 备注字段接口 +/// +public interface IFieldSummary +{ + /// + /// 备注 + /// + string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldVersion.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldVersion.cs new file mode 100644 index 00000000..56d7d54b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/Fields/IFieldVersion.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.DbMaps.Dependency.Fields; + +/// +/// 版本字段接口 +/// +public interface IFieldVersion +{ + /// + /// 数据版本 + /// + long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/ImmutableEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/ImmutableEntity.cs new file mode 100644 index 00000000..09d5ed74 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/ImmutableEntity.cs @@ -0,0 +1,44 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record ImmutableEntity : ImmutableEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 不可变实体 +/// +/// 主键类型 +public abstract record ImmutableEntity : LiteImmutableEntity, IFieldCreatedUser + where T : IEquatable +{ + /// + /// 创建者编号 + /// + [Column(CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public long? CreatedUserId { get; init; } + + /// + /// 创建者用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual string CreatedUserName { get; init; } + + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + public override T Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteImmutableEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteImmutableEntity.cs new file mode 100644 index 00000000..7d22adc6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteImmutableEntity.cs @@ -0,0 +1,37 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record LiteImmutableEntity : LiteImmutableEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 轻型不可变实体 +/// +/// 主键类型 +public abstract record LiteImmutableEntity : EntityBase, IFieldCreatedTime + where T : IEquatable +{ + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime CreatedTime { get; init; } + + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [JsonIgnore] + public override T Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteMutableEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteMutableEntity.cs new file mode 100644 index 00000000..06824910 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteMutableEntity.cs @@ -0,0 +1,35 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record LiteMutableEntity : LiteMutableEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 轻型可变实体 +/// +public abstract record LiteMutableEntity : LiteImmutableEntity, IFieldModifiedTime + where T : IEquatable +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + public override T Id { get; init; } + + /// + /// 修改时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanInsert = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime? ModifiedTime { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteVersionEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteVersionEntity.cs new file mode 100644 index 00000000..53e7f209 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/LiteVersionEntity.cs @@ -0,0 +1,36 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record LiteVersionEntity : LiteVersionEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 乐观锁轻型可变实体 +/// +public abstract record LiteVersionEntity : LiteMutableEntity, IFieldVersion + where T : IEquatable +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override T Id { get; init; } + + /// + /// 数据版本 + /// + [Column(IsVersion = true, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/MutableEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/MutableEntity.cs new file mode 100644 index 00000000..81120cc3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/MutableEntity.cs @@ -0,0 +1,59 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record MutableEntity : MutableEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 可变实体 +/// +public abstract record MutableEntity : LiteMutableEntity, IFieldCreatedUser, IFieldModifiedUser + where T : IEquatable +{ + /// + /// 创建者编号 + /// + [Column(CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual long? CreatedUserId { get; init; } + + /// + /// 创建者用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual string CreatedUserName { get; init; } + + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + public override T Id { get; init; } + + /// + /// 修改者编号 + /// + [Column(CanInsert = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public long? ModifiedUserId { get; init; } + + /// + /// 修改者用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanInsert = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public string ModifiedUserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/SimpleEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/SimpleEntity.cs new file mode 100644 index 00000000..12698287 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/SimpleEntity.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record SimpleEntity : SimpleEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 简单实体 +/// +public abstract record SimpleEntity : EntityBase + where T : IEquatable; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/VersionEntity.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/VersionEntity.cs new file mode 100644 index 00000000..939b8093 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Dependency/VersionEntity.cs @@ -0,0 +1,59 @@ +namespace NetAdmin.Domain.DbMaps.Dependency; + +/// +public abstract record VersionEntity : VersionEntity +{ + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [Snowflake] + public override long Id { get; init; } +} + +/// +/// 乐观锁可变实体 +/// +public abstract record VersionEntity : LiteVersionEntity, IFieldModifiedUser, IFieldCreatedUser + where T : IEquatable +{ + /// + /// 创建者编号 + /// + [Column(CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual long? CreatedUserId { get; init; } + + /// + /// 创建者用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual string CreatedUserName { get; init; } + + /// + /// 唯一编码 + /// + [Column(IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + public override T Id { get; init; } + + /// + /// 修改者编号 + /// + [Column(CanInsert = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual long? ModifiedUserId { get; init; } + + /// + /// 修改者用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31, CanInsert = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual string ModifiedUserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Tpl/Tpl_Example.cs b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Tpl/Tpl_Example.cs new file mode 100644 index 00000000..1bcbaff2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/DbMaps/Tpl/Tpl_Example.cs @@ -0,0 +1,7 @@ +namespace NetAdmin.Domain.DbMaps.Tpl; + +/// +/// 示例表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Tpl_Example))] +public record Tpl_Example : VersionEntity; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/BulkReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/BulkReq.cs new file mode 100644 index 00000000..db5d48a7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/BulkReq.cs @@ -0,0 +1,16 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 批量请求 +/// +public sealed record BulkReq : DataAbstraction + where T : DataAbstraction, new() +{ + /// + /// 请求对象 + /// + [MaxLength(Numbers.MAX_LIMIT_BULK_REQ)] + [MinLength(1)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.请求对象不能为空))] + public IEnumerable Items { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/DelReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/DelReq.cs new file mode 100644 index 00000000..c1638384 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/DelReq.cs @@ -0,0 +1,14 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +public sealed record DelReq : DelReq; + +/// +/// 请求:通过编号删除 +/// +public record DelReq : EntityBase + where T : IEquatable +{ + /// + public override T Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/IPagedInfo.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/IPagedInfo.cs new file mode 100644 index 00000000..c02bb3fe --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/IPagedInfo.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 信息:分页 +/// +public interface IPagedInfo +{ + /// + /// 当前页码 + /// + int Page { get; init; } + + /// + /// 页容量 + /// + int PageSize { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/NopReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/NopReq.cs new file mode 100644 index 00000000..667c3045 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/NopReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 空请求 +/// +public sealed record NopReq : DataAbstraction; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryReq.cs new file mode 100644 index 00000000..71e55d33 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryReq.cs @@ -0,0 +1,16 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 请求:分页查询 +/// +public sealed record PagedQueryReq : QueryReq, IPagedInfo + where T : DataAbstraction, new() +{ + /// + [Range(1, Numbers.MAX_LIMIT_QUERY_PAGE_NO)] + public int Page { get; init; } = 1; + + /// + [Range(1, Numbers.MAX_LIMIT_QUERY_PAGE_SIZE)] + public int PageSize { get; init; } = Numbers.DEF_PAGE_SIZE_QUERY; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryRsp.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryRsp.cs new file mode 100644 index 00000000..b94aeff0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/PagedQueryRsp.cs @@ -0,0 +1,24 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 响应:分页查询 +/// +public sealed record PagedQueryRsp(int Page, int PageSize, long Total, IEnumerable Rows) : IPagedInfo + where T : DataAbstraction +{ + /// + /// 数据行 + /// + public IEnumerable Rows { get; } = Rows; + + /// + public int Page { get; init; } = Page; + + /// + public int PageSize { get; init; } = PageSize; + + /// + /// 数据总条 + /// + public long Total { get; init; } = Total; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/QueryReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/QueryReq.cs new file mode 100644 index 00000000..3b8ff65d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Dependency/QueryReq.cs @@ -0,0 +1,72 @@ +namespace NetAdmin.Domain.Dto.Dependency; + +/// +/// 请求:查询 +/// +public record QueryReq : DataAbstraction + where T : DataAbstraction, new() +{ + /// + /// 取前n条 + /// + [Range(1, Numbers.MAX_LIMIT_QUERY)] + public int Count { get; init; } = Numbers.MAX_LIMIT_QUERY; + + /// + /// 动态查询条件 + /// + public DynamicFilterInfo DynamicFilter { get; init; } + + /// + /// 查询条件 + /// + public T Filter { get; init; } + + /// + /// 查询关键字 + /// + public string Keywords { get; init; } + + /// + /// 排序方式 + /// + public Orders? Order { get; init; } + + /// + /// 排序字段 + /// + public string Prop { get; init; } + + /// + /// 所需字段 + /// + public string[] RequiredFields { get; set; } + + /// + /// 列表表达式 + /// + public Expression> GetToListExp() + { + if (RequiredFields.NullOrEmpty()) { + return null; + } + + var expParameter = Expression.Parameter(typeof(TEntity), "a"); + var bindings = new List(); + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in RequiredFields) { + var prop = typeof(TEntity).GetProperty(field); + if (prop == null || prop.GetCustomAttribute() != null) { + continue; + } + + var propExp = Expression.Property(expParameter, prop); + var binding = Expression.Bind(prop, propExp); + bindings.Add(binding); + } + + var expBody = Expression.MemberInit(Expression.New(typeof(TEntity)), bindings); + return Expression.Lambda>(expBody, expParameter); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/DfBuilder.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/DfBuilder.cs new file mode 100644 index 00000000..087d8297 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/DfBuilder.cs @@ -0,0 +1,17 @@ +using NetAdmin.Domain.Enums; + +namespace NetAdmin.Domain.Dto; + +/// +/// 动态过滤条件生成器 +/// +public sealed record DfBuilder +{ + /// + /// 构建生成器 + /// + public static DynamicFilterInfo New(DynamicFilterLogics logic) + { + return new DynamicFilterInfo { Logic = logic, Filters = [] }; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/DynamicFilterInfo.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/DynamicFilterInfo.cs new file mode 100644 index 00000000..4742cdbb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/DynamicFilterInfo.cs @@ -0,0 +1,125 @@ +using NetAdmin.Domain.Enums; + +namespace NetAdmin.Domain.Dto; + +/// +/// 动态过滤条件 +/// +public sealed record DynamicFilterInfo : DataAbstraction +{ + /// + /// 字段名 + /// + public string Field { get; init; } + + /// + /// 子过滤条件 + /// + public List Filters { get; init; } + + /// + /// 子过滤条件逻辑关系 + /// + public DynamicFilterLogics Logic { get; init; } + + /// + /// 操作符 + /// + public DynamicFilterOperators Operator { get; init; } + + /// + /// 值 + /// + public object Value { get; init; } + + /// + /// 隐式转换为 FreeSql 的 DynamicFilterInfo 对象 + /// + public static implicit operator FreeSql.Internal.Model.DynamicFilterInfo(DynamicFilterInfo d) + { + var ret = d.Adapt(); + ProcessDynamicFilter(ret); + return ret; + } + + /// + /// 添加子过滤条件 + /// + public DynamicFilterInfo Add(DynamicFilterInfo df) + { + if (Filters == null) { + return this with { Filters = [df] }; + } + + Filters.Add(df); + return this; + } + + /// + /// 添加过滤条件 + /// + public DynamicFilterInfo Add(string field, DynamicFilterOperators opt, object val) + { + return Add(new DynamicFilterInfo { Field = field, Operator = opt, Value = val }); + } + + /// + /// 添加过滤条件 + /// + public DynamicFilterInfo AddIf(bool condition, string field, DynamicFilterOperators opt, object val) + { + return !condition ? this : Add(field, opt, val); + } + + /// + /// 添加过滤条件 + /// + public DynamicFilterInfo AddIf(bool condition, DynamicFilterInfo df) + { + return !condition ? this : Add(df); + } + + private static void ParseDateExp(FreeSql.Internal.Model.DynamicFilterInfo d) + { + var values = ((JsonElement)d.Value).Deserialize(); + if (!DateTime.TryParse(values[0], CultureInfo.InvariantCulture, out _)) { + var result = values[0] + .ExecuteCSharpCodeAsync([typeof(DateTime).Assembly], nameof(System)) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + values[0] = $"{result:yyyy-MM-dd HH:mm:ss}"; + } + + if (!DateTime.TryParse(values[1], CultureInfo.InvariantCulture, out _)) { + var result = values[1] + .ExecuteCSharpCodeAsync([typeof(DateTime).Assembly], nameof(System)) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + values[1] = $"{result:yyyy-MM-dd HH:mm:ss}"; + } + + d.Value = values; + } + + private static void ProcessDynamicFilter(FreeSql.Internal.Model.DynamicFilterInfo d) + { + if (d?.Filters != null) { + foreach (var filterInfo in d.Filters) { + ProcessDynamicFilter(filterInfo); + } + } + + if (new[] { nameof(IFieldCreatedClientIp.CreatedClientIp), nameof(IFieldModifiedClientIp.ModifiedClientIp) }.Contains( + d?.Field, StringComparer.OrdinalIgnoreCase)) { + var val = d!.Value?.ToString(); + if (val?.IsIpV4() == true) { + d.Value = val.IpV4ToInt32(); + } + } + else if (d?.Operator == DynamicFilterOperator.DateRange) { + ParseDateExp(d); + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/RestfulInfo.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/RestfulInfo.cs new file mode 100644 index 00000000..3286b5e6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/RestfulInfo.cs @@ -0,0 +1,25 @@ +namespace NetAdmin.Domain.Dto; + +/// +/// 信息:RESTful 风格结果集 +/// +public record RestfulInfo : DataAbstraction +{ + /// + /// 代码 + /// + /// succeed + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public ErrorCodes Code { get; init; } + + /// + /// 数据 + /// + public T Data { get; init; } + + /// + /// 字符串:"消息内容",或数组:[{"参数名1":"消息内容1"},{"参数名2":"消息内容2"}] + /// + /// 请求成功 + public object Msg { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/CreateExampleReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/CreateExampleReq.cs new file mode 100644 index 00000000..bf6eeb95 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/CreateExampleReq.cs @@ -0,0 +1,8 @@ +using NetAdmin.Domain.DbMaps.Tpl; + +namespace NetAdmin.Domain.Dto.Tpl.Example; + +/// +/// 请求:创建示例 +/// +public record CreateExampleReq : Tpl_Example; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleReq.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleReq.cs new file mode 100644 index 00000000..dbeb8849 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleReq.cs @@ -0,0 +1,13 @@ +using NetAdmin.Domain.DbMaps.Tpl; + +namespace NetAdmin.Domain.Dto.Tpl.Example; + +/// +/// 请求:查询示例 +/// +public sealed record QueryExampleReq : Tpl_Example +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleRsp.cs b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleRsp.cs new file mode 100644 index 00000000..27e7dffe --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Dto/Tpl/Example/QueryExampleRsp.cs @@ -0,0 +1,17 @@ +using NetAdmin.Domain.DbMaps.Tpl; + +namespace NetAdmin.Domain.Dto.Tpl.Example; + +/// +/// 响应:查询示例 +/// +public sealed record QueryExampleRsp : Tpl_Example +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterLogics.cs b/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterLogics.cs new file mode 100644 index 00000000..13c768eb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterLogics.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.Domain.Enums; + +/// +/// 动态查询条件逻辑运算符 +/// +[Export] +public enum DynamicFilterLogics +{ + /// + /// 并且 + /// + [ResourceDescription(nameof(Ln.并且))] + And = 0 + + , + + /// + /// 或者 + /// + [ResourceDescription(nameof(Ln.或者))] + Or = 1 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterOperators.cs b/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterOperators.cs new file mode 100644 index 00000000..dce8162b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Enums/DynamicFilterOperators.cs @@ -0,0 +1,158 @@ +namespace NetAdmin.Domain.Enums; + +/// +/// 动态查询条件操作符 +/// +[Export] +public enum DynamicFilterOperators +{ + /// + /// 包含 + /// + [ResourceDescription(nameof(Ln.包含))] + Contains = 0 + + , + + /// + /// 以什么开始 + /// + [ResourceDescription(nameof(Ln.以什么开始))] + StartsWith = 1 + + , + + /// + /// 以什么结束 + /// + [ResourceDescription(nameof(Ln.以什么结束))] + EndsWith = 2 + + , + + /// + /// 不包含 + /// + [ResourceDescription(nameof(Ln.不包含))] + NotContains = 3 + + , + + /// + /// 不以什么开始 + /// + [ResourceDescription(nameof(Ln.不以什么开始))] + NotStartsWith = 4 + + , + + /// + /// 不以什么结束 + /// + [ResourceDescription(nameof(Ln.不以什么结束))] + NotEndsWith = 5 + + , + + /// + /// 等于 + /// + [ResourceDescription(nameof(Ln.等于))] + Equal = 6 + + , + + /// + /// 等于 + /// + [ResourceDescription(nameof(Ln.等于))] + Equals = 7 + + , + + /// + /// 等于 + /// + [ResourceDescription(nameof(Ln.等于))] + Eq = 8 + + , + + /// + /// 不等于 + /// + [ResourceDescription(nameof(Ln.不等于))] + NotEqual = 9 + + , + + /// + /// 大于 + /// + [ResourceDescription(nameof(Ln.大于))] + GreaterThan = 10 + + , + + /// + /// 大于等于 + /// + [ResourceDescription(nameof(Ln.大于等于))] + GreaterThanOrEqual = 11 + + , + + /// + /// 小于 + /// + [ResourceDescription(nameof(Ln.小于))] + LessThan = 12 + + , + + /// + /// 小于等于 + /// + [ResourceDescription(nameof(Ln.小于等于))] + LessThanOrEqual = 13 + + , + + /// + /// 范围 + /// + [ResourceDescription(nameof(Ln.范围))] + Range = 14 + + , + + /// + /// 日期范围 + /// + [ResourceDescription(nameof(Ln.日期范围))] + DateRange = 15 + + , + + /// + /// 为其中之一 + /// + [ResourceDescription(nameof(Ln.为其中之一))] + Any = 16 + + , + + /// + /// 不为其中之一 + /// + [ResourceDescription(nameof(Ln.不为其中之一))] + NotAny = 17 + + , + + /// + /// 自定义 + /// + [ResourceDescription(nameof(Ln.自定义))] + Custom = 18 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Enums/HttpMethods.cs b/src/backend/NetAdmin/NetAdmin.Domain/Enums/HttpMethods.cs new file mode 100644 index 00000000..972bc766 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Enums/HttpMethods.cs @@ -0,0 +1,69 @@ +namespace NetAdmin.Domain.Enums; + +/// +/// HTTP 请求方法 +/// +[Export] +public enum HttpMethods +{ + /// + /// Connect + /// + Connect = 1 + + , + + /// + /// Delete + /// + Delete = 2 + + , + + /// + /// Get + /// + Get = 3 + + , + + /// + /// Head + /// + Head = 4 + + , + + /// + /// Options + /// + Options = 5 + + , + + /// + /// Patch + /// + Patch = 6 + + , + + /// + /// Post + /// + Post = 7 + + , + + /// + /// Put + /// + Put = 8 + + , + + /// + /// Trace + /// + Trace = 9 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/IEventSourceGeneric.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/IEventSourceGeneric.cs new file mode 100644 index 00000000..b784b80b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/IEventSourceGeneric.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Domain.Events; + +/// +/// 泛型事件源接口 +/// +public interface IEventSourceGeneric : IEventSource +{ + /// + /// 事件承载(携带)数据 + /// + T Data { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SeedDataInsertedEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SeedDataInsertedEvent.cs new file mode 100644 index 00000000..fe9ea3e2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SeedDataInsertedEvent.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.Domain.Events; + +/// +/// 种子数据插入完毕事件 +/// +public sealed record SeedDataInsertedEvent : DataAbstraction, IEventSource +{ + /// + /// Initializes a new instance of the class. + /// + public SeedDataInsertedEvent(int insertedCount, bool isConsumOnce = false) + { + IsConsumOnce = isConsumOnce; + InsertedCount = insertedCount; + CreatedTime = DateTime.Now; + EventId = nameof(SeedDataInsertedEvent); + } + + /// + public DateTime CreatedTime { get; } + + /// + public string EventId { get; } + + /// + public bool IsConsumOnce { get; } + + /// + public CancellationToken CancellationToken { get; init; } + + /// + /// 插入数量 + /// + public int InsertedCount { get; set; } + + /// + public object Payload { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandAfterEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandAfterEvent.cs new file mode 100644 index 00000000..a320670f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandAfterEvent.cs @@ -0,0 +1,29 @@ +namespace NetAdmin.Domain.Events; + +/// +/// Sql命令执行后事件 +/// +public sealed record SqlCommandAfterEvent : SqlCommandBeforeEvent +{ + /// + /// Initializes a new instance of the class. + /// + public SqlCommandAfterEvent(CommandAfterEventArgs e) // + : base(e) + { + ElapsedMilliseconds = (long)((double)e.ElapsedTicks / Stopwatch.Frequency * 1_000); + EventId = nameof(SqlCommandAfterEvent); + } + + /// + /// 耗时(单位:毫秒) + /// + /// de + private long ElapsedMilliseconds { get; } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "SQL-{0}: {2} ms {1}", Id, Sql, ElapsedMilliseconds); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandBeforeEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandBeforeEvent.cs new file mode 100644 index 00000000..5a96632a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandBeforeEvent.cs @@ -0,0 +1,24 @@ +namespace NetAdmin.Domain.Events; + +/// +/// Sql命令执行前事件 +/// +public record SqlCommandBeforeEvent : SqlCommandEvent +{ + /// + /// Initializes a new instance of the class. + /// + public SqlCommandBeforeEvent(CommandBeforeEventArgs e) + { + Identifier = e.Identifier; + Sql = e.Command.ParameterFormat().RemoveWrapped(); + EventId = nameof(SqlCommandBeforeEvent); + CreatedTime = DateTime.Now; + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "SQL-{0}: Executing...", Id); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandEvent.cs new file mode 100644 index 00000000..88a877a7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SqlCommandEvent.cs @@ -0,0 +1,45 @@ +namespace NetAdmin.Domain.Events; + +/// +/// Sql命令事件 +/// +public abstract record SqlCommandEvent : DataAbstraction, IEventSource +{ + /// + /// Initializes a new instance of the class. + /// + protected SqlCommandEvent(bool isConsumOnce = false) + { + IsConsumOnce = isConsumOnce; + } + + /// + public bool IsConsumOnce { get; } + + /// + public CancellationToken CancellationToken { get; init; } + + /// + public DateTime CreatedTime { get; protected init; } + + /// + public string EventId { get; protected init; } + + /// + public object Payload { get; init; } + + /// + /// 标识符缩写 + /// + protected string Id => Identifier.ToString()[..8].ToUpperInvariant(); + + /// + /// 标识符,可将 CommandBefore 与 CommandAfter 进行匹配 + /// + protected Guid Identifier { get; init; } + + /// + /// 关联的Sql语句 + /// + protected string Sql { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureAfterEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureAfterEvent.cs new file mode 100644 index 00000000..2bdee6ac --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureAfterEvent.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.Domain.Events; + +/// +/// 同步数据库结构之后事件 +/// +public sealed record SyncStructureAfterEvent : SyncStructureBeforeEvent +{ + /// + /// Initializes a new instance of the class. + /// + public SyncStructureAfterEvent(SyncStructureBeforeEventArgs e) // + : base(e) + { + EventId = nameof(SyncStructureAfterEvent); + } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}: {1}: {2}", Id, Ln.数据库结构同步完成, string.Join(',', EntityTypes.Select(x => x.Name))); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureBeforeEvent.cs b/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureBeforeEvent.cs new file mode 100644 index 00000000..a1327036 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/Events/SyncStructureBeforeEvent.cs @@ -0,0 +1,29 @@ +namespace NetAdmin.Domain.Events; + +/// +/// 同步数据库结构之前事件 +/// +public record SyncStructureBeforeEvent : SqlCommandEvent +{ + /// + /// Initializes a new instance of the class. + /// + public SyncStructureBeforeEvent(SyncStructureBeforeEventArgs e) + { + Identifier = e.Identifier; + EventId = nameof(SyncStructureBeforeEvent); + CreatedTime = DateTime.Now; + EntityTypes = e.EntityTypes; + } + + /// + /// 实体类型 + /// + protected Type[] EntityTypes { get; } + + /// + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}: {1}", Id, Ln.数据库同步开始); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/NetAdmin.Domain.csproj b/src/backend/NetAdmin/NetAdmin.Domain/NetAdmin.Domain.csproj new file mode 100644 index 00000000..681c72f9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/NetAdmin.Domain.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Domain/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.Domain/ProjectUsings.cs new file mode 100644 index 00000000..4f775145 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Domain/ProjectUsings.cs @@ -0,0 +1,5 @@ +global using NetAdmin.Domain.Attributes; +global using NetAdmin.Domain.DbMaps.Dependency; +global using NetAdmin.Domain.DbMaps.Dependency.Fields; +global using CsvIgnore = CsvHelper.Configuration.Attributes.IgnoreAttribute; +global using DynamicFilterOperators = NetAdmin.Domain.Enums.DynamicFilterOperators; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Attributes/RemoveNullNodeAttribute.cs b/src/backend/NetAdmin/NetAdmin.Host/Attributes/RemoveNullNodeAttribute.cs new file mode 100644 index 00000000..1cc787f9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Attributes/RemoveNullNodeAttribute.cs @@ -0,0 +1,7 @@ +namespace NetAdmin.Host.Attributes; + +/// +/// 标记一个Action,其响应的json结果会被删除值为null的节点 +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class RemoveNullNodeAttribute : Attribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Attributes/TransactionAttribute.cs b/src/backend/NetAdmin/NetAdmin.Host/Attributes/TransactionAttribute.cs new file mode 100644 index 00000000..2c74403d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Attributes/TransactionAttribute.cs @@ -0,0 +1,41 @@ +namespace NetAdmin.Host.Attributes; + +/// +/// 标记一个Action启用事务 +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class TransactionAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public TransactionAttribute() { } + + /// + /// Initializes a new instance of the class. + /// + public TransactionAttribute(Propagation propagation) // + : this(null, propagation) { } + + /// + /// Initializes a new instance of the class. + /// + public TransactionAttribute(IsolationLevel isolationLevel, Propagation propagation) // + : this(new IsolationLevel?(isolationLevel), propagation) { } + + private TransactionAttribute(IsolationLevel? isolationLevel, Propagation propagation) + { + IsolationLevel = isolationLevel; + Propagation = propagation; + } + + /// + /// 事务隔离级别 + /// + public IsolationLevel? IsolationLevel { get; } + + /// + /// 事务传播方式 + /// + public Propagation Propagation { get; } = Propagation.Required; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/IPollingWork.cs b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/IPollingWork.cs new file mode 100644 index 00000000..c3a73437 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/IPollingWork.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Host.BackgroundRunning; + +/// +/// 轮询工作接口 +/// +public interface IPollingWork +{ + /// + /// 启动工作 + /// + ValueTask StartAsync(CancellationToken cancelToken); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/PollingWork.cs b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/PollingWork.cs new file mode 100644 index 00000000..9b0f68c3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/PollingWork.cs @@ -0,0 +1,15 @@ +using NetAdmin.Domain; + +namespace NetAdmin.Host.BackgroundRunning; + +/// +/// 轮询工作 +/// +public abstract class PollingWork(TWorkData workData) : WorkBase + where TWorkData : DataAbstraction +{ + /// + /// 工作数据 + /// + protected TWorkData WorkData => workData; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/WorkBase.cs b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/WorkBase.cs new file mode 100644 index 00000000..0400fddb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/BackgroundRunning/WorkBase.cs @@ -0,0 +1,75 @@ +using StackExchange.Redis; + +namespace NetAdmin.Host.BackgroundRunning; + +/// +/// 工作基类 +/// +public abstract class WorkBase +{ + /// + /// Initializes a new instance of the class. + /// + protected WorkBase() + { + ServiceProvider = App.GetService().CreateScope().ServiceProvider; + UowManager = ServiceProvider.GetService(); + Logger = ServiceProvider.GetService>(); + } + + /// + /// 日志记录器 + /// + protected ILogger Logger { get; } + + /// + /// 服务提供器 + /// + protected IServiceProvider ServiceProvider { get; } + + /// + /// 事务单元管理器 + /// + protected UnitOfWorkManager UowManager { get; } + + /// + /// 通用工作流 + /// + protected abstract ValueTask WorkflowAsync( // + + // ReSharper disable once UnusedParameter.Global + #pragma warning disable SA1114 + CancellationToken cancelToken); + #pragma warning restore SA1114 + + /// + /// 通用工作流 + /// + /// 加锁失败异常 + protected async ValueTask WorkflowAsync(bool singleInstance, CancellationToken cancelToken) + { + if (singleInstance) { + // 加锁 + var lockName = GetType().FullName; + await using var redisLocker = await GetLockerAsync(lockName).ConfigureAwait(false); + + await WorkflowAsync(cancelToken).ConfigureAwait(false); + return; + } + + await WorkflowAsync(cancelToken).ConfigureAwait(false); + } + + /// + /// 获取锁 + /// + private Task GetLockerAsync(string lockId) + { + var db = ServiceProvider.GetService() + .GetDatabase(ServiceProvider.GetService>() + .Value.Instances.First(x => x.Name == Chars.FLG_REDIS_INSTANCE_DATA_CACHE) + .Database); + return RedisLocker.GetLockerAsync(db, lockId, TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_EXPIRY), Numbers.MAX_LIMIT_RETRY_CNT_REDIS_LOCK + , TimeSpan.FromSeconds(Numbers.SECS_REDIS_LOCK_RETRY_DELAY)); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Controllers/ControllerBase.cs b/src/backend/NetAdmin/NetAdmin.Host/Controllers/ControllerBase.cs new file mode 100644 index 00000000..fe7bce0e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Controllers/ControllerBase.cs @@ -0,0 +1,17 @@ +using NetAdmin.Application.Services; +using NetAdmin.Cache; + +namespace NetAdmin.Host.Controllers; + +/// +/// 控制器基类 +/// +public abstract class ControllerBase(TCache cache = default) : IDynamicApiController + where TCache : ICache // + where TService : IService +{ + /// + /// 关联的缓存 + /// + protected TCache Cache => cache; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Controllers/ProbeController.cs b/src/backend/NetAdmin/NetAdmin.Host/Controllers/ProbeController.cs new file mode 100644 index 00000000..29d2f174 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Controllers/ProbeController.cs @@ -0,0 +1,98 @@ +using NetAdmin.Application.Services; +using NetAdmin.Cache; +using NetAdmin.Host.Middlewares; + +namespace NetAdmin.Host.Controllers; + +/// +/// 探针组件 +/// +[ApiDescriptionSettings("Probe")] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ProbeController : ControllerBase, IService> +{ + /// + /// 退出程序 + /// + [AllowAnonymous] + [HttpGet] + #pragma warning disable CA1822 + public ActionResult Exit(string token) + #pragma warning restore CA1822 + { + if (token != GlobalStatic.SecretKey) { + return new UnauthorizedResult(); + } + + Environment.Exit(0); + return new OkResult(); + } + + /// + /// 健康检查 + /// + [AllowAnonymous] + [HttpGet] + #pragma warning disable CA1822, S3400 + public object HealthCheck() + #pragma warning restore S3400, CA1822 + { + return new { + HostName = Environment.MachineName + , CurrentConnections = SafetyShopHostMiddleware.Connections + , GlobalStatic.ProductVersion + , ThreadCounts = GlobalStatic.CurrentProcess.Threads.Count + , GlobalStatic.LatestLogTime + }; + } + + /// + /// 系统是否已经安全停止 + /// + [AllowAnonymous] + [HttpGet] + [NonUnify] + #pragma warning disable CA1822, S3400 + public IActionResult IsSystemSafetyStopped(int logTimeoutSeconds = 15) + #pragma warning restore S3400, CA1822 + { + return new ContentResult { Content = (DateTime.Now - GlobalStatic.LatestLogTime).TotalSeconds > logTimeoutSeconds ? "1" : "0" }; + } + + /// + /// 实例下线 + /// + /// + /// 流量只出不进 + /// + [AllowAnonymous] + [HttpGet] + #pragma warning disable CA1822 + public ActionResult Offline(string token) + #pragma warning restore CA1822 + { + if (token != GlobalStatic.SecretKey) { + return new UnauthorizedResult(); + } + + SafetyShopHostMiddleware.Stop(); + return new OkResult(); + } + + /// + /// 停止日志计数器 + /// + [AllowAnonymous] + [HttpGet] + #pragma warning disable CA1822 + public ActionResult StopLogCounter(string token) + #pragma warning restore CA1822 + { + if (token != GlobalStatic.SecretKey) { + return new UnauthorizedResult(); + } + + GlobalStatic.LogCounterOff = true; + return new OkResult(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Controllers/Tpl/ExampleController.cs b/src/backend/NetAdmin/NetAdmin.Host/Controllers/Tpl/ExampleController.cs new file mode 100644 index 00000000..29698b7b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Controllers/Tpl/ExampleController.cs @@ -0,0 +1,94 @@ +using NetAdmin.Application.Modules.Tpl; +using NetAdmin.Application.Services.Tpl.Dependency; +using NetAdmin.Cache.Tpl.Dependency; +using NetAdmin.Domain.Dto.Dependency; +using NetAdmin.Domain.Dto.Tpl.Example; +using NetAdmin.Host.Attributes; + +namespace NetAdmin.Host.Controllers.Tpl; + +/// +/// 示例服务 +/// +[ApiDescriptionSettings(nameof(Tpl), Module = nameof(Tpl))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ExampleController(IExampleCache cache) : ControllerBase(cache), IExampleModule +{ + /// + /// 批量删除示例 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 示例计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建示例 + /// + [Transaction] + public Task CreateAsync(CreateExampleReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除示例 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 示例是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出示例 + /// + [NonAction] + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个示例 + /// + public Task GetAsync(QueryExampleReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询示例 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询示例 + /// + [NonAction] + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/HttpContextExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..1cff45e1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/HttpContextExtensions.cs @@ -0,0 +1,34 @@ +namespace NetAdmin.Host.Extensions; + +/// +/// HttpContext 扩展方法 +/// +public static class HttpContextExtensions +{ + private static readonly Regex _nullRegex = new("\"[^\"]+?\":null,?", RegexOptions.Compiled); + + /// + /// 删除 response json body 中value 为null的节点 + /// + public static async Task RemoveJsonNodeWithNullValueAsync(this HttpContext me) + { + // 非json格式,退出 + if (!(me.Response.ContentType?.Contains(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON) ?? false)) { + return; + } + + // 流不可读,退出 + if (!me.Response.Body.CanSeek || !me.Response.Body.CanRead || !me.Response.Body.CanWrite) { + return; + } + + _ = me.Response.Body.Seek(0, SeekOrigin.Begin); + var sr = new StreamReader(me.Response.Body); + var bodyString = await sr.ReadToEndAsync().ConfigureAwait(false); + bodyString = _nullRegex.Replace(bodyString, string.Empty).Replace(",}", "}"); + _ = me.Response.Body.Seek(0, SeekOrigin.Begin); + var bytes = Encoding.UTF8.GetBytes(bodyString); + me.Response.Body.SetLength(bytes.Length); + await me.Response.Body.WriteAsync(bytes).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/IApplicationBuilderExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/IApplicationBuilderExtensions.cs new file mode 100644 index 00000000..d1c9d7bb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/IApplicationBuilderExtensions.cs @@ -0,0 +1,62 @@ +#if DEBUG +using IGeekFan.AspNetCore.Knife4jUI; + +#else +using Prometheus; +using Prometheus.HttpMetrics; +#endif + +namespace NetAdmin.Host.Extensions; + +/// +/// ApplicationBuilder对象 扩展方法 +/// +[SuppressSniffer] + +// ReSharper disable once InconsistentNaming +public static class IApplicationBuilderExtensions +{ + /// + /// 执行匹配的端点 + /// + public static IApplicationBuilder UseEndpoints(this IApplicationBuilder me) + { + return me.UseEndpoints(endpoints => { + _ = endpoints.MapControllers(); + #if !DEBUG + _ = endpoints.MapMetrics(); + #endif + }); + } + #if DEBUG + /// + /// 使用 api skin (knife4j-vue) + /// + public static IApplicationBuilder UseOpenApiSkin(this IApplicationBuilder me) + { + return me.UseKnife4UI(options => { + options.RoutePrefix = string.Empty; // 配置 Knife4UI 路由地址 + foreach (var groupInfo in SpecificationDocumentBuilder.GetOpenApiGroups()) { + options.SwaggerEndpoint(groupInfo.RouteTemplate, groupInfo.Title); + } + }); + } + #else + /// + /// 使用 Prometheus + /// + public static IApplicationBuilder UsePrometheus(this IApplicationBuilder me) + { + return me.UseHttpMetrics(opt => { + opt.RequestDuration.Histogram = Metrics.CreateHistogram( // + "http_request_duration_seconds" + , "The duration of HTTP requests processed by an ASP.NET Core application." + , HttpRequestLabelNames.All + , new HistogramConfiguration { + Buckets = Histogram.PowersOfTenDividedBuckets( + -2, 2, 4) + }); + }); + } + #endif +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/IMvcBuilderExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/IMvcBuilderExtensions.cs new file mode 100644 index 00000000..e1b6a19a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/IMvcBuilderExtensions.cs @@ -0,0 +1,115 @@ +using NetAdmin.Host.Filters; +using NetAdmin.Host.Utils; + +namespace NetAdmin.Host.Extensions; + +/// +/// IMvcBuilder 扩展方法 +/// +[SuppressSniffer] + +// ReSharper disable once InconsistentNaming +public static class IMvcBuilderExtensions +{ + /// + /// api结果处理器 + /// + public static IMvcBuilder AddDefaultApiResultHandler(this IMvcBuilder me) + { + return me.AddInjectWithUnifyResult(injectOptions => { + injectOptions.ConfigureSwaggerGen(genOptions => { + // 替换自定义的EnumSchemaFilter,支持多语言Resx资源 (需将SpecificationDocumentSettings.EnableEnumSchemaFilter配置为false) + genOptions.SchemaFilter(); + + // 枚举显示自身xml comment 而不是$ref原型引用 + genOptions.UseInlineDefinitionsForEnums(); + + // 将程序集版本号与OpenApi版本号同步 + foreach (var doc in genOptions.SwaggerGeneratorOptions.SwaggerDocs) { + doc.Value.Version = FileVersionInfo + .GetVersionInfo(Assembly.GetEntryAssembly()!.Location) + .ProductVersion; + } + }); + }); + } + + /// + /// Json序列化配置 + /// + /// + /// 正反序列化规则: + /// object->json: + /// 1、值为 null 或 default 的节点将被忽略 + /// 2、值为 "" 的节点将被忽略。 + /// 3、值为 [] 的节点将被忽略。 + /// 4、节点名:大驼峰转小驼峰 + /// 5、不转义除对json结构具有破坏性(如")以外的任何字符 + /// 6、大数字原样输出(不加引号),由前端处理js大数兼容问题 + /// json->object: + /// 1、允许带注释的json(自动忽略) + /// 2、允许尾随逗号 + /// 3、节点名大小写不敏感 + /// 4、允许带双引号的数字 + /// 5、值为"" 转 null + /// 6、值为[] 转 null + /// + public static IMvcBuilder AddJsonSerializer(this IMvcBuilder me, bool enumToString = false) + { + return me.AddJsonOptions(options => SetJsonOptions(enumToString, options)); + } + + /// + /// 设置Json选项 + /// + private static void SetJsonOptions(bool enumToString, JsonOptions options) + { + ////////////////////////////// json -> object + + // 允许带注释 + options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; + + // 允许尾随逗号 + options.JsonSerializerOptions.AllowTrailingCommas = true; + + // 允许数字带双引号 + options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + + // 大小写不敏感 + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + + // 允许读取引号包围的数字 + options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + + ///////////////////////////// object -> json + + // 转小驼峰 + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + // 不严格转义 + options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + + // 写入时,忽略null、default + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + + ////////////////////////////// object <-> json + + // "" 转 null 双向 + options.JsonSerializerOptions.Converters.Add(new ToNullIfEmptyStringConverter()); + + // [] 转 null 双向 + options.JsonSerializerOptions.TypeInfoResolver = new CollectionJsonTypeInfoResolver(); + + // 日期格式 2023-01-18 20:02:12 + _ = options.JsonSerializerOptions.Converters.AddDateTimeTypeConverters(); + + // object->json 枚举显名 而非数字 ,json->object 可以枚举名 也可以数值 + if (enumToString) { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); + } + + // 快捷访问方式 + GlobalStatic.JsonSerializerOptions = options.JsonSerializerOptions; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/MethodInfoExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/MethodInfoExtensions.cs new file mode 100644 index 00000000..e99d91c9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/MethodInfoExtensions.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Host.Extensions; + +/// +/// Type 扩展方法 +/// +public static class MethodInfoExtensions +{ + /// + /// 获取路由路径 + /// + public static string GetRoutePath(this MethodInfo me, IServiceProvider serviceProvider) + { + return serviceProvider.GetService() + .ActionDescriptors.Items.FirstOrDefault(x => x.DisplayName!.StartsWith( // + $"{me.DeclaringType}.{me.Name}", StringComparison.Ordinal)) + ?.AttributeRouteInfo?.Template; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/ResourceExecutingContextExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/ResourceExecutingContextExtensions.cs new file mode 100644 index 00000000..3d5c1a79 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/ResourceExecutingContextExtensions.cs @@ -0,0 +1,19 @@ +using NetAdmin.Domain.Dto; + +namespace NetAdmin.Host.Extensions; + +/// +/// ResourceExecutingContextExtensions +/// +public static class ResourceExecutingContextExtensions +{ + /// + /// 设置失败结果 + /// + public static void SetFailResult(this ResourceExecutingContext me, ErrorCodes errorCode, string errorMsg = null) + where T : RestfulInfo, new() + { + me.Result = new JsonResult(new T { Code = errorCode, Msg = errorMsg }); + me.HttpContext.Response.StatusCode = Numbers.HTTP_STATUS_BIZ_FAIL; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Extensions/ServiceCollectionExtensions.cs b/src/backend/NetAdmin/NetAdmin.Host/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..ed9a3264 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,216 @@ +using Gurion.Logging; +using NetAdmin.Domain.Contexts; +using StackExchange.Redis; +using Yitter.IdGenerator; +#if DEBUG +using Spectre.Console; +#endif + +namespace NetAdmin.Host.Extensions; + +/// +/// ServiceCollection 扩展方法 +/// +[SuppressSniffer] +public static class ServiceCollectionExtensions +{ + #if DEBUG + private static readonly Dictionary _consoleColors // + = new() { + { + new Regex( // + @"(\d{2,}\.\d+ ?ms)", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Magenta)}]$1[/]" + } + , { + new Regex( // + "(Tb[a-zA-Z0-9]+)", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Cyan)}]$1[/]" + } + , { + new Regex( // + "(INSERT) ", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Blue)}]$1[/] " + } + , { + new Regex( // + "(SELECT) ", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Green)}]$1[/] " + } + , { + new Regex( // + "(UPDATE) ", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Yellow)}]$1[/] " + } + , { + new Regex( // + "(DELETE) ", RegexOptions.Compiled) + , $"[{nameof(ConsoleColor.Red)}]$1[/] " + } + , { + new Regex( // + "()", RegexOptions.Compiled) + , $"[underline {nameof(ConsoleColor.Gray)}]$1[/] " + } + , { + new Regex( // + "(ResponseBody)", RegexOptions.Compiled) + , $"[underline {nameof(ConsoleColor.Cyan)}]$1[/] " + } + , { + new Regex( // + "(RequestBody)", RegexOptions.Compiled) + , $"[underline {nameof(ConsoleColor.Magenta)}]$1[/] " + } + , { + new Regex( // + @"(\[\[dbo\]\]\.)(\[\[.+?\]\]) ", RegexOptions.Compiled) + , $"$1[{nameof(ConsoleColor.Magenta)}]$2[/] " + } + }; + + #endif + + /// + /// 扫描程序集中继承自IConfigurableOptions的选项,注册 + /// + public static IServiceCollection AddAllOptions( // + this IServiceCollection me) + { + var optionsTypes + = from type in App.EffectiveTypes.Where(x => !x.IsAbstract && !x.FullName!.Contains(nameof(Gurion)) && + x.GetInterfaces().Contains(typeof(IConfigurableOptions))) + select type; + + var sbLog = new StringBuilder(); + foreach (var type in optionsTypes) { + var configureMethod = typeof(ConfigurableOptionsServiceCollectionExtensions).GetMethod( + nameof(ConfigurableOptionsServiceCollectionExtensions.AddConfigurableOptions), BindingFlags.Public | BindingFlags.Static + , [typeof(IServiceCollection)]); + _ = configureMethod!.MakeGenericMethod(type).Invoke(me, [me]); + _ = sbLog.Append(CultureInfo.InvariantCulture, $" {type.Name}"); + } + + LogHelper.Get()?.Info($"{Ln.配置文件初始化完毕} {sbLog}"); + return me; + } + + /// + /// 添加控制台日志模板 + /// + public static IServiceCollection AddConsoleFormatter(this IServiceCollection me) + { + return me.AddConsoleFormatter(options => { + var logLevels = Enum.GetValues().ToDictionary(x => x, x => x.GetDisplay()); + + options.WriteHandler = (message, _, _, _, _) => { + #if DEBUG + MarkupLine(message.Message.EscapeMarkup(), message, logLevels); + if (message.Exception != null) { + MarkupLine(message.Exception.ToString().EscapeMarkup(), message, logLevels); + } + #else + var msg = message.Message.ReplaceLineEndings(string.Empty); + var (date, logName, logFormat) = ParseMessage(message, false); + Console.WriteLine( // + logFormat, date, logLevels[(LogLevels)message.LogLevel].ShortName, logName, message.ThreadId, msg); + #endif + GlobalStatic.IncrementLogCounter(); + }; + }); + } + + /// + /// 添加上下文用户令牌 + /// + public static IServiceCollection AddContextUserToken(this IServiceCollection me) + { + return me.AddScoped(typeof(ContextUserToken), _ => ContextUserToken.Create()); + } + + /// + /// 添加事件总线 + /// + public static IServiceCollection AddEventBus(this IServiceCollection me) + { + return me.AddEventBus(builder => builder.AddSubscribers(App.Assemblies.ToArray())); + } + + /// + /// 添加内存缓存 + /// + public static IServiceCollection AddMemCache(this IServiceCollection me) + { + return me.AddMemoryCache(options => options.TrackStatistics = true); + } + + /// + /// OpenTelemetry数据监控 + /// + public static IServiceCollection AddOpenTelemetryNet(this IServiceCollection me) + { + // _ = me.AddOpenTelemetry() + // .WithMetrics(builder => builder.AddAspNetCoreInstrumentation() + // .AddHttpClientInstrumentation() + // .AddRuntimeInstrumentation() + // .AddProcessInstrumentation() + // .AddPrometheusExporter()); + return me; + } + + /// + /// 添加 Redis缓存 + /// + public static IServiceCollection AddRedisCache(this IServiceCollection me) + { + var redisOptions = App.GetOptions().Instances.First(x => x.Name == Chars.FLG_REDIS_INSTANCE_DATA_CACHE); + + // IDistributedCache 分布式缓存通用接口 + _ = me.AddStackExchangeRedisCache(options => { + // 连接字符串 + options.Configuration = redisOptions.ConnStr; + }); + + // Redis原生接口 + return me.AddSingleton(ConnectionMultiplexer.Connect(redisOptions.ConnStr)); + } + + /// + /// 添加雪花编号生成器 + /// + public static IServiceCollection AddSnowflake(this IServiceCollection me) + { + // 雪花漂移算法 + var workerId = Environment.GetEnvironmentVariable(Chars.FLG_SNOWFLAKE_WORK_ID).Int32Try(0); + var idGeneratorOptions = new IdGeneratorOptions((ushort)workerId) { WorkerIdBitLength = 6 }; + YitIdHelper.SetIdGenerator(idGeneratorOptions); + return me; + } + + #if DEBUG + private static void MarkupLine( // + string msg // + , LogMessage message // + , Dictionary logLevels) + { + msg = _consoleColors.Aggregate( // + msg, (current, regex) => regex.Key.Replace(current, regex.Value)); + msg = msg.ReplaceLineEndings(string.Empty); + var colorName = logLevels[(LogLevels)message.LogLevel].Name!; + var (date, logName, logFormat) = ParseMessage(message, true); + AnsiConsole.MarkupLine( // + CultureInfo.InvariantCulture, logFormat, date, colorName, logName, message.ThreadId, msg); + } + + #endif + private static (string Date, string LogName, string LogFormat) ParseMessage(LogMessage message, bool showColor) + { + var date = $"{message.LogDateTime:HH:mm:ss.ffffff}"; + var logName = message.LogName.PadRight(64, ' ')[^64..]; + var format = showColor + ? $"[{nameof(ConsoleColor.Gray)}][[{{0}} {{1}} {{2,-{64}}} #{{3,4}}]][/] {{4}}" + : $"[{{0}} {{1}} {{2,-{64}}} #{{3,4}}] {{4}}"; + + return (date, logName, format); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Filters/ApiResultHandler.cs b/src/backend/NetAdmin/NetAdmin.Host/Filters/ApiResultHandler.cs new file mode 100644 index 00000000..edd7d494 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Filters/ApiResultHandler.cs @@ -0,0 +1,108 @@ +using Gurion.FriendlyException; +using NetAdmin.Domain.Dto; + +namespace NetAdmin.Host.Filters; + +/// +/// Api结果格式化处理器 +/// +/// +/// 约定: +/// 1、业务异常需要设置HttpStatusCode与成功请求区分,不要统一返回200( 为了方便前端调试:在浏览器F12中快速观察失败请求【红色高亮】) +/// 2、不得占用常见HttpStatusCode,例如4xx、5xx,以免与传输层错误混淆,干扰运维。 +/// 实现: +/// 1、本系统代码覆盖范围内占用4个HttpStatusCode:200(表示业务成功)、401(身份未确认)、403(权限不足)、900(其他所有业务异常) +/// 2、当HttpStatusCode为900时,通过子码(JsonBody里面的Code区分具体异常),同时将子码写入RspHeader中,方便日志系统快速筛选归类。 +/// 3、子码定义,见枚举 +/// +public abstract class ApiResultHandler + where T : RestfulInfo, new() +{ + /// + /// 发生异常 + /// + public IActionResult OnException(ExceptionContext context, ExceptionMetadata metadata) + { + var naException = context.Exception switch { + NetAdminException ex => ex + , _ => context.Exception.Message.Contains(Chars.FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT) + ? new NetAdminInvalidOperationException(Ln.记录已存在) + : null + }; + var errorCode = naException?.Code ?? ErrorCodes.Unhandled; + var result = RestfulResult(errorCode, metadata.Data + , naException is NetAdminValidateException vEx + ? vEx.ValidateResults + : naException?.Message ?? errorCode.ResDesc()); + + SetErrorCodeToHeader(context.HttpContext, errorCode); + + return new JsonResult(result) { StatusCode = Numbers.HTTP_STATUS_BIZ_FAIL }; + } + + /// + /// HTTP状态码处理 + /// + #pragma warning disable ASA001, VSTHRD200 + public Task OnResponseStatusCodes( // + HttpContext context, int statusCode, UnifyResultSettingsOptions unifyResultSettings = null) + { + // 设置响应状态码 + UnifyContext.SetResponseStatusCodes(context, statusCode, unifyResultSettings); + return Task.CompletedTask; + } + + #pragma warning restore ASA001, VSTHRD200 + + /// + /// 请求成功 + /// + public IActionResult OnSucceeded(ActionExecutedContext _, object data) + { + return new JsonResult(RestfulResult(0, data)); + } + + /// + /// 校验失败 + /// + public IActionResult OnValidateFailed(ActionExecutingContext context, ValidationMetadata metadata) + { + SetErrorCodeToHeader(context.HttpContext, ErrorCodes.InvalidInput); + + return new JsonResult(RestfulResult(ErrorCodes.InvalidInput, metadata.Data, GetValidationResult(metadata.ValidationResult))) { + StatusCode = Numbers.HTTP_STATUS_BIZ_FAIL + }; + } + + private static object GetValidationResult(object validationResult) + { + var startWithDollar = false; + try { + return validationResult is Dictionary dic + ? dic.ToDictionary( // + x => (startWithDollar = x.Key.StartsWith('$')) + ? x.Key[1..].TrimStart('.').NullOrEmpty(null) ?? throw new NetAdminInvalidInputException() + : x.Key, x => startWithDollar ? [Ln.参数格式不正确] : x.Value) + : validationResult; + } + catch (NetAdminInvalidInputException) { + return Ln.参数格式不正确; + } + } + + /// + /// 返回 RESTful 风格结果集 + /// + private static T RestfulResult(ErrorCodes errorCode, object data = default, object message = default) + { + return new T { Code = errorCode, Data = data, Msg = message }; + } + + /// + /// 写入错误码到HttpHeader + /// + private static void SetErrorCodeToHeader(HttpContext context, ErrorCodes errorCode) + { + context.Response.Headers[nameof(ErrorCodes)] = Enum.GetName(errorCode); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Filters/DefaultApiResultHandler.cs b/src/backend/NetAdmin/NetAdmin.Host/Filters/DefaultApiResultHandler.cs new file mode 100644 index 00000000..d6777752 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Filters/DefaultApiResultHandler.cs @@ -0,0 +1,17 @@ +using Gurion.FriendlyException; +using NetAdmin.Domain.Dto; + +namespace NetAdmin.Host.Filters; + +/// +[SuppressSniffer] +[UnifyModel(typeof(RestfulInfo<>))] +public sealed class DefaultApiResultHandler : ApiResultHandler>, IUnifyResultProvider +{ + /// + public IActionResult OnAuthorizeException(DefaultHttpContext context, ExceptionMetadata metadata) + { + LogHelper.Get().Error(metadata.Exception); + throw metadata.Exception; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Filters/GlobalExceptionHandler.cs b/src/backend/NetAdmin/NetAdmin.Host/Filters/GlobalExceptionHandler.cs new file mode 100644 index 00000000..2d0a7e95 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Filters/GlobalExceptionHandler.cs @@ -0,0 +1,23 @@ +using Gurion.FriendlyException; + +namespace NetAdmin.Host.Filters; + +/// +public sealed class GlobalExceptionHandler(ILogger logger) : IGlobalExceptionHandler, ISingleton +{ + /// + public Task OnExceptionAsync(ExceptionContext context) + { + if (context.Exception is NetAdminException and not NetAdminUnexpectedException) { + logger.Warn(context.Exception); + } + else { + logger.Error(context.Exception); + } + + // 将异常设置到HttpContext.Features中 以便中间件能获取到他 + context.HttpContext.Features.Set(new ExceptionHandlerFeature { Error = context.Exception }); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Filters/TransactionInterceptor.cs b/src/backend/NetAdmin/NetAdmin.Host/Filters/TransactionInterceptor.cs new file mode 100644 index 00000000..1abadd7e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Filters/TransactionInterceptor.cs @@ -0,0 +1,30 @@ +using NetAdmin.Application.Extensions; +using NetAdmin.Host.Attributes; + +namespace NetAdmin.Host.Filters; + +/// +/// 事务拦截器 +/// +[SuppressSniffer] +public sealed class TransactionInterceptor(UnitOfWorkManager uowManager) : IAsyncActionFilter +{ + /// + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + // 跳过没有事务特性标记的方法 + if (context.HttpContext.GetControllerActionDescriptor().MethodInfo.GetCustomAttribute() == null) { + _ = await next().ConfigureAwait(false); + return; + } + + // 事务操作 + await uowManager.AtomicOperateAsync(async () => { + var result = await next().ConfigureAwait(false); + if (result.Exception != null) { + throw result.Exception; + } + }) + .ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Middlewares/RemoveNullNodeMiddleware.cs b/src/backend/NetAdmin/NetAdmin.Host/Middlewares/RemoveNullNodeMiddleware.cs new file mode 100644 index 00000000..6534de32 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Middlewares/RemoveNullNodeMiddleware.cs @@ -0,0 +1,24 @@ +using NetAdmin.Host.Attributes; +using NetAdmin.Host.Extensions; + +namespace NetAdmin.Host.Middlewares; + +/// +/// 删除 response json body 中value 为null的节点 +/// +public sealed class RemoveNullNodeMiddleware(RequestDelegate next) +{ + /// + /// 主函数 + /// + public async Task InvokeAsync(HttpContext context) + { + await next(context).ConfigureAwait(false); + + if (context.GetMetadata() is null) { + return; + } + + await context.RemoveJsonNodeWithNullValueAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Middlewares/SafetyShopHostMiddleware.cs b/src/backend/NetAdmin/NetAdmin.Host/Middlewares/SafetyShopHostMiddleware.cs new file mode 100644 index 00000000..dc0a0ff6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Middlewares/SafetyShopHostMiddleware.cs @@ -0,0 +1,72 @@ +namespace NetAdmin.Host.Middlewares; + +/// +/// 安全停机中间件 +/// +/// +/// 放在所有中间件最前面 +/// +public sealed class SafetyShopHostMiddleware(RequestDelegate next) +{ + private static long _connections; + private static bool _trafficOff; + + /// + /// 当前连接数 + /// + public static long Connections => Interlocked.Read(ref _connections); + + /// + /// 是否已停机 + /// + public static bool IsShutdown => Volatile.Read(ref _trafficOff); + + /// + /// 停机处理 + /// + public static void OnStopping() + { + Stop(); + while (Interlocked.Read(ref _connections) > 0) { + Thread.Sleep(10); + } + } + + /// + /// 系统启机 + /// + public static void Start() + { + Volatile.Write(ref _trafficOff, false); + } + + /// + /// 系统停机 + /// + public static void Stop() + { + Volatile.Write(ref _trafficOff, true); + } + + /// + /// 主函数 + /// + public async Task InvokeAsync(HttpContext context) + { + if (Volatile.Read(ref _trafficOff) && !context.Request.Path.StartsWithSegments($"/{Chars.FLG_PATH_API_RPOBE}") && + !context.Request.Path.StartsWithSegments($"/{Chars.FLG_PATH_API_METRICS}")) { + context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + return; + } + + // webSocket链接不参与计算 + if (context.Request.Path.StartsWithSegments($"/{Chars.FLG_PATH_WEBSOCKET_PREFIX}")) { + await next(context).ConfigureAwait(false); + } + else { + _ = Interlocked.Increment(ref _connections); + await next(context).ConfigureAwait(false); + _ = Interlocked.Decrement(ref _connections); + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/NetAdmin.Host.csproj b/src/backend/NetAdmin/NetAdmin.Host/NetAdmin.Host.csproj new file mode 100644 index 00000000..1dbae83b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/NetAdmin.Host.csproj @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Startup.cs b/src/backend/NetAdmin/NetAdmin.Host/Startup.cs new file mode 100644 index 00000000..1e1c1fec --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Startup.cs @@ -0,0 +1,46 @@ +using Spectre.Console; +using Spectre.Console.Cli; + +namespace NetAdmin.Host; + +/// +/// 启动类 +/// +public abstract class Startup : AppStartup +{ + /// + /// 程序入口 + /// + public static void Entry(IEnumerable args, Action commandConfig = null) + where T : class, ICommand + { + ShowBanner(); + var app = new CommandApp(); + if (commandConfig != null) { + app.Configure(commandConfig); + } + + _ = app.Run(args); + } + + /// + /// 打印Banner + /// + private static void ShowBanner() + { + AnsiConsole.WriteLine(); + var gridInfo = new Grid().AddColumn(new GridColumn().NoWrap().Width(50).PadRight(10)).AddColumn(new GridColumn().NoWrap()).Expand(); + foreach (var kv in ApplicationHelper.GetEnvironmentInfo().OrderBy(x => x.Key)) { + _ = gridInfo.AddRow(kv.Key, kv.Value.ToString()!.EscapeMarkup()); + } + + var gridWrap = new Grid().AddColumn(); + var entryAssembly = Assembly.GetEntryAssembly(); + var assemblyName = entryAssembly!.GetName(); + _ = gridWrap.AddRow(new FigletText(assemblyName.Name!).Color(Color.Green)); + + _ = gridWrap.AddRow(gridInfo); + AnsiConsole.Write(new Panel(gridWrap).Header(GlobalStatic.ProductVersion).Expand()); + AnsiConsole.WriteLine(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Subscribers/SqlProfiler.cs b/src/backend/NetAdmin/NetAdmin.Host/Subscribers/SqlProfiler.cs new file mode 100644 index 00000000..77d48283 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Subscribers/SqlProfiler.cs @@ -0,0 +1,64 @@ +using NetAdmin.Domain.Events; + +namespace NetAdmin.Host.Subscribers; + +/// +/// Sql性能分析 +/// +public sealed class SqlProfiler(ILogger logger) : IEventSubscriber +{ + /// + /// Sql命令执行后 + /// + [EventSubscribe(nameof(SqlCommandAfterEvent))] + public Task CommandAfterAsync(EventHandlerExecutingContext context) + { + var source = context.Source as SqlCommandAfterEvent; + logger.Info(source); + return Task.CompletedTask; + } + + /// + /// Sql命令执行前 + /// + [EventSubscribe(nameof(SqlCommandBeforeEvent))] + public Task CommandBeforeAsync(EventHandlerExecutingContext context) + { + var source = context.Source as SqlCommandBeforeEvent; + logger.Debug(source); + return Task.CompletedTask; + } + + /// + /// 种子数据插入完毕 + /// + [EventSubscribe(nameof(SeedDataInsertedEvent))] + public Task SeedDataInsertedEventAsync(EventHandlerExecutingContext context) + { + var source = context.Source as SeedDataInsertedEvent; + logger.Info(source); + return Task.CompletedTask; + } + + /// + /// 同步数据库结构之后 + /// + [EventSubscribe(nameof(SyncStructureAfterEvent))] + public Task SyncStructureAfterAsync(EventHandlerExecutingContext context) + { + var source = context.Source as SyncStructureAfterEvent; + logger.Info(source); + return Task.CompletedTask; + } + + /// + /// 同步数据库结构之前 + /// + [EventSubscribe(nameof(SyncStructureBeforeEvent))] + public Task SyncStructureBeforeAsync(EventHandlerExecutingContext context) + { + var source = context.Source as SyncStructureBeforeEvent; + logger.Info(source); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Utils/CollectionJsonTypeInfoResolver.cs b/src/backend/NetAdmin/NetAdmin.Host/Utils/CollectionJsonTypeInfoResolver.cs new file mode 100644 index 00000000..ed6b0199 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Utils/CollectionJsonTypeInfoResolver.cs @@ -0,0 +1,86 @@ +namespace NetAdmin.Host.Utils; + +/// +/// 处理集合类型的Json解析器 +/// +public sealed class CollectionJsonTypeInfoResolver : DefaultJsonTypeInfoResolver +{ + /// + /// Initializes a new instance of the class. + /// + public CollectionJsonTypeInfoResolver() + { + Modifiers.Add(CollectionValueModifier); + } + + private static void CollectionValueModifier(JsonTypeInfo typeInfo) + { + foreach (var property in typeInfo.Properties) { + // 跳过非集合属性 + if (!property.PropertyType.GetInterfaces().Contains(typeof(IEnumerable))) { + continue; + } + + // 跳过字符串 + if (property.PropertyType == typeof(string)) { + continue; + } + + var memberName = GetMemberName(property); + + // object->json 只在count>0时返回其值,否则返回null + property.Get = PropertyGet(memberName); + + // json->object 时 为对象分配属性, 改为只在count>0分配 ,否则分配null,而不是分配[] + property.Set = PropertySet(memberName); + } + } + + private static string GetMemberName(JsonPropertyInfo property) + { + return property.GetType().GetRuntimeProperties().First(x => x.Name == "MemberName").GetValue(property) as string; + } + + /// + /// 这里处理子类new隐藏父类同名属性, 取得多个同名属性的问题 + /// + private static PropertyInfo GetNewProperty(string memberName, object obj) + { + return obj.GetType().GetProperties().Where(x => x.Name == memberName).First(x => x.DeclaringType == x.ReflectedType); + } + + /// + /// object->json 只在count>0时返回其值,否则返回null + /// + private static Func PropertyGet(string memberName) + { + return obj => { + object prop; + try { + prop = obj.GetType().GetProperty(memberName!)?.GetValue(obj); + } + catch (AmbiguousMatchException) { + // 这里处理子类new隐藏父类同名属性, 取得多个同名属性的问题 + prop = GetNewProperty(memberName, obj).GetValue(obj); + } + + return prop switch { string => prop, ICollection { Count: > 0 } => prop, _ => null }; + }; + } + + /// + /// json->object 时 为对象分配属性, 改为只在count>0分配 ,否则分配null,而不是分配[] + /// + private static Action PropertySet(string memberName) + { + return (obj, val) => { + val = val switch { string => val, ICollection { Count: > 0 } => val, _ => null }; + try { + obj.GetType().GetProperty(memberName!)?.SetValue(obj, val); + } + catch (AmbiguousMatchException) { + GetNewProperty(memberName, obj).SetValue(obj, val); + } + }; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Utils/SwaggerEnumSchemaFixer.cs b/src/backend/NetAdmin/NetAdmin.Host/Utils/SwaggerEnumSchemaFixer.cs new file mode 100644 index 00000000..9833faef --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Utils/SwaggerEnumSchemaFixer.cs @@ -0,0 +1,44 @@ +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace NetAdmin.Host.Utils; + +/// +/// 修正 规范化文档 Enum 提示 +/// +[SuppressSniffer] +public sealed class SwaggerEnumSchemaFixer : ISchemaFilter +{ + /// + /// 实现过滤器方法 + /// + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + // 非枚举退出 + if (!context.Type.IsEnum) { + return; + } + + const string wrap = "
"; + schema.Enum.Clear(); + schema.Type = context.Type.Name; + var sb = new StringBuilder(); + _ = sb.Append(schema.Description); + + foreach (var e in Enum.GetValues(context.Type).Cast()) { + var value = Convert.ToInt64(e, CultureInfo.InvariantCulture); + schema.Enum.Add(new OpenApiLong(value)); + var enumName = Enum.GetName(context.Type, e).ToLowerCamelCase(); + if (enumName.Length <= 3) { + enumName = enumName.ToLowerInvariant(); + } + + _ = sb.Append(wrap) + .Append( // + CultureInfo.InvariantCulture, $"{enumName} = {value} ({e.ResDesc()})"); + } + + schema.Description = sb.ToString(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Host/Utils/ToNullIfEmptyStringConverter.cs b/src/backend/NetAdmin/NetAdmin.Host/Utils/ToNullIfEmptyStringConverter.cs new file mode 100644 index 00000000..ccb819aa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Host/Utils/ToNullIfEmptyStringConverter.cs @@ -0,0 +1,25 @@ +namespace NetAdmin.Host.Utils; + +/// +/// "" -> null 转换器 +/// +public sealed class ToNullIfEmptyStringConverter : JsonConverter +{ + /// + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) { + var success = reader.TryGetDecimal(out var dec); + return success ? dec.ToString(CultureInfo.InvariantCulture) : "0"; + } + + var ret = reader.GetString(); + return ret?.Length == 0 ? null : ret; + } + + /// + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Length == 0 ? null : value); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/CountryAttribute.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/CountryAttribute.cs new file mode 100644 index 00000000..7a2b8ad1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/CountryAttribute.cs @@ -0,0 +1,56 @@ +namespace NetAdmin.Infrastructure.Attributes; + +/// +/// 国家信息特性 +/// +/// +/// https://github.com/countries/countries +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)] +public sealed class CountryAttribute : Attribute +{ + /// + /// 三个字母的国家代码 + /// + public string Alpha3 { get; set; } + + /// + /// 国际电话呼号 + /// + public int CallingCode { get; set; } + + /// + /// 国际电话子呼号(区分同一呼号不同国家) + /// + public string CallingSubCode { get; set; } + + /// + /// 货币代码 + /// + public string CurrencyCode { get; set; } + + /// + /// 当命中多个国家时的首选国家 + /// + public bool IsPreferred { get; set; } + + /// + /// 官方语言代码 + /// + public string Languages { get; set; } + + /// + /// 国家全称 + /// + public string LongName { get; set; } + + /// + /// 国家简称 + /// + public string ShortName { get; set; } + + /// + /// 非正式名称 + /// + public string UnofficialNames { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/DomainAttribute.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/DomainAttribute.cs new file mode 100644 index 00000000..5ce02505 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/DomainAttribute.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.Infrastructure.Attributes; + +/// +/// 域名特性 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)] +public sealed class DomainAttribute : Attribute +{ + /// + /// 主机名称 + /// + public string HostName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/ExportAttribute.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/ExportAttribute.cs new file mode 100644 index 00000000..64ad66ce --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Attributes/ExportAttribute.cs @@ -0,0 +1,7 @@ +namespace NetAdmin.Infrastructure.Attributes; + +/// +/// 标记一个此字段(枚举)将通过接口暴露到前端 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum)] +public sealed class ExportAttribute : Attribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/ApiClientOptions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/ApiClientOptions.cs new file mode 100644 index 00000000..e2294468 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/ApiClientOptions.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.Infrastructure.Configuration.Dependency; + +/// +/// API客户端基础选项 +/// +public abstract record ApiClientOptions : OptionAbstraction +{ + /// + /// 网关地址 + /// + public string Gateway { get; set; } + + /// + /// 密钥 + /// + public string Token { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/OptionAbstraction.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/OptionAbstraction.cs new file mode 100644 index 00000000..880bdad4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Dependency/OptionAbstraction.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.Infrastructure.Configuration.Dependency; + +/// +/// 选项抽象基类 +/// +public abstract record OptionAbstraction : IConfigurableOptions; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/CaptchaOptions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/CaptchaOptions.cs new file mode 100644 index 00000000..7e362cf9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/CaptchaOptions.cs @@ -0,0 +1,28 @@ +using NetAdmin.Infrastructure.Configuration.Dependency; + +namespace NetAdmin.Infrastructure.Configuration.Options; + +/// +/// 人机验证配置 +/// +public sealed record CaptchaOptions : OptionAbstraction +{ + private static readonly double _seed = BitConverter.ToInt32("yyyyMMdd"[..4].Select(x => (byte)x).ToArray()); + + #pragma warning disable S3963 + static CaptchaOptions() + #pragma warning restore S3963 + { + var rtn = new char[32]; + for (var i = 0; i != 32; i++) { + rtn[i] = Chars.FLGL_VISIBLE_ASCIIS[(int)(Math.Abs(Math.Sin(_seed * (i + 1))) * Chars.FLGL_VISIBLE_ASCIIS.Length)]; + } + + SecretKey = new string(rtn); + } + + /// + /// 密钥 + /// + public static string SecretKey { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/DatabaseOptions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/DatabaseOptions.cs new file mode 100644 index 00000000..ea46cecf --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/DatabaseOptions.cs @@ -0,0 +1,25 @@ +using NetAdmin.Infrastructure.Configuration.Dependency; +using DataType = FreeSql.DataType; + +namespace NetAdmin.Infrastructure.Configuration.Options; + +/// +/// 数据库连接字符串配置 +/// +public sealed record DatabaseOptions : OptionAbstraction +{ + /// + /// 链接字符串 + /// + public string ConnStr { get; init; } + + /// + /// 数据库类型 + /// + public DataType DbType { get; init; } + + /// + /// 种子数据路径(相对) + /// + public string SeedDataRelativePath { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/RedisOptions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/RedisOptions.cs new file mode 100644 index 00000000..dfafd920 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/RedisOptions.cs @@ -0,0 +1,14 @@ +using NetAdmin.Infrastructure.Configuration.Dependency; + +namespace NetAdmin.Infrastructure.Configuration.Options; + +/// +/// Redis 配置 +/// +public sealed record RedisOptions : OptionAbstraction +{ + /// + /// 实例列表 + /// + public InstanceNode[] Instances { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Redis/InstanceNode.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Redis/InstanceNode.cs new file mode 100644 index 00000000..cb33bad7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Redis/InstanceNode.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.Infrastructure.Configuration.Options.SubNodes.Redis; + +/// +/// Redis 实例节点 +/// +public sealed record InstanceNode +{ + /// + /// 链接字符串 + /// + public string ConnStr { get; init; } + + /// + /// 数据库 + /// + public int Database { get; init; } + + /// + /// 实例名称 + /// + public string Name { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Upload/MinioNode.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Upload/MinioNode.cs new file mode 100644 index 00000000..f1f8b61a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/SubNodes/Upload/MinioNode.cs @@ -0,0 +1,37 @@ +namespace NetAdmin.Infrastructure.Configuration.Options.SubNodes.Upload; + +/// +/// Minio 节点 +/// +public sealed record MinioNode +{ + /// + /// 访问令牌 + /// + public string AccessKey { get; init; } + + /// + /// 文件访问Url地址 + /// + public string AccessUrl { get; init; } + + /// + /// 使用的存储桶名称 + /// + public string BucketName { get; init; } + + /// + /// 安全码 + /// + public string SecretKey { get; init; } + + /// + /// 是否启用ssl + /// + public bool Secure { get; init; } + + /// + /// 服务器地址 + /// + public string ServerAddress { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/UploadOptions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/UploadOptions.cs new file mode 100644 index 00000000..7dba5c74 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Configuration/Options/UploadOptions.cs @@ -0,0 +1,24 @@ +using NetAdmin.Infrastructure.Configuration.Dependency; + +namespace NetAdmin.Infrastructure.Configuration.Options; + +/// +/// 上传配置 +/// +public sealed record UploadOptions : OptionAbstraction +{ + /// + /// 允许的文件类型 + /// + public IReadOnlyCollection ContentTypes { get; init; } + + /// + /// 允许的文件大小(字节) + /// + public long MaxSize { get; init; } + + /// + /// Minio 配置 + /// + public MinioNode Minio { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Chars.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Chars.cs new file mode 100644 index 00000000..5d660e8e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Chars.cs @@ -0,0 +1,111 @@ +#pragma warning disable CS1591 + +namespace NetAdmin.Infrastructure.Constant; + +/// +/// 字符串常量表 +/// +/// +/// public类型会通过接口暴露给前端 +/// +public static class Chars +{ + public const string FLG_CONTEXT_MEMBER_INFO = nameof(FLG_CONTEXT_MEMBER_INFO); + public const string FLG_CONTEXT_OWNER_DEPT_ID = nameof(FLG_CONTEXT_OWNER_DEPT_ID); + public const string FLG_CONTEXT_USER_ID = nameof(FLG_CONTEXT_USER_ID); + public const string FLG_CONTEXT_USER_INFO = nameof(FLG_CONTEXT_USER_INFO); + public const string FLG_CRON_PER_SECS = "* * * * * *"; + public const string FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT = "PRIMARY KEY"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_127 = "nvarchar(127)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_15 = "nvarchar(15)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_255 = "nvarchar(255)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_31 = "nvarchar(31)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_4094 = "nvarchar(4094)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_510 = "nvarchar(510)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_63 = "nvarchar(63)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_7 = "nvarchar(7)"; + public const string FLG_DB_FIELD_TYPE_NVARCHAR_MAX = "nvarchar(max)"; + public const string FLG_DB_FIELD_TYPE_SMALL_INT = "smallint"; + public const string FLG_DB_FIELD_TYPE_TEXT = "text"; + public const string FLG_DB_FIELD_TYPE_TINY_INT = "tinyint"; + public const string FLG_DB_FIELD_TYPE_VARCHAR = "varchar"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_1022 = "varchar(1022)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_127 = "varchar(127)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_15 = "varchar(15)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_255 = "varchar(255)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_31 = "varchar(31)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_4094 = "varchar(4094)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_510 = "varchar(510)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_63 = "varchar(63)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_7 = "varchar(7)"; + public const string FLG_DB_FIELD_TYPE_VARCHAR_MAX = "varchar(max)"; + public const string FLG_DB_INDEX_PREFIX = "idx_{tablename}_"; + public const string FLG_DB_TABLE_NAME_PREFIX = ""; + public const string FLG_FREE_SQL_GLOBAL_FILTER_DATA = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DATA); + public const string FLG_FREE_SQL_GLOBAL_FILTER_DELETE = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DELETE); + public const string FLG_FREE_SQL_GLOBAL_FILTER_MEMBER = nameof(FLG_FREE_SQL_GLOBAL_FILTER_MEMBER); + public const string FLG_FREE_SQL_GLOBAL_FILTER_SELF = nameof(FLG_FREE_SQL_GLOBAL_FILTER_SELF); + public const string FLG_FREE_SQL_GLOBAL_FILTER_TENANT = nameof(FLG_FREE_SQL_GLOBAL_FILTER_TENANT); + public const string FLG_FRONT_APP_SET_HOME_GRID = "APP_SET_HOME_GRID"; + public const string FLG_HTTP_HEADER_KEY_ACCESS_TOKEN = "ACCESS-TOKEN"; + public const string FLG_HTTP_HEADER_KEY_AUTHORIZATION = "Authorization"; + public const string FLG_HTTP_HEADER_KEY_REFERER = "Referer"; + public const string FLG_HTTP_HEADER_KEY_USER_AGENT = "User-Agent"; + public const string FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN = "X-ACCESS-TOKEN"; + public const string FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN_HEADER_KEY = "X-Authorization"; + public const string FLG_HTTP_HEADER_KEY_X_CACHE_CONTROL = "X-Cache-Control"; + public const string FLG_HTTP_HEADER_KEY_X_FORWARDED_FOR = "X-Forwarded-For"; + public const string FLG_HTTP_HEADER_KEY_X_REAL_IP = "X-Real-IP"; + public const string FLG_HTTP_HEADER_VALUE_APPLICATION_JSON = "application/json"; + public const string FLG_HTTP_HEADER_VALUE_APPLICATION_OCTET_STREAM = "application/octet-stream"; + public const string FLG_HTTP_HEADER_VALUE_APPLICATION_URLENCODED = "application/x-www-form-urlencoded"; + public const string FLG_HTTP_HEADER_VALUE_ATTACHMENT = "attachment"; + public const string FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA = "Bearer"; + public const string FLG_HTTP_HEADER_VALUE_NO_CACHE = "no-cache"; + public const string FLG_HTTP_METHOD_CONNECT = "CONNECT"; + public const string FLG_HTTP_METHOD_DELETE = "DELETE"; + public const string FLG_HTTP_METHOD_GET = "GET"; + public const string FLG_HTTP_METHOD_HEAD = "HEAD"; + public const string FLG_HTTP_METHOD_OPTIONS = "OPTIONS"; + public const string FLG_HTTP_METHOD_PATCH = "PATCH"; + public const string FLG_HTTP_METHOD_POST = "POST"; + public const string FLG_HTTP_METHOD_PUT = "PUT"; + public const string FLG_HTTP_METHOD_TRACE = "TRACE"; + public const string FLG_PATH_API_METRICS = "metrics"; + public const string FLG_PATH_API_RPOBE = "api/probe"; + public const string FLG_PATH_WEBSOCKET_PREFIX = "ws"; + public const string FLG_REDIS_INSTANCE_DATA_CACHE = "DataCache"; + public const string FLG_SNOWFLAKE_WORK_ID = "SNOWFLAKE_WORK_ID"; + + public const string FLGL_HTTP_HEADER_VALUE_UA_MOBILE + = "Mozilla/5.0 (Linux; Android 9; Redmi Note 8 Pro Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.96 Mobile Safari/537.36"; + + public const string FLGL_HTTP_HEADER_VALUE_UA_PC + = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"; + + public const string FLGL_VISIBLE_ASCIIS = """!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"""; + + public const string RGX_CERTIFICATE = "^[a-zA-Z0-9-_]+$"; + public const string RGX_INVITE_CODE = """^\d{8}$"""; + public const string RGX_MOBILE = """^1(3\d|4[5-9]|5[0-35-9]|6[6]|7[2-8]|8\d|9[0-35-9])\d{8}$"""; + public const string RGX_PASSWORD = "^(?![0-9]+$)(?![a-zA-Z]+$).{8,16}$"; + public const string RGX_PAY_PASSWORD = """^\d{6}$"""; + public const string RGX_TELEPHONE = """^((\d{3,4}\-)|)\d{7,8}(|([-\u8f6c]{1}\d{1,5}))$"""; + public const string RGX_UP_AND_LOWER_NUMBER = """^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$"""; + public const string RGX_URL = """^(https?|ftp):\/\/[^\s/$.?#].[^\s]*\.[^\s]{2,}$"""; + public const string RGX_USERNAME = "^[a-zA-Z0-9_]{4,16}$"; + public const string RGX_VERIFY_CODE = """^\d{4}$"""; + + public const string RGXL_CHINESE_NAME + = """^(?:赵|钱|孙|李|周|吴|郑|王|冯|陈|褚|卫|蒋|沈|韩|杨|朱|秦|尤|许|何|吕|施|张|孔|曹|严|华|金|魏|陶|姜|戚|谢|邹|喻|柏|水|窦|章|云|苏|潘|葛|奚|范|彭|郎|鲁|韦|昌|马|苗|凤|花|方|俞|任|袁|柳|酆|鲍|史|唐|费|廉|岑|薛|雷|贺|倪|汤|滕|殷|罗|毕|郝|邬|安|常|乐|于|时|傅|皮|卞|齐|康|伍|余|元|卜|顾|孟|平|黄|和|穆|萧|尹|姚|邵|湛|汪|祁|毛|禹|狄|米|贝|明|臧|计|伏|成|戴|谈|宋|茅|庞|熊|纪|舒|屈|项|祝|董|梁|杜|阮|蓝|闵|席|季|麻|强|贾|路|娄|危|江|童|颜|郭|梅|盛|林|刁|钟|徐|邱|骆|高|夏|蔡|田|樊|胡|凌|霍|虞|万|支|柯|昝|管|卢|莫|经|房|裘|缪|干|解|应|宗|丁|宣|贲|邓|郁|单|杭|洪|包|诸|左|石|崔|吉|钮|龚|程|嵇|邢|滑|裴|陆|荣|翁|荀|羊|於|惠|甄|曲|家|封|芮|羿|储|靳|汲|邴|糜|松|井|段|富|巫|乌|焦|巴|弓|牧|隗|山|谷|车|侯|宓|蓬|全|郗|班|仰|秋|仲|伊|宫|宁|仇|栾|暴|甘|钭|厉|戎|祖|武|符|刘|景|詹|束|龙|叶|幸|司|韶|郜|黎|蓟|薄|印|宿|白|怀|蒲|邰|从|鄂|索|咸|籍|赖|卓|蔺|屠|蒙|池|乔|阴|胥|能|苍|双|闻|莘|党|翟|谭|贡|劳|逄|姬|申|扶|堵|冉|宰|郦|雍|郤|璩|桑|桂|濮|牛|寿|通|边|扈|燕|冀|郏|浦|尚|农|温|别|庄|晏|柴|瞿|阎|充|慕|连|茹|习|宦|艾|鱼|容|向|古|易|慎|戈|廖|庾|终|暨|居|衡|步|都|耿|满|弘|匡|国|文|寇|广|禄|阙|东|欧|殳|沃|利|蔚|越|夔|隆|师|巩|厍|聂|晁|勾|敖|融|冷|訾|辛|阚|那|简|饶|空|曾|毋|沙|乜|养|鞠|须|丰|巢|关|蒯|相|查|後|荆|红|游|竺|权|逯|盖|益|桓|公|万俟|司马|上官|欧阳|夏侯|诸葛|闻人|东方|赫连|皇甫|尉迟|公羊|澹台|公冶|宗政|濮阳|淳于|单于|太叔|申屠|公孙|仲孙|轩辕|令狐|钟离|宇文|长孙|慕容|鲜于|闾丘|司徒|司空|亓官|司寇|仉|督|子车|颛孙|端木|巫马|公西|漆雕|乐正|壤驷|公良|拓跋|夹谷|宰父|谷梁|晋|楚|闫|法|汝|鄢|涂|钦|段干|百里|东郭|南门|呼延|归|海|羊舌|微生|岳|帅|缑|亢|况|后|有|琴|梁丘|左丘|东门|西门|商|牟|佘|佴|伯|赏|南宫|墨|哈|谯|笪|年|爱|阳|佟|第五|言|福)[\u4e00-\u9fa5]{1,3}$"""; + + public const string RGXL_CRON + = "^\\s*($|#|\\w+\\s*=|(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?(?:,(?:[0-5]?\\d)(?:(?:-|\\/|\\,)(?:[0-5]?\\d))?)*)\\s+(\\?|\\*|(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?(?:,(?:[01]?\\d|2[0-3])(?:(?:-|\\/|\\,)(?:[01]?\\d|2[0-3]))?)*)\\s+(\\?|\\*|(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?(?:,(?:0?[1-9]|[12]\\d|3[01])(?:(?:-|\\/|\\,)(?:0?[1-9]|[12]\\d|3[01]))?)*)\\s+(\\?|\\*|(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\\/|\\,)(?:[1-9]|1[012]))?(?:L|W)?)*|\\?|\\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\\s+(\\?|\\*|(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?(?:,(?:[0-6])(?:(?:-|\\/|\\,|#)(?:[0-6]))?(?:L)?)*|\\?|\\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)(|\\s)+(\\?|\\*|(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?(?:,(?:|\\d{4})(?:(?:-|\\/|\\,)(?:|\\d{4}))?)*))$"; + + public const string RGXL_EMAIL + = """^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$"""; + + public const string RGXL_IP_V4 = @"^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}$"; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Numbers.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Numbers.cs new file mode 100644 index 00000000..b2360c87 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Constant/Numbers.cs @@ -0,0 +1,23 @@ +#pragma warning disable CS1591 + +namespace NetAdmin.Infrastructure.Constant; + +/// +/// 数字常量表 +/// +/// +/// public类型会通过接口暴露给前端 +/// +public static class Numbers +{ + public const int DEF_PAGE_SIZE_QUERY = 20; // 默认值:分页查询页容量 + public const int HTTP_STATUS_BIZ_FAIL = 900; // HTTP状态码:业务异常 + public const int MAX_LIMIT_BULK_REQ = 100; // 最大限制:批量请求数 + public const int MAX_LIMIT_EXPORT = 50000; // 最大限制:导出为CSV文件的条数 + public const int MAX_LIMIT_QUERY = 1000; // 最大限制:非分页查询条数 + public const int MAX_LIMIT_QUERY_PAGE_NO = 10000; // 最大限制:分页查询页码 + public const int MAX_LIMIT_QUERY_PAGE_SIZE = 100; // 最大限制:分页查询页容量 + public const int MAX_LIMIT_RETRY_CNT_REDIS_LOCK = 10; // 最大限制:Redis锁重试次数 + public const int SECS_REDIS_LOCK_EXPIRY = 60; // 秒:Redis锁过期时间 + public const int SECS_REDIS_LOCK_RETRY_DELAY = 1; // 秒:Redis锁重试间隔 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/CertificateTypes.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/CertificateTypes.cs new file mode 100644 index 00000000..659b4a25 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/CertificateTypes.cs @@ -0,0 +1,46 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 证件类型 +/// +[Export] +public enum CertificateTypes +{ + /// + /// 身份证 + /// + [ResourceDescription(nameof(Ln.身份证))] + IdentityCard = 1 + + , + + /// + /// 护照 + /// + [ResourceDescription(nameof(Ln.护照))] + Passport = 2 + + , + + /// + /// 外国人居留证 + /// + [ResourceDescription(nameof(Ln.外国人居留证))] + ForeignerResidencePermit = 3 + + , + + /// + /// 港澳台通行证 + /// + [ResourceDescription(nameof(Ln.港澳台通行证))] + HKorMacauPermit = 4 + + , + + /// + /// 出生证 + /// + [ResourceDescription(nameof(Ln.出生证))] + BirthCertificate = 5 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Countries.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Countries.cs new file mode 100644 index 00000000..9074ed34 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Countries.cs @@ -0,0 +1,2581 @@ +// ReSharper disable UnusedMember.Global + +#pragma warning disable RCS1154 + +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 世界各国 +/// +/// +/// ISO3166-1 +/// +[Export] +public enum Countries +{ + /// + /// 多米尼加 + /// + [Country(CallingCode = 1, CallingSubCode = "809,829,849", Alpha3 = "DOM", ShortName = "Dominican Republic", LongName = "The Dominican Republic" + , CurrencyCode = "DOP", Languages = "es" + , UnofficialNames + = "Dominican Republic|Dominikanische Republik|République Dominicaine|República Dominicana|ドミニカ共和国|Dominicaanse Republiek|多米尼加")] + [ResourceDescription(nameof(Ln.多米尼加))] + DO = 214 + + , + + /// + /// 波多黎各 + /// + [Country(CallingCode = 1, CallingSubCode = "787,939", Alpha3 = "PRI", ShortName = "Puerto Rico", LongName = "The Commonwealth of Puerto Rico" + , CurrencyCode = "USD", Languages = "es|en", UnofficialNames = "Puerto Rico|プエルトリコ|波多黎各")] + [ResourceDescription(nameof(Ln.波多黎各))] + PR = 630 + + , + + /// + /// 牙买加 + /// + [Country(CallingCode = 1, CallingSubCode = "658,876", Alpha3 = "JAM", ShortName = "Jamaica", LongName = "Jamaica", CurrencyCode = "JMD" + , Languages = "en", UnofficialNames = "Jamaica|Jamaika|Jamaïque|ジャマイカ|牙买加")] + [ResourceDescription(nameof(Ln.牙买加))] + JM = 388 + + , + + /// + /// 圣基茨和尼维斯 + /// + [Country(CallingCode = 1, CallingSubCode = "869", Alpha3 = "KNA", ShortName = "Saint Kitts and Nevis", LongName = "Saint Kitts and Nevis" + , CurrencyCode = "XCD", Languages = "en" + , UnofficialNames + = "Saint Kitts and Nevis|Föderation St. Kitts und Nevis|Saint Kitts et Nevis|Saint Kitts y Nevis|セントクリストファー・ネイビス|Saint Kitts en Nevis|St. Kitts and Nevis|St Kitts and Nevis|圣基茨和尼维斯")] + [ResourceDescription(nameof(Ln.圣基茨和尼维斯))] + KN = 659 + + , + + /// + /// 特立尼达和多巴哥 + /// + [Country(CallingCode = 1, CallingSubCode = "868", Alpha3 = "TTO", ShortName = "Trinidad and Tobago" + , LongName = "The Republic of Trinidad and Tobago", CurrencyCode = "TTD", Languages = "en" + , UnofficialNames = "Trinidad and Tobago|Trinidad und Tobago|Trinité et Tobago|Trinidad y Tobago|トリニダード・トバゴ|Trinidad en Tobago|特立尼达和多巴哥")] + [ResourceDescription(nameof(Ln.特立尼达和多巴哥))] + TT = 780 + + , + + /// + /// 圣文森特和格林纳丁斯 + /// + [Country(CallingCode = 1, CallingSubCode = "784", Alpha3 = "VCT", ShortName = "Saint Vincent and the Grenadines" + , LongName = "Saint Vincent and the Grenadines", CurrencyCode = "XCD", Languages = "en" + , UnofficialNames + = "Saint Vincent and the Grenadines|Saint Vincent und die Grenadinen|Saint-Vincent et les Grenadines|San Vicente y Granadinas|セントビンセントおよびグレナディーン諸島|Saint Vincent en de Grenadines|St. Vincent Grenadines|St Vincent Grenadines|圣文森特和格林纳丁斯")] + [ResourceDescription(nameof(Ln.圣文森特和格林纳丁斯))] + VC = 670 + + , + + /// + /// 多米尼克 + /// + [Country(CallingCode = 1, CallingSubCode = "767", Alpha3 = "DMA", ShortName = "Dominica", LongName = "The Commonwealth of Dominica" + , CurrencyCode = "XCD", Languages = "en", UnofficialNames = "Dominica|ドミニカ国|多米尼克")] + [ResourceDescription(nameof(Ln.多米尼克))] + DM = 212 + + , + + /// + /// 圣卢西亚 + /// + [Country(CallingCode = 1, CallingSubCode = "758", Alpha3 = "LCA", ShortName = "Saint Lucia", LongName = "Saint Lucia", CurrencyCode = "XCD" + , Languages = "en", UnofficialNames = "Saint Lucia|Saint-Lucie|Santa Lucía|セントルシア|St. Lucia|St Lucia|圣卢西亚")] + [ResourceDescription(nameof(Ln.圣卢西亚))] + LC = 662 + + , + + /// + /// 荷属圣马丁 + /// + [Country(CallingCode = 1, CallingSubCode = "721", Alpha3 = "SXM", ShortName = "Sint Maarten (Dutch part)", LongName = "Sint Maarten" + , CurrencyCode = "ANG", Languages = "nl|en", UnofficialNames = "Sint Maarten|セント・マーチン島|荷属圣马丁")] + [ResourceDescription(nameof(Ln.荷属圣马丁))] + SX = 534 + + , + + /// + /// 美属萨摩亚 + /// + [Country(CallingCode = 1, CallingSubCode = "684", Alpha3 = "ASM", ShortName = "American Samoa", LongName = "The Territory of American Samoa" + , CurrencyCode = "USD", Languages = "en|sm" + , UnofficialNames = "American Samoa|Amerikanisch-Samoa|Samoa américaines|Samoa Americana|アメリカ領サモア|Amerikaans Samoa|美属萨摩亚")] + [ResourceDescription(nameof(Ln.美属萨摩亚))] + AS = 016 + + , + + /// + /// 关岛 + /// + [Country(CallingCode = 1, CallingSubCode = "671", Alpha3 = "GUM", ShortName = "Guam", LongName = "The Territory of Guam", CurrencyCode = "USD" + , Languages = "en|ch|es", UnofficialNames = "Guam|グアム|关岛")] + [ResourceDescription(nameof(Ln.关岛))] + GU = 316 + + , + + /// + /// 北马里亚纳群岛 + /// + [Country(CallingCode = 1, CallingSubCode = "670", Alpha3 = "MNP", ShortName = "Northern Mariana Islands" + , LongName = "The Commonwealth of the Northern Mariana Islands", CurrencyCode = "USD", Languages = "en|ch" + , UnofficialNames + = "Northern Mariana Islands|Nördliche Marianen|Mariannes du Nord|Islas Marianas del Norte|北マリアナ諸島|Noordelijke Marianeneilanden|北马里亚纳群岛")] + [ResourceDescription(nameof(Ln.北马里亚纳群岛))] + MP = 580 + + , + + /// + /// 蒙特塞拉特 + /// + [Country(CallingCode = 1, CallingSubCode = "664", Alpha3 = "MSR", ShortName = "Montserrat", LongName = "Montserrat", CurrencyCode = "XCD" + , Languages = "en", UnofficialNames = "Montserrat|モントセラト|蒙特塞拉特")] + [ResourceDescription(nameof(Ln.蒙特塞拉特))] + MS = 500 + + , + + /// + /// 特克斯和凯科斯群岛 + /// + [Country(CallingCode = 1, CallingSubCode = "649", Alpha3 = "TCA", ShortName = "Turks and Caicos Islands" + , LongName = "The Turks and Caicos Islands", CurrencyCode = "USD", Languages = "en" + , UnofficialNames + = "Turks and Caicos Islands|Turks- und Caicosinseln|Îles Turks et Caïcos|Islas Turks y Caicos|タークス・カイコス諸島|Turks- en Caicoseilanden|Turks and Caicos|特克斯和凯科斯群岛")] + [ResourceDescription(nameof(Ln.特克斯和凯科斯群岛))] + TC = 796 + + , + + /// + /// 格林纳达 + /// + [Country(CallingCode = 1, CallingSubCode = "473", Alpha3 = "GRD", ShortName = "Grenada", LongName = "Grenada", CurrencyCode = "XCD" + , Languages = "en", UnofficialNames = "Grenada|グレナダ|格林纳达")] + [ResourceDescription(nameof(Ln.格林纳达))] + GD = 308 + + , + + /// + /// 百慕大 + /// + [Country(CallingCode = 1, CallingSubCode = "441", Alpha3 = "BMU", ShortName = "Bermuda", LongName = "Bermuda", CurrencyCode = "BMD" + , Languages = "en", UnofficialNames = "Bermuda|Bermudes|Bermudas|バミューダ|百慕大")] + [ResourceDescription(nameof(Ln.百慕大))] + BM = 060 + + , + + /// + /// 开曼群岛 + /// + [Country(CallingCode = 1, CallingSubCode = "345", Alpha3 = "CYM", ShortName = "Cayman Islands", LongName = "The Cayman Islands" + , CurrencyCode = "KYD", Languages = "en" + , UnofficialNames = "Cayman Islands|Kaimaninseln|Îles Caïmans|Islas Caimán|ケイマン諸島|Caymaneilanden|开曼群岛")] + [ResourceDescription(nameof(Ln.开曼群岛))] + KY = 136 + + , + + /// + /// 美属维尔京群岛 + /// + [Country(CallingCode = 1, CallingSubCode = "340", Alpha3 = "VIR", ShortName = "Virgin Islands (U.S.)" + , LongName = "The Virgin Islands of the United States", CurrencyCode = "USD", Languages = "en" + , UnofficialNames + = "Virgin Islands of the United States|Amerikanische Jungferninseln|Îles Vierges américaines|Islas Vírgenes de los Estados Unidos|アメリカ領ヴァージン諸島|Amerikaanse Maagdeneilanden|Virgin Islands (U.S.)|United States Virgin Islands|U.S. Virgin Islands|美属维尔京群岛")] + [ResourceDescription(nameof(Ln.美属维尔京群岛))] + VI = 850 + + , + + /// + /// 英属维尔京群岛 + /// + [Country(CallingCode = 1, CallingSubCode = "284", Alpha3 = "VGB", ShortName = "Virgin Islands (British)", LongName = "The Virgin Islands" + , CurrencyCode = "USD", Languages = "en" + , UnofficialNames + = "British Virgin Islands|Britische Jungferninseln|Îles Vierges britanniques|Islas Vírgenes del Reino Unido|イギリス領ヴァージン諸島|Britse Maagdeneilanden|Virgin Islands (British)|英属维尔京群岛")] + [ResourceDescription(nameof(Ln.英属维尔京群岛))] + VG = 092 + + , + + /// + /// 安提瓜和巴布达 + /// + [Country(CallingCode = 1, CallingSubCode = "268", Alpha3 = "ATG", ShortName = "Antigua and Barbuda", LongName = "Antigua and Barbuda" + , CurrencyCode = "XCD", Languages = "en" + , UnofficialNames + = "Antigua and Barbuda|Antigua und Barbuda|Antigua et Barbuda|Antigua y Barbuda|アンティグア・バーブーダ|Antigua en Barbuda|安提瓜和巴布达")] + [ResourceDescription(nameof(Ln.安提瓜和巴布达))] + AG = 028 + + , + + /// + /// 安圭拉 + /// + [Country(CallingCode = 1, CallingSubCode = "264", Alpha3 = "AIA", ShortName = "Anguilla", LongName = "Anguilla", CurrencyCode = "XCD" + , Languages = "en", UnofficialNames = "Anguilla|アンギラ|安圭拉")] + [ResourceDescription(nameof(Ln.安圭拉))] + AI = 660 + + , + + /// + /// 巴巴多斯 + /// + [Country(CallingCode = 1, CallingSubCode = "246", Alpha3 = "BRB", ShortName = "Barbados", LongName = "Barbados", CurrencyCode = "BBD" + , Languages = "en", UnofficialNames = "Barbade|Barbados|バルバドス|巴巴多斯")] + [ResourceDescription(nameof(Ln.巴巴多斯))] + BB = 052 + + , + + /// + /// 巴哈马 + /// + [Country(CallingCode = 1, CallingSubCode = "242", Alpha3 = "BHS", ShortName = "Bahamas", LongName = "The Commonwealth of The Bahamas" + , CurrencyCode = "BSD", Languages = "en", UnofficialNames = "The Bahamas|バハマ|巴哈马")] + [ResourceDescription(nameof(Ln.巴哈马))] + BS = 044 + + , + + /// + /// 加拿大 + /// + [Country(CallingCode = 1, Alpha3 = "CAN", ShortName = "Canada", LongName = "Canada", CurrencyCode = "CAD", Languages = "en|fr" + , UnofficialNames = "Canada|Kanada|Canadá|カナダ|加拿大")] + [ResourceDescription(nameof(Ln.加拿大))] + CA = 124 + + , + + /// + /// 美国本土外小岛屿 + /// + [Country(CallingCode = 1, Alpha3 = "UMI", ShortName = "United States Minor Outlying Islands", LongName = "United States Minor Outlying Islands" + , CurrencyCode = "USD", Languages = "en" + , UnofficialNames + = "United States Minor Outlying Islands|US-Amerikanische Hoheitsgebiete|Dépendances américaines|Islas menores de Estados Unidos|合衆国領有小離島|Kleine afgelegen eilanden van de Verenigde Staten|美国本土外小岛屿")] + [ResourceDescription(nameof(Ln.美国本土外小岛屿))] + UM = 581 + + , + + /// + /// 美国 + /// + [Country(CallingCode = 1, Alpha3 = "USA", ShortName = "United States of America", LongName = "The United States of America", CurrencyCode = "USD" + , Languages = "en" + , UnofficialNames + = "United States|USA|Vereinigte Staaten von Amerika|États-Unis|Estados Unidos|アメリカ合衆国|Verenigde Staten|Соединенные Штаты Америки|美国" + , IsPreferred = true)] + [ResourceDescription(nameof(Ln.美国))] + US = 840 + + , + + /// + /// 哈萨克斯坦 + /// + [Country(CallingCode = 7, CallingSubCode = "6,7", Alpha3 = "KAZ", ShortName = "Kazakhstan", LongName = "The Republic of Kazakhstan" + , CurrencyCode = "KZT", Languages = "kk|ru", UnofficialNames = "Kazakhstan|Kasachstan|Kazajistán|カザフスタン|Kazachstan|哈萨克斯坦")] + [ResourceDescription(nameof(Ln.哈萨克斯坦))] + KZ = 398 + + , + + /// + /// 俄罗斯 + /// + [Country(CallingCode = 7, Alpha3 = "RUS", ShortName = "Russian Federation", LongName = "The Russian Federation", CurrencyCode = "RUB" + , Languages = "ru", UnofficialNames = "Russia|Russland|Russie|Rusia|ロシア連邦|Rusland|Россия|Расія|俄罗斯", IsPreferred = true)] + [ResourceDescription(nameof(Ln.俄罗斯))] + RU = 643 + + , + + /// + /// 埃及 + /// + [Country(CallingCode = 20, Alpha3 = "EGY", ShortName = "Egypt", LongName = "The Arab Republic of Egypt", CurrencyCode = "EGP", Languages = "ar" + , UnofficialNames = "Egypt|مصر|Ägypten|Égypte|Egipto|エジプト|Egypte|埃及")] + [ResourceDescription(nameof(Ln.埃及))] + EG = 818 + + , + + /// + /// 南非 + /// + [Country(CallingCode = 27, Alpha3 = "ZAF", ShortName = "South Africa", LongName = "The Republic of South Africa", CurrencyCode = "ZAR" + , Languages = "af|en|nr|st|ss|tn|ts|ve|xh|zu" + , UnofficialNames = "South Africa|Republik Südafrika|Afrique du Sud|República de Sudáfrica|南アフリカ|Zuid-Afrika|南非")] + [ResourceDescription(nameof(Ln.南非))] + ZA = 710 + + , + + /// + /// 希腊 + /// + [Country(CallingCode = 30, Alpha3 = "GRC", ShortName = "Greece", LongName = "The Hellenic Republic", CurrencyCode = "EUR", Languages = "el" + , UnofficialNames = "Greece|Griechenland|Grèce|Grecia|ギリシャ|Griekenland|希腊")] + [ResourceDescription(nameof(Ln.希腊))] + GR = 300 + + , + + /// + /// 荷兰 + /// + [Country(CallingCode = 31, Alpha3 = "NLD", ShortName = "Netherlands", LongName = "The Kingdom of the Netherlands", CurrencyCode = "EUR" + , Languages = "nl|fy", UnofficialNames = "Netherlands|The Netherlands|Niederlande|Pays-Bas|Países Bajos|オランダ|Nederland|Нидерландия|荷兰")] + [ResourceDescription(nameof(Ln.荷兰))] + NL = 528 + + , + + /// + /// 比利时 + /// + [Country(CallingCode = 32, Alpha3 = "BEL", ShortName = "Belgium", LongName = "The Kingdom of Belgium", CurrencyCode = "EUR" + , Languages = "nl|fr|de", UnofficialNames = "Belgium|Belgien|Belgique|Bélgica|ベルギー|België|比利时")] + [ResourceDescription(nameof(Ln.比利时))] + BE = 056 + + , + + /// + /// 法国 + /// + [Country(CallingCode = 33, Alpha3 = "FRA", ShortName = "France", LongName = "The French Republic", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "France|Frankreich|the French Republic|フランス|Frankrijk|Francia|法国")] + [ResourceDescription(nameof(Ln.法国))] + FR = 250 + + , + + /// + /// 西班牙 + /// + [Country(CallingCode = 34, Alpha3 = "ESP", ShortName = "Spain", LongName = "The Kingdom of Spain", CurrencyCode = "EUR", Languages = "es" + , UnofficialNames = "Spain|Spanien|Espagne|España|スペイン|Spanje|西班牙")] + [ResourceDescription(nameof(Ln.西班牙))] + ES = 724 + + , + + /// + /// 匈牙利 + /// + [Country(CallingCode = 36, Alpha3 = "HUN", ShortName = "Hungary", LongName = "Hungary", CurrencyCode = "HUF", Languages = "hu" + , UnofficialNames = "Hungary|Ungarn|Hongrie|Hungría|ハンガリー|Hongarije|匈牙利")] + [ResourceDescription(nameof(Ln.匈牙利))] + HU = 348 + + , + + /// + /// 意大利 + /// + [Country(CallingCode = 39, Alpha3 = "ITA", ShortName = "Italy", LongName = "The Italian Republic", CurrencyCode = "EUR", Languages = "it" + , UnofficialNames = "Italy|Italien|Italie|Italia|イタリア|Italië|意大利", IsPreferred = true)] + [ResourceDescription(nameof(Ln.意大利))] + IT = 380 + + , + + /// + /// 罗马尼亚 + /// + [Country(CallingCode = 40, Alpha3 = "ROU", ShortName = "Romania", LongName = "Romania", CurrencyCode = "RON", Languages = "ro" + , UnofficialNames = "Romania|Rumänien|Roumanie|Rumania|ルーマニア|Roemenië|罗马尼亚")] + [ResourceDescription(nameof(Ln.罗马尼亚))] + RO = 642 + + , + + /// + /// 瑞士 + /// + [Country(CallingCode = 41, Alpha3 = "CHE", ShortName = "Switzerland", LongName = "The Swiss Confederation", CurrencyCode = "CHF" + , Languages = "de|fr|it", UnofficialNames = "Switzerland|Schweiz|Suisse|Suiza|スイス|Zwitserland|瑞士")] + [ResourceDescription(nameof(Ln.瑞士))] + CH = 756 + + , + + /// + /// 奥地利 + /// + [Country(CallingCode = 43, Alpha3 = "AUT", ShortName = "Austria", LongName = "The Republic of Austria", CurrencyCode = "EUR", Languages = "de" + , UnofficialNames = "Austria|Österreich|Autriche|オーストリア|Oostenrijk|奥地利")] + [ResourceDescription(nameof(Ln.奥地利))] + AT = 040 + + , + + /// + /// 英国 + /// + [Country(CallingCode = 44, Alpha3 = "GBR", ShortName = "United Kingdom of Great Britain and Northern Ireland" + , LongName = "The United Kingdom of Great Britain and Northern Ireland", CurrencyCode = "GBP", Languages = "en" + , UnofficialNames + = "United Kingdom|The United Kingdom|England|Großbritannien|Vereinigtes Königreich|Royaume-Uni|Reino Unido|イギリス|Verenigd Koninkrijk|Great Britain (UK)|UK|Великобритания|Velká Británie|İngiltere|Великобританія|英国" + , IsPreferred = true)] + [ResourceDescription(nameof(Ln.英国))] + GB = 826 + + , + + /// + /// 根西 + /// + [Country(CallingCode = 44, CallingSubCode = "1481", Alpha3 = "GGY", ShortName = "Guernsey", LongName = "The Bailiwick of Guernsey" + , CurrencyCode = "GBP", Languages = "en|fr" + , UnofficialNames = "Guernsey and Alderney|Guernsey und Alderney|Guernsey et Alderney|Guernsey y Alderney|ガーンジー|Guernsey|根西")] + [ResourceDescription(nameof(Ln.根西))] + GG = 831 + + , + + /// + /// 马恩岛 + /// + [Country(CallingCode = 44, CallingSubCode = "1624", Alpha3 = "IMN", ShortName = "Isle of Man", LongName = "The Isle of Man", CurrencyCode = "GBP" + , Languages = "en|gv", UnofficialNames = "Isle of Man|Insel Man|Île de Man|Isla de Man|マン島|马恩岛")] + [ResourceDescription(nameof(Ln.马恩岛))] + IM = 833 + + , + + /// + /// 泽西 + /// + [Country(CallingCode = 44, CallingSubCode = "1534", Alpha3 = "JEY", ShortName = "Jersey", LongName = "The Bailiwick of Jersey" + , CurrencyCode = "GBP", Languages = "en|fr", UnofficialNames = "Jersey|ジャージー|泽西")] + [ResourceDescription(nameof(Ln.泽西))] + JE = 832 + + , + + /// + /// 丹麦 + /// + [Country(CallingCode = 45, Alpha3 = "DNK", ShortName = "Denmark", LongName = "The Kingdom of Denmark", CurrencyCode = "DKK", Languages = "da" + , UnofficialNames = "Denmark|Dänemark|Danemark|Dinamarca|デンマーク|Denemarken|丹麦")] + [ResourceDescription(nameof(Ln.丹麦))] + DK = 208 + + , + + /// + /// 瑞典 + /// + [Country(CallingCode = 46, Alpha3 = "SWE", ShortName = "Sweden", LongName = "The Kingdom of Sweden", CurrencyCode = "SEK", Languages = "sv" + , UnofficialNames = "Sweden|Schweden|Suède|Suecia|スウェーデン|Zweden|瑞典")] + [ResourceDescription(nameof(Ln.瑞典))] + SE = 752 + + , + + /// + /// 斯瓦尔巴和扬马延 + /// + [Country(CallingCode = 47, CallingSubCode = "79", Alpha3 = "SJM", ShortName = "Svalbard and Jan Mayen", LongName = "Svalbard and Jan Mayen" + , CurrencyCode = "NOK", Languages = "no" + , UnofficialNames + = "Svalbard and Jan Mayen|Svalbard und Jan Mayen|Îles Svalbard et Jan Mayen|Islas Svalbard y Jan Mayen|スヴァールバル諸島およびヤンマイエン島|Svalbard en Jan Mayen|斯瓦尔巴和扬马延")] + [ResourceDescription(nameof(Ln.斯瓦尔巴和扬马延))] + SJ = 744 + + , + + /// + /// 布韦岛 + /// + [Country(CallingCode = 47, Alpha3 = "BVT", ShortName = "Bouvet Island", LongName = "Bouvet Island", CurrencyCode = "NOK", Languages = "" + , UnofficialNames = "Bouvet Island|Bouvetinsel|ブーベ島|Bouveteiland|布韦岛")] + [ResourceDescription(nameof(Ln.布韦岛))] + BV = 074 + + , + + /// + /// 挪威 + /// + [Country(CallingCode = 47, Alpha3 = "NOR", ShortName = "Norway", LongName = "The Kingdom of Norway", CurrencyCode = "NOK", Languages = "nb|nn" + , UnofficialNames = "Norway|Norwegen|Norvège|Noruega|ノルウェー|Noorwegen|挪威", IsPreferred = true)] + [ResourceDescription(nameof(Ln.挪威))] + NO = 578 + + , + + /// + /// 波兰 + /// + [Country(CallingCode = 48, Alpha3 = "POL", ShortName = "Poland", LongName = "The Republic of Poland", CurrencyCode = "PLN", Languages = "pl" + , UnofficialNames = "Poland|Polen|Pologne|Polonia|ポーランド|波兰")] + [ResourceDescription(nameof(Ln.波兰))] + PL = 616 + + , + + /// + /// 德国 + /// + [Country(CallingCode = 49, Alpha3 = "DEU", ShortName = "Germany", LongName = "The Federal Republic of Germany", CurrencyCode = "EUR" + , Languages = "de", UnofficialNames = "Germany|Deutschland|Allemagne|Alemania|ドイツ|Duitsland|德国")] + [ResourceDescription(nameof(Ln.德国))] + DE = 276 + + , + + /// + /// 秘鲁 + /// + [Country(CallingCode = 51, Alpha3 = "PER", ShortName = "Peru", LongName = "The Republic of Perú", CurrencyCode = "PEN", Languages = "es" + , UnofficialNames = "Peru|Pérou|Perú|ペルー|秘鲁")] + [ResourceDescription(nameof(Ln.秘鲁))] + PE = 604 + + , + + /// + /// 墨西哥 + /// + [Country(CallingCode = 52, Alpha3 = "MEX", ShortName = "Mexico", LongName = "The United Mexican States", CurrencyCode = "MXN", Languages = "es" + , UnofficialNames = "Mexico|Mexiko|Mexique|México|メキシコ|墨西哥")] + [ResourceDescription(nameof(Ln.墨西哥))] + MX = 484 + + , + + /// + /// 古巴 + /// + [Country(CallingCode = 53, Alpha3 = "CUB", ShortName = "Cuba", LongName = "The Republic of Cuba", CurrencyCode = "CUP", Languages = "es" + , UnofficialNames = "Cuba|Kuba|キューバ|古巴")] + [ResourceDescription(nameof(Ln.古巴))] + CU = 192 + + , + + /// + /// 阿根廷 + /// + [Country(CallingCode = 54, Alpha3 = "ARG", ShortName = "Argentina", LongName = "The Argentine Republic", CurrencyCode = "ARS", Languages = "es|gn" + , UnofficialNames = "Argentina|Argentinien|Argentine|アルゼンチン|Argentinië|阿根廷")] + [ResourceDescription(nameof(Ln.阿根廷))] + AR = 032 + + , + + /// + /// 巴西 + /// + [Country(CallingCode = 55, Alpha3 = "BRA", ShortName = "Brazil", LongName = "The Federative Republic of Brazil", CurrencyCode = "BRL" + , Languages = "pt", UnofficialNames = "Brazil|Brasilien|Brésil|Brasil|ブラジル|Brazilië|巴西")] + [ResourceDescription(nameof(Ln.巴西))] + BR = 076 + + , + + /// + /// 智利 + /// + [Country(CallingCode = 56, Alpha3 = "CHL", ShortName = "Chile", LongName = "The Republic of Chile", CurrencyCode = "CLP", Languages = "es" + , UnofficialNames = "Chile|チリ|Chili|智利")] + [ResourceDescription(nameof(Ln.智利))] + CL = 152 + + , + + /// + /// 哥伦比亚 + /// + [Country(CallingCode = 57, Alpha3 = "COL", ShortName = "Colombia", LongName = "The Republic of Colombia", CurrencyCode = "COP", Languages = "es" + , UnofficialNames = "Colombia|Kolumbien|Colombie|コロンビア|哥伦比亚")] + [ResourceDescription(nameof(Ln.哥伦比亚))] + CO = 170 + + , + + /// + /// 委内瑞拉 + /// + [Country(CallingCode = 58, Alpha3 = "VEN", ShortName = "Venezuela (Bolivarian Republic of)", LongName = "The Bolivarian Republic of Venezuela" + , CurrencyCode = "VES", Languages = "es", UnofficialNames = "Venezuela|ベネズエラ・ボリバル共和国|委内瑞拉")] + [ResourceDescription(nameof(Ln.委内瑞拉))] + VE = 862 + + , + + /// + /// 马来西亚 + /// + [Country(CallingCode = 60, Alpha3 = "MYS", ShortName = "Malaysia", LongName = "Malaysia", CurrencyCode = "MYR", Languages = "ms|en" + , UnofficialNames = "Malaysia|Malaisie|Malasia|マレーシア|Maleisië|马来西亚")] + [ResourceDescription(nameof(Ln.马来西亚))] + MY = 458 + + , + + /// + /// 澳大利亚 + /// + [Country(CallingCode = 61, Alpha3 = "AUS", ShortName = "Australia", LongName = "The Commonwealth of Australia", CurrencyCode = "AUD" + , Languages = "en", UnofficialNames = "Australia|Australien|Australie|オーストラリア|Australië|澳洲|澳大利亚", IsPreferred = true)] + [ResourceDescription(nameof(Ln.澳大利亚))] + AU = 036 + + , + + /// + /// 科科斯基林群岛 + /// + [Country(CallingCode = 61, CallingSubCode = "89162", Alpha3 = "CCK", ShortName = "Cocos (Keeling) Islands" + , LongName = "The Territory of Cocos (Keeling) Islands", CurrencyCode = "AUD", Languages = "en" + , UnofficialNames = "Cocos (Keeling) Islands|Kokosinseln|ココス(キーリング)諸島|Cocoseilanden|科科斯基林群岛")] + [ResourceDescription(nameof(Ln.科科斯基林群岛))] + CC = 166 + + , + + /// + /// 圣诞岛 + /// + [Country(CallingCode = 61, CallingSubCode = "89164", Alpha3 = "CXR", ShortName = "Christmas Island" + , LongName = "The Territory of Christmas Island", CurrencyCode = "AUD", Languages = "en|zh|ms" + , UnofficialNames = "Christmas Island|Weihnachtsinsel|クリスマス島|Christmaseiland|圣诞岛")] + [ResourceDescription(nameof(Ln.圣诞岛))] + CX = 162 + + , + + /// + /// 赫德岛和麦克唐纳群岛 + /// + [Country(CallingCode = 672, CallingSubCode = "1", Alpha3 = "HMD", ShortName = "Heard Island and McDonald Islands" + , LongName = "The Territory of Heard Island and McDonald Islands", CurrencyCode = "AUD", Languages = "en" + , UnofficialNames + = "Heard and McDonald Islands|Heard und die McDonaldinseln|ハード島とマクドナルド諸島|Heard- en McDonaldeilanden|Heard Island and McDonald Islands|赫德岛和麦克唐纳群岛")] + [ResourceDescription(nameof(Ln.赫德岛和麦克唐纳群岛))] + HM = 334 + + , + + /// + /// 印度尼西亚 + /// + [Country(CallingCode = 62, Alpha3 = "IDN", ShortName = "Indonesia", LongName = "The Republic of Indonesia", CurrencyCode = "IDR", Languages = "id" + , UnofficialNames = "Indonesia|Indonesien|Indonésie|インドネシア|Indonesië|印度尼西亚")] + [ResourceDescription(nameof(Ln.印度尼西亚))] + ID = 360 + + , + + /// + /// 菲律宾 + /// + [Country(CallingCode = 63, Alpha3 = "PHL", ShortName = "Philippines", LongName = "The Republic of the Philippines", CurrencyCode = "PHP" + , Languages = "tl|en", UnofficialNames = "Philippines|Philippinen|Filipinas|フィリピン|Filipijnen|菲律宾")] + [ResourceDescription(nameof(Ln.菲律宾))] + PH = 608 + + , + + /// + /// 新西兰 + /// + [Country(CallingCode = 64, Alpha3 = "NZL", ShortName = "New Zealand", LongName = "New Zealand", CurrencyCode = "NZD", Languages = "en" + , UnofficialNames = "New Zealand|Neuseeland|Nouvelle Zélande|Nueva Zelanda|ニュージーランド|Nieuw-Zeeland|新西兰", IsPreferred = true)] + [ResourceDescription(nameof(Ln.新西兰))] + NZ = 554 + + , + + /// + /// 皮特凯恩群岛 + /// + [Country(CallingCode = 64, Alpha3 = "PCN", ShortName = "Pitcairn", LongName = "The Pitcairn, Henderson, Ducie and Oeno Islands" + , CurrencyCode = "NZD", Languages = "en", UnofficialNames = "Pitcairn|ピトケアン|Pitcairneilanden|Pitcairn Islands|皮特凯恩群岛")] + [ResourceDescription(nameof(Ln.皮特凯恩群岛))] + PN = 612 + + , + + /// + /// 新加坡 + /// + [Country(CallingCode = 65, Alpha3 = "SGP", ShortName = "Singapore", LongName = "The Republic of Singapore", CurrencyCode = "SGD" + , Languages = "en|ms|ta", UnofficialNames = "Singapore|Singapur|Singapour|シンガポール|新加坡")] + [ResourceDescription(nameof(Ln.新加坡))] + SG = 702 + + , + + /// + /// 泰国 + /// + [Country(CallingCode = 66, Alpha3 = "THA", ShortName = "Thailand", LongName = "The Kingdom of Thailand", CurrencyCode = "THB", Languages = "th" + , UnofficialNames = "Thailand|Thaïlande|Tailandia|タイ|ประเทศไทย|泰国")] + [ResourceDescription(nameof(Ln.泰国))] + TH = 764 + + , + + /// + /// 日本 + /// + [Country(CallingCode = 81, Alpha3 = "JPN", ShortName = "Japan", LongName = "Japan", CurrencyCode = "JPY", Languages = "ja" + , UnofficialNames = "Japan|Japon|Japón|日本")] + [ResourceDescription(nameof(Ln.日本))] + JP = 392 + + , + + /// + /// 韩国 + /// + [Country(CallingCode = 82, Alpha3 = "KOR", ShortName = "Korea (Republic of)", LongName = "The Republic of Korea", CurrencyCode = "KRW" + , Languages = "ko" + , UnofficialNames = "South Korea|Korea (South)|Südkorea|Corée du Sud|Corea del Sur|大韓民国|Zuid-Korea|Korea (Republic of)|韩国")] + [ResourceDescription(nameof(Ln.韩国))] + KR = 410 + + , + + /// + /// 越南 + /// + [Country(CallingCode = 84, Alpha3 = "VNM", ShortName = "Viet Nam", LongName = "The Socialist Republic of Viet Nam", CurrencyCode = "VND" + , Languages = "vi", UnofficialNames = "Vietnam|ベトナム|Viet Nam|越南")] + [ResourceDescription(nameof(Ln.越南))] + VN = 704 + + , + + /// + /// 中国 + /// + [Country(CallingCode = 86, Alpha3 = "CHN", ShortName = "China", LongName = "The People's Republic of China", CurrencyCode = "CNY" + , Languages = "zh", UnofficialNames = "China|Chine|中国")] + [ResourceDescription(nameof(Ln.中国))] + CN = 156 + + , + + /// + /// 土耳其 + /// + [Country(CallingCode = 90, Alpha3 = "TUR", ShortName = "Türkiye", LongName = "The Republic of Türkiye", CurrencyCode = "TRY", Languages = "tr" + , UnofficialNames = "Turkey|Türkei|Turquie|Turquía|トルコ|Turkije|土耳其")] + [ResourceDescription(nameof(Ln.土耳其))] + TR = 792 + + , + + /// + /// 印度 + /// + [Country(CallingCode = 91, Alpha3 = "IND", ShortName = "India", LongName = "The Republic of India", CurrencyCode = "INR", Languages = "hi|en" + , UnofficialNames = "India|Indien|Inde|インド|印度")] + [ResourceDescription(nameof(Ln.印度))] + IN = 356 + + , + + /// + /// 巴基斯坦 + /// + [Country(CallingCode = 92, Alpha3 = "PAK", ShortName = "Pakistan", LongName = "The Islamic Republic of Pakistan", CurrencyCode = "PKR" + , Languages = "en|ur", UnofficialNames = "Pakistan|Paquistán|パキスタン|巴基斯坦")] + [ResourceDescription(nameof(Ln.巴基斯坦))] + PK = 586 + + , + + /// + /// 阿富汗 + /// + [Country(CallingCode = 93, Alpha3 = "AFG", ShortName = "Afghanistan", LongName = "The Islamic Republic of Afghanistan", CurrencyCode = "AFN" + , Languages = "ps|uz|tk", UnofficialNames = "Afghanistan|Afganistán|アフガニスタン|阿富汗")] + [ResourceDescription(nameof(Ln.阿富汗))] + AF = 004 + + , + + /// + /// 斯里兰卡 + /// + [Country(CallingCode = 94, Alpha3 = "LKA", ShortName = "Sri Lanka", LongName = "The Democratic Socialist Republic of Sri Lanka" + , CurrencyCode = "LKR", Languages = "si|ta", UnofficialNames = "Sri Lanka|スリランカ|斯里兰卡")] + [ResourceDescription(nameof(Ln.斯里兰卡))] + LK = 144 + + , + + /// + /// 缅甸 + /// + [Country(CallingCode = 95, Alpha3 = "MMR", ShortName = "Myanmar", LongName = "The Republic of the Union of Myanmar", CurrencyCode = "MMK" + , Languages = "my", UnofficialNames = "Myanmar (Burma)|ミャンマー|缅甸")] + [ResourceDescription(nameof(Ln.缅甸))] + MM = 104 + + , + + /// + /// 伊朗 + /// + [Country(CallingCode = 98, Alpha3 = "IRN", ShortName = "Iran (Islamic Republic of)", LongName = "The Islamic Republic of Iran" + , CurrencyCode = "IRR", Languages = "fa" + , UnofficialNames = "Iran|Irán|Iran (Islamic Republic Of)|イラン・イスラム共和国|Islamic Republic of Iran|伊朗")] + [ResourceDescription(nameof(Ln.伊朗))] + IR = 364 + + , + + /// + /// 南苏丹 + /// + [Country(CallingCode = 211, Alpha3 = "SSD", ShortName = "South Sudan", LongName = "The Republic of South Sudan", CurrencyCode = "SSP" + , Languages = "ar|en", UnofficialNames = "South Sudan|Südsudan|南スーダン|Zuid-Soedan|南苏丹")] + [ResourceDescription(nameof(Ln.南苏丹))] + SS = 728 + + , + + /// + /// 西撒哈拉 + /// + [Country(CallingCode = 212, Alpha3 = "ESH", ShortName = "Western Sahara", LongName = "The Sahrawi Arab Democratic Republic", CurrencyCode = "MAD" + , Languages = "es|fr", UnofficialNames = "Western Sahara|الصحراء الغربية|Westsahara|Sahara Occidental|西サハラ|Westelijke Sahara|西撒哈拉")] + [ResourceDescription(nameof(Ln.西撒哈拉))] + EH = 732 + + , + + /// + /// 摩洛哥 + /// + [Country(CallingCode = 212, Alpha3 = "MAR", ShortName = "Morocco", LongName = "The Kingdom of Morocco", CurrencyCode = "MAD", Languages = "ar" + , UnofficialNames = "Morocco|المغرب|Marokko|Maroc|Marruecos|モロッコ|摩洛哥", IsPreferred = true)] + [ResourceDescription(nameof(Ln.摩洛哥))] + MA = 504 + + , + + /// + /// 阿尔及利亚 + /// + [Country(CallingCode = 213, Alpha3 = "DZA", ShortName = "Algeria", LongName = "The People's Democratic Republic of Algeria", CurrencyCode = "DZD" + , Languages = "ar", UnofficialNames = "Algeria|الجزائر|Algerien|Algérie|Argelia|アルジェリア|Algerije|阿尔及利亚")] + [ResourceDescription(nameof(Ln.阿尔及利亚))] + DZ = 012 + + , + + /// + /// 突尼斯 + /// + [Country(CallingCode = 216, Alpha3 = "TUN", ShortName = "Tunisia", LongName = "The Republic of Tunisia", CurrencyCode = "TND", Languages = "ar|fr" + , UnofficialNames = "Tunisia|تونس|Tunesien|Tunisie|Túnez|チュニジア|Tunesië|突尼斯")] + [ResourceDescription(nameof(Ln.突尼斯))] + TN = 788 + + , + + /// + /// 利比亚 + /// + [Country(CallingCode = 218, Alpha3 = "LBY", ShortName = "Libya", LongName = "The State of Libya", CurrencyCode = "LYD", Languages = "ar" + , UnofficialNames = "Libya|ليبيا|Libyen|Libye|Libia|リビア|Libië|Libyan Arab Jamahiriya|利比亚")] + [ResourceDescription(nameof(Ln.利比亚))] + LY = 434 + + , + + /// + /// 冈比亚 + /// + [Country(CallingCode = 220, Alpha3 = "GMB", ShortName = "Gambia", LongName = "The Republic of The Gambia", CurrencyCode = "GMD", Languages = "en" + , UnofficialNames = "The Gambia|ガンビア|冈比亚")] + [ResourceDescription(nameof(Ln.冈比亚))] + GM = 270 + + , + + /// + /// 塞内加尔 + /// + [Country(CallingCode = 221, Alpha3 = "SEN", ShortName = "Senegal", LongName = "The Republic of Senegal", CurrencyCode = "XOF", Languages = "fr" + , UnofficialNames = "Senegal|Sénégal|セネガル|塞内加尔")] + [ResourceDescription(nameof(Ln.塞内加尔))] + SN = 686 + + , + + /// + /// 毛里塔尼亚 + /// + [Country(CallingCode = 222, Alpha3 = "MRT", ShortName = "Mauritania", LongName = "The Islamic Republic of Mauritania", CurrencyCode = "MRU" + , Languages = "ar|fr", UnofficialNames = "Mauritania|موريتانيا|Mauretanien|Mauritanie|モーリタニア|Mauritanië|毛里塔尼亚")] + [ResourceDescription(nameof(Ln.毛里塔尼亚))] + MR = 478 + + , + + /// + /// 马里 + /// + [Country(CallingCode = 223, Alpha3 = "MLI", ShortName = "Mali", LongName = "The Republic of Mali", CurrencyCode = "XOF", Languages = "fr" + , UnofficialNames = "Mali|マリ|马里")] + [ResourceDescription(nameof(Ln.马里))] + ML = 466 + + , + + /// + /// 几内亚 + /// + [Country(CallingCode = 224, Alpha3 = "GIN", ShortName = "Guinea", LongName = "The Republic of Guinea", CurrencyCode = "GNF", Languages = "fr|ff" + , UnofficialNames = "Guinea|Guinée|ギニア|Guinee|几内亚")] + [ResourceDescription(nameof(Ln.几内亚))] + GN = 324 + + , + + /// + /// 科特迪瓦 + /// + [Country(CallingCode = 225, Alpha3 = "CIV", ShortName = "Côte d'Ivoire", LongName = "The Republic of Côte d'Ivoire", CurrencyCode = "XOF" + , Languages = "fr" + , UnofficialNames + = "Côte D'Ivoire|Elfenbeinküste|コートジボワール|Ivoorkust|Cote D'Ivoire (Ivory Coast)|Cote d Ivoire (Ivory Coast)|Ivory Coast|科特迪瓦")] + [ResourceDescription(nameof(Ln.科特迪瓦))] + CI = 384 + + , + + /// + /// 布基纳法索 + /// + [Country(CallingCode = 226, Alpha3 = "BFA", ShortName = "Burkina Faso", LongName = "Burkina Faso", CurrencyCode = "XOF", Languages = "fr|ff" + , UnofficialNames = "Burkina Faso|ブルキナファソ|布基纳法索")] + [ResourceDescription(nameof(Ln.布基纳法索))] + BF = 854 + + , + + /// + /// 尼日尔 + /// + [Country(CallingCode = 227, Alpha3 = "NER", ShortName = "Niger", LongName = "The Republic of the Niger", CurrencyCode = "XOF", Languages = "fr" + , UnofficialNames = "Niger|Níger|ニジェール|尼日尔")] + [ResourceDescription(nameof(Ln.尼日尔))] + NE = 562 + + , + + /// + /// 多哥 + /// + [Country(CallingCode = 228, Alpha3 = "TGO", ShortName = "Togo", LongName = "The Togolese Republic", CurrencyCode = "XOF", Languages = "fr" + , UnofficialNames = "Togo|トーゴ|多哥")] + [ResourceDescription(nameof(Ln.多哥))] + TG = 768 + + , + + /// + /// 贝宁 + /// + [Country(CallingCode = 229, Alpha3 = "BEN", ShortName = "Benin", LongName = "The Republic of Benin", CurrencyCode = "XOF", Languages = "fr" + , UnofficialNames = "Benin|Bénin|ベナン|贝宁")] + [ResourceDescription(nameof(Ln.贝宁))] + BJ = 204 + + , + + /// + /// 毛里求斯 + /// + [Country(CallingCode = 230, Alpha3 = "MUS", ShortName = "Mauritius", LongName = "The Republic of Mauritius", CurrencyCode = "MUR" + , Languages = "en", UnofficialNames = "Mauritius|Île Maurice|Mauricio|モーリシャス|毛里求斯")] + [ResourceDescription(nameof(Ln.毛里求斯))] + MU = 480 + + , + + /// + /// 利比里亚 + /// + [Country(CallingCode = 231, Alpha3 = "LBR", ShortName = "Liberia", LongName = "The Republic of Liberia", CurrencyCode = "LRD", Languages = "en" + , UnofficialNames = "Liberia|リベリア|利比里亚")] + [ResourceDescription(nameof(Ln.利比里亚))] + LR = 430 + + , + + /// + /// 塞拉利昂 + /// + [Country(CallingCode = 232, Alpha3 = "SLE", ShortName = "Sierra Leone", LongName = "The Republic of Sierra Leone", CurrencyCode = "SLL" + , Languages = "en", UnofficialNames = "Sierra Leone|シエラレオネ|塞拉利昂")] + [ResourceDescription(nameof(Ln.塞拉利昂))] + SL = 694 + + , + + /// + /// 加纳 + /// + [Country(CallingCode = 233, Alpha3 = "GHA", ShortName = "Ghana", LongName = "The Republic of Ghana", CurrencyCode = "GHS", Languages = "en" + , UnofficialNames = "Ghana|ガーナ|加纳")] + [ResourceDescription(nameof(Ln.加纳))] + GH = 288 + + , + + /// + /// 尼日利亚 + /// + [Country(CallingCode = 234, Alpha3 = "NGA", ShortName = "Nigeria", LongName = "The Federal Republic of Nigeria", CurrencyCode = "NGN" + , Languages = "en", UnofficialNames = "Nigeria|Nigéria|the Federal Republic of Nigeria|ナイジェリア|尼日利亚")] + [ResourceDescription(nameof(Ln.尼日利亚))] + NG = 566 + + , + + /// + /// 乍得 + /// + [Country(CallingCode = 235, Alpha3 = "TCD", ShortName = "Chad", LongName = "The Republic of Chad", CurrencyCode = "XAF", Languages = "ar|fr" + , UnofficialNames = "Chad|تشاد|Tschad|Tchad|チャド|Tsjaad|乍得")] + [ResourceDescription(nameof(Ln.乍得))] + TD = 148 + + , + + /// + /// 中非 + /// + [Country(CallingCode = 236, Alpha3 = "CAF", ShortName = "Central African Republic", LongName = "The Central African Republic" + , CurrencyCode = "XAF", Languages = "fr|sg" + , UnofficialNames + = "Central African Republic|Zentralafrikanische Republik|République Centrafricaine|República Centroafricana|中央アフリカ共和国|Centraal-Afrikaanse Republiek|中非")] + [ResourceDescription(nameof(Ln.中非))] + CF = 140 + + , + + /// + /// 喀麦隆 + /// + [Country(CallingCode = 237, Alpha3 = "CMR", ShortName = "Cameroon", LongName = "The Republic of Cameroon", CurrencyCode = "XAF" + , Languages = "en|fr", UnofficialNames = "Cameroon|Kamerun|Cameroun|Camerún|カメルーン|Kameroen|喀麦隆")] + [ResourceDescription(nameof(Ln.喀麦隆))] + CM = 120 + + , + + /// + /// 佛得角 + /// + [Country(CallingCode = 238, Alpha3 = "CPV", ShortName = "Cabo Verde", LongName = "The Republic of Cabo Verde", CurrencyCode = "CVE" + , Languages = "pt", UnofficialNames = "Cape Verde|Kap Verde|Cap Vert|Cabo Verde|カーボベルデ|Kaapverdië|佛得角")] + [ResourceDescription(nameof(Ln.佛得角))] + CV = 132 + + , + + /// + /// 圣多美和普林西比 + /// + [Country(CallingCode = 239, Alpha3 = "STP", ShortName = "Sao Tome and Principe", LongName = "The Democratic Republic of São Tomé and Príncipe" + , CurrencyCode = "STD", Languages = "pt" + , UnofficialNames + = "São Tomé and Príncipe|São Tomé und Príncipe|São Tomé et Príncipe|Santo Tomé y Príncipe|サントメ・プリンシペ|Sao Tomé en Principe|圣多美和普林西比")] + [ResourceDescription(nameof(Ln.圣多美和普林西比))] + ST = 678 + + , + + /// + /// 赤道几内亚 + /// + [Country(CallingCode = 240, Alpha3 = "GNQ", ShortName = "Equatorial Guinea", LongName = "The Republic of Equatorial Guinea", CurrencyCode = "XAF" + , Languages = "es|fr" + , UnofficialNames = "Equatorial Guinea|Äquatorial-Guinea|Guinée Équatoriale|Guinea Ecuatorial|赤道ギニア|Equatoriaal-Guinea|赤道几内亚")] + [ResourceDescription(nameof(Ln.赤道几内亚))] + GQ = 226 + + , + + /// + /// 加蓬 + /// + [Country(CallingCode = 241, Alpha3 = "GAB", ShortName = "Gabon", LongName = "The Gabonese Republic", CurrencyCode = "XAF", Languages = "fr" + , UnofficialNames = "Gabon|Gabun|Gabón|ガボン|加蓬")] + [ResourceDescription(nameof(Ln.加蓬))] + GA = 266 + + , + + /// + /// 刚果共和国 + /// + [Country(CallingCode = 242, Alpha3 = "COG", ShortName = "Congo", LongName = "The Republic of the Congo", CurrencyCode = "XAF", Languages = "fr|ln" + , UnofficialNames = "Congo|Kongo|コンゴ共和国|Congo [Republiek]|Congo, Republic of|刚果共和国")] + [ResourceDescription(nameof(Ln.刚果共和国))] + CG = 178 + + , + + /// + /// 刚果民主共和国 + /// + [Country(CallingCode = 243, Alpha3 = "COD", ShortName = "Congo (Democratic Republic of the)", LongName = "The Democratic Republic of the Congo" + , CurrencyCode = "CDF", Languages = "fr|ln|kg|sw|lu" + , UnofficialNames + = "Congo (Dem. Rep.)|Kongo (Dem. Rep.)|Congo (Rep. Dem.)|コンゴ民主共和国|Congo [DRC]|Congo (The Democratic Republic Of The)|Democratic Republic of the Congo|Congo, Democratic Republic of|刚果民主共和国")] + [ResourceDescription(nameof(Ln.刚果民主共和国))] + CD = 180 + + , + + /// + /// 安哥拉 + /// + [Country(CallingCode = 244, Alpha3 = "AGO", ShortName = "Angola", LongName = "The Republic of Angola", CurrencyCode = "AOA", Languages = "pt" + , UnofficialNames = "Angola|アンゴラ|安哥拉")] + [ResourceDescription(nameof(Ln.安哥拉))] + AO = 024 + + , + + /// + /// 几内亚比绍 + /// + [Country(CallingCode = 245, Alpha3 = "GNB", ShortName = "Guinea-Bissau", LongName = "The Republic of Guinea-Bissau", CurrencyCode = "XOF" + , Languages = "pt", UnofficialNames = "Guinea-Bissau|Guinée-Bissau|ギニアビサウ|Guinee-Bissau|Guinea Bissau|几内亚比绍")] + [ResourceDescription(nameof(Ln.几内亚比绍))] + GW = 624 + + , + + /// + /// 英属印度洋领地 + /// + [Country(CallingCode = 246, Alpha3 = "IOT", ShortName = "British Indian Ocean Territory", LongName = "The British Indian Ocean Territory" + , CurrencyCode = "USD", Languages = "en" + , UnofficialNames + = "British Indian Ocean Territory|Britisches Territorium im Indischen Ozean|イギリス領インド洋地域|Britse Gebieden in de Indische Oceaan|英属印度洋领地")] + [ResourceDescription(nameof(Ln.英属印度洋领地))] + IO = 086 + + , + + /// + /// 塞舌尔 + /// + [Country(CallingCode = 248, Alpha3 = "SYC", ShortName = "Seychelles", LongName = "The Republic of Seychelles", CurrencyCode = "SCR" + , Languages = "fr|en", UnofficialNames = "Seychelles|Seychellen|セーシェル|塞舌尔")] + [ResourceDescription(nameof(Ln.塞舌尔))] + SC = 690 + + , + + /// + /// 苏丹 + /// + [Country(CallingCode = 249, Alpha3 = "SDN", ShortName = "Sudan", LongName = "The Republic of the Sudan", CurrencyCode = "SDG", Languages = "ar|en" + , UnofficialNames = "Sudan|السودان|Soudan|Sudán|スーダン|Soedan|苏丹")] + [ResourceDescription(nameof(Ln.苏丹))] + SD = 729 + + , + + /// + /// 卢旺达 + /// + [Country(CallingCode = 250, Alpha3 = "RWA", ShortName = "Rwanda", LongName = "The Republic of Rwanda", CurrencyCode = "RWF" + , Languages = "rw|en|fr", UnofficialNames = "Rwanda|Ruanda|ルワンダ|卢旺达")] + [ResourceDescription(nameof(Ln.卢旺达))] + RW = 646 + + , + + /// + /// 埃塞俄比亚 + /// + [Country(CallingCode = 251, Alpha3 = "ETH", ShortName = "Ethiopia", LongName = "The Federal Democratic Republic of Ethiopia", CurrencyCode = "ETB" + , Languages = "am", UnofficialNames = "Ethiopia|Äthiopien|Éthiopie|Etiopía|エチオピア|Ethiopië|埃塞俄比亚")] + [ResourceDescription(nameof(Ln.埃塞俄比亚))] + ET = 231 + + , + + /// + /// 索马里 + /// + [Country(CallingCode = 252, Alpha3 = "SOM", ShortName = "Somalia", LongName = "The Federal Republic of Somalia", CurrencyCode = "SOS" + , Languages = "so|ar", UnofficialNames = "Somalia|الصومال|ソマリア|Somalië|索马里")] + [ResourceDescription(nameof(Ln.索马里))] + SO = 706 + + , + + /// + /// 吉布提 + /// + [Country(CallingCode = 253, Alpha3 = "DJI", ShortName = "Djibouti", LongName = "The Republic of Djibouti", CurrencyCode = "DJF" + , Languages = "ar|fr", UnofficialNames = "Djibouti|جيبوتي|Dschibuti|ジブチ|吉布提")] + [ResourceDescription(nameof(Ln.吉布提))] + DJ = 262 + + , + + /// + /// 肯尼亚 + /// + [Country(CallingCode = 254, Alpha3 = "KEN", ShortName = "Kenya", LongName = "The Republic of Kenya", CurrencyCode = "KES", Languages = "en|sw" + , UnofficialNames = "Kenya|Kenia|ケニア|肯尼亚")] + [ResourceDescription(nameof(Ln.肯尼亚))] + KE = 404 + + , + + /// + /// 坦桑尼亚 + /// + [Country(CallingCode = 255, Alpha3 = "TZA", ShortName = "Tanzania, United Republic of", LongName = "The United Republic of Tanzania" + , CurrencyCode = "TZS", Languages = "sw|en", UnofficialNames = "Tanzania|Tansania|Tanzanie|タンザニア|Tanzania United Republic|坦桑尼亚")] + [ResourceDescription(nameof(Ln.坦桑尼亚))] + TZ = 834 + + , + + /// + /// 乌干达 + /// + [Country(CallingCode = 256, Alpha3 = "UGA", ShortName = "Uganda", LongName = "The Republic of Uganda", CurrencyCode = "UGX", Languages = "en|sw" + , UnofficialNames = "Uganda|ウガンダ|Oeganda|乌干达")] + [ResourceDescription(nameof(Ln.乌干达))] + UG = 800 + + , + + /// + /// 布隆迪 + /// + [Country(CallingCode = 257, Alpha3 = "BDI", ShortName = "Burundi", LongName = "The Republic of Burundi", CurrencyCode = "BIF", Languages = "fr|rn" + , UnofficialNames = "Burundi|ブルンジ|布隆迪")] + [ResourceDescription(nameof(Ln.布隆迪))] + BI = 108 + + , + + /// + /// 莫桑比克 + /// + [Country(CallingCode = 258, Alpha3 = "MOZ", ShortName = "Mozambique", LongName = "The Republic of Mozambique", CurrencyCode = "MZN" + , Languages = "pt", UnofficialNames = "Mozambique|Mosambik|モザンビーク|莫桑比克")] + [ResourceDescription(nameof(Ln.莫桑比克))] + MZ = 508 + + , + + /// + /// 赞比亚 + /// + [Country(CallingCode = 260, Alpha3 = "ZMB", ShortName = "Zambia", LongName = "The Republic of Zambia", CurrencyCode = "ZMW", Languages = "en" + , UnofficialNames = "Zambia|Sambia|Zambie|ザンビア|赞比亚")] + [ResourceDescription(nameof(Ln.赞比亚))] + ZM = 894 + + , + + /// + /// 马达加斯加 + /// + [Country(CallingCode = 261, Alpha3 = "MDG", ShortName = "Madagascar", LongName = "The Republic of Madagascar", CurrencyCode = "MGA" + , Languages = "fr|mg", UnofficialNames = "Madagascar|Madagaskar|the Republic of Madagascar|マダガスカル|马达加斯加")] + [ResourceDescription(nameof(Ln.马达加斯加))] + MG = 450 + + , + + /// + /// 留尼汪 + /// + [Country(CallingCode = 262, Alpha3 = "REU", ShortName = "Réunion", LongName = "Réunion", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "Réunion|Reunión|Reunion|レユニオン|留尼汪", IsPreferred = true)] + [ResourceDescription(nameof(Ln.留尼汪))] + RE = 638 + + , + + /// + /// 法属南部和南极领地 + /// + [Country(CallingCode = 262, Alpha3 = "ATF", ShortName = "French Southern Territories", LongName = "The French Southern and Antarctic Lands" + , CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames + = "French Southern Territories|Französische Süd- und Antarktisgebiete|Terres Australes Françaises|Territorios Franceses del Sur|フランス領南方・南極地域|Franse Gebieden in de zuidelijke Indische Oceaan|French Southern and Antarctic Lands|法属南部和南极领地")] + [ResourceDescription(nameof(Ln.法属南部和南极领地))] + TF = 260 + + , + + /// + /// 马约特 + /// + [Country(CallingCode = 262, CallingSubCode = "269,639", Alpha3 = "MYT", ShortName = "Mayotte", LongName = "The Department of Mayotte" + , CurrencyCode = "EUR", Languages = "fr", UnofficialNames = "Mayotte|マヨット|马约特")] + [ResourceDescription(nameof(Ln.马约特))] + YT = 175 + + , + + /// + /// 津巴布韦 + /// + [Country(CallingCode = 263, Alpha3 = "ZWE", ShortName = "Zimbabwe", LongName = "The Republic of Zimbabwe", CurrencyCode = "USD" + , Languages = "en|sn|nd", UnofficialNames = "Zimbabwe|Simbabwe|Zimbabue|ジンバブエ|津巴布韦")] + [ResourceDescription(nameof(Ln.津巴布韦))] + ZW = 716 + + , + + /// + /// 纳米比亚 + /// + [Country(CallingCode = 264, Alpha3 = "NAM", ShortName = "Namibia", LongName = "The Republic of Namibia", CurrencyCode = "NAD", Languages = "en|af" + , UnofficialNames = "Namibia|Namibie|ナミビア|Namibië|纳米比亚")] + [ResourceDescription(nameof(Ln.纳米比亚))] + NA = 516 + + , + + /// + /// 马拉维 + /// + [Country(CallingCode = 265, Alpha3 = "MWI", ShortName = "Malawi", LongName = "The Republic of Malawi", CurrencyCode = "MWK", Languages = "en|ny" + , UnofficialNames = "Malawi|マラウイ|马拉维")] + [ResourceDescription(nameof(Ln.马拉维))] + MW = 454 + + , + + /// + /// 莱索托 + /// + [Country(CallingCode = 266, Alpha3 = "LSO", ShortName = "Lesotho", LongName = "The Kingdom of Lesotho", CurrencyCode = "LSL", Languages = "en|st" + , UnofficialNames = "Lesotho|レソト|莱索托")] + [ResourceDescription(nameof(Ln.莱索托))] + LS = 426 + + , + + /// + /// 博茨瓦纳 + /// + [Country(CallingCode = 267, Alpha3 = "BWA", ShortName = "Botswana", LongName = "The Republic of Botswana", CurrencyCode = "BWP" + , Languages = "en|tn", UnofficialNames = "Botswana|ボツワナ|博茨瓦纳")] + [ResourceDescription(nameof(Ln.博茨瓦纳))] + BW = 072 + + , + + /// + /// 斯威士兰 + /// + [Country(CallingCode = 268, Alpha3 = "SWZ", ShortName = "Eswatini", LongName = "The Kingdom of Eswatini", CurrencyCode = "SZL" + , Languages = "en|ss", UnofficialNames = "Swaziland|Swasiland|Suazilandia|スワジランド|斯威士兰")] + [ResourceDescription(nameof(Ln.斯威士兰))] + SZ = 748 + + , + + /// + /// 科摩罗 + /// + [Country(CallingCode = 269, Alpha3 = "COM", ShortName = "Comoros", LongName = "The Union of the Comoros", CurrencyCode = "KMF" + , Languages = "ar|fr", UnofficialNames = "Comoros|Union der Komoren|Comores|コモロ|Comoren|科摩罗")] + [ResourceDescription(nameof(Ln.科摩罗))] + KM = 174 + + , + + /// + /// 圣赫勒拿 + /// + [Country(CallingCode = 290, Alpha3 = "SHN", ShortName = "Saint Helena, Ascension and Tristan da Cunha" + , LongName = "Saint Helena, Ascension and Tristan da Cunha", CurrencyCode = "SHP", Languages = "en" + , UnofficialNames + = "Saint Helena|Sankt Helena|Sainte Hélène|Santa Helena|セントヘレナ・アセンションおよびトリスタンダクーニャ|Sint-Helena|Saint Helena, Ascension and Tristan da Cunha|圣赫勒拿")] + [ResourceDescription(nameof(Ln.圣赫勒拿))] + SH = 654 + + , + + /// + /// 厄立特里亚 + /// + [Country(CallingCode = 291, Alpha3 = "ERI", ShortName = "Eritrea", LongName = "The State of Eritrea", CurrencyCode = "ETB", Languages = "en|ar|ti" + , UnofficialNames = "Eritrea|إريتريا|Érythrée|エリトリア|厄立特里亚")] + [ResourceDescription(nameof(Ln.厄立特里亚))] + ER = 232 + + , + + /// + /// 阿鲁巴 + /// + [Country(CallingCode = 297, Alpha3 = "ABW", ShortName = "Aruba", LongName = "Aruba", CurrencyCode = "AWG", Languages = "nl" + , UnofficialNames = "Aruba|アルバ|阿鲁巴")] + [ResourceDescription(nameof(Ln.阿鲁巴))] + AW = 533 + + , + + /// + /// 法罗群岛 + /// + [Country(CallingCode = 298, Alpha3 = "FRO", ShortName = "Faroe Islands", LongName = "The Faroe Islands", CurrencyCode = "DKK", Languages = "fo" + , UnofficialNames = "Faroe Islands|Färöer-Inseln|Îles Féroé|Islas Faroe|フェロー諸島|Faeröer|法罗群岛")] + [ResourceDescription(nameof(Ln.法罗群岛))] + FO = 234 + + , + + /// + /// 格陵兰 + /// + [Country(CallingCode = 299, Alpha3 = "GRL", ShortName = "Greenland", LongName = "Kalaallit Nunaat", CurrencyCode = "DKK", Languages = "kl" + , UnofficialNames = "Greenland|Grönland|Groenland|Groenlandia|グリーンランド|格陵兰")] + [ResourceDescription(nameof(Ln.格陵兰))] + GL = 304 + + , + + /// + /// 直布罗陀 + /// + [Country(CallingCode = 350, Alpha3 = "GIB", ShortName = "Gibraltar", LongName = "Gibraltar", CurrencyCode = "GIP", Languages = "en" + , UnofficialNames = "Gibraltar|ジブラルタル|直布罗陀")] + [ResourceDescription(nameof(Ln.直布罗陀))] + GI = 292 + + , + + /// + /// 葡萄牙 + /// + [Country(CallingCode = 351, Alpha3 = "PRT", ShortName = "Portugal", LongName = "The Portuguese Republic", CurrencyCode = "EUR", Languages = "pt" + , UnofficialNames = "Portugal|ポルトガル|葡萄牙")] + [ResourceDescription(nameof(Ln.葡萄牙))] + PT = 620 + + , + + /// + /// 卢森堡 + /// + [Country(CallingCode = 352, Alpha3 = "LUX", ShortName = "Luxembourg", LongName = "The Grand Duchy of Luxembourg", CurrencyCode = "EUR" + , Languages = "fr|de|lb", UnofficialNames = "Luxembourg|Luxemburg|Luxemburgo|ルクセンブルク|卢森堡")] + [ResourceDescription(nameof(Ln.卢森堡))] + LU = 442 + + , + + /// + /// 爱尔兰 + /// + [Country(CallingCode = 353, Alpha3 = "IRL", ShortName = "Ireland", LongName = "Ireland", CurrencyCode = "EUR", Languages = "en|ga" + , UnofficialNames = "Ireland|Irland|Irlande|Irlanda|アイルランド|Ierland|爱尔兰")] + [ResourceDescription(nameof(Ln.爱尔兰))] + IE = 372 + + , + + /// + /// 冰岛 + /// + [Country(CallingCode = 354, Alpha3 = "ISL", ShortName = "Iceland", LongName = "Iceland", CurrencyCode = "ISK", Languages = "is" + , UnofficialNames = "Iceland|Island|Islande|Islandia|アイスランド|IJsland|冰岛")] + [ResourceDescription(nameof(Ln.冰岛))] + IS = 352 + + , + + /// + /// 阿尔巴尼亚 + /// + [Country(CallingCode = 355, Alpha3 = "ALB", ShortName = "Albania", LongName = "The Republic of Albania", CurrencyCode = "ALL", Languages = "sq" + , UnofficialNames = "Albania|Albanien|Albanie|アルバニア|Albanië|阿尔巴尼亚")] + [ResourceDescription(nameof(Ln.阿尔巴尼亚))] + AL = 008 + + , + + /// + /// 马耳他 + /// + [Country(CallingCode = 356, Alpha3 = "MLT", ShortName = "Malta", LongName = "The Republic of Malta", CurrencyCode = "EUR", Languages = "mt|en" + , UnofficialNames = "Malta|Malte|マルタ|马耳他")] + [ResourceDescription(nameof(Ln.马耳他))] + MT = 470 + + , + + /// + /// 塞浦路斯 + /// + [Country(CallingCode = 357, Alpha3 = "CYP", ShortName = "Cyprus", LongName = "The Republic of Cyprus", CurrencyCode = "EUR" + , Languages = "el|tr|hy", UnofficialNames = "Cyprus|Zypern|Chypre|Chipre|キプロス|塞浦路斯")] + [ResourceDescription(nameof(Ln.塞浦路斯))] + CY = 196 + + , + + /// + /// 奥兰 + /// + [Country(CallingCode = 358, CallingSubCode = "18", Alpha3 = "ALA", ShortName = "Åland Islands", LongName = "Åland", CurrencyCode = "EUR" + , Languages = "sv", UnofficialNames = "Åland Islands|Åland|オーランド諸島|Ålandeilanden|奥兰")] + [ResourceDescription(nameof(Ln.奥兰))] + AX = 248 + + , + + /// + /// 芬兰 + /// + [Country(CallingCode = 358, Alpha3 = "FIN", ShortName = "Finland", LongName = "The Republic of Finland", CurrencyCode = "EUR", Languages = "fi|sv" + , UnofficialNames = "Finland|Finnland|Finlande|Finlandia|フィンランド|芬兰", IsPreferred = true)] + [ResourceDescription(nameof(Ln.芬兰))] + FI = 246 + + , + + /// + /// 保加利亚 + /// + [Country(CallingCode = 359, Alpha3 = "BGR", ShortName = "Bulgaria", LongName = "The Republic of Bulgaria", CurrencyCode = "BGN", Languages = "bg" + , UnofficialNames = "Bulgaria|България|Bulgarien|Bulgarie|ブルガリア|Bulgarije|保加利亚")] + [ResourceDescription(nameof(Ln.保加利亚))] + BG = 100 + + , + + /// + /// 立陶宛 + /// + [Country(CallingCode = 370, Alpha3 = "LTU", ShortName = "Lithuania", LongName = "The Republic of Lithuania", CurrencyCode = "EUR" + , Languages = "lt", UnofficialNames = "Lithuania|Litauen|Lituanie|Lituania|リトアニア|Litouwen|Літва|Lietuva|立陶宛")] + [ResourceDescription(nameof(Ln.立陶宛))] + LT = 440 + + , + + /// + /// 拉脱维亚 + /// + [Country(CallingCode = 371, Alpha3 = "LVA", ShortName = "Latvia", LongName = "The Republic of Latvia", CurrencyCode = "EUR", Languages = "lv" + , UnofficialNames = "Latvia|Lettland|Lettonie|Letonia|ラトビア|Letland|拉脱维亚")] + [ResourceDescription(nameof(Ln.拉脱维亚))] + LV = 428 + + , + + /// + /// 爱沙尼亚 + /// + [Country(CallingCode = 372, Alpha3 = "EST", ShortName = "Estonia", LongName = "The Republic of Estonia", CurrencyCode = "EUR", Languages = "et" + , UnofficialNames = "Estonia|Estland|Estonie|エストニア|爱沙尼亚")] + [ResourceDescription(nameof(Ln.爱沙尼亚))] + EE = 233 + + , + + /// + /// 摩尔多瓦 + /// + [Country(CallingCode = 373, Alpha3 = "MDA", ShortName = "Moldova (Republic of)", LongName = "The Republic of Moldova", CurrencyCode = "MDL" + , Languages = "ro", UnofficialNames = "Moldova|Moldawien|Moldavie|Moldavia|the Republic of Moldova|モルドバ共和国|Moldavië|摩尔多瓦")] + [ResourceDescription(nameof(Ln.摩尔多瓦))] + MD = 498 + + , + + /// + /// 亚美尼亚 + /// + [Country(CallingCode = 374, Alpha3 = "ARM", ShortName = "Armenia", LongName = "The Republic of Armenia", CurrencyCode = "AMD", Languages = "hy|ru" + , UnofficialNames = "Armenia|Armenien|Arménie|アルメニア|Armenië|亚美尼亚")] + [ResourceDescription(nameof(Ln.亚美尼亚))] + AM = 051 + + , + + /// + /// 白俄罗斯 + /// + [Country(CallingCode = 375, Alpha3 = "BLR", ShortName = "Belarus", LongName = "The Republic of Belarus", CurrencyCode = "BYN", Languages = "be|ru" + , UnofficialNames = "Belarus|Weißrussland|Biélorussie|Bielorrusia|ベラルーシ|Wit-Rusland|Беларусь|白俄罗斯")] + [ResourceDescription(nameof(Ln.白俄罗斯))] + BY = 112 + + , + + /// + /// 安道尔 + /// + [Country(CallingCode = 376, Alpha3 = "AND", ShortName = "Andorra", LongName = "The Principality of Andorra", CurrencyCode = "EUR" + , Languages = "ca", UnofficialNames = "Andorre|Andorra|アンドラ|安道尔")] + [ResourceDescription(nameof(Ln.安道尔))] + AD = 020 + + , + + /// + /// 摩纳哥 + /// + [Country(CallingCode = 377, Alpha3 = "MCO", ShortName = "Monaco", LongName = "The Principality of Monaco", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "Monaco|Mónaco|モナコ|摩纳哥")] + [ResourceDescription(nameof(Ln.摩纳哥))] + MC = 492 + + , + + /// + /// 圣马力诺 + /// + [Country(CallingCode = 378, Alpha3 = "SMR", ShortName = "San Marino", LongName = "The Republic of San Marino", CurrencyCode = "EUR" + , Languages = "it", UnofficialNames = "San Marino|Saint-Marin|サンマリノ|圣马力诺")] + [ResourceDescription(nameof(Ln.圣马力诺))] + SM = 674 + + , + + /// + /// 梵蒂冈 + /// + [Country(CallingCode = 39, CallingSubCode = "06698", Alpha3 = "VAT", ShortName = "Holy See", LongName = "The Holy See", CurrencyCode = "EUR" + , Languages = "it|la" + , UnofficialNames = "Vatican City|Vatikan|Cité du Vatican|Ciudad del Vaticano|バチカン市国|Vaticaanstad|Vatican City State (Holy See)|梵蒂冈")] + [ResourceDescription(nameof(Ln.梵蒂冈))] + VA = 336 + + , + + /// + /// 乌克兰 + /// + [Country(CallingCode = 380, Alpha3 = "UKR", ShortName = "Ukraine", LongName = "Ukraine", CurrencyCode = "UAH", Languages = "uk" + , UnofficialNames = "Ukraine|Ucrania|ウクライナ|Oekraïne|Украина|Україна|Украіна|乌克兰")] + [ResourceDescription(nameof(Ln.乌克兰))] + UA = 804 + + , + + /// + /// 塞尔维亚 + /// + [Country(CallingCode = 381, Alpha3 = "SRB", ShortName = "Serbia", LongName = "The Republic of Serbia", CurrencyCode = "RSD", Languages = "sr" + , UnofficialNames = "Serbia|Serbien|Serbie|セルビア|Servië|塞尔维亚")] + [ResourceDescription(nameof(Ln.塞尔维亚))] + RS = 688 + + , + + /// + /// 黑山 + /// + [Country(CallingCode = 382, Alpha3 = "MNE", ShortName = "Montenegro", LongName = "Montenegro", CurrencyCode = "EUR", Languages = "sr|bs|sq|hr" + , UnofficialNames = "Crna Gora|Montenegro|モンテネグロ|黑山")] + [ResourceDescription(nameof(Ln.黑山))] + ME = 499 + + , + + /// + /// 克罗地亚 + /// + [Country(CallingCode = 385, Alpha3 = "HRV", ShortName = "Croatia", LongName = "The Republic of Croatia", CurrencyCode = "EUR", Languages = "hr" + , UnofficialNames = "Croatia|Kroatien|Croatie|Croacia|クロアチア|Kroatië|Croatia (Hrvatska)|克罗地亚")] + [ResourceDescription(nameof(Ln.克罗地亚))] + HR = 191 + + , + + /// + /// 斯洛文尼亚 + /// + [Country(CallingCode = 386, Alpha3 = "SVN", ShortName = "Slovenia", LongName = "The Republic of Slovenia", CurrencyCode = "EUR", Languages = "sl" + , UnofficialNames = "Slovenia|Slowenien|Slovénie|Eslovenia|スロベニア|Slovenië|斯洛文尼亚")] + [ResourceDescription(nameof(Ln.斯洛文尼亚))] + SI = 705 + + , + + /// + /// 波黑 + /// + [Country(CallingCode = 387, Alpha3 = "BIH", ShortName = "Bosnia and Herzegovina", LongName = "Bosnia and Herzegovina", CurrencyCode = "BAM" + , Languages = "bs|hr|sr" + , UnofficialNames + = "Bosnia and Herzegovina|Bosnien und Herzegowina|Bosnie et Herzégovine|Bosnia y Herzegovina|ボスニア・ヘルツェゴビナ|Bosnië en Herzegovina|Bosnia Herzegovina|波黑")] + [ResourceDescription(nameof(Ln.波黑))] + BA = 070 + + , + + /// + /// 北马其顿 + /// + [Country(CallingCode = 389, Alpha3 = "MKD", ShortName = "North Macedonia", LongName = "The Republic of North Macedonia", CurrencyCode = "MKD" + , Languages = "mk" + , UnofficialNames + = "Macedonia|Mazedonien|Macédoine|F.Y.R.O.M (Macedonia)|マケドニア旧ユーゴスラビア共和国|Macedonië [FYROM]|Macedonia (The Former Yugoslav Republic of)|North Macedonia|Macedonia (FYROM)|北马其顿")] + [ResourceDescription(nameof(Ln.北马其顿))] + MK = 807 + + , + + /// + /// 捷克 + /// + [Country(CallingCode = 420, Alpha3 = "CZE", ShortName = "Czechia", LongName = "The Czech Republic", CurrencyCode = "CZK", Languages = "cs" + , UnofficialNames = "Czech Republic|Tschechische Republik|République Tchèque|República Checa|チェコ|Tsjechië|Czechia|Česká republika|捷克")] + [ResourceDescription(nameof(Ln.捷克))] + CZ = 203 + + , + + /// + /// 斯洛伐克 + /// + [Country(CallingCode = 421, Alpha3 = "SVK", ShortName = "Slovakia", LongName = "The Slovak Republic", CurrencyCode = "EUR", Languages = "sk" + , UnofficialNames = "Slovakia|Slowakei|Slovaquie|República Eslovaca|スロバキア|Slowakije|斯洛伐克")] + [ResourceDescription(nameof(Ln.斯洛伐克))] + SK = 703 + + , + + /// + /// 列支敦士登 + /// + [Country(CallingCode = 423, Alpha3 = "LIE", ShortName = "Liechtenstein", LongName = "The Principality of Liechtenstein", CurrencyCode = "CHF" + , Languages = "de", UnofficialNames = "Liechtenstein|リヒテンシュタイン|列支敦士登")] + [ResourceDescription(nameof(Ln.列支敦士登))] + LI = 438 + + , + + /// + /// 福克兰群岛 + /// + [Country(CallingCode = 500, Alpha3 = "FLK", ShortName = "Falkland Islands (Malvinas)", LongName = "The Falkland Islands", CurrencyCode = "FKP" + , Languages = "en" + , UnofficialNames + = "Falkland Islands|Falklandinseln|Îles Malouines|Islas Malvinas|フォークランド(マルビナス)諸島|Falklandeilanden [Islas Malvinas]|福克兰群岛" + , IsPreferred = true)] + [ResourceDescription(nameof(Ln.福克兰群岛))] + FK = 238 + + , + + /// + /// 南乔治亚和南桑威奇群岛 + /// + [Country(CallingCode = 500, Alpha3 = "SGS", ShortName = "South Georgia and the South Sandwich Islands" + , LongName = "South Georgia and the South Sandwich Islands", CurrencyCode = "GBP", Languages = "en" + , UnofficialNames + = "South Georgia|South Georgia and the South Sandwich Islands|Südgeorgien und die Südlichen Sandwichinseln|サウスジョージア・サウスサンドウィッチ諸島|Zuid-Georgia en Zuidelijke Sandwicheilanden|南乔治亚和南桑威奇群岛")] + [ResourceDescription(nameof(Ln.南乔治亚和南桑威奇群岛))] + GS = 239 + + , + + /// + /// 伯利兹 + /// + [Country(CallingCode = 501, Alpha3 = "BLZ", ShortName = "Belize", LongName = "Belize", CurrencyCode = "BZD", Languages = "en|es" + , UnofficialNames = "Belize|Belice|ベリーズ|伯利兹")] + [ResourceDescription(nameof(Ln.伯利兹))] + BZ = 084 + + , + + /// + /// 危地马拉 + /// + [Country(CallingCode = 502, Alpha3 = "GTM", ShortName = "Guatemala", LongName = "The Republic of Guatemala", CurrencyCode = "GTQ" + , Languages = "es", UnofficialNames = "Guatemala|グアテマラ|危地马拉")] + [ResourceDescription(nameof(Ln.危地马拉))] + GT = 320 + + , + + /// + /// 萨尔瓦多 + /// + [Country(CallingCode = 503, Alpha3 = "SLV", ShortName = "El Salvador", LongName = "The Republic of El Salvador", CurrencyCode = "USD" + , Languages = "es", UnofficialNames = "El Salvador|Salvador|エルサルバドル|萨尔瓦多")] + [ResourceDescription(nameof(Ln.萨尔瓦多))] + SV = 222 + + , + + /// + /// 洪都拉斯 + /// + [Country(CallingCode = 504, Alpha3 = "HND", ShortName = "Honduras", LongName = "The Republic of Honduras", CurrencyCode = "HNL", Languages = "es" + , UnofficialNames = "Honduras|ホンジュラス|洪都拉斯")] + [ResourceDescription(nameof(Ln.洪都拉斯))] + HN = 340 + + , + + /// + /// 尼加拉瓜 + /// + [Country(CallingCode = 505, Alpha3 = "NIC", ShortName = "Nicaragua", LongName = "The Republic of Nicaragua", CurrencyCode = "NIO" + , Languages = "es", UnofficialNames = "Nicaragua|ニカラグア|尼加拉瓜")] + [ResourceDescription(nameof(Ln.尼加拉瓜))] + NI = 558 + + , + + /// + /// 哥斯达黎加 + /// + [Country(CallingCode = 506, Alpha3 = "CRI", ShortName = "Costa Rica", LongName = "The Republic of Costa Rica", CurrencyCode = "CRC" + , Languages = "es", UnofficialNames = "Costa Rica|コスタリカ|哥斯达黎加")] + [ResourceDescription(nameof(Ln.哥斯达黎加))] + CR = 188 + + , + + /// + /// 巴拿马 + /// + [Country(CallingCode = 507, Alpha3 = "PAN", ShortName = "Panama", LongName = "The Republic of Panamá", CurrencyCode = "PAB", Languages = "es" + , UnofficialNames = "Panama|Panamá|パナマ|巴拿马")] + [ResourceDescription(nameof(Ln.巴拿马))] + PA = 591 + + , + + /// + /// 圣皮埃尔和密克隆 + /// + [Country(CallingCode = 508, Alpha3 = "SPM", ShortName = "Saint Pierre and Miquelon" + , LongName = "The Overseas Collectivity of Saint-Pierre and Miquelon", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames + = "Saint Pierre and Miquelon|Saint-Pierre und Miquelon|Saint-Pierre-et-Miquelon|San Pedro y Miquelón|サンピエール島・ミクロン島|Saint Pierre en Miquelon|圣皮埃尔和密克隆")] + [ResourceDescription(nameof(Ln.圣皮埃尔和密克隆))] + PM = 666 + + , + + /// + /// 海地 + /// + [Country(CallingCode = 509, Alpha3 = "HTI", ShortName = "Haiti", LongName = "The Republic of Haiti", CurrencyCode = "HTG", Languages = "fr|ht" + , UnofficialNames = "Haiti|ハイチ|Haïti|海地")] + [ResourceDescription(nameof(Ln.海地))] + HT = 332 + + , + + /// + /// 圣巴泰勒米 + /// + [Country(CallingCode = 590, Alpha3 = "BLM", ShortName = "Saint Barthélemy", LongName = "The Collectivity of Saint-Barthélemy" + , CurrencyCode = "EUR", Languages = "fr", UnofficialNames = "Saint Barthélemy|Saint-Barthélemy|サン・バルテルミー|圣巴泰勒米")] + [ResourceDescription(nameof(Ln.圣巴泰勒米))] + BL = 652 + + , + + /// + /// 瓜德罗普 + /// + [Country(CallingCode = 590, Alpha3 = "GLP", ShortName = "Guadeloupe", LongName = "Guadeloupe", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "Guadeloupe|Guadalupe|グアドループ|瓜德罗普", IsPreferred = true)] + [ResourceDescription(nameof(Ln.瓜德罗普))] + GP = 312 + + , + + /// + /// 法属圣马丁 + /// + [Country(CallingCode = 590, Alpha3 = "MAF", ShortName = "Saint Martin (French part)", LongName = "The Collectivity of Saint-Martin" + , CurrencyCode = "EUR", Languages = "en|fr|nl", UnofficialNames = "Saint Martin|サン・マルタン(フランス領)|Saint-Martin|法属圣马丁")] + [ResourceDescription(nameof(Ln.法属圣马丁))] + MF = 663 + + , + + /// + /// 玻利维亚 + /// + [Country(CallingCode = 591, Alpha3 = "BOL", ShortName = "Bolivia (Plurinational State of)", LongName = "The Plurinational State of Bolivia" + , CurrencyCode = "BOB", Languages = "es|ay|qu", UnofficialNames = "Bolivia|Bolivien|Bolivie|ボリビア多民族国|玻利维亚")] + [ResourceDescription(nameof(Ln.玻利维亚))] + BO = 068 + + , + + /// + /// 圭亚那 + /// + [Country(CallingCode = 592, Alpha3 = "GUY", ShortName = "Guyana", LongName = "The Co-operative Republic of Guyana", CurrencyCode = "GYD" + , Languages = "en", UnofficialNames = "Guyana|ガイアナ|圭亚那")] + [ResourceDescription(nameof(Ln.圭亚那))] + GY = 328 + + , + + /// + /// 厄瓜多尔 + /// + [Country(CallingCode = 593, Alpha3 = "ECU", ShortName = "Ecuador", LongName = "The Republic of Ecuador", CurrencyCode = "USD", Languages = "es" + , UnofficialNames = "Ecuador|Équateur|エクアドル|厄瓜多尔")] + [ResourceDescription(nameof(Ln.厄瓜多尔))] + EC = 218 + + , + + /// + /// 法属圭亚那 + /// + [Country(CallingCode = 594, Alpha3 = "GUF", ShortName = "French Guiana", LongName = "Guyane", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "French Guiana|Französisch Guyana|Guayana Francesa|フランス領ギアナ|Frans-Guyana|法属圭亚那")] + [ResourceDescription(nameof(Ln.法属圭亚那))] + GF = 254 + + , + + /// + /// 巴拉圭 + /// + [Country(CallingCode = 595, Alpha3 = "PRY", ShortName = "Paraguay", LongName = "The Republic of Paraguay", CurrencyCode = "PYG" + , Languages = "es|gn", UnofficialNames = "Paraguay|パラグアイ|巴拉圭")] + [ResourceDescription(nameof(Ln.巴拉圭))] + PY = 600 + + , + + /// + /// 马提尼克 + /// + [Country(CallingCode = 596, Alpha3 = "MTQ", ShortName = "Martinique", LongName = "Martinique", CurrencyCode = "EUR", Languages = "fr" + , UnofficialNames = "Martinique|Martinica|マルティニーク|马提尼克")] + [ResourceDescription(nameof(Ln.马提尼克))] + MQ = 474 + + , + + /// + /// 苏里南 + /// + [Country(CallingCode = 597, Alpha3 = "SUR", ShortName = "Suriname", LongName = "The Republic of Suriname", CurrencyCode = "SRD", Languages = "nl" + , UnofficialNames = "Suriname|Surinam|スリナム|苏里南")] + [ResourceDescription(nameof(Ln.苏里南))] + SR = 740 + + , + + /// + /// 乌拉圭 + /// + [Country(CallingCode = 598, Alpha3 = "URY", ShortName = "Uruguay", LongName = "The Oriental Republic of Uruguay", CurrencyCode = "UYU" + , Languages = "es", UnofficialNames = "Uruguay|ウルグアイ|乌拉圭")] + [ResourceDescription(nameof(Ln.乌拉圭))] + UY = 858 + + , + + /// + /// 库拉索 + /// + [Country(CallingCode = 599, CallingSubCode = "9", Alpha3 = "CUW", ShortName = "Curaçao", LongName = "The Country of Curaçao", CurrencyCode = "ANG" + , Languages = "nl", UnofficialNames = "Curaçao|キュラソー島|库拉索")] + [ResourceDescription(nameof(Ln.库拉索))] + CW = 531 + + , + + /// + /// 荷兰加勒比区 + /// + [Country(CallingCode = 599, Alpha3 = "BES", ShortName = "Bonaire, Sint Eustatius and Saba", LongName = "Bonaire, Sint Eustatius and Saba" + , CurrencyCode = "USD", Languages = "nl|en" + , UnofficialNames = "Bonaire, Sint Eustatius and Saba|Caribbean Netherlands|Caribisch Nederland|ボネール、シント・ユースタティウスおよびサバ|荷兰加勒比区" + , IsPreferred = true)] + [ResourceDescription(nameof(Ln.荷兰加勒比区))] + BQ = 535 + + , + + /// + /// 东帝汶 + /// + [Country(CallingCode = 670, Alpha3 = "TLS", ShortName = "Timor-Leste", LongName = "The Democratic Republic of Timor-Leste", CurrencyCode = "IDR" + , Languages = "pt", UnofficialNames = "East Timor|Timor-Leste|Timor oriental|Timor Oriental|東ティモール|Oost-Timor|东帝汶")] + [ResourceDescription(nameof(Ln.东帝汶))] + TL = 626 + + , + + /// + /// 南极洲 + /// + [Country(CallingCode = 672, Alpha3 = "ATA", ShortName = "Antarctica", LongName = "Antarctica", CurrencyCode = "USD", Languages = "" + , UnofficialNames = "Antarctica|Antarktis|Antarctique|Antártida|南極|南极洲", IsPreferred = true)] + [ResourceDescription(nameof(Ln.南极洲))] + AQ = 010 + + , + + /// + /// 诺福克岛 + /// + [Country(CallingCode = 672, CallingSubCode = "3", Alpha3 = "NFK", ShortName = "Norfolk Island", LongName = "The Territory of Norfolk Island" + , CurrencyCode = "AUD", Languages = "en" + , UnofficialNames = "Norfolk Island|Norfolkinsel|Île de Norfolk|Isla de Norfolk|ノーフォーク島|Norfolkeiland|诺福克岛")] + [ResourceDescription(nameof(Ln.诺福克岛))] + NF = 574 + + , + + /// + /// 文莱 + /// + [Country(CallingCode = 673, Alpha3 = "BRN", ShortName = "Brunei Darussalam", LongName = "The Nation of Brunei, the Abode of Peace" + , CurrencyCode = "BND", Languages = "ms", UnofficialNames = "Brunei|ブルネイ・ダルサラーム|文莱")] + [ResourceDescription(nameof(Ln.文莱))] + BN = 096 + + , + + /// + /// 瑙鲁 + /// + [Country(CallingCode = 674, Alpha3 = "NRU", ShortName = "Nauru", LongName = "The Republic of Nauru", CurrencyCode = "AUD", Languages = "en|na" + , UnofficialNames = "Nauru|ナウル|瑙鲁")] + [ResourceDescription(nameof(Ln.瑙鲁))] + NR = 520 + + , + + /// + /// 巴布亚新几内亚 + /// + [Country(CallingCode = 675, Alpha3 = "PNG", ShortName = "Papua New Guinea", LongName = "The Independent State of Papua New Guinea" + , CurrencyCode = "PGK", Languages = "en" + , UnofficialNames = "Papua New Guinea|Papua-Neuguinea|Papouasie Nouvelle-Guinée|Papúa Nueva Guinea|パプアニューギニア|Papoea-Nieuw-Guinea|巴布亚新几内亚")] + [ResourceDescription(nameof(Ln.巴布亚新几内亚))] + PG = 598 + + , + + /// + /// 汤加 + /// + [Country(CallingCode = 676, Alpha3 = "TON", ShortName = "Tonga", LongName = "The Kingdom of Tonga", CurrencyCode = "TOP", Languages = "en|to" + , UnofficialNames = "Tonga|トンガ|汤加")] + [ResourceDescription(nameof(Ln.汤加))] + TO = 776 + + , + + /// + /// 所罗门群岛 + /// + [Country(CallingCode = 677, Alpha3 = "SLB", ShortName = "Solomon Islands", LongName = "The Solomon Islands", CurrencyCode = "SBD" + , Languages = "en", UnofficialNames = "Solomon Islands|Salomonen|Îles Salomon|Islas Salomón|ソロモン諸島|Salomonseilanden|所罗门群岛")] + [ResourceDescription(nameof(Ln.所罗门群岛))] + SB = 090 + + , + + /// + /// 瓦努阿图 + /// + [Country(CallingCode = 678, Alpha3 = "VUT", ShortName = "Vanuatu", LongName = "The Republic of Vanuatu", CurrencyCode = "VUV" + , Languages = "bi|en|fr", UnofficialNames = "Vanuatu|バヌアツ|瓦努阿图")] + [ResourceDescription(nameof(Ln.瓦努阿图))] + VU = 548 + + , + + /// + /// 斐济 + /// + [Country(CallingCode = 679, Alpha3 = "FJI", ShortName = "Fiji", LongName = "The Republic of Fiji", CurrencyCode = "FJD", Languages = "en|fj|hi|ur" + , UnofficialNames = "Fiji|Fidschi|Fidji|フィジー|斐济")] + [ResourceDescription(nameof(Ln.斐济))] + FJ = 242 + + , + + /// + /// 帕劳 + /// + [Country(CallingCode = 680, Alpha3 = "PLW", ShortName = "Palau", LongName = "The Republic of Palau", CurrencyCode = "USD", Languages = "en" + , UnofficialNames = "Palau|パラオ|帕劳")] + [ResourceDescription(nameof(Ln.帕劳))] + PW = 585 + + , + + /// + /// 瓦利斯和富图纳 + /// + [Country(CallingCode = 681, Alpha3 = "WLF", ShortName = "Wallis and Futuna", LongName = "The Territory of the Wallis and Futuna Islands" + , CurrencyCode = "XPF", Languages = "fr" + , UnofficialNames = "Wallis and Futuna|Wallis und Futuna|Wallis et Futuna|Wallis y Futuna|ウォリス・フツナ|Wallis en Futuna|瓦利斯和富图纳")] + [ResourceDescription(nameof(Ln.瓦利斯和富图纳))] + WF = 876 + + , + + /// + /// 库克群岛 + /// + [Country(CallingCode = 682, Alpha3 = "COK", ShortName = "Cook Islands", LongName = "The Cook Islands", CurrencyCode = "NZD", Languages = "en" + , UnofficialNames = "Cook Islands|Cookinseln|Îles Cook|Islas Cook|クック諸島|Cookeilanden|库克群岛")] + [ResourceDescription(nameof(Ln.库克群岛))] + CK = 184 + + , + + /// + /// 纽埃 + /// + [Country(CallingCode = 683, Alpha3 = "NIU", ShortName = "Niue", LongName = "Niue", CurrencyCode = "NZD", Languages = "en" + , UnofficialNames = "Niue|ニウエ|纽埃")] + [ResourceDescription(nameof(Ln.纽埃))] + NU = 570 + + , + + /// + /// 萨摩亚 + /// + [Country(CallingCode = 685, Alpha3 = "WSM", ShortName = "Samoa", LongName = "The Independent State of Samoa", CurrencyCode = "WST" + , Languages = "sm|en", UnofficialNames = "Samoa|サモア|萨摩亚")] + [ResourceDescription(nameof(Ln.萨摩亚))] + WS = 882 + + , + + /// + /// 基里巴斯 + /// + [Country(CallingCode = 686, Alpha3 = "KIR", ShortName = "Kiribati", LongName = "The Republic of Kiribati", CurrencyCode = "AUD", Languages = "en" + , UnofficialNames = "Kiribati|キリバス|基里巴斯")] + [ResourceDescription(nameof(Ln.基里巴斯))] + KI = 296 + + , + + /// + /// 新喀里多尼亚 + /// + [Country(CallingCode = 687, Alpha3 = "NCL", ShortName = "New Caledonia", LongName = "New Caledonia", CurrencyCode = "XPF", Languages = "fr" + , UnofficialNames = "New Caledonia|Neukaledonien|Nouvelle-Calédonie|Nueva Caledonia|ニューカレドニア|Nieuw-Caledonië|新喀里多尼亚")] + [ResourceDescription(nameof(Ln.新喀里多尼亚))] + NC = 540 + + , + + /// + /// 图瓦卢 + /// + [Country(CallingCode = 688, Alpha3 = "TUV", ShortName = "Tuvalu", LongName = "Tuvalu", CurrencyCode = "AUD", Languages = "en" + , UnofficialNames = "Tuvalu|ツバル|图瓦卢")] + [ResourceDescription(nameof(Ln.图瓦卢))] + TV = 798 + + , + + /// + /// 法属波利尼西亚 + /// + [Country(CallingCode = 689, Alpha3 = "PYF", ShortName = "French Polynesia", LongName = "French Polynesia", CurrencyCode = "XPF", Languages = "fr" + , UnofficialNames = "French Polynesia|Französisch-Polynesien|Polynésie Française|Polinesia Francesa|フランス領ポリネシア|Frans-Polynesië|法属波利尼西亚")] + [ResourceDescription(nameof(Ln.法属波利尼西亚))] + PF = 258 + + , + + /// + /// 托克劳 + /// + [Country(CallingCode = 690, Alpha3 = "TKL", ShortName = "Tokelau", LongName = "Tokelau", CurrencyCode = "NZD", Languages = "en" + , UnofficialNames = "Tokelau|Îles Tokelau|Islas Tokelau|トケラウ|托克劳")] + [ResourceDescription(nameof(Ln.托克劳))] + TK = 772 + + , + + /// + /// 密克罗尼西亚联邦 + /// + [Country(CallingCode = 691, Alpha3 = "FSM", ShortName = "Micronesia (Federated States of)", LongName = "The Federated States of Micronesia" + , CurrencyCode = "USD", Languages = "en", UnofficialNames = "Micronesia|Mikronesien|Micronésie|ミクロネシア連邦|Micronesië|密克罗尼西亚联邦")] + [ResourceDescription(nameof(Ln.密克罗尼西亚联邦))] + FM = 583 + + , + + /// + /// 马绍尔群岛 + /// + [Country(CallingCode = 692, Alpha3 = "MHL", ShortName = "Marshall Islands", LongName = "The Republic of the Marshall Islands" + , CurrencyCode = "USD", Languages = "en|mh" + , UnofficialNames = "Marshall Islands|Marshallinseln|Îles Marshall|Islas Marshall|マーシャル諸島|Marshalleilanden|马绍尔群岛")] + [ResourceDescription(nameof(Ln.马绍尔群岛))] + MH = 584 + + , + + /// + /// 朝鲜 + /// + [Country(CallingCode = 850, Alpha3 = "PRK", ShortName = "Korea (Democratic People's Republic of)" + , LongName = "The Democratic People's Republic of Korea", CurrencyCode = "KPW", Languages = "ko" + , UnofficialNames + = "Korea (North)|North Korea|Nordkorea|Corée du Nord|Corea del Norte|朝鮮民主主義人民共和国|Noord-Korea|Korea Democratic People's Republic|Korea (Democratic People s Republic of)|朝鲜")] + [ResourceDescription(nameof(Ln.朝鲜))] + KP = 408 + + , + + /// + /// 香港 + /// + [Country(CallingCode = 852, Alpha3 = "HKG", ShortName = "Hong Kong", LongName = "The Hong Kong Special Administrative Region of China" + , CurrencyCode = "HKD", Languages = "en|zh", UnofficialNames = "Hong Kong|香港|Hongkong")] + [ResourceDescription(nameof(Ln.香港))] + HK = 344 + + , + + /// + /// 澳门 + /// + [Country(CallingCode = 853, Alpha3 = "MAC", ShortName = "Macao", LongName = "The Macao Special Administrative Region of China" + , CurrencyCode = "MOP", Languages = "zh|pt", UnofficialNames = "Macao|Macau|マカオ|澳门")] + [ResourceDescription(nameof(Ln.澳门))] + MO = 446 + + , + + /// + /// 柬埔寨 + /// + [Country(CallingCode = 855, Alpha3 = "KHM", ShortName = "Cambodia", LongName = "The Kingdom of Cambodia", CurrencyCode = "KHR", Languages = "km" + , UnofficialNames = "Cambodia|Kambodscha|Cambodge|Camboya|カンボジア|Cambodja|柬埔寨")] + [ResourceDescription(nameof(Ln.柬埔寨))] + KH = 116 + + , + + /// + /// 老挝 + /// + [Country(CallingCode = 856, Alpha3 = "LAO", ShortName = "Lao People's Democratic Republic", LongName = "The Lao People's Democratic Republic" + , CurrencyCode = "LAK", Languages = "lo", UnofficialNames = "Laos|ラオス人民民主共和国|Lao People s Democratic Republic|老挝")] + [ResourceDescription(nameof(Ln.老挝))] + LA = 418 + + , + + /// + /// 孟加拉国 + /// + [Country(CallingCode = 880, Alpha3 = "BGD", ShortName = "Bangladesh", LongName = "The People's Republic of Bangladesh", CurrencyCode = "BDT" + , Languages = "bn", UnofficialNames = "Bangladesh|Bangladesch|バングラデシュ|孟加拉国")] + [ResourceDescription(nameof(Ln.孟加拉国))] + BD = 050 + + , + + /// + /// 台湾 + /// + [Country(CallingCode = 886, Alpha3 = "TWN", ShortName = "Taiwan, Province of China", LongName = "Taiwan, Province of China", CurrencyCode = "TWD" + , Languages = "zh", UnofficialNames = "Taiwan|Taiwán|台灣|臺灣|台湾")] + [ResourceDescription(nameof(Ln.台湾))] + TW = 158 + + , + + /// + /// 马尔代夫 + /// + [Country(CallingCode = 960, Alpha3 = "MDV", ShortName = "Maldives", LongName = "The Republic of Maldives", CurrencyCode = "MVR", Languages = "dv" + , UnofficialNames = "Maldives|Malediven|Maldivas|モルディブ|Maldiven|马尔代夫")] + [ResourceDescription(nameof(Ln.马尔代夫))] + MV = 462 + + , + + /// + /// 黎巴嫩 + /// + [Country(CallingCode = 961, Alpha3 = "LBN", ShortName = "Lebanon", LongName = "The Lebanese Republic", CurrencyCode = "LBP", Languages = "ar|fr" + , UnofficialNames = "Lebanon|لبنان|Libanon|Liban|Líbano|レバノン|黎巴嫩")] + [ResourceDescription(nameof(Ln.黎巴嫩))] + LB = 422 + + , + + /// + /// 约旦 + /// + [Country(CallingCode = 962, Alpha3 = "JOR", ShortName = "Jordan", LongName = "The Hashemite Kingdom of Jordan", CurrencyCode = "JOD" + , Languages = "ar", UnofficialNames = "Jordan|الأردن|Jordanien|Jordanie|Jordania|ヨルダン|Jordanië|约旦")] + [ResourceDescription(nameof(Ln.约旦))] + JO = 400 + + , + + /// + /// 叙利亚 + /// + [Country(CallingCode = 963, Alpha3 = "SYR", ShortName = "Syrian Arab Republic", LongName = "The Syrian Arab Republic", CurrencyCode = "SYP" + , Languages = "ar", UnofficialNames = "Syria|سوريا|سورية|Syrien|Syrie|Siria|シリア・アラブ共和国|Syrië|叙利亚")] + [ResourceDescription(nameof(Ln.叙利亚))] + SY = 760 + + , + + /// + /// 伊拉克 + /// + [Country(CallingCode = 964, Alpha3 = "IRQ", ShortName = "Iraq", LongName = "The Republic of Iraq", CurrencyCode = "IQD", Languages = "ar" + , UnofficialNames = "Iraq|العراق|Irak|イラク|伊拉克")] + [ResourceDescription(nameof(Ln.伊拉克))] + IQ = 368 + + , + + /// + /// 科威特 + /// + [Country(CallingCode = 965, Alpha3 = "KWT", ShortName = "Kuwait", LongName = "The State of Kuwait", CurrencyCode = "KWD", Languages = "ar" + , UnofficialNames = "Kuwait|الكويت|Koweït|クウェート|Koeweit|科威特")] + [ResourceDescription(nameof(Ln.科威特))] + KW = 414 + + , + + /// + /// 沙特阿拉伯 + /// + [Country(CallingCode = 966, Alpha3 = "SAU", ShortName = "Saudi Arabia", LongName = "The Kingdom of Saudi Arabia", CurrencyCode = "SAR" + , Languages = "ar" + , UnofficialNames + = "Saudi Arabia|Kingdom of Saudi Arabia|السعودية|Saudi-Arabien|Arabie Saoudite|Arabia Saudí|サウジアラビア|Saoedi-Arabië|沙特阿拉伯")] + [ResourceDescription(nameof(Ln.沙特阿拉伯))] + SA = 682 + + , + + /// + /// 也门 + /// + [Country(CallingCode = 967, Alpha3 = "YEM", ShortName = "Yemen", LongName = "The Republic of Yemen", CurrencyCode = "YER", Languages = "ar" + , UnofficialNames = "Yemen|اليمن|Jemen|Yémen|イエメン|也门")] + [ResourceDescription(nameof(Ln.也门))] + YE = 887 + + , + + /// + /// 阿曼 + /// + [Country(CallingCode = 968, Alpha3 = "OMN", ShortName = "Oman", LongName = "The Sultanate of Oman", CurrencyCode = "OMR", Languages = "ar" + , UnofficialNames = "Oman|عمان|Omán|オマーン|阿曼")] + [ResourceDescription(nameof(Ln.阿曼))] + OM = 512 + + , + + /// + /// 巴勒斯坦 + /// + [Country(CallingCode = 970, Alpha3 = "PSE", ShortName = "Palestine, State of", LongName = "The State of Palestine", CurrencyCode = "ILS" + , Languages = "ar|he|en" + , UnofficialNames + = "Palestine|فلسطين|Palästina|Palestina|the Occupied Palestinian Territory|パレスチナ|Palestijnse gebieden|Palestinian Territory Occupied|Palestinian Authority|巴勒斯坦")] + [ResourceDescription(nameof(Ln.巴勒斯坦))] + PS = 275 + + , + + /// + /// 阿联酋 + /// + [Country(CallingCode = 971, Alpha3 = "ARE", ShortName = "United Arab Emirates", LongName = "The United Arab Emirates", CurrencyCode = "AED" + , Languages = "ar" + , UnofficialNames + = "United Arab Emirates|الإمارات العربية المتحدة|Vereinigte Arabische Emirate|Émirats Arabes Unis|Emiratos Árabes Unidos|アラブ首長国連邦|Verenigde Arabische Emiraten|阿联酋")] + [ResourceDescription(nameof(Ln.阿联酋))] + AE = 784 + + , + + /// + /// 以色列 + /// + [Country(CallingCode = 972, Alpha3 = "ISR", ShortName = "Israel", LongName = "The State of Israel", CurrencyCode = "ILS", Languages = "he|ar" + , UnofficialNames = "Israel|Israël|イスラエル|以色列")] + [ResourceDescription(nameof(Ln.以色列))] + IL = 376 + + , + + /// + /// 巴林 + /// + [Country(CallingCode = 973, Alpha3 = "BHR", ShortName = "Bahrain", LongName = "The Kingdom of Bahrain", CurrencyCode = "BHD", Languages = "ar" + , UnofficialNames = "Bahrain|البحرين|Bahreïn|Bahrein|バーレーン|巴林")] + [ResourceDescription(nameof(Ln.巴林))] + BH = 048 + + , + + /// + /// 卡塔尔 + /// + [Country(CallingCode = 974, Alpha3 = "QAT", ShortName = "Qatar", LongName = "The State of Qatar", CurrencyCode = "QAR", Languages = "ar" + , UnofficialNames = "Qatar|قطر|Katar|カタール|卡塔尔")] + [ResourceDescription(nameof(Ln.卡塔尔))] + QA = 634 + + , + + /// + /// 不丹 + /// + [Country(CallingCode = 975, Alpha3 = "BTN", ShortName = "Bhutan", LongName = "The Kingdom of Bhutan", CurrencyCode = "BTN", Languages = "dz" + , UnofficialNames = "Bhutan|Bhoutan|Bután|ブータン|不丹")] + [ResourceDescription(nameof(Ln.不丹))] + BT = 064 + + , + + /// + /// 蒙古 + /// + [Country(CallingCode = 976, Alpha3 = "MNG", ShortName = "Mongolia", LongName = "Mongolia", CurrencyCode = "MNT", Languages = "mn" + , UnofficialNames = "Mongolia|Mongolei|Mongolie|モンゴル|Mongolië|蒙古")] + [ResourceDescription(nameof(Ln.蒙古))] + MN = 496 + + , + + /// + /// 尼泊尔 + /// + [Country(CallingCode = 977, Alpha3 = "NPL", ShortName = "Nepal", LongName = "The Federal Democratic Republic of Nepal", CurrencyCode = "NPR" + , Languages = "ne|mai|bho|new|urd", UnofficialNames = "Nepal|Népal|the Federal Democratic Republic of Nepal|ネパール|尼泊尔")] + [ResourceDescription(nameof(Ln.尼泊尔))] + NP = 524 + + , + + /// + /// 塔吉克斯坦 + /// + [Country(CallingCode = 992, Alpha3 = "TJK", ShortName = "Tajikistan", LongName = "The Republic of Tajikistan", CurrencyCode = "TJS" + , Languages = "tg|ru", UnofficialNames = "Tajikistan|Tadschikistan|Tayikistán|タジキスタン|Tadzjikistan|Tajikstan|塔吉克斯坦")] + [ResourceDescription(nameof(Ln.塔吉克斯坦))] + TJ = 762 + + , + + /// + /// 土库曼斯坦 + /// + [Country(CallingCode = 993, Alpha3 = "TKM", ShortName = "Turkmenistan", LongName = "Turkmenistan", CurrencyCode = "TMT", Languages = "tk|ru" + , UnofficialNames = "Turkmenistan|Turkménistan|Turkmenistán|トルクメニスタン|Turkmenia|土库曼斯坦")] + [ResourceDescription(nameof(Ln.土库曼斯坦))] + TM = 795 + + , + + /// + /// 阿塞拜疆 + /// + [Country(CallingCode = 994, Alpha3 = "AZE", ShortName = "Azerbaijan", LongName = "The Republic of Azerbaijan", CurrencyCode = "AZN" + , Languages = "az|hy", UnofficialNames = "Azerbaijan|Aserbaidschan|Azerbaïdjan|Azerbaiyán|アゼルバイジャン|Azerbeidzjan|阿塞拜疆")] + [ResourceDescription(nameof(Ln.阿塞拜疆))] + AZ = 031 + + , + + /// + /// 格鲁吉亚 + /// + [Country(CallingCode = 995, Alpha3 = "GEO", ShortName = "Georgia", LongName = "Georgia", CurrencyCode = "GEL", Languages = "ka" + , UnofficialNames = "Georgia|Georgien|Géorgie|グルジア|Georgië|格鲁吉亚")] + [ResourceDescription(nameof(Ln.格鲁吉亚))] + GE = 268 + + , + + /// + /// 吉尔吉斯斯坦 + /// + [Country(CallingCode = 996, Alpha3 = "KGZ", ShortName = "Kyrgyzstan", LongName = "The Kyrgyz Republic", CurrencyCode = "KGS", Languages = "ky|ru" + , UnofficialNames = "Kyrgyzstan|Kirgisistan|Kirghizistan|Kirguizistán|キルギス|Kirgizië|Kyrgzstan|吉尔吉斯斯坦")] + [ResourceDescription(nameof(Ln.吉尔吉斯斯坦))] + KG = 417 + + , + + /// + /// 乌兹别克斯坦 + /// + [Country(CallingCode = 998, Alpha3 = "UZB", ShortName = "Uzbekistan", LongName = "The Republic of Uzbekistan", CurrencyCode = "UZS" + , Languages = "uz|ru", UnofficialNames = "Uzbekistan|Usbekistan|Ouzbékistan|Uzbekistán|ウズベキスタン|Oezbekistan|乌兹别克斯坦")] + [ResourceDescription(nameof(Ln.乌兹别克斯坦))] + UZ = 860 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Educations.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Educations.cs new file mode 100644 index 00000000..cf88e935 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Educations.cs @@ -0,0 +1,78 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 学历 +/// +[Export] +public enum Educations +{ + /// + /// 小学 + /// + [ResourceDescription(nameof(Ln.小学))] + Primary = 1 + + , + + /// + /// 初中 + /// + [ResourceDescription(nameof(Ln.初中))] + Junior = 2 + + , + + /// + /// 高中 + /// + [ResourceDescription(nameof(Ln.高中))] + Higher = 3 + + , + + /// + /// 中专 + /// + [ResourceDescription(nameof(Ln.中专))] + Technical = 4 + + , + + /// + /// 大专 + /// + [ResourceDescription(nameof(Ln.大专))] + College = 5 + + , + + /// + /// 本科 + /// + [ResourceDescription(nameof(Ln.本科))] + Bachelor = 6 + + , + + /// + /// 硕士 + /// + [ResourceDescription(nameof(Ln.硕士))] + Master = 7 + + , + + /// + /// 博士 + /// + [ResourceDescription(nameof(Ln.博士))] + Doctor = 8 + + , + + /// + /// 博士后 + /// + [ResourceDescription(nameof(Ln.博士后))] + Post = 9 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ErrorCodes.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ErrorCodes.cs new file mode 100644 index 00000000..49ff76f1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ErrorCodes.cs @@ -0,0 +1,54 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 错误码 +/// +[Export] +public enum ErrorCodes +{ + /// + /// 成功 + /// + [ResourceDescription(nameof(Ln.成功))] + Succeed = 0 + + , + + /// + /// 未处理异常 + /// + [ResourceDescription(nameof(Ln.未处理异常))] + Unhandled = 9000 + + , + + /// + /// 结果非预期 + /// + [ResourceDescription(nameof(Ln.结果非预期))] + Unexpected = 9100 + + , + + /// + /// 无效输入 + /// + [ResourceDescription(nameof(Ln.无效输入))] + InvalidInput = 9200 + + , + + /// + /// 无效操作 + /// + [ResourceDescription(nameof(Ln.无效操作))] + InvalidOperation = 9300 + + , + + /// + /// 外部错误 + /// + [ResourceDescription(nameof(Ln.外部错误))] + ExternalError = 9400 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/FreeSqlInitMethods.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/FreeSqlInitMethods.cs new file mode 100644 index 00000000..7cc4fc79 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/FreeSqlInitMethods.cs @@ -0,0 +1,37 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// FreeSql 初始化方式 +/// +[Flags] +public enum FreeSqlInitMethods +{ + /// + /// 无 + /// + None = 0 + + , + + /// + /// 同步数据库结构 + /// + [ResourceDescription(nameof(Ln.同步数据库结构))] + SyncStructure = 1 + + , + + /// + /// 插入种子数据 + /// + [ResourceDescription(nameof(Ln.插入种子数据))] + InsertSeedData = 1 << 1 + + , + + /// + /// 比较数据库结构 + /// + [ResourceDescription(nameof(Ln.比较数据库结构))] + CompareStructure = 1 << 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Indicates.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Indicates.cs new file mode 100644 index 00000000..0eecef3f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Indicates.cs @@ -0,0 +1,41 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 状态表示 +/// +[Export] +public enum Indicates +{ + /// + /// 信息 + /// + Info = 1 + + , + + /// + /// 主要 + /// + Primary = 2 + + , + + /// + /// 警告 + /// + Warning = 3 + + , + + /// + /// 成功 + /// + Success = 4 + + , + + /// + /// 危险 + /// + Danger = 5 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/LogLevels.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/LogLevels.cs new file mode 100644 index 00000000..c1d221e6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/LogLevels.cs @@ -0,0 +1,60 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 日志等级 +/// +[Export] +public enum LogLevels +{ + /// + /// 跟踪 + /// + [Display(Name = "[gray]TCE[/]", ShortName = "TCE")] + [ResourceDescription(nameof(Ln.跟踪))] + Trace = 0 + + , + + /// + /// 调试 + /// + [Display(Name = "[gray]DBG[/]", ShortName = "DBG")] + [ResourceDescription(nameof(Ln.调试))] + Debug = 1 + + , + + /// + /// 信息 + /// + [Display(Name = "[green]INF[/]", ShortName = "INF")] + [ResourceDescription(nameof(Ln.信息))] + Information = 2 + + , + + /// + /// 警告 + /// + [Display(Name = "[yellow]WRN[/]", ShortName = "WRN")] + [ResourceDescription(nameof(Ln.警告))] + Warning = 3 + + , + + /// + /// 错误 + /// + [Display(Name = "[red]ERR[/]", ShortName = "ERR")] + [ResourceDescription(nameof(Ln.错误))] + Error = 4 + + , + + /// + /// 宕机 + /// + [Display(Name = "[red]CTL[/]", ShortName = "CTL")] + [ResourceDescription(nameof(Ln.宕机))] + Critical = 5 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/MarriageStatues.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/MarriageStatues.cs new file mode 100644 index 00000000..0153174b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/MarriageStatues.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 婚姻状况 +/// +[Export] +public enum MarriageStatues +{ + /// + /// 未婚 + /// + [ResourceDescription(nameof(Ln.未婚))] + Unmarried = 1 + + , + + /// + /// 已婚 + /// + [ResourceDescription(nameof(Ln.已婚))] + Married = 2 + + , + + /// + /// 离异 + /// + [ResourceDescription(nameof(Ln.离异))] + Divorced = 3 + + , + + /// + /// 丧偶 + /// + [ResourceDescription(nameof(Ln.丧偶))] + Bereft = 4 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ModuleTypes.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ModuleTypes.cs new file mode 100644 index 00000000..1dbe39f5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/ModuleTypes.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 模块类型 +/// +[Export] +public enum ModuleTypes +{ + /// + /// 系统模块 + /// + [ResourceDescription(nameof(Ln.系统模块))] + SysComponent = 1 + + , + + /// + /// 管理模块 + /// + [ResourceDescription(nameof(Ln.管理模块))] + AdmServer = 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Nations.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Nations.cs new file mode 100644 index 00000000..9f794bb1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Nations.cs @@ -0,0 +1,457 @@ +// ReSharper disable IdentifierTypo +// ReSharper disable UnusedMember.Global + +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 民族 +/// +[Export] +public enum Nations +{ + /// + /// 汉族 + /// + [ResourceDescription(nameof(Ln.汉族))] + Han = 1 + + , + + /// + /// 壮族 + /// + [ResourceDescription(nameof(Ln.壮族))] + Zhuang = 2 + + , + + /// + /// 满族 + /// + [ResourceDescription(nameof(Ln.满族))] + Manchu = 3 + + , + + /// + /// 回族 + /// + [ResourceDescription(nameof(Ln.回族))] + Hui = 4 + + , + + /// + /// 苗族 + /// + [ResourceDescription(nameof(Ln.苗族))] + Miao = 5 + + , + + /// + /// 维吾尔族 + /// + [ResourceDescription(nameof(Ln.维吾尔族))] + Uyghur = 6 + + , + + /// + /// 土家族 + /// + [ResourceDescription(nameof(Ln.土家族))] + Tujia = 7 + + , + + /// + /// 彝族 + /// + [ResourceDescription(nameof(Ln.彝族))] + Yi = 8 + + , + + /// + /// 蒙古族 + /// + [ResourceDescription(nameof(Ln.蒙古族))] + Mongolian = 9 + + , + + /// + /// 藏族 + /// + [ResourceDescription(nameof(Ln.藏族))] + Tibetan = 10 + + , + + /// + /// 布依族 + /// + [ResourceDescription(nameof(Ln.布依族))] + Buyei = 11 + + , + + /// + /// 侗族 + /// + [ResourceDescription(nameof(Ln.侗族))] + Dong = 12 + + , + + /// + /// 瑶族 + /// + [ResourceDescription(nameof(Ln.瑶族))] + Yao = 13 + + , + + /// + /// 朝鲜族 + /// + [ResourceDescription(nameof(Ln.朝鲜族))] + Korean = 14 + + , + + /// + /// 白族 + /// + [ResourceDescription(nameof(Ln.白族))] + Bai = 15 + + , + + /// + /// 哈尼族 + /// + [ResourceDescription(nameof(Ln.哈尼族))] + Hani = 16 + + , + + /// + /// 哈萨克族 + /// + [ResourceDescription(nameof(Ln.哈萨克族))] + Kazakh = 17 + + , + + /// + /// 黎族 + /// + [ResourceDescription(nameof(Ln.黎族))] + Li = 18 + + , + + /// + /// 傣族 + /// + [ResourceDescription(nameof(Ln.傣族))] + Dai = 19 + + , + + /// + /// 畲族 + /// + [ResourceDescription(nameof(Ln.畲族))] + She = 20 + + , + + /// + /// 傈僳族 + /// + [ResourceDescription(nameof(Ln.傈僳族))] + Lisu = 21 + + , + + /// + /// 仡佬族 + /// + [ResourceDescription(nameof(Ln.仡佬族))] + Gelao = 22 + + , + + /// + /// 东乡族 + /// + [ResourceDescription(nameof(Ln.东乡族))] + Dongxiang = 23 + + , + + /// + /// 高山族 + /// + [ResourceDescription(nameof(Ln.高山族))] + Gaoshan = 24 + + , + + /// + /// 拉祜族族 + /// + [ResourceDescription(nameof(Ln.拉祜族族))] + Lahu = 25 + + , + + /// + /// 水族 + /// + [ResourceDescription(nameof(Ln.水族))] + Shui = 26 + + , + + /// + /// 佤族 + /// + [ResourceDescription(nameof(Ln.佤族))] + Va = 27 + + , + + /// + /// 纳西族 + /// + [ResourceDescription(nameof(Ln.纳西族))] + Nakhi = 28 + + , + + /// + /// 羌族 + /// + [ResourceDescription(nameof(Ln.羌族))] + Qiang = 29 + + , + + /// + /// 土族 + /// + [ResourceDescription(nameof(Ln.土族))] + Monguor = 30 + + , + + /// + /// 仫佬族 + /// + [ResourceDescription(nameof(Ln.仫佬族))] + Mulao = 31 + + , + + /// + /// 锡伯族 + /// + [ResourceDescription(nameof(Ln.锡伯族))] + Xibe = 32 + + , + + /// + /// 柯尔克孜族 + /// + [ResourceDescription(nameof(Ln.柯尔克孜族))] + Kyrgyz = 33 + + , + + /// + /// 达斡尔族 + /// + [ResourceDescription(nameof(Ln.达斡尔族))] + Daur = 34 + + , + + /// + /// 景颇族 + /// + [ResourceDescription(nameof(Ln.景颇族))] + Jingpo = 35 + + , + + /// + /// 毛南族 + /// + [ResourceDescription(nameof(Ln.毛南族))] + Maonan = 36 + + , + + /// + /// 撒拉族 + /// + [ResourceDescription(nameof(Ln.撒拉族))] + Salar = 37 + + , + + /// + /// 布朗族 + /// + [ResourceDescription(nameof(Ln.布朗族))] + Blang = 38 + + , + + /// + /// 塔吉克族 + /// + [ResourceDescription(nameof(Ln.塔吉克族))] + Tajik = 39 + + , + + /// + /// 阿昌族 + /// + [ResourceDescription(nameof(Ln.阿昌族))] + Achang = 40 + + , + + /// + /// 普米族 + /// + [ResourceDescription(nameof(Ln.普米族))] + Pumi = 41 + + , + + /// + /// 鄂温克族 + /// + [ResourceDescription(nameof(Ln.鄂温克族))] + Evenk = 42 + + , + + /// + /// 怒族 + /// + [ResourceDescription(nameof(Ln.怒族))] + Nu = 43 + + , + + /// + /// 京族 + /// + [ResourceDescription(nameof(Ln.京族))] + Kinh = 44 + + , + + /// + /// 基诺族 + /// + [ResourceDescription(nameof(Ln.基诺族))] + Jino = 45 + + , + + /// + /// 德昂族 + /// + [ResourceDescription(nameof(Ln.德昂族))] + Deang = 46 + + , + + /// + /// 保安族 + /// + [ResourceDescription(nameof(Ln.保安族))] + Bonan = 47 + + , + + /// + /// 俄罗斯族 + /// + [ResourceDescription(nameof(Ln.俄罗斯族))] + Russian = 48 + + , + + /// + /// 裕固族 + /// + [ResourceDescription(nameof(Ln.裕固族))] + Yughur = 49 + + , + + /// + /// 乌孜别克族 + /// + [ResourceDescription(nameof(Ln.乌孜别克族))] + Uzbek = 50 + + , + + /// + /// 门巴族 + /// + [ResourceDescription(nameof(Ln.门巴族))] + Monpa = 51 + + , + + /// + /// 鄂伦春族 + /// + [ResourceDescription(nameof(Ln.鄂伦春族))] + Oroqen = 52 + + , + + /// + /// 独龙族 + /// + [ResourceDescription(nameof(Ln.独龙族))] + Derung = 53 + + , + + /// + /// 塔塔尔族 + /// + [ResourceDescription(nameof(Ln.塔塔尔族))] + Tatar = 54 + + , + + /// + /// 赫哲族 + /// + [ResourceDescription(nameof(Ln.赫哲族))] + Nanai = 55 + + , + + /// + /// 珞巴族 + /// + [ResourceDescription(nameof(Ln.珞巴族))] + Lhoba = 56 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Orders.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Orders.cs new file mode 100644 index 00000000..6ba56128 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Orders.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 排序方式 +/// +[Export] +public enum Orders +{ + /// + /// 顺序排序 + /// + [ResourceDescription(nameof(Ln.顺序排序))] + Ascending = 1 + + , + + /// + /// 倒序排序 + /// + [ResourceDescription(nameof(Ln.倒序排序))] + Descending = 2 + + , + + /// + /// 随机排序 + /// + [ResourceDescription(nameof(Ln.随机排序))] + Random = 3 + + , + + /// + /// 不排序 + /// + [ResourceDescription(nameof(Ln.不排序))] + None = 4 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/PoliticalStatues.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/PoliticalStatues.cs new file mode 100644 index 00000000..62b1be2e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/PoliticalStatues.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 政治面貌 +/// +[Export] +public enum PoliticalStatues +{ + /// + /// 中共党员 + /// + [ResourceDescription(nameof(Ln.中共党员))] + MemberOfCommunistParty = 1 + + , + + /// + /// 共青团员 + /// + [ResourceDescription(nameof(Ln.共青团员))] + MemberOfCommunistYouthLeague = 2 + + , + + /// + /// 群众 + /// + [ResourceDescription(nameof(Ln.群众))] + CommonPeople = 3 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Sexes.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Sexes.cs new file mode 100644 index 00000000..9abb48a6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Enums/Sexes.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.Infrastructure.Enums; + +/// +/// 性别 +/// +[Export] +public enum Sexes +{ + /// + /// 男 + /// + [ResourceDescription(nameof(Ln.男))] + Male = 1 + + , + + /// + /// 女 + /// + [ResourceDescription(nameof(Ln.女))] + Female = 2 + + , + + /// + /// 保密 + /// + [ResourceDescription(nameof(Ln.保密))] + Secrecy = 3 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminException.cs new file mode 100644 index 00000000..230e8906 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminException.cs @@ -0,0 +1,23 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// NetAdmin异常基类 +/// +#pragma warning disable RCS1194 +public abstract class NetAdminException(string message, Exception innerException) : Exception(message, innerException) +#pragma warning restore RCS1194 +{ + /// + /// Initializes a new instance of the class. + /// + protected NetAdminException(ErrorCodes code, string message = null, Exception innerException = null) // + : this(message, innerException) + { + Code = code; + } + + /// + /// 错误码 + /// + public ErrorCodes Code { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminExternalErrorException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminExternalErrorException.cs new file mode 100644 index 00000000..8532406e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminExternalErrorException.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 外部错误异常 +/// +/// +/// 外部接口调用未得到预期的结果 +/// +#pragma warning disable RCS1194 +public sealed class NetAdminExternalErrorException(string message, Exception innerException = null) + #pragma warning restore RCS1194 + : NetAdminException(ErrorCodes.ExternalError, message, innerException) { } \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminGetLockerException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminGetLockerException.cs new file mode 100644 index 00000000..fb8871b8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminGetLockerException.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 加锁失败异常 +/// +/// +/// 并发执行时锁竞争失败 +/// +#pragma warning disable RCS1194 +public sealed class NetAdminGetLockerException(string message = null) : NetAdminInvalidOperationException(message) { } +#pragma warning restore RCS1194 \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidInputException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidInputException.cs new file mode 100644 index 00000000..f3d79819 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidInputException.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 无效输入异常 +/// +/// +/// 参数格式错误、内容校验错误等 +/// +#pragma warning disable DesignedForInheritance, RCS1194 +public class NetAdminInvalidInputException(string message = null, Exception innerException = null) + #pragma warning restore RCS1194, DesignedForInheritance + : NetAdminException(ErrorCodes.InvalidInput, message, innerException) { } \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidOperationException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidOperationException.cs new file mode 100644 index 00000000..0f84c508 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminInvalidOperationException.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 无效操作异常 +/// +/// +/// 非正常的业务流程或逻辑 +/// +#pragma warning disable DesignedForInheritance, RCS1194 +public class NetAdminInvalidOperationException(string message, Exception innerException = null) + #pragma warning restore RCS1194, DesignedForInheritance + : NetAdminException(ErrorCodes.InvalidOperation, message, innerException) { } \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminUnexpectedException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminUnexpectedException.cs new file mode 100644 index 00000000..72830a6e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminUnexpectedException.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 非预期结果异常 +/// +/// +/// 运行结果是非预期的,例如事务失败回滚 +/// +#pragma warning disable RCS1194 +public sealed class NetAdminUnexpectedException(string message, Exception innerException = null) + #pragma warning restore RCS1194 + : NetAdminException(ErrorCodes.Unexpected, message, innerException); \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminValidateException.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminValidateException.cs new file mode 100644 index 00000000..79260bd5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Exceptions/NetAdminValidateException.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.Infrastructure.Exceptions; + +/// +/// 验证失败异常 +/// +/// +/// 手动调用模型验证方法抛出 +/// +#pragma warning disable RCS1194 +public sealed class NetAdminValidateException(Dictionary validateResults) + #pragma warning restore RCS1194 + : NetAdminInvalidInputException +{ + /// + /// 验证结果 + /// + public Dictionary ValidateResults { get; } = validateResults; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpContextExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..a2ec84be --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpContextExtensions.cs @@ -0,0 +1,33 @@ +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// HttpContext 扩展方法 +/// +public static class HttpContextExtensions +{ + /// + /// 获取客户端真实IP + /// + public static IPAddress GetRealIpAddress(this HttpContext me) + { + #pragma warning disable IDE0046 + if (me.Request.Headers.TryGetValue(Chars.FLG_HTTP_HEADER_KEY_X_FORWARDED_FOR, out var ips1) && + #pragma warning restore IDE0046 + IPAddress.TryParse(ips1.FirstOrDefault()?.Split(',').FirstOrDefault(), out var ip1)) { + return ip1; + } + + return me.Request.Headers.TryGetValue(Chars.FLG_HTTP_HEADER_KEY_X_REAL_IP, out var ips2) && + IPAddress.TryParse(ips2.FirstOrDefault()?.Split(',').FirstOrDefault(), out var ip2) + ? ip2 + : me.Connection.RemoteIpAddress; + } + + /// + /// 获取跟踪标识 + /// + public static Guid GetTraceId(this HttpContext me) + { + return me.TraceIdentifier.Md5(Encoding.UTF8).Guid(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestMessageExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestMessageExtensions.cs new file mode 100644 index 00000000..560dca9c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestMessageExtensions.cs @@ -0,0 +1,25 @@ +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// HttpRequestMessage 扩展方法 +/// +public static class HttpRequestMessageExtensions +{ + /// + /// 记录日志 + /// + public static async Task LogAsync(this HttpRequestMessage me, ILogger logger) + { + logger.Info($"HTTP Request {await me.BuildJsonAsync().ConfigureAwait(false)}"); + return me; + } + + /// + /// 将Http请求的Uri、Header、Body打包成Json字符串 + /// + private static async Task BuildJsonAsync(this HttpRequestMessage me) + { + var body = me?.Content == null ? null : await me.Content!.ReadAsStringAsync().ConfigureAwait(false); + return new { Uri = me?.RequestUri, Header = me?.ToString(), Body = body }.ToJson(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestPartExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestPartExtensions.cs new file mode 100644 index 00000000..7c7ff880 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpRequestPartExtensions.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// HttpRequestPart 扩展方法 +/// +public static class HttpRequestPartExtensions +{ + /// + /// 设置日志 + /// + public static HttpRequestPart SetLog(this HttpRequestPart me, ILogger logger, Func bodyHandle = null) + { + return me.OnRequesting(RequestHandleAsync).OnResponsing(ResponseHandleAsync).OnException(ExceptionHandleAsync); + + Task ExceptionHandleAsync(HttpClient _, HttpResponseMessage rsp, string errors) + { + return rsp.LogExceptionAsync(errors, logger, bodyHandle); + } + + Task ResponseHandleAsync(HttpClient _, HttpResponseMessage rsp) + { + return rsp.LogAsync(logger, bodyHandle); + } + + Task RequestHandleAsync(HttpClient _, HttpRequestMessage req) + { + return req.LogAsync(logger); + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpResponseMessageExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpResponseMessageExtensions.cs new file mode 100644 index 00000000..663c0f45 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/HttpResponseMessageExtensions.cs @@ -0,0 +1,50 @@ +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// HttpResponseMessage 扩展方法 +/// +public static class HttpResponseMessageExtensions +{ + /// + /// 记录日志 + /// + public static async Task LogAsync(this HttpResponseMessage me, ILogger logger, Func bodyPreHandle = null) + { + logger.Info($"HTTP Response {await me.BuildJsonAsync(bodyPreHandle).ConfigureAwait(false)}"); + } + + /// + /// 记录异常日志 + /// + public static async Task LogExceptionAsync(this HttpResponseMessage me, string errors, ILogger logger + , Func bodyHandle = null) + { + logger.Warn($"{errors}: {await me.BuildJsonAsync(bodyHandle).ConfigureAwait(false)}"); + } + + /// + /// 将Http请求的Uri、Header、Body打包成Json字符串 + /// + private static async Task BuildJsonAsync( // + this HttpResponseMessage me, Func bodyHandle = null) + { + var body = string.Empty; + try { + body = me?.Content is null ? null : await me.Content!.ReadAsStringAsync().ConfigureAwait(false); + } + catch (Exception ex) when (ex.Message.Contains("The character set provided in ContentType is invalid") && + ex.InnerException?.Message.Contains("is not a supported encoding name") == true) { + #pragma warning disable S2589 + var sr = me?.Content is null ? null : await me.Content!.ReadAsStreamAsync().ConfigureAwait(false); + if (sr != null) { + #pragma warning restore S2589 + await using var ms = new MemoryStream(); + await sr.CopyToAsync(ms).ConfigureAwait(false); + return Encoding.UTF8.GetString(ms.ToArray()); + } + } + + return new { Header = me?.ToString(), RequestHeader = me?.RequestMessage?.Headers, Body = bodyHandle is null ? body : bodyHandle(body) } + .Json(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/ObjectExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..0ad2047e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/ObjectExtensions.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// Object 扩展方法 +/// +public static class ObjectExtensions +{ + /// + /// object -> json + /// + public static string ToJson(this object me) + { + return me.Json(GlobalStatic.JsonSerializerOptions); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/StringExtensions.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/StringExtensions.cs new file mode 100644 index 00000000..df9cd3da --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Extensions/StringExtensions.cs @@ -0,0 +1,160 @@ +using System.Numerics; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; + +namespace NetAdmin.Infrastructure.Extensions; + +/// +/// String 扩展方法 +/// +public static class StringExtensions +{ + private const string _CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static readonly Regex _regexIpV4 = new(Chars.RGXL_IP_V4); + + /// + /// 将指定的输入字符串进行Base62解码 + /// + /// ArgumentException + public static string Base62Decode(this string me) + { + BigInteger result = 0; + + foreach (var index in me.Select(c => _CHARACTERS.IndexOf(c))) { + if (index < 0) { + throw new ArgumentException("Invalid character in Base62 string."); + } + + #pragma warning disable IDE0048, RCS1123, SA1407 + result = result * 62 + index; + #pragma warning restore SA1407, RCS1123, IDE0048 + } + + // Convert BigInteger back to byte array and then to string + var bytes = result.ToByteArray(); + + // Handle the sign bit + if (bytes[^1] == 0) { + Array.Resize(ref bytes, bytes.Length - 1); + } + + return Encoding.UTF8.GetString(bytes); + } + + /// + /// 将指定的输入字符串进行Base62编码 + /// + public static string Base62Encode(this string me) + { + // Convert string to byte array + var bytes = Encoding.UTF8.GetBytes(me); + + // Convert byte array to BigInteger for easier processing + var bigInteger = new BigInteger(bytes); + + if (bigInteger == 0) { + return _CHARACTERS[0].ToString(); + } + + var result = new StringBuilder(); + + while (bigInteger > 0) { + var remainder = (int)(bigInteger % 62); + bigInteger /= 62; + _ = result.Insert(0, _CHARACTERS[remainder]); + } + + return result.ToString(); + } + + /// + /// 解码避免转义的Base64 + /// + public static string Base64InUrlDecode(this string me) + { + return me.Replace("-", "+").Replace("_", "/"); + } + + /// + /// 编码避免转义的Base64 + /// + public static string Base64InUrlEncode(this string me) + { + return me.Replace("+", "-").Replace("/", "_"); + } + + /// + /// 计算Crc32 + /// + public static int Crc32(this string me) + { + return BitConverter.ToInt32(System.IO.Hashing.Crc32.Hash(Encoding.UTF8.GetBytes(me))); + } + + /// + /// 执行C#代码 + /// + public static Task ExecuteCSharpCodeAsync(this string me, Assembly[] assemblies, params string[] importNamespaces) + { + // 使用 Roslyn 编译并执行代码 + return CSharpScript.EvaluateAsync(me, ScriptOptions.Default.WithReferences(assemblies).WithImports(importNamespaces)); + } + + /// + /// 是否IPV4地址 + /// + public static bool IsIpV4(this string me) + { + return _regexIpV4.IsMatch(me); + } + + /// + /// object -> json + /// + public static T ToObject(this string me) + { + return me.Object(GlobalStatic.JsonSerializerOptions); + } + + /// + /// object -> json + /// + public static object ToObject(this string me, Type toType) + { + return me.Object(toType, GlobalStatic.JsonSerializerOptions); + } + + /// + /// 去掉前部字符串 + /// + public static string TrimPrefix(this string me, string clearStr) + { + return Regex.Replace(me, $"^{clearStr}", string.Empty); + } + + /// + /// 去掉尾部字符串 + /// + public static string TrimSuffix(this string me, string clearStr) + { + return Regex.Replace(me, $"{clearStr}$", string.Empty); + } + + /// + /// 去掉尾部字符串“Async” + /// + #pragma warning disable RCS1047, ASA002, VSTHRD200 + public static string TrimSuffixAsync(this string me) + #pragma warning restore VSTHRD200, ASA002, RCS1047 + { + return TrimSuffix(me, "Async"); + } + + /// + /// 去掉尾部字符串“Options” + /// + public static string TrimSuffixOptions(this string me) + { + return TrimSuffix(me, "Options"); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/GlobalStatic.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/GlobalStatic.cs new file mode 100644 index 00000000..7fe706db --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/GlobalStatic.cs @@ -0,0 +1,95 @@ +using DataType = FreeSql.DataType; + +namespace NetAdmin.Infrastructure; + +/// +/// 全局静态类 +/// +public static class GlobalStatic +{ + /// + /// 当前进程 + /// + public static readonly Process CurrentProcess = Process.GetCurrentProcess(); + + /// + /// 产品版本 + /// + public static readonly string ProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly()!.Location).ProductVersion; + + private static long _latestLogTime; + + /// + /// 调试模式 + /// + public static bool DebugMode => + #if DEBUG + true + #else + false + #endif + ; + + /// + /// 最后一次日志时间 + /// + public static DateTime LatestLogTime => LogCounterOff ? DateTime.MinValue : Volatile.Read(ref _latestLogTime).Time(); + + /// + /// 系统内部密钥 + /// + public static string SecretKey => "{6C4922D3-499A-46db-BFC4-0B51A9C4395F}"; + + /// + /// SQL 随机排序语法 + /// + /// NotImplementedException + public static string SqlRandomSorting => + App.GetOptions().DbType switch { + DataType.MySql => "RAND()" + , DataType.SqlServer => "NEWID()" + , DataType.PostgreSQL => "RANDOM()" + , DataType.Oracle => "DBMS_RANDOM.value" + , DataType.Sqlite => "RANDOM()" + , DataType.OdbcOracle => throw new NotImplementedException() + , DataType.OdbcSqlServer => throw new NotImplementedException() + , DataType.OdbcMySql => throw new NotImplementedException() + , DataType.OdbcPostgreSQL => throw new NotImplementedException() + , DataType.Odbc => throw new NotImplementedException() + , DataType.OdbcDameng => throw new NotImplementedException() + , DataType.MsAccess => throw new NotImplementedException() + , DataType.Dameng => throw new NotImplementedException() + , DataType.OdbcKingbaseES => throw new NotImplementedException() + , DataType.ShenTong => throw new NotImplementedException() + , DataType.KingbaseES => throw new NotImplementedException() + , DataType.Firebird => throw new NotImplementedException() + , DataType.Custom => throw new NotImplementedException() + , DataType.ClickHouse => throw new NotImplementedException() + , DataType.GBase => throw new NotImplementedException() + , DataType.QuestDb => throw new NotImplementedException() + , DataType.Xugu => throw new NotImplementedException() + , DataType.CustomOracle => throw new NotImplementedException() + , DataType.CustomSqlServer => throw new NotImplementedException() + , DataType.CustomMySql => throw new NotImplementedException() + , DataType.CustomPostgreSQL => throw new NotImplementedException() + , _ => throw new NotImplementedException() + }; + + /// + /// Json序列化选项 + /// + public static JsonSerializerOptions JsonSerializerOptions { get; set; } + + /// + /// 停止更新日志时间 + /// + public static bool LogCounterOff { get; set; } + + /// + /// 增加日志计数器 + /// + public static void IncrementLogCounter() + { + Volatile.Write(ref _latestLogTime, DateTime.Now.TimeUnixUtcMs()); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj new file mode 100644 index 00000000..18fe7cf0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdmin.Infrastructure.csproj @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + Languages/NetAdmin.Statements.ln + + + Languages/Nations.ln + + + Languages/NetAdmin.Fields.ln + + + Languages/CountryCodes.ln + + + Languages/Ln.resx + PublicResXFileCodeGenerator + + + Languages/Ln.Designer.cs + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Development.json b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Development.json new file mode 100644 index 00000000..dce35d64 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Development.json @@ -0,0 +1,5 @@ +{ + "AppSettings": { + "InjectSpecificationDocument": true + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Remote.json b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Remote.json new file mode 100644 index 00000000..dce35d64 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Remote.json @@ -0,0 +1,5 @@ +{ + "AppSettings": { + "InjectSpecificationDocument": true + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Test.json b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Test.json new file mode 100644 index 00000000..dce35d64 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.Test.json @@ -0,0 +1,5 @@ +{ + "AppSettings": { + "InjectSpecificationDocument": true + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.json b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.json new file mode 100644 index 00000000..2fa43268 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/NetAdminSettings.json @@ -0,0 +1,141 @@ +{ + // App基本配置 + "AppSettings": { + "InjectSpecificationDocument": false + }, + // Swagger文档配置 ------------------------------------------------------------------------------ + "SpecificationDocumentSettings": { + "EnableEnumSchemaFilter": false, + "EnableAuthorized": false, + "RoutePrefix": "swagger", + "SecurityDefinitions": [ + { + "Id": "Bearer", + "Type": "ApiKey", + "Name": "Authorization", + "Description": "JWT Authorization header using the Bearer scheme.", + "BearerFormat": "JWT", + "Scheme": "bearer", + "In": "Header", + "Requirement": { + "Scheme": { + "Reference": { + "Id": "Bearer", + "Type": "SecurityScheme" + }, + "Accesses": [] + } + } + } + ], + }, + // 验证码配置 -------------------------------------------------------------------------------------------------------- + "Captcha": { + "ImageRelativePath": ".data/captcha", + "SecretKey": "1Z?f(2)%v?:X5NYRl+]PSi.rDf7Ip#lB" + }, + // 跨域配置 ---------------------------------------------------------------------------------------------------------- + "CorsAccessorSettings": { + "WithExposedHeaders": [ + "access-token", + "x-access-token", + "content-disposition" + ] + }, + // 动态webapi配置 ---------------------------------------------------------------------------------------------------- + "DynamicApiControllerSettings": { + "VerbToHttpMethods": [ + [ + "post", + "POST" + ], + [ + "add", + "POST" + ], + [ + "create", + "POST" + ], + [ + "insert", + "POST" + ], + [ + "submit", + "POST" + ], + [ + "get", + "POST" + ], + [ + "find", + "POST" + ], + [ + "fetch", + "POST" + ], + [ + "query", + "POST" + ], + [ + "getlist", + "POST" + ], + [ + "getall", + "POST" + ], + [ + "put", + "POST" + ], + [ + "update", + "POST" + ], + [ + "delete", + "POST" + ], + [ + "remove", + "POST" + ], + [ + "clear", + "POST" + ], + [ + "patch", + "POST" + ] + ], + "CamelCaseSeparator": ".", + "UrlParameterization": true, + "KeepVerb": true, + "AbandonControllerAffixes": [ + "Controller" + ] + }, + // 友好异常配置 ------------------------------------------------------------------------------------------------------- + "FriendlyExceptionSettings": { + "LogError": false + }, + // 日志配置 ---------------------------------------------------------------------------------------------------------- + "Logging": { + "Monitor": { + "GlobalEnabled": false, + "ReturnValueThreshold": 1000 + } + }, + // UnifyResultSettings 规范化配置 ------------------------------------------------------------------------------------ + "UnifyResultSettings": { + "Return200StatusCodes": [ + 999 + ] + }, +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/ProjectUsings.cs new file mode 100644 index 00000000..5137640f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/ProjectUsings.cs @@ -0,0 +1 @@ +global using Gurion.RemoteRequest; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/ApplicationHelper.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/ApplicationHelper.cs new file mode 100644 index 00000000..6f0a74b2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/ApplicationHelper.cs @@ -0,0 +1,28 @@ +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 应用程序帮助类 +/// +public static class ApplicationHelper +{ + /// + /// 获取系统环境 + /// + public static Dictionary GetEnvironmentInfo() + { + var ret = typeof(Environment).GetProperties(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.Name is not (nameof(Environment.StackTrace) or nameof(Environment.NewLine))) + .ToDictionary(x => x.Name, x => x.GetValue(null)); + + var vars = Environment.GetEnvironmentVariables(); + var keys = new ArrayList(vars.Keys); + keys.Sort(); + var sb = new StringBuilder(vars.Count); + foreach (var key in keys) { + _ = sb.AppendLine(CultureInfo.InvariantCulture, $"{key}: {vars[key]}"); + } + + _ = ret.TryAdd("EnvironmentVars", sb.ToString().Trim()); + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/CaptchaImageHelper.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/CaptchaImageHelper.cs new file mode 100644 index 00000000..110697dc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/CaptchaImageHelper.cs @@ -0,0 +1,177 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Drawing; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 验证码图片工具类 +/// +public static class CaptchaImageHelper +{ + private static readonly int[] _randRange = [70, 100]; + + /// + /// 创建一个缺口滑块验证码图片 + /// + /// 包含资源的程序集 + /// 背景图路径 + /// 滑块模板小图路径 + /// 背景图随机序号范围(1-x) + /// 模板图随机序号范围(1-x) + /// 滑块尺寸 + /// 背景图(base64),滑块图(base64),缺口坐标 + #pragma warning disable SA1414 + public static async Task<(string BackgroundImage, string SliderImage, Point OffsetSaw)> CreateSawSliderImageAsync( + Assembly resAsm, string bgPath, string tempPath, (int, int) bgIndexScope, (int, int) tempIndexScope, Size sliderSize) + #pragma warning restore SA1414 + { + // 深色模板图 + var templateIndex = new[] { tempIndexScope.Item1, tempIndexScope.Item2 }.Rand(); + + await using var bgStream = resAsm.GetManifestResourceStream($"{bgPath}.{new[] { bgIndexScope.Item1, bgIndexScope.Item2 }.Rand()}.jpg"); + await using var darkStream = resAsm.GetManifestResourceStream($"{tempPath}._{templateIndex}.dark.png"); + await using var tranStream = resAsm.GetManifestResourceStream($"{tempPath}._{templateIndex}.transparent.png"); + + // 底图 + using var backgroundImage = await Image.LoadAsync(bgStream).ConfigureAwait(false); + + using var darkTemplateImage = await Image.LoadAsync(darkStream).ConfigureAwait(false); + + // 透明模板图 + using var transparentTemplateImage = await Image.LoadAsync(tranStream).ConfigureAwait(false); + + // 调整模板图大小 + darkTemplateImage.Mutate(x => x.Resize(sliderSize)); + transparentTemplateImage.Mutate(x => x.Resize(sliderSize)); + + // 新建拼图 + using var blockImage = new Image(sliderSize.Width, sliderSize.Height); + + // 新建滑块拼图 + using var sliderBlockImage = new Image(sliderSize.Width, backgroundImage.Height); + + // 随机生成拼图坐标 + var offsetRand = GeneratePoint(backgroundImage.Width, backgroundImage.Height, sliderSize.Width, sliderSize.Height); + + // 根据深色模板图计算轮廓形状 + var blockShape = CalcBlockShape(darkTemplateImage); + + // 生成拼图 + blockImage.Mutate(x => { + // ReSharper disable once AccessToDisposedClosure + _ = x.Clip(blockShape, p => p.DrawImage(backgroundImage, new Point(-offsetRand.X, -offsetRand.Y), 1)); + }); + + // 拼图叠加透明模板图层 + // ReSharper disable once AccessToDisposedClosure + blockImage.Mutate(x => x.DrawImage(transparentTemplateImage, new Point(0, 0), 1)); + + // 生成滑块拼图 + // ReSharper disable once AccessToDisposedClosure + sliderBlockImage.Mutate(x => x.DrawImage(blockImage, new Point(0, offsetRand.Y), 1)); + + var opacity = (float)(_randRange.Rand() * 0.01); + + // 底图叠加深色模板图 + // ReSharper disable once AccessToDisposedClosure + backgroundImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(offsetRand.X, offsetRand.Y), opacity)); + + // 生成干扰图坐标 + var interferencePoint = GenerateInterferencePoint(backgroundImage.Width, backgroundImage.Height, sliderSize.Width, sliderSize.Height + , offsetRand.X, offsetRand.Y); + + // 底图叠加深色干扰模板图 + // ReSharper disable once AccessToDisposedClosure + backgroundImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(interferencePoint.X, interferencePoint.Y), opacity)); + return (backgroundImage.ToBase64String(PngFormat.Instance), sliderBlockImage.ToBase64String(PngFormat.Instance), offsetRand); + } + + private static int BuildPathList(Span rowSpan, int temp, List pathList, int y) + { + for (var x = 0; x < rowSpan.Length; x++) { + ref var pixel = ref rowSpan[x]; + if (pixel.A != 0) { + temp = temp switch { 0 => x, _ => temp }; + } + else { + if (temp == 0) { + continue; + } + + pathList.Add(new RectangularPolygon(temp, y, x - temp, 1)); + temp = 0; + } + } + + return temp; + } + + private static ComplexPolygon CalcBlockShape(Image templateDarkImage) + { + var temp = 0; + var pathList = new List(); + templateDarkImage.ProcessPixelRows(accessor => { + for (var y = 0; y < templateDarkImage.Height; y++) { + var rowSpan = accessor.GetRowSpan(y); + temp = BuildPathList(rowSpan, temp, pathList, y); + } + }); + + return new ComplexPolygon(new PathCollection(pathList)); + } + + /// + /// 随机生成干扰图坐标 + /// + private static Point GenerateInterferencePoint(int originalWidth, int originalHeight, int templateWidth, int templateHeight, int blockX + , int blockY) + { + var x = + + // 在原扣图右边插入干扰图 + originalWidth - blockX - 5 > templateWidth * 2 + ? GetRandomInt(blockX + templateWidth + 5, originalWidth - templateWidth) + : + + // 在原扣图左边插入干扰图 + GetRandomInt(100, blockX - templateWidth - 5); + + var y = + + // 在原扣图下边插入干扰图 + originalHeight - blockY - 5 > templateHeight * 2 + ? GetRandomInt(blockY + templateHeight + 5, originalHeight - templateHeight) + : + + // 在原扣图上边插入干扰图 + GetRandomInt(5, blockY - templateHeight - 5); + + return new Point(x, y); + } + + /// + /// 随机生成拼图坐标 + /// + private static Point GeneratePoint(int originalWidth, int originalHeight, int templateWidth, int templateHeight) + { + var widthDifference = originalWidth - templateWidth; + var heightDifference = originalHeight - templateHeight; + var x = widthDifference switch { <= 0 => 5, _ => new[] { 0, originalWidth - templateWidth - 100 }.Rand() + 100 }; + + var y = heightDifference switch { <= 0 => 5, _ => new[] { 0, originalHeight - templateHeight - 5 }.Rand() + 5 }; + + return new Point(x, y); + } + + /// + /// 随机范围内数字 + /// + private static int GetRandomInt(int startNum, int endNum) + { + return (endNum > startNum ? new[] { 0, endNum - startNum }.Rand() : 0) + startNum; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/FreeSqlBuilder.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/FreeSqlBuilder.cs new file mode 100644 index 00000000..e5e58b8e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/FreeSqlBuilder.cs @@ -0,0 +1,152 @@ +#if DBTYPE_SQLSERVER +using Microsoft.Data.SqlClient; +#endif +using DataType = FreeSql.DataType; + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// FreeSql 构建器 +/// +public sealed class FreeSqlBuilder(DatabaseOptions databaseOptions) +{ + /// + /// 构建freeSql对象 + /// + public IFreeSql Build(FreeSqlInitMethods initMethods, Func onSeedDataInserted = null) + { + var freeSql = new FreeSql.FreeSqlBuilder() + #if DBTYPE_SQLSERVER + .UseConnectionFactory(databaseOptions.DbType, () => new SqlConnection(databaseOptions.ConnStr)) + .UseAdoConnectionPool(true) + #else + .UseConnectionString(databaseOptions.DbType, databaseOptions.ConnStr) + #endif + .UseGenerateCommandParameterWithLambda(true) + .UseAutoSyncStructure(initMethods.HasFlag(FreeSqlInitMethods.SyncStructure)) + .Build(); + _ = InitDbAsync(freeSql, initMethods, onSeedDataInserted); // 初始化数据库 ,异步 + return freeSql; + } + + private static void CompareStructure(IFreeSql freeSql, Type[] entityTypes) + { + File.WriteAllText( // + $"{nameof(CompareStructure)}.sql", freeSql.CodeFirst.GetComparisonDDLStatements(entityTypes)); + } + + /// + /// 获取所有实体类型定义 + /// + private static Type[] GetEntityTypes() + { + return (from type in App.EffectiveTypes + from attr in type.GetCustomAttributes() + where attr is TableAttribute { DisableSyncStructure: false } + select type).ToArray(); + } + + private static MethodInfo MakeGetRepositoryMethod(Type entityType) + { + return typeof(FreeSqlDbContextExtensions).GetMethods() + .Where(x => x.Name == nameof(FreeSqlDbContextExtensions.GetRepository)) + .FirstOrDefault(x => x.GetGenericArguments().Length == 1) + ?.MakeGenericMethod(entityType); + } + + private static MethodInfo MakeInsertMethod(Type entityType) + { + return typeof(IBaseRepository<>).MakeGenericType(entityType) + .GetMethod( // + nameof(IBaseRepository.Insert) // + , BindingFlags.Public | BindingFlags.Instance // + , null // + , CallingConventions.Any // + , [typeof(IEnumerable<>).MakeGenericType(entityType)] // + , null); + } + + /// + /// 初始化数据库 + /// + private Task InitDbAsync(IFreeSql freeSql, FreeSqlInitMethods initMethods, Func onSeedDataInserted) + { + return Task.Run(() => { + if (initMethods == FreeSqlInitMethods.None) { + return; + } + + var entityTypes = GetEntityTypes(); + if (initMethods.HasFlag(FreeSqlInitMethods.SyncStructure)) { + SyncStructure(freeSql, entityTypes); + } + + if (initMethods.HasFlag(FreeSqlInitMethods.InsertSeedData)) { + var insertCount = InsertSeedData(freeSql, entityTypes); + _ = onSeedDataInserted?.Invoke(insertCount); + } + + if (initMethods.HasFlag(FreeSqlInitMethods.CompareStructure)) { + CompareStructure(freeSql, entityTypes); + } + }); + } + + /// + /// 插入种子数据 + /// + private int InsertSeedData(IFreeSql freeSql, IEnumerable entityTypes) + { + var ret = 0; + foreach (var entityType in entityTypes) { + var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, databaseOptions.SeedDataRelativePath, $"{entityType.Name}.json"); + if (!File.Exists(file)) { + continue; + } + + using var fs = File.OpenRead(file); + var jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerOptions.Default) // + { + AllowTrailingCommas = true + , ReadCommentHandling = JsonCommentHandling.Skip + , TypeInfoResolver + = new DefaultJsonTypeInfoResolver { Modifiers = { JsonIgnoreRemover.RemoveJsonIgnore(entityType) } } + }; + _ = jsonSerializerOptions.Converters.AddDateTimeTypeConverters(); + + var jsonTypeInfo = JsonTypeInfo.CreateJsonTypeInfo(typeof(IEnumerable<>).MakeGenericType(entityType), jsonSerializerOptions); + dynamic entities = JsonSerializer.Deserialize(fs, jsonTypeInfo); + + // 如果表存在数据,跳过 + var select = typeof(IFreeSql).GetMethod(nameof(freeSql.Select), 1, Type.EmptyTypes)?.MakeGenericMethod(entityType).Invoke(freeSql, null); + if (select?.GetType().GetMethod(nameof(ISelect.Any), 0, Type.EmptyTypes)?.Invoke(select, null) as bool? ?? true) { + continue; + } + + var rep = MakeGetRepositoryMethod(entityType)?.Invoke(null, [freeSql, null]); + if (rep?.GetType().GetProperty(nameof(DbContextOptions))?.GetValue(rep) is DbContextOptions options) { + options.EnableCascadeSave = true; + options.NoneParameter = true; + } + + var insert = MakeInsertMethod(entityType); + + _ = insert?.Invoke(rep, [entities]); + ret += entities!.Count; + } + + return ret; + } + + /// + /// 同步数据库结构 + /// + private void SyncStructure(IFreeSql freeSql, Type[] entityTypes) + { + if (databaseOptions.DbType == DataType.Oracle) { + freeSql.CodeFirst.IsSyncStructureToUpper = true; + } + + freeSql.CodeFirst.SyncStructure(entityTypes); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/JsonIgnoreRemover.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/JsonIgnoreRemover.cs new file mode 100644 index 00000000..cc99c836 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/JsonIgnoreRemover.cs @@ -0,0 +1,98 @@ +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 忽略 JsonIgnore 特性 +/// +public static class JsonIgnoreRemover +{ + private delegate TValue RefFunc(ref TObject arg); + + /// + /// 忽略 JsonIgnore 特性 + /// + public static Action RemoveJsonIgnore(Type type) + { + return typeInfo => { + if (!type.IsAssignableFrom(typeInfo.Type) || typeInfo.Kind != JsonTypeInfoKind.Object) { + return; + } + + foreach (var property in typeInfo.Properties.Where(property => property.ShouldSerialize != null && + property.AttributeProvider?.IsDefined(typeof(JsonIgnoreAttribute), true) == + true)) { + property.Get ??= CreatePropertyGetter(property); + property.Set ??= CreatePropertySetter(property); + if (property.Get != null) { + property.ShouldSerialize = null; + } + } + }; + } + + private static Func CreateGetter(Type type, MethodInfo method) + { + if (method == null) { + return null; + } + + #pragma warning disable S3011 + var myMethod = typeof(JsonIgnoreRemover).GetMethod(nameof(CreateGetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; + #pragma warning restore S3011 + return (Func)myMethod.MakeGenericMethod(type, method.ReturnType).Invoke(null, [method])!; + } + + private static Func CreateGetterGeneric(MethodInfo method) + { + ArgumentNullException.ThrowIfNull(method); + + if (typeof(TObject).IsValueType) { + var func = (RefFunc)Delegate.CreateDelegate(typeof(RefFunc), null, method); + return o => { + var tObj = (TObject)o; + return func(ref tObj); + }; + } + else { + var func = (Func)Delegate.CreateDelegate(typeof(Func), method); + return o => func((TObject)o); + } + } + + private static Func CreatePropertyGetter(JsonPropertyInfo property) + { + return property.AttributeProvider as PropertyInfo is { ReflectedType: not null } info && info.GetGetMethod() is { } getMethod + ? CreateGetter(info.ReflectedType, getMethod) + : null; + } + + private static Action CreatePropertySetter(JsonPropertyInfo property) + { + return property.AttributeProvider as PropertyInfo is { ReflectedType: not null } info && info.GetSetMethod() is { } setMethod + ? CreateSetter(info.ReflectedType, setMethod) + : null; + } + + private static Action CreateSetter(Type type, MethodInfo method) + { + if (method == null) { + return null; + } + + #pragma warning disable S3011 + var myMethod = typeof(JsonIgnoreRemover).GetMethod(nameof(CreateSetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!; + #pragma warning restore S3011 + return (Action)myMethod.MakeGenericMethod(type, method.GetParameters().Single().ParameterType).Invoke(null, [method])!; + } + + private static Action CreateSetterGeneric(MethodInfo method) + { + ArgumentNullException.ThrowIfNull(method); + + if (typeof(TObject).IsValueType) { + return (o, v) => method.Invoke(o, [v]); + } + + var func = (Action)Delegate.CreateDelegate(typeof(Action), method); + return (o, v) => func((TObject)o, (TValue)v); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/LogHelper.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/LogHelper.cs new file mode 100644 index 00000000..5ac74075 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/LogHelper.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 日志帮助类 +/// +public static class LogHelper +{ + /// + /// 获取ILogger + /// + public static ILogger Get() + { + return App.GetService>(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MimeTypeHelper.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MimeTypeHelper.cs new file mode 100644 index 00000000..690cb3a5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MimeTypeHelper.cs @@ -0,0 +1,1034 @@ +namespace NetAdmin.Infrastructure.Utils; + +/// +/// MIME 类型帮助类 +/// +public static class MimeTypeHelper +{ + private const string _MIME_TYPES_RAW_STRING = """ + 123 application/vnd.lotus-1-2-3 + 3dml text/vnd.in3d.3dml + 3ds image/x-3ds + 3g2 video/3gpp2 + 3gp video/3gpp + 7z application/x-7z-compressed + aab application/x-authorware-bin + aac audio/x-aac + aam application/x-authorware-map + aas application/x-authorware-seg + abs audio/x-mpeg + abw application/x-abiword + ac application/pkix-attr-cert + acc application/vnd.americandynamics.acc + ace application/x-ace-compressed + acu application/vnd.acucobol + acutc application/vnd.acucorp + adp audio/adpcm + aep application/vnd.audiograph + afm application/x-font-type1 + afp application/vnd.ibm.modcap + ahead application/vnd.ahead.space + ai application/postscript + aif audio/x-aiff + aifc audio/x-aiff + aiff audio/x-aiff + aim application/x-aim + air application/vnd.adobe.air-application-installer-package+zip + ait application/vnd.dvb.ait + ami application/vnd.amiga.ami + anx application/annodex + apk application/vnd.android.package-archive + appcache text/cache-manifest + application application/x-ms-application + apr application/vnd.lotus-approach + arc application/x-freearc + art image/x-jg + asc application/pgp-signature + asf video/x-ms-asf + asm text/x-asm + aso application/vnd.accpac.simply.aso + asx video/x-ms-asf + atc application/vnd.acucorp + atom application/atom+xml + atomcat application/atomcat+xml + atomsvc application/atomsvc+xml + atx application/vnd.antix.game-component + au audio/basic + avi video/x-msvideo + avx video/x-rad-screenplay + aw application/applixware + axa audio/annodex + axv video/annodex + azf application/vnd.airzip.filesecure.azf + azs application/vnd.airzip.filesecure.azs + azw application/vnd.amazon.ebook + bat application/x-msdownload + bcpio application/x-bcpio + bdf application/x-font-bdf + bdm application/vnd.syncml.dm+wbxml + bed application/vnd.realvnc.bed + bh2 application/vnd.fujitsu.oasysprs + bin application/octet-stream + blb application/x-blorb + blorb application/x-blorb + bmi application/vnd.bmi + bmp image/bmp + body text/html + book application/vnd.framemaker + box application/vnd.previewsystems.box + boz application/x-bzip2 + bpk application/octet-stream + btif image/prs.btif + bz application/x-bzip + bz2 application/x-bzip2 + c text/x-c + c11amc application/vnd.cluetrust.cartomobile-config + c11amz application/vnd.cluetrust.cartomobile-config-pkg + c4d application/vnd.clonk.c4group + c4f application/vnd.clonk.c4group + c4g application/vnd.clonk.c4group + c4p application/vnd.clonk.c4group + c4u application/vnd.clonk.c4group + cab application/vnd.ms-cab-compressed + caf audio/x-caf + cap application/vnd.tcpdump.pcap + car application/vnd.curl.car + cat application/vnd.ms-pki.seccat + cb7 application/x-cbr + cba application/x-cbr + cbr application/x-cbr + cbt application/x-cbr + cbz application/x-cbr + cc text/x-c + cct application/x-director + ccxml application/ccxml+xml + cdbcmsg application/vnd.contact.cmsg + cdf application/x-cdf + cdkey application/vnd.mediastation.cdkey + cdmia application/cdmi-capability + cdmic application/cdmi-container + cdmid application/cdmi-domain + cdmio application/cdmi-object + cdmiq application/cdmi-queue + cdx chemical/x-cdx + cdxml application/vnd.chemdraw+xml + cdy application/vnd.cinderella + cer application/pkix-cert + cfs application/x-cfs-compressed + cgm image/cgm + chat application/x-chat + chm application/vnd.ms-htmlhelp + chrt application/vnd.kde.kchart + cif chemical/x-cif + cii application/vnd.anser-web-certificate-issue-initiation + cil application/vnd.ms-artgalry + cla application/vnd.claymore + class application/java + clkk application/vnd.crick.clicker.keyboard + clkp application/vnd.crick.clicker.palette + clkt application/vnd.crick.clicker.template + clkw application/vnd.crick.clicker.wordbank + clkx application/vnd.crick.clicker + clp application/x-msclip + cmc application/vnd.cosmocaller + cmdf chemical/x-cmdf + cml chemical/x-cml + cmp application/vnd.yellowriver-custom-menu + cmx image/x-cmx + cod application/vnd.rim.cod + com application/x-msdownload + conf text/plain + cpio application/x-cpio + cpp text/x-c + cpt application/mac-compactpro + crd application/x-mscardfile + crl application/pkix-crl + crt application/x-x509-ca-cert + cryptonote application/vnd.rig.cryptonote + csh application/x-csh + csml chemical/x-csml + csp application/vnd.commonspace + css text/css + cst application/x-director + csv text/csv + cu application/cu-seeme + curl text/vnd.curl + cww application/prs.cww + cxt application/x-director + cxx text/x-c + dae model/vnd.collada+xml + daf application/vnd.mobius.daf + dart application/vnd.dart + dataless application/vnd.fdsn.seed + davmount application/davmount+xml + dbk application/docbook+xml + dcr application/x-director + dcurl text/vnd.curl.dcurl + dd2 application/vnd.oma.dd2+xml + ddd application/vnd.fujixerox.ddd + deb application/x-debian-package + def text/plain + deploy application/octet-stream + der application/x-x509-ca-cert + dfac application/vnd.dreamfactory + dgc application/x-dgc-compressed + dib image/bmp + dic text/x-c + dir application/x-director + dis application/vnd.mobius.dis + dist application/octet-stream + distz application/octet-stream + djv image/vnd.djvu + djvu image/vnd.djvu + dll application/x-msdownload + dmg application/x-apple-diskimage + dmp application/vnd.tcpdump.pcap + dms application/octet-stream + dna application/vnd.dna + doc application/msword + docm application/vnd.ms-word.document.macroenabled.12 + docx application/vnd.openxmlformats-officedocument.wordprocessingml.document + dot application/msword + dotm application/vnd.ms-word.template.macroenabled.12 + dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template + dp application/vnd.osgi.dp + dpg application/vnd.dpgraph + dra audio/vnd.dra + dsc text/prs.lines.tag + dssc application/dssc+der + dtb application/x-dtbook+xml + dtd application/xml-dtd + dts audio/vnd.dts + dtshd audio/vnd.dts.hd + dump application/octet-stream + dv video/x-dv + dvb video/vnd.dvb.file + dvi application/x-dvi + dwf model/vnd.dwf + dwg image/vnd.dwg + dxf image/vnd.dxf + dxp application/vnd.spotfire.dxp + dxr application/x-director + ecelp4800 audio/vnd.nuera.ecelp4800 + ecelp7470 audio/vnd.nuera.ecelp7470 + ecelp9600 audio/vnd.nuera.ecelp9600 + ecma application/ecmascript + edm application/vnd.novadigm.edm + edx application/vnd.novadigm.edx + efif application/vnd.picsel + ei6 application/vnd.pg.osasli + elc application/octet-stream + emf application/x-msmetafile + eml message/rfc822 + emma application/emma+xml + emz application/x-msmetafile + eol audio/vnd.digital-winds + eot application/vnd.ms-fontobject + eps application/postscript + epub application/epub+zip + es3 application/vnd.eszigno3+xml + esa application/vnd.osgi.subsystem + esf application/vnd.epson.esf + et3 application/vnd.eszigno3+xml + etx text/x-setext + eva application/x-eva + evy application/x-envoy + exe application/octet-stream + exi application/exi + ext application/vnd.novadigm.ext + ez application/andrew-inset + ez2 application/vnd.ezpix-album + ez3 application/vnd.ezpix-package + f text/x-fortran + f4v video/x-f4v + f77 text/x-fortran + f90 text/x-fortran + fbs image/vnd.fastbidsheet + fcdt application/vnd.adobe.formscentral.fcdt + fcs application/vnd.isac.fcs + fdf application/vnd.fdf + fe_launch application/vnd.denovo.fcselayout-link + fg5 application/vnd.fujitsu.oasysgp + fgd application/x-director + fh image/x-freehand + fh4 image/x-freehand + fh5 image/x-freehand + fh7 image/x-freehand + fhc image/x-freehand + fig application/x-xfig + flac audio/flac + fli video/x-fli + flo application/vnd.micrografx.flo + flv video/x-flv + flw application/vnd.kde.kivio + flx text/vnd.fmi.flexstor + fly text/vnd.fly + fm application/vnd.framemaker + fnc application/vnd.frogans.fnc + for text/x-fortran + fpx image/vnd.fpx + frame application/vnd.framemaker + fsc application/vnd.fsc.weblaunch + fst image/vnd.fst + ftc application/vnd.fluxtime.clip + fti application/vnd.anser-web-funds-transfer-initiation + fvt video/vnd.fvt + fxp application/vnd.adobe.fxp + fxpl application/vnd.adobe.fxp + fzs application/vnd.fuzzysheet + g2w application/vnd.geoplan + g3 image/g3fax + g3w application/vnd.geospace + gac application/vnd.groove-account + gam application/x-tads + gbr application/rpki-ghostbusters + gca application/x-gca-compressed + gdl model/vnd.gdl + geo application/vnd.dynageo + gex application/vnd.geometry-explorer + ggb application/vnd.geogebra.file + ggt application/vnd.geogebra.tool + ghf application/vnd.groove-help + gif image/gif + gim application/vnd.groove-identity-message + gml application/gml+xml + gmx application/vnd.gmx + gnumeric application/x-gnumeric + gph application/vnd.flographit + gpx application/gpx+xml + gqf application/vnd.grafeq + gqs application/vnd.grafeq + gram application/srgs + gramps application/x-gramps-xml + gre application/vnd.geometry-explorer + grv application/vnd.groove-injector + grxml application/srgs+xml + gsf application/x-font-ghostscript + gtar application/x-gtar + gtm application/vnd.groove-tool-message + gtw model/vnd.gtw + gv text/vnd.graphviz + gxf application/gxf + gxt application/vnd.geonext + gz application/x-gzip + h text/x-c + h261 video/h261 + h263 video/h263 + h264 video/h264 + hal application/vnd.hal+xml + hbci application/vnd.hbci + hdf application/x-hdf + hh text/x-c + hlp application/winhlp + hpgl application/vnd.hp-hpgl + hpid application/vnd.hp-hpid + hps application/vnd.hp-hps + hqx application/mac-binhex40 + htc text/x-component + htke application/vnd.kenameaapp + htm text/html + html text/html + hvd application/vnd.yamaha.hv-dic + hvp application/vnd.yamaha.hv-voice + hvs application/vnd.yamaha.hv-script + i2g application/vnd.intergeo + icc application/vnd.iccprofile + ice x-conference/x-cooltalk + icm application/vnd.iccprofile + ico image/x-icon + ics text/calendar + ief image/ief + ifb text/calendar + ifm application/vnd.shana.informed.formdata + iges model/iges + igl application/vnd.igloader + igm application/vnd.insors.igm + igs model/iges + igx application/vnd.micrografx.igx + iif application/vnd.shana.informed.interchange + imp application/vnd.accpac.simply.imp + ims application/vnd.ms-ims + in text/plain + ink application/inkml+xml + inkml application/inkml+xml + install application/x-install-instructions + iota application/vnd.astraea-software.iota + ipfix application/ipfix + ipk application/vnd.shana.informed.package + irm application/vnd.ibm.rights-management + irp application/vnd.irepository.package+xml + iso application/x-iso9660-image + itp application/vnd.shana.informed.formtemplate + ivp application/vnd.immervision-ivp + ivu application/vnd.immervision-ivu + jad text/vnd.sun.j2me.app-descriptor + jam application/vnd.jam + jar application/java-archive + java text/x-java-source + jisp application/vnd.jisp + jlt application/vnd.hp-jlyt + jnlp application/x-java-jnlp-file + joda application/vnd.joost.joda-archive + jpe image/jpeg + jpeg image/jpeg + jpg image/jpeg + jpgm video/jpm + jpgv video/jpeg + jpm video/jpm + js application/javascript + jsf text/plain + json application/json + jsonml application/jsonml+json + jspf text/plain + kar audio/midi + karbon application/vnd.kde.karbon + kfo application/vnd.kde.kformula + kia application/vnd.kidspiration + kml application/vnd.google-earth.kml+xml + kmz application/vnd.google-earth.kmz + kne application/vnd.kinar + knp application/vnd.kinar + kon application/vnd.kde.kontour + kpr application/vnd.kde.kpresenter + kpt application/vnd.kde.kpresenter + kpxx application/vnd.ds-keypoint + ksp application/vnd.kde.kspread + ktr application/vnd.kahootz + ktx image/ktx + ktz application/vnd.kahootz + kwd application/vnd.kde.kword + kwt application/vnd.kde.kword + lasxml application/vnd.las.las+xml + latex application/x-latex + lbd application/vnd.llamagraphics.life-balance.desktop + lbe application/vnd.llamagraphics.life-balance.exchange+xml + les application/vnd.hhe.lesson-player + lha application/x-lzh-compressed + link66 application/vnd.route66.link66+xml + list text/plain + list3820 application/vnd.ibm.modcap + listafp application/vnd.ibm.modcap + lnk application/x-ms-shortcut + log text/plain + lostxml application/lost+xml + lrf application/octet-stream + lrm application/vnd.ms-lrm + ltf application/vnd.frogans.ltf + lvp audio/vnd.lucent.voice + lwp application/vnd.lotus-wordpro + lzh application/x-lzh-compressed + m13 application/x-msmediaview + m14 application/x-msmediaview + m1v video/mpeg + m21 application/mp21 + m2a audio/mpeg + m2v video/mpeg + m3a audio/mpeg + m3u audio/x-mpegurl + m3u8 application/vnd.apple.mpegurl + m4a audio/mp4 + m4b audio/mp4 + m4r audio/mp4 + m4u video/vnd.mpegurl + m4v video/mp4 + ma application/mathematica + mac image/x-macpaint + mads application/mads+xml + mag application/vnd.ecowin.chart + maker application/vnd.framemaker + man text/troff + mar application/octet-stream + mathml application/mathml+xml + mb application/mathematica + mbk application/vnd.mobius.mbk + mbox application/mbox + mc1 application/vnd.medcalcdata + mcd application/vnd.mcd + mcurl text/vnd.curl.mcurl + mdb application/x-msaccess + mdi image/vnd.ms-modi + me text/troff + mesh model/mesh + meta4 application/metalink4+xml + metalink application/metalink+xml + mets application/mets+xml + mfm application/vnd.mfmp + mft application/rpki-manifest + mgp application/vnd.osgeo.mapguide.package + mgz application/vnd.proteus.magazine + mid audio/midi + midi audio/midi + mie application/x-mie + mif application/x-mif + mime message/rfc822 + mj2 video/mj2 + mjp2 video/mj2 + mk3d video/x-matroska + mka audio/x-matroska + mks video/x-matroska + mkv video/x-matroska + mlp application/vnd.dolby.mlp + mmd application/vnd.chipnuts.karaoke-mmd + mmf application/vnd.smaf + mmr image/vnd.fujixerox.edmics-mmr + mng video/x-mng + mny application/x-msmoney + mobi application/x-mobipocket-ebook + mods application/mods+xml + mov video/quicktime + movie video/x-sgi-movie + mp1 audio/mpeg + mp2 audio/mpeg + mp21 application/mp21 + mp2a audio/mpeg + mp3 audio/mpeg + mp4 video/mp4 + mp4a audio/mp4 + mp4s application/mp4 + mp4v video/mp4 + mpa audio/mpeg + mpc application/vnd.mophun.certificate + mpe video/mpeg + mpeg video/mpeg + mpega audio/x-mpeg + mpg video/mpeg + mpg4 video/mp4 + mpga audio/mpeg + mpkg application/vnd.apple.installer+xml + mpm application/vnd.blueice.multipass + mpn application/vnd.mophun.application + mpp application/vnd.ms-project + mpt application/vnd.ms-project + mpv2 video/mpeg2 + mpy application/vnd.ibm.minipay + mqy application/vnd.mobius.mqy + mrc application/marc + mrcx application/marcxml+xml + ms text/troff + mscml application/mediaservercontrol+xml + mseed application/vnd.fdsn.mseed + mseq application/vnd.mseq + msf application/vnd.epson.msf + msh model/mesh + msi application/x-msdownload + msl application/vnd.mobius.msl + msty application/vnd.muvee.style + mts model/vnd.mts + mus application/vnd.musician + musicxml application/vnd.recordare.musicxml+xml + mvb application/x-msmediaview + mwf application/vnd.mfer + mxf application/mxf + mxl application/vnd.recordare.musicxml + mxml application/xv+xml + mxs application/vnd.triscape.mxs + mxu video/vnd.mpegurl + n-gage application/vnd.nokia.n-gage.symbian.install + n3 text/n3 + nb application/mathematica + nbp application/vnd.wolfram.player + nc application/x-netcdf + ncx application/x-dtbncx+xml + nfo text/x-nfo + ngdat application/vnd.nokia.n-gage.data + nitf application/vnd.nitf + nlu application/vnd.neurolanguage.nlu + nml application/vnd.enliven + nnd application/vnd.noblenet-directory + nns application/vnd.noblenet-sealer + nnw application/vnd.noblenet-web + npx image/vnd.net-fpx + nsc application/x-conference + nsf application/vnd.lotus-notes + ntf application/vnd.nitf + nzb application/x-nzb + oa2 application/vnd.fujitsu.oasys2 + oa3 application/vnd.fujitsu.oasys3 + oas application/vnd.fujitsu.oasys + obd application/x-msbinder + obj application/x-tgif + oda application/oda + odb application/vnd.oasis.opendocument.database + odc application/vnd.oasis.opendocument.chart + odf application/vnd.oasis.opendocument.formula + odft application/vnd.oasis.opendocument.formula-template + odg application/vnd.oasis.opendocument.graphics + odi application/vnd.oasis.opendocument.image + odm application/vnd.oasis.opendocument.text-master + odp application/vnd.oasis.opendocument.presentation + ods application/vnd.oasis.opendocument.spreadsheet + odt application/vnd.oasis.opendocument.text + oga audio/ogg + ogg audio/ogg + ogv video/ogg + ogx application/ogg + omdoc application/omdoc+xml + onepkg application/onenote + onetmp application/onenote + onetoc application/onenote + onetoc2 application/onenote + opf application/oebps-package+xml + opml text/x-opml + oprc application/vnd.palm + org application/vnd.lotus-organizer + osf application/vnd.yamaha.openscoreformat + osfpvg application/vnd.yamaha.openscoreformat.osfpvg+xml + otc application/vnd.oasis.opendocument.chart-template + otf application/x-font-otf + otg application/vnd.oasis.opendocument.graphics-template + oth application/vnd.oasis.opendocument.text-web + oti application/vnd.oasis.opendocument.image-template + otp application/vnd.oasis.opendocument.presentation-template + ots application/vnd.oasis.opendocument.spreadsheet-template + ott application/vnd.oasis.opendocument.text-template + oxps application/oxps + oxt application/vnd.openofficeorg.extension + p text/x-pascal + p10 application/pkcs10 + p12 application/x-pkcs12 + p7b application/x-pkcs7-certificates + p7c application/pkcs7-mime + p7m application/pkcs7-mime + p7r application/x-pkcs7-certreqresp + p7s application/pkcs7-signature + p8 application/pkcs8 + pas text/x-pascal + paw application/vnd.pawaafile + pbd application/vnd.powerbuilder6 + pbm image/x-portable-bitmap + pcap application/vnd.tcpdump.pcap + pcf application/x-font-pcf + pcl application/vnd.hp-pcl + pclxl application/vnd.hp-pclxl + pct image/pict + pcurl application/vnd.curl.pcurl + pcx image/x-pcx + pdb application/vnd.palm + pdf application/pdf + pfa application/x-font-type1 + pfb application/x-font-type1 + pfm application/x-font-type1 + pfr application/font-tdpfr + pfx application/x-pkcs12 + pgm image/x-portable-graymap + pgn application/x-chess-pgn + pgp application/pgp-encrypted + pic image/pict + pict image/pict + pkg application/octet-stream + pki application/pkixcmp + pkipath application/pkix-pkipath + plb application/vnd.3gpp.pic-bw-large + plc application/vnd.mobius.plc + plf application/vnd.pocketlearn + pls audio/x-scpls + pml application/vnd.ctc-posml + png image/png + pnm image/x-portable-anymap + pnt image/x-macpaint + portpkg application/vnd.macports.portpkg + pot application/vnd.ms-powerpoint + potm application/vnd.ms-powerpoint.template.macroenabled.12 + potx application/vnd.openxmlformats-officedocument.presentationml.template + ppam application/vnd.ms-powerpoint.addin.macroenabled.12 + ppd application/vnd.cups-ppd + ppm image/x-portable-pixmap + pps application/vnd.ms-powerpoint + ppsm application/vnd.ms-powerpoint.slideshow.macroenabled.12 + ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow + ppt application/vnd.ms-powerpoint + pptm application/vnd.ms-powerpoint.presentation.macroenabled.12 + pptx application/vnd.openxmlformats-officedocument.presentationml.presentation + pqa application/vnd.palm + prc application/x-mobipocket-ebook + pre application/vnd.lotus-freelance + prf application/pics-rules + ps application/postscript + psb application/vnd.3gpp.pic-bw-small + psd image/vnd.adobe.photoshop + psf application/x-font-linux-psf + pskcxml application/pskc+xml + ptid application/vnd.pvi.ptid1 + pub application/x-mspublisher + pvb application/vnd.3gpp.pic-bw-var + pwn application/vnd.3m.post-it-notes + pya audio/vnd.ms-playready.media.pya + pyv video/vnd.ms-playready.media.pyv + qam application/vnd.epson.quickanime + qbo application/vnd.intu.qbo + qfx application/vnd.intu.qfx + qps application/vnd.publishare-delta-tree + qt video/quicktime + qti image/x-quicktime + qtif image/x-quicktime + qwd application/vnd.quark.quarkxpress + qwt application/vnd.quark.quarkxpress + qxb application/vnd.quark.quarkxpress + qxd application/vnd.quark.quarkxpress + qxl application/vnd.quark.quarkxpress + qxt application/vnd.quark.quarkxpress + ra audio/x-pn-realaudio + ram audio/x-pn-realaudio + rar application/x-rar-compressed + ras image/x-cmu-raster + rcprofile application/vnd.ipunplugged.rcprofile + rdf application/rdf+xml + rdz application/vnd.data-vision.rdz + rep application/vnd.businessobjects + res application/x-dtbresource+xml + rgb image/x-rgb + rif application/reginfo+xml + rip audio/vnd.rip + ris application/x-research-info-systems + rl application/resource-lists+xml + rlc image/vnd.fujixerox.edmics-rlc + rld application/resource-lists-diff+xml + rm application/vnd.rn-realmedia + rmi audio/midi + rmp audio/x-pn-realaudio-plugin + rms application/vnd.jcp.javame.midlet-rms + rmvb application/vnd.rn-realmedia-vbr + rnc application/relax-ng-compact-syntax + roa application/rpki-roa + roff text/troff + rp9 application/vnd.cloanto.rp9 + rpss application/vnd.nokia.radio-presets + rpst application/vnd.nokia.radio-preset + rq application/sparql-query + rs application/rls-services+xml + rsd application/rsd+xml + rss application/rss+xml + rtf application/rtf + rtx text/richtext + s text/x-asm + s3m audio/s3m + saf application/vnd.yamaha.smaf-audio + sbml application/sbml+xml + sc application/vnd.ibm.secure-container + scd application/x-msschedule + scm application/vnd.lotus-screencam + scq application/scvp-cv-request + scs application/scvp-cv-response + scurl text/vnd.curl.scurl + sda application/vnd.stardivision.draw + sdc application/vnd.stardivision.calc + sdd application/vnd.stardivision.impress + sdkd application/vnd.solent.sdkm+xml + sdkm application/vnd.solent.sdkm+xml + sdp application/sdp + sdw application/vnd.stardivision.writer + see application/vnd.seemail + seed application/vnd.fdsn.seed + sema application/vnd.sema + semd application/vnd.semd + semf application/vnd.semf + ser application/java-serialized-object + setpay application/set-payment-initiation + setreg application/set-registration-initiation + sfd-hdstx application/vnd.hydrostatix.sof-data + sfs application/vnd.spotfire.sfs + sfv text/x-sfv + sgi image/sgi + sgl application/vnd.stardivision.writer-global + sgm text/sgml + sgml text/sgml + sh application/x-sh + shar application/x-shar + shf application/shf+xml + sid image/x-mrsid-image + sig application/pgp-signature + sil audio/silk + silo model/mesh + sis application/vnd.symbian.install + sisx application/vnd.symbian.install + sit application/x-stuffit + sitx application/x-stuffitx + skd application/vnd.koan + skm application/vnd.koan + skp application/vnd.koan + skt application/vnd.koan + sldm application/vnd.ms-powerpoint.slide.macroenabled.12 + sldx application/vnd.openxmlformats-officedocument.presentationml.slide + slt application/vnd.epson.salt + sm application/vnd.stepmania.stepchart + smf application/vnd.stardivision.math + smi application/smil+xml + smil application/smil+xml + smv video/x-smv + smzip application/vnd.stepmania.package + snd audio/basic + snf application/x-font-snf + so application/octet-stream + spc application/x-pkcs7-certificates + spf application/vnd.yamaha.smaf-phrase + spl application/x-futuresplash + spot text/vnd.in3d.spot + spp application/scvp-vp-response + spq application/scvp-vp-request + spx audio/ogg + sql application/x-sql + src application/x-wais-source + srt application/x-subrip + sru application/sru+xml + srx application/sparql-results+xml + ssdl application/ssdl+xml + sse application/vnd.kodak-descriptor + ssf application/vnd.epson.ssf + ssml application/ssml+xml + st application/vnd.sailingtracker.track + stc application/vnd.sun.xml.calc.template + std application/vnd.sun.xml.draw.template + stf application/vnd.wt.stf + sti application/vnd.sun.xml.impress.template + stk application/hyperstudio + stl application/vnd.ms-pki.stl + str application/vnd.pg.format + stw application/vnd.sun.xml.writer.template + sub text/vnd.dvb.subtitle + sus application/vnd.sus-calendar + susp application/vnd.sus-calendar + sv4cpio application/x-sv4cpio + sv4crc application/x-sv4crc + svc application/vnd.dvb.service + svd application/vnd.svd + svg image/svg+xml + svgz image/svg+xml + swa application/x-director + swf application/x-shockwave-flash + swi application/vnd.aristanetworks.swi + sxc application/vnd.sun.xml.calc + sxd application/vnd.sun.xml.draw + sxg application/vnd.sun.xml.writer.global + sxi application/vnd.sun.xml.impress + sxm application/vnd.sun.xml.math + sxw application/vnd.sun.xml.writer + t text/troff + t3 application/x-t3vm-image + taglet application/vnd.mynfc + tao application/vnd.tao.intent-module-archive + tar application/x-tar + tcap application/vnd.3gpp2.tcap + tcl application/x-tcl + teacher application/vnd.smart.teacher + tei application/tei+xml + teicorpus application/tei+xml + tex application/x-tex + texi application/x-texinfo + texinfo application/x-texinfo + text text/plain + tfi application/thraud+xml + tfm application/x-tex-tfm + tga image/x-tga + thmx application/vnd.ms-officetheme + tif image/tiff + tiff image/tiff + tmo application/vnd.tmobile-livetv + torrent application/x-bittorrent + tpl application/vnd.groove-tool-template + tpt application/vnd.trid.tpt + tr text/troff + tra application/vnd.trueapp + trm application/x-msterminal + tsd application/timestamped-data + tsv text/tab-separated-values + ttc application/x-font-ttf + ttf application/x-font-ttf + ttl text/turtle + twd application/vnd.simtech-mindmapper + twds application/vnd.simtech-mindmapper + txd application/vnd.genomatix.tuxedo + txf application/vnd.mobius.txf + txt text/plain + u32 application/x-authorware-bin + udeb application/x-debian-package + ufd application/vnd.ufdl + ufdl application/vnd.ufdl + ulw audio/basic + ulx application/x-glulx + umj application/vnd.umajin + unityweb application/vnd.unity + uoml application/vnd.uoml+xml + uri text/uri-list + uris text/uri-list + urls text/uri-list + ustar application/x-ustar + utz application/vnd.uiq.theme + uu text/x-uuencode + uva audio/vnd.dece.audio + uvd application/vnd.dece.data + uvf application/vnd.dece.data + uvg image/vnd.dece.graphic + uvh video/vnd.dece.hd + uvi image/vnd.dece.graphic + uvm video/vnd.dece.mobile + uvp video/vnd.dece.pd + uvs video/vnd.dece.sd + uvt application/vnd.dece.ttml+xml + uvu video/vnd.uvvu.mp4 + uvv video/vnd.dece.video + uvva audio/vnd.dece.audio + uvvd application/vnd.dece.data + uvvf application/vnd.dece.data + uvvg image/vnd.dece.graphic + uvvh video/vnd.dece.hd + uvvi image/vnd.dece.graphic + uvvm video/vnd.dece.mobile + uvvp video/vnd.dece.pd + uvvs video/vnd.dece.sd + uvvt application/vnd.dece.ttml+xml + uvvu video/vnd.uvvu.mp4 + uvvv video/vnd.dece.video + uvvx application/vnd.dece.unspecified + uvvz application/vnd.dece.zip + uvx application/vnd.dece.unspecified + uvz application/vnd.dece.zip + vcard text/vcard + vcd application/x-cdlink + vcf text/x-vcard + vcg application/vnd.groove-vcard + vcs text/x-vcalendar + vcx application/vnd.vcx + vis application/vnd.visionary + viv video/vnd.vivo + vob video/x-ms-vob + vor application/vnd.stardivision.writer + vox application/x-authorware-bin + vrml model/vrml + vsd application/vnd.visio + vsf application/vnd.vsf + vss application/vnd.visio + vst application/vnd.visio + vsw application/vnd.visio + vtu model/vnd.vtu + vxml application/voicexml+xml + w3d application/x-director + wad application/x-doom + wav audio/x-wav + wax audio/x-ms-wax + wbmp image/vnd.wap.wbmp + wbs application/vnd.criticaltools.wbs+xml + wbxml application/vnd.wap.wbxml + wcm application/vnd.ms-works + wdb application/vnd.ms-works + wdp image/vnd.ms-photo + weba audio/webm + webm video/webm + webp image/webp + wg application/vnd.pmi.widget + wgt application/widget + wks application/vnd.ms-works + wm video/x-ms-wm + wma audio/x-ms-wma + wmd application/x-ms-wmd + wmf application/x-msmetafile + wml text/vnd.wap.wml + wmlc application/vnd.wap.wmlc + wmls text/vnd.wap.wmlscript + wmlsc application/vnd.wap.wmlscriptc + wmv video/x-ms-wmv + wmx video/x-ms-wmx + wmz application/x-msmetafile + woff application/x-font-woff + wpd application/vnd.wordperfect + wpl application/vnd.ms-wpl + wps application/vnd.ms-works + wqd application/vnd.wqd + wri application/x-mswrite + wrl model/vrml + wsdl application/wsdl+xml + wspolicy application/wspolicy+xml + wtb application/vnd.webturbo + wvx video/x-ms-wvx + x32 application/x-authorware-bin + x3d model/x3d+xml + x3db model/x3d+binary + x3dbz model/x3d+binary + x3dv model/x3d+vrml + x3dvz model/x3d+vrml + x3dz model/x3d+xml + xaml application/xaml+xml + xap application/x-silverlight-app + xar application/vnd.xara + xbap application/x-ms-xbap + xbd application/vnd.fujixerox.docuworks.binder + xbm image/x-xbitmap + xdf application/xcap-diff+xml + xdm application/vnd.syncml.dm+xml + xdp application/vnd.adobe.xdp+xml + xdssc application/dssc+xml + xdw application/vnd.fujixerox.docuworks + xenc application/xenc+xml + xer application/patch-ops-error+xml + xfdf application/vnd.adobe.xfdf + xfdl application/vnd.xfdl + xht application/xhtml+xml + xhtml application/xhtml+xml + xhvml application/xv+xml + xif image/vnd.xiff + xla application/vnd.ms-excel + xlam application/vnd.ms-excel.addin.macroenabled.12 + xlc application/vnd.ms-excel + xlf application/x-xliff+xml + xlm application/vnd.ms-excel + xls application/vnd.ms-excel + xlsb application/vnd.ms-excel.sheet.binary.macroenabled.12 + xlsm application/vnd.ms-excel.sheet.macroenabled.12 + xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlt application/vnd.ms-excel + xltm application/vnd.ms-excel.template.macroenabled.12 + xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template + xlw application/vnd.ms-excel + xm audio/xm + xml application/xml + xo application/vnd.olpc-sugar + xop application/xop+xml + xpi application/x-xpinstall + xpl application/xproc+xml + xpm image/x-xpixmap + xpr application/vnd.is-xpr + xps application/vnd.ms-xpsdocument + xpw application/vnd.intercon.formnet + xpx application/vnd.intercon.formnet + xsl application/xml + xslt application/xslt+xml + xsm application/vnd.syncml+xml + xspf application/xspf+xml + xul application/vnd.mozilla.xul+xml + xvm application/xv+xml + xvml application/xv+xml + xwd image/x-xwindowdump + xyz chemical/x-xyz + xz application/x-xz + yang application/yang + yin application/yin+xml + z application/x-compress + Z application/x-compress + z1 application/x-zmachine + z2 application/x-zmachine + z3 application/x-zmachine + z4 application/x-zmachine + z5 application/x-zmachine + z6 application/x-zmachine + z7 application/x-zmachine + z8 application/x-zmachine + zaz application/vnd.zzazz.deck+xml + zip application/zip + zir application/vnd.zul + zirz application/vnd.zul + zmm application/vnd.handheld-entertainment+xml + """; + + private static readonly Dictionary _mimeTypeDic = _MIME_TYPES_RAW_STRING.Split('\n', StringSplitOptions.RemoveEmptyEntries) + .ToDictionary( // + x => x.Split(' ')[0].Trim() + , x => x.Split(' ')[1].Trim()); + + /// + /// 通过扩展名获取MIME类型 + /// + public static string GetMimeTypeByExtName(string extName) + { + _ = _mimeTypeDic.TryGetValue(extName.ToLowerInvariant(), out var ret); + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MinioHelper.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MinioHelper.cs new file mode 100644 index 00000000..f7d039bc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/MinioHelper.cs @@ -0,0 +1,43 @@ +using Minio; +using Minio.DataModel.Args; + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// Minio 帮助类 +/// +public sealed class MinioHelper(IOptions uploadOptions) : IScoped +{ + /// + /// 上传文件 + /// + /// 对象名称 + /// 文件流 + /// 文件类型 + /// 文件大小 + /// 可访问的url地址 + public async Task UploadAsync(string objectName, Stream fileStream, string contentType, long fileSize) + { + using var minio = new MinioClient().WithEndpoint(uploadOptions.Value.Minio.ServerAddress) + .WithCredentials( // + uploadOptions.Value.Minio.AccessKey, uploadOptions.Value.Minio.SecretKey) + .WithSSL(uploadOptions.Value.Minio.Secure) + .Build(); + + var beArgs = new BucketExistsArgs().WithBucket(uploadOptions.Value.Minio.BucketName); + + if (!await minio.BucketExistsAsync(beArgs).ConfigureAwait(false)) { + var mbArgs = new MakeBucketArgs().WithBucket(uploadOptions.Value.Minio.BucketName); + await minio.MakeBucketAsync(mbArgs).ConfigureAwait(false); + } + + var putArgs = new PutObjectArgs().WithBucket(uploadOptions.Value.Minio.BucketName) + .WithObject(objectName) + .WithStreamData(fileStream) + .WithObjectSize(fileSize) + .WithContentType(contentType); + _ = await minio.PutObjectAsync(putArgs).ConfigureAwait(false); + + return $"{uploadOptions.Value.Minio.AccessUrl}/{uploadOptions.Value.Minio.BucketName}/{objectName}"; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/RedisLocker.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/RedisLocker.cs new file mode 100644 index 00000000..d3b850e8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/RedisLocker.cs @@ -0,0 +1,68 @@ +using StackExchange.Redis; + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// Redis 分布锁 +/// +/// +/// Initializes a new instance of the class. +/// +#pragma warning disable DesignedForInheritance +public sealed class RedisLocker : IAsyncDisposable +#pragma warning restore DesignedForInheritance +{ + private readonly IDatabase _redisDatabase; + + private readonly string _redisKey; + + /// + /// Initializes a new instance of the class. + /// Redis 分布锁 + /// + private RedisLocker(IDatabase redisDatabase, string redisKey) + { + _redisDatabase = redisDatabase; + _redisKey = redisKey; + } + + /// + /// 获取锁 + /// + /// NetAdminGetLockerException + public static async Task GetLockerAsync(IDatabase redisDatabase, string lockerName, TimeSpan lockerExpire, int retryCount + , TimeSpan retryDelay) + { + lockerName = $"{nameof(RedisLocker)}.{lockerName}"; + var setOk = false; + for (var i = 0; i != retryCount; ++i) { + try { + setOk = await redisDatabase + .StringSetAsync(lockerName, RedisValue.EmptyString, lockerExpire, When.NotExists, CommandFlags.DemandMaster) + .ConfigureAwait(false); + } + catch (Exception ex) { + LogHelper.Get().Error(ex.Message); + } + + if (setOk) { + return new RedisLocker(redisDatabase, lockerName); + } + + await Task.Delay(retryDelay).ConfigureAwait(false); + } + + throw new NetAdminGetLockerException(lockerName); + } + + /// + public async ValueTask DisposeAsync() + { + try { + _ = await _redisDatabase.KeyDeleteAsync(_redisKey, CommandFlags.DemandMaster | CommandFlags.FireAndForget).ConfigureAwait(false); + } + catch (Exception ex) { + LogHelper.Get().Error(ex.Message); + } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/UserAgentParser.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/UserAgentParser.cs new file mode 100644 index 00000000..9247e56e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/UserAgentParser.cs @@ -0,0 +1,331 @@ +// ReSharper disable StringLiteralTypo + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 用户代理字符串解析器 +/// +public sealed class UserAgentParser +{ + private static readonly Dictionary _browsers = new() { + { "OPR", "Opera" } + , { "Flock", "Flock" } + , { "Edge", "Spartan" } + , { "Chrome", "Chrome" } + , { "Opera.*?Version", "Opera" } + , { "Opera", "Opera" } + , { "MSIE", "Internet Explorer" } + , { "Internet Explorer", "Internet Explorer" } + , { "Trident.* rv", "Internet Explorer" } + , { "Shiira", "Shiira" } + , { "Firefox", "Firefox" } + , { "Chimera", "Chimera" } + , { "Phoenix", "Phoenix" } + , { "Firebird", "Firebird" } + , { "Camino", "Camino" } + , { "Netscape", "Netscape" } + , { "OmniWeb", "OmniWeb" } + , { "Safari", "Safari" } + , { "Mozilla", "Mozilla" } + , { "Konqueror", "Konqueror" } + , { "icab", "iCab" } + , { "Lynx", "Lynx" } + , { "Links", "Links" } + , { "hotjava", "HotJava" } + , { "amaya", "Amaya" } + , { "IBrowse", "IBrowse" } + , { "Maxthon", "Maxthon" } + , { "Ubuntu", "Ubuntu Web Browser" } + }; + + private static readonly Dictionary _mobiles = new() { + // Legacy + { "mobileexplorer", "Mobile Explorer" } + , { "palmsource", "Palm" } + , { "palmscape", "Palmscape" } + , + + // Phones and Manufacturers + { "motorola", "Motorola" } + , { "nokia", "Nokia" } + , { "palm", "Palm" } + , { "iphone", "Apple iPhone" } + , { "ipad", "iPad" } + , { "ipod", "Apple iPod Touch" } + , { "sony", "Sony Ericsson" } + , { "ericsson", "Sony Ericsson" } + , { "blackberry", "BlackBerry" } + , { "cocoon", "O2 Cocoon" } + , { "blazer", "Treo" } + , { "lg", "LG" } + , { "amoi", "Amoi" } + , { "xda", "XDA" } + , { "mda", "MDA" } + , { "vario", "Vario" } + , { "htc", "HTC" } + , { "samsung", "Samsung" } + , { "sharp", "Sharp" } + , { "sie-", "Siemens" } + , { "alcatel", "Alcatel" } + , { "benq", "BenQ" } + , { "ipaq", "HP iPaq" } + , { "mot-", "Motorola" } + , { "playstation portable", "PlayStation Portable" } + , { "playstation 3", "PlayStation 3" } + , { "playstation vita", "PlayStation Vita" } + , { "hiptop", "Danger Hiptop" } + , { "nec-", "NEC" } + , { "panasonic", "Panasonic" } + , { "philips", "Philips" } + , { "sagem", "Sagem" } + , { "sanyo", "Sanyo" } + , { "spv", "SPV" } + , { "zte", "ZTE" } + , { "sendo", "Sendo" } + , { "nintendo dsi", "Nintendo DSi" } + , { "nintendo ds", "Nintendo DS" } + , { "nintendo 3ds", "Nintendo 3DS" } + , { "wii", "Nintendo Wii" } + , { "open web", "Open Web" } + , { "openweb", "OpenWeb" } + , + + // Operating Systems + { "android", "Android" } + , { "symbian", "Symbian" } + , { "SymbianOS", "SymbianOS" } + , { "elaine", "Palm" } + , { "series60", "Symbian S60" } + , { "windows ce", "Windows CE" } + , + + // Browsers + { "obigo", "Obigo" } + , { "netfront", "Netfront Browser" } + , { "openwave", "Openwave Browser" } + , { "mobilexplorer", "Mobile Explorer" } + , { "operamini", "Opera Mini" } + , { "opera mini", "Opera Mini" } + , { "opera mobi", "Opera Mobile" } + , { "fennec", "Firefox Mobile" } + , + + // Other + { "digital paths", "Digital Paths" } + , { "avantgo", "AvantGo" } + , { "xiino", "Xiino" } + , { "novarra", "Novarra Transcoder" } + , { "vodafone", "Vodafone" } + , { "docomo", "NTT DoCoMo" } + , { "o2", "O2" } + , + + // Fallback + { "mobile", "Generic Mobile" } + , { "wireless", "Generic Mobile" } + , { "j2me", "Generic Mobile" } + , { "midp", "Generic Mobile" } + , { "cldc", "Generic Mobile" } + , { "up.link", "Generic Mobile" } + , { "up.browser", "Generic Mobile" } + , { "smartphone", "Generic Mobile" } + , { "cellphone", "Generic Mobile" } + }; + + private static readonly Dictionary _platforms = new() { + { "windows nt 10.0", "Windows 10" } + , { "windows nt 6.3", "Windows 8.1" } + , { "windows nt 6.2", "Windows 8" } + , { "windows nt 6.1", "Windows 7" } + , { "windows nt 6.0", "Windows Vista" } + , { "windows nt 5.2", "Windows 2003" } + , { "windows nt 5.1", "Windows XP" } + , { "windows nt 5.0", "Windows 2000" } + , { "windows nt 4.0", "Windows NT 4.0" } + , { "winnt4.0", "Windows NT 4.0" } + , { "winnt 4.0", "Windows NT" } + , { "winnt", "Windows NT" } + , { "windows 98", "Windows 98" } + , { "win98", "Windows 98" } + , { "windows 95", "Windows 95" } + , { "win95", "Windows 95" } + , { "windows phone", "Windows Phone" } + , { "windows", "Unknown Windows OS" } + , { "android", "Android" } + , { "blackberry", "BlackBerry" } + , { "iphone", "iOS" } + , { "ipad", "iOS" } + , { "ipod", "iOS" } + , { "os x", "Mac OS X" } + , { "ppc mac", "Power PC Mac" } + , { "freebsd", "FreeBSD" } + , { "ppc", "Macintosh" } + , { "linux", "Linux" } + , { "debian", "Debian" } + , { "sunos", "Sun Solaris" } + , { "beos", "BeOS" } + , { "apachebench", "ApacheBench" } + , { "aix", "AIX" } + , { "irix", "Irix" } + , { "osf", "DEC OSF" } + , { "hp-ux", "HP-UX" } + , { "netbsd", "NetBSD" } + , { "bsdi", "BSDi" } + , { "openbsd", "OpenBSD" } + , { "gnu", "GNU/Linux" } + , { "unix", "Unknown Unix OS" } + , { "symbian", "Symbian OS" } + }; + + private static readonly Dictionary _robots = new() { + { "googlebot", "Googlebot" } + , { "msnbot", "MSNBot" } + , { "baiduspider", "Baiduspider" } + , { "bingbot", "Bing" } + , { "slurp", "Inktomi Slurp" } + , { "yahoo", "Yahoo" } + , { "ask jeeves", "Ask Jeeves" } + , { "fastcrawler", "FastCrawler" } + , { "infoseek", "InfoSeek Robot 1.0" } + , { "lycos", "Lycos" } + , { "yandex", "YandexBot" } + , { "mediapartners-google", "MediaPartners Google" } + , { "CRAZYWEBCRAWLER", "Crazy Webcrawler" } + , { "adsbot-google", "AdsBot Google" } + , { "feedfetcher-google", "Feedfetcher Google" } + , { "curious george", "Curious George" } + , { "ia_archiver", "Alexa Crawler" } + , { "MJ12bot", "Majestic-12" } + , { "Uptimebot", "Uptimebot" } + }; + + private readonly string _agent; + + /// + /// Initializes a new instance of the class. + /// + private UserAgentParser(string userAgentString) + { + _agent = userAgentString.Trim(); + _ = SetPlatform(); + if (SetRobot()) { + return; + } + + if (SetBrowser()) { + return; + } + + if (SetMobile()) { + // + } + } + + /// + /// 浏览器 + /// + public string Browser { get; set; } = string.Empty; + + /// + /// 浏览器版本 + /// + public string BrowserVersion { get; set; } = string.Empty; + + /// + /// 是浏览器 + /// + public bool IsBrowser { get; set; } + + /// + /// 是手机 + /// + public bool IsMobile { get; set; } + + /// + /// 是机器人 + /// + public bool IsRobot { get; set; } + + /// + /// 手机 + /// + public string Mobile { get; set; } = string.Empty; + + /// + /// 平台 + /// + public string Platform { get; private set; } = string.Empty; + + /// + /// 机器人 + /// + public string Robot { get; set; } = string.Empty; + + /// + /// 创建 UserAgentParser + /// + public static UserAgentParser Create(string userAgentString) + { + return userAgentString == null ? null : new UserAgentParser(userAgentString); + } + + private bool SetBrowser() + { + foreach (var item in _browsers) { + var match = Regex.Match(_agent, $@"{item.Key}.*?([0-9\.]+)", RegexOptions.IgnoreCase); + if (!match.Success) { + continue; + } + + IsBrowser = true; + BrowserVersion = match.Groups[1].Value; + Browser = item.Value; + _ = SetMobile(); + return true; + } + + return false; + } + + private bool SetMobile() + { + var kv = _mobiles.FirstOrDefault(x => // + _agent.Contains(x.Key, StringComparison.OrdinalIgnoreCase)); + + if (kv.Key == null) { + return false; + } + + IsMobile = true; + Mobile = kv.Value; + return false; + } + + private bool SetPlatform() + { + var kv = _platforms.FirstOrDefault(x => // + Regex.IsMatch(_agent, $"{Regex.Escape(x.Key)}", RegexOptions.IgnoreCase)); + + if (kv.Key == null) { + Platform = "Unknown Platform"; + return false; + } + + Platform = kv.Value; + return true; + } + + private bool SetRobot() + { + var kv = _robots.FirstOrDefault(x => // + Regex.IsMatch(_agent, $"{Regex.Escape(x.Key)}", RegexOptions.IgnoreCase)); + if (kv.Key == null) { + return false; + } + + IsRobot = true; + Robot = kv.Value; + _ = SetMobile(); + return true; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/XmlCommentReader.cs b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/XmlCommentReader.cs new file mode 100644 index 00000000..1b0a3d7b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Infrastructure/Utils/XmlCommentReader.cs @@ -0,0 +1,92 @@ +using System.Xml; + +namespace NetAdmin.Infrastructure.Utils; + +/// +/// 程序集注释文档读取器 +/// +public sealed class XmlCommentReader : ISingleton +{ + private const string _XPATH = "//doc/members/member[@name=\"{0}\"]"; + private static readonly Regex _regex = new(@"`\d+"); + private readonly List _xmlDocuments = []; + + /// + /// Initializes a new instance of the class. + /// + public XmlCommentReader(IOptions specificationDocumentSettings) + { + var xmlComments = specificationDocumentSettings.Value.XmlComments // + ?? App.GetConfig(nameof(SpecificationDocumentSettingsOptions).TrimSuffixOptions()) + .XmlComments; + foreach (var commentFile in xmlComments) { + var xmlDoc = new XmlDocument(); + var xmlFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, commentFile); + if (!File.Exists(xmlFilePath)) { + LogHelper.Get().Warn($"{Ln.XML注释文件不存在}: {xmlFilePath}"); + continue; + } + + xmlDoc.Load(xmlFilePath); + _xmlDocuments.Add(xmlDoc); + } + } + + /// + /// 获取指定类型的注释 + /// + /// InvalidCastException + public string GetComments(MemberInfo memberInfo) + { + var node = memberInfo switch { + MethodInfo method => GetNodeByMethod(method), Type type => GetNodeByType(type), _ => throw new InvalidCastException() + }; + + if (node?.FirstChild?.Name != "inheritdoc") { + return node?.FirstChild?.InnerText.Trim(); + } + + var cref = node.FirstChild.Attributes?["cref"]?.Value; + if (cref != null) { + return GetComments(App.EffectiveTypes.Single(x => x.FullName == cref[2..])); + } + + var methodFromBaseType = memberInfo.DeclaringType?.BaseType?.GetMethod(memberInfo.Name); + + if (methodFromBaseType != null) { + return GetComments(methodFromBaseType); + } + + var methodFromInterface = memberInfo.DeclaringType?.GetInterfaces().Select(x => x.GetMethod(memberInfo.Name)).FirstOrDefault(x => x != null); + return methodFromInterface == null ? null : GetComments(methodFromInterface); + } + + private XmlNode GetNodeByMethod(MethodInfo method) + { + var nodeName = $"M:{method.DeclaringType}.{method.Name}"; + var parameters = method.GetParameters(); + if (parameters.Length != 0) { + nodeName += $"({string.Join(',', parameters.Select(Replace))})"; + } + + return _xmlDocuments.Select(xmlDoc => xmlDoc.SelectSingleNode( + #pragma warning disable CA1863 + string.Format(NumberFormatInfo.InvariantInfo, _XPATH, nodeName))) + #pragma warning restore CA1863 + .FirstOrDefault(ret => ret != null); + + static string Replace(ParameterInfo parameterInfo) + { + return _regex.Replace(parameterInfo.ParameterType.ToString(), string.Empty).Replace("[", "{").Replace("]", "}"); + } + } + + private XmlNode GetNodeByType(Type type) + { + return _xmlDocuments.Select(xmlDoc => xmlDoc.SelectSingleNode( + #pragma warning disable CA1863 + string.Format(NumberFormatInfo.InvariantInfo, _XPATH, $"T:{type.FullName}"))) + #pragma warning restore CA1863 + .FirstOrDefault(ret => ret != null); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IApiModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IApiModule.cs new file mode 100644 index 00000000..ec4df342 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IApiModule.cs @@ -0,0 +1,17 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 接口模块 +/// +public interface IApiModule : ICrudModule +{ + /// + /// 同步接口 + /// + Task SyncAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICacheModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICacheModule.cs new file mode 100644 index 00000000..e8e755a5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICacheModule.cs @@ -0,0 +1,34 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 缓存模块 +/// +public interface ICacheModule +{ + /// + /// 批量删除缓存项 + /// + Task BulkDeleteEntryAsync(BulkReq req); + + /// + /// 缓存统计 + /// + Task CacheStatisticsAsync(); + + /// + /// 删除缓存项 + /// + Task DeleteEntryAsync(DelEntryReq req); + + /// + /// 获取所有缓存项 + /// + Task> GetAllEntriesAsync(GetAllEntriesReq req); + + /// + /// 获取缓存项 + /// + Task GetEntryAsync(GetEntriesReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICaptchaModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICaptchaModule.cs new file mode 100644 index 00000000..3e15a386 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ICaptchaModule.cs @@ -0,0 +1,19 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 人机验证模块 +/// +public interface ICaptchaModule +{ + /// + /// 获取人机校验图 + /// + Task GetCaptchaImageAsync(); + + /// + /// 完成人机校验 + /// + Task VerifyCaptchaAsync(VerifyCaptchaReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConfigModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConfigModule.cs new file mode 100644 index 00000000..6614f1de --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConfigModule.cs @@ -0,0 +1,27 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 配置模块 +/// +public interface IConfigModule : ICrudModule +{ + /// + /// 编辑配置 + /// + Task EditAsync(EditConfigReq req); + + /// + /// 获取最新有效配置 + /// + Task GetLatestConfigAsync(); + + /// + /// 设置配置启用状态 + /// + Task SetEnabledAsync(SetConfigEnabledReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConstantModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConstantModule.cs new file mode 100644 index 00000000..57057bff --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IConstantModule.cs @@ -0,0 +1,27 @@ +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 常量模块 +/// +public interface IConstantModule +{ + /// + /// 获得常量字符串 + /// + IDictionary GetCharsDic(); + + /// + /// 获得公共枚举值 + /// + IDictionary> GetEnums(); + + /// + /// 获得本地化字符串 + /// + IDictionary GetLocalizedStrings(); + + /// + /// 获得数字常量表 + /// + IDictionary GetNumbersDic(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDeptModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDeptModule.cs new file mode 100644 index 00000000..d34043d3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDeptModule.cs @@ -0,0 +1,22 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 部门模块 +/// +public interface IDeptModule : ICrudModule +{ + /// + /// 编辑部门 + /// + Task EditAsync(EditDeptReq req); + + /// + /// 启用/禁用部门 + /// + Task SetEnabledAsync(SetDeptEnabledReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDevModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDevModule.cs new file mode 100644 index 00000000..d8db9483 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDevModule.cs @@ -0,0 +1,24 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 开发模块 +/// +public interface IDevModule +{ + /// + /// 生成后端代码 + /// + Task GenerateCsCodeAsync(GenerateCsCodeReq req); + + /// + /// 生成图标代码 + /// + Task GenerateIconCodeAsync(GenerateIconCodeReq req); + + /// + /// 生成接口代码 + /// + Task GenerateJsCodeAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicCatalogModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicCatalogModule.cs new file mode 100644 index 00000000..679eb82d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicCatalogModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 字典目录模块 +/// +public interface IDicCatalogModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicContentModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicContentModule.cs new file mode 100644 index 00000000..c995fa24 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicContentModule.cs @@ -0,0 +1,17 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 字典内容模块 +/// +public interface IDicContentModule : ICrudModule +{ + /// + /// 启用/禁用字典内容 + /// + Task SetEnabledAsync(SetDicContentEnabledReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicModule.cs new file mode 100644 index 00000000..4f57971f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IDicModule.cs @@ -0,0 +1,95 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 字典模块 +/// +public interface IDicModule +{ + /// + /// 批量删除字典目录 + /// + Task BulkDeleteCatalogAsync(BulkReq req); + + /// + /// 批量删除字典内容 + /// + Task BulkDeleteContentAsync(BulkReq req); + + /// + /// 创建字典目录 + /// + Task CreateCatalogAsync(CreateDicCatalogReq req); + + /// + /// 创建字典内容 + /// + Task CreateContentAsync(CreateDicContentReq req); + + /// + /// 删除字典目录 + /// + Task DeleteCatalogAsync(DelReq req); + + /// + /// 删除字典内容 + /// + Task DeleteContentAsync(DelReq req); + + /// + /// 编辑字典目录 + /// + Task EditCatalogAsync(EditDicCatalogReq req); + + /// + /// 编辑字典内容 + /// + Task EditContentAsync(EditDicContentReq req); + + /// + /// 导出字典内容 + /// + Task ExportContentAsync(QueryReq req); + + /// + /// 获取单个字典目录 + /// + Task GetCatalogAsync(QueryDicCatalogReq req); + + /// + /// 获取单个字典内容 + /// + Task GetContentAsync(QueryDicContentReq req); + + /// + /// 获取字典值 + /// + Task GetDicValueAsync(GetDicValueReq req); + + /// + /// 分页查询字典目录 + /// + Task> PagedQueryCatalogAsync(PagedQueryReq req); + + /// + /// 分页查询字典内容 + /// + Task> PagedQueryContentAsync(PagedQueryReq req); + + /// + /// 查询字典目录 + /// + Task> QueryCatalogAsync(QueryReq req); + + /// + /// 查询字典内容 + /// + Task> QueryContentAsync(QueryReq req); + + /// + /// 启用/禁用字典内容 + /// + Task SetEnabledAsync(SetDicContentEnabledReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IFileModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IFileModule.cs new file mode 100644 index 00000000..c6c54631 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IFileModule.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 文件模块 +/// +public interface IFileModule +{ + /// + /// 文件上传 + /// + Task UploadAsync(IFormFile file); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs new file mode 100644 index 00000000..1da32e29 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobModule.cs @@ -0,0 +1,63 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 计划作业模块 +/// +public interface IJobModule : ICrudModule +{ + /// + /// 作业记录计数 + /// + Task CountRecordAsync(QueryReq req); + + /// + /// 编辑作业 + /// + Task EditAsync(EditJobReq req); + + /// + /// 执行作业 + /// + Task ExecuteAsync(QueryJobReq req); + + /// + /// 导出作业记录 + /// + Task ExportRecordAsync(QueryReq req); + + /// + /// 获取单个作业记录 + /// + Task GetRecordAsync(QueryJobRecordReq req); + + /// + /// 获取作业记录条形图数据 + /// + Task> GetRecordBarChartAsync(QueryReq req); + + /// + /// 状态码分组作业记录饼图数据 + /// + Task> GetRecordPieChartByHttpStatusCodeAsync(QueryReq req); + + /// + /// 名称分组作业记录饼图数据 + /// + Task> GetRecordPieChartByNameAsync(QueryReq req); + + /// + /// 分页查询作业记录 + /// + Task> PagedQueryRecordAsync(PagedQueryReq req); + + /// + /// 设置计划作业启用状态 + /// + Task SetEnabledAsync(SetJobEnabledReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs new file mode 100644 index 00000000..91080612 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IJobRecordModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 计划作业执行记录模块 +/// +public interface IJobRecordModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ILoginLogModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ILoginLogModule.cs new file mode 100644 index 00000000..00f977cc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ILoginLogModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 登录日志模块 +/// +public interface ILoginLogModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IMenuModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IMenuModule.cs new file mode 100644 index 00000000..5e41c58b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IMenuModule.cs @@ -0,0 +1,22 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 菜单模块 +/// +public interface IMenuModule : ICrudModule +{ + /// + /// 编辑菜单 + /// + Task EditAsync(EditMenuReq req); + + /// + /// 当前用户菜单 + /// + Task> UserMenusAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogDetailModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogDetailModule.cs new file mode 100644 index 00000000..6a7fe99f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogDetailModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 请求日志明细模块 +/// +public interface IRequestLogDetailModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogModule.cs new file mode 100644 index 00000000..9691eb7f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRequestLogModule.cs @@ -0,0 +1,27 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 请求日志模块 +/// +public interface IRequestLogModule : ICrudModule +{ + /// + /// 获取条形图数据 + /// + Task> GetBarChartAsync(QueryReq req); + + /// + /// 描述分组饼图数据 + /// + Task> GetPieChartByApiSummaryAsync(QueryReq req); + + /// + /// 状态码分组饼图数据 + /// + Task> GetPieChartByHttpStatusCodeAsync(QueryReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRoleModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRoleModule.cs new file mode 100644 index 00000000..0b1f3284 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IRoleModule.cs @@ -0,0 +1,32 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 角色模块 +/// +public interface IRoleModule : ICrudModule +{ + /// + /// 编辑角色 + /// + Task EditAsync(EditRoleReq req); + + /// + /// 设置是否显示仪表板 + /// + Task SetDisplayDashboardAsync(SetDisplayDashboardReq req); + + /// + /// 启用/禁用角色 + /// + Task SetEnabledAsync(SetRoleEnabledReq req); + + /// + /// 设置是否忽略权限控制 + /// + Task SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgDeptModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgDeptModule.cs new file mode 100644 index 00000000..1487bc98 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgDeptModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 站内信-部门映射模块 +/// +public interface ISiteMsgDeptModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgFlagModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgFlagModule.cs new file mode 100644 index 00000000..738b1a1e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgFlagModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 站内信标记模块 +/// +public interface ISiteMsgFlagModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgModule.cs new file mode 100644 index 00000000..7c80c27a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgModule.cs @@ -0,0 +1,38 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 站内信模块 +/// +public interface ISiteMsgModule : ICrudModule +{ + /// + /// 编辑站内信 + /// + Task EditAsync(EditSiteMsgReq req); + + /// + /// 获取单个我的站内信 + /// + Task GetMineAsync(QuerySiteMsgReq req); + + /// + /// 分页查询我的站内信 + /// + Task> PagedQueryMineAsync(PagedQueryReq req); + + /// + /// 设置站内信状态 + /// + Task SetSiteMsgStatusAsync(SetUserSiteMsgStatusReq req); + + /// + /// 未读数量 + /// + Task UnreadCountAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgRoleModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgRoleModule.cs new file mode 100644 index 00000000..c2093f69 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgRoleModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 站内信-角色映射模块 +/// +public interface ISiteMsgRoleModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgUserModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgUserModule.cs new file mode 100644 index 00000000..f7ac0d6c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/ISiteMsgUserModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 站内信-用户映射模块 +/// +public interface ISiteMsgUserModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IToolsModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IToolsModule.cs new file mode 100644 index 00000000..912a4ff2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IToolsModule.cs @@ -0,0 +1,39 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 工具模块 +/// +public interface IToolsModule +{ + /// + /// Aes解密 + /// + string AesDecode(AesDecodeReq req); + + /// + /// 执行SQL语句 + /// + Task ExecuteSqlAsync(ExecuteSqlReq req); + + /// + /// 获取更新日志 + /// + Task GetChangeLogAsync(); + + /// + /// 获取模块信息 + /// + Task> GetModulesAsync(); + + /// + /// 获取服务器时间 + /// + Task GetServerUtcTimeAsync(); + + /// + /// 获取版本信息 + /// + Task GetVersionAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserModule.cs new file mode 100644 index 00000000..be2c28fb --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserModule.cs @@ -0,0 +1,83 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 用户模块 +/// +public partial interface IUserModule : ICrudModule +{ + /// + /// 检查手机号码是否可用 + /// + Task CheckMobileAvailableAsync(CheckMobileAvailableReq req); + + /// + /// 检查用户名是否可用 + /// + Task CheckUserNameAvailableAsync(CheckUserNameAvailableReq req); + + /// + /// 编辑用户 + /// + Task EditAsync(EditUserReq req); + + /// + /// 密码登录 + /// + Task LoginByPwdAsync(LoginByPwdReq req); + + /// + /// 短信登录 + /// + Task LoginBySmsAsync(LoginBySmsReq req); + + /// + /// 查询用户档案 + /// + Task> QueryProfileAsync(QueryReq req); + + /// + /// 注册用户 + /// + Task RegisterAsync(RegisterUserReq req); + + /// + /// 重设密码 + /// + Task ResetPasswordAsync(ResetPasswordReq req); + + /// + /// 设置用户头像 + /// + Task SetAvatarAsync(SetAvatarReq req); + + /// + /// 设置邮箱 + /// + Task SetEmailAsync(SetEmailReq req); + + /// + /// 启用/禁用用户 + /// + Task SetEnabledAsync(SetUserEnabledReq req); + + /// + /// 设置手机号码 + /// + Task SetMobileAsync(SetMobileReq req); + + /// + /// 设置密码 + /// + Task SetPasswordAsync(SetPasswordReq req); + + /// + /// 当前用户信息 + /// + Task UserInfoAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserProfileModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserProfileModule.cs new file mode 100644 index 00000000..59964736 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IUserProfileModule.cs @@ -0,0 +1,11 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 用户档案模块 +/// +public interface IUserProfileModule : ICrudModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IVerifyCodeModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IVerifyCodeModule.cs new file mode 100644 index 00000000..7c53f851 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/IVerifyCodeModule.cs @@ -0,0 +1,25 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +/// +/// 验证码模块 +/// +public interface IVerifyCodeModule : ICrudModule +{ + /// + /// 发送验证码 + /// + Task SendVerifyCodeAsync(SendVerifyCodeReq req); + + /// + /// 完成验证 + /// + /// + /// 对于验证失败的,不主动删除缓存,通过防火墙来应对暴力破解 + /// + Task VerifyAsync(VerifyCodeReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/Session/IUserModule.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/Session/IUserModule.cs new file mode 100644 index 00000000..8527320b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Modules/Sys/Session/IUserModule.cs @@ -0,0 +1,17 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +// ReSharper disable once CheckNamespace +namespace NetAdmin.SysComponent.Application.Modules.Sys; + +public partial interface IUserModule +{ + /// + /// 获取当前用户应用配置 + /// + Task GetSessionUserAppConfigAsync(); + + /// + /// 设置当前用户应用配置 + /// + Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/NetAdmin.SysComponent.Application.csproj b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/NetAdmin.SysComponent.Application.csproj new file mode 100644 index 00000000..b2e71582 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/NetAdmin.SysComponent.Application.csproj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/ProjectUsings.cs new file mode 100644 index 00000000..62d5b2cf --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/ProjectUsings.cs @@ -0,0 +1,11 @@ +global using NetAdmin.Application.Modules; +global using NetAdmin.Application.Repositories; +global using NetAdmin.Application.Services; +global using NetAdmin.Domain.Dto.Dependency; +global using NetAdmin.SysComponent.Application.Modules.Sys; +global using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +global using NetAdmin.SysComponent.Domain.DbMaps.Sys; +global using NetAdmin.SysComponent.Domain.Dto.Sys; +global using NetAdmin.SysComponent.Domain.Enums.Sys; +global using DynamicFilterInfo = NetAdmin.Domain.Dto.DynamicFilterInfo; +global using DynamicFilterOperators = NetAdmin.Domain.Enums.DynamicFilterOperators; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs new file mode 100644 index 00000000..c4a59518 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ApiService.cs @@ -0,0 +1,168 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class ApiService( + BasicRepository rpo // + , XmlCommentReader xmlCommentReader // + , IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) // + : RedisService(rpo), IApiService +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public Task CreateAsync(CreateApiReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.接口导出); + } + + /// + public Task GetAsync(QueryApiReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter).ToTreeListAsync().ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public IEnumerable ReflectionList(bool excludeAnonymous = true) + { + var regex = new Regex(@"\.(\w+)$", RegexOptions.Compiled); + + var actionDescriptors // + = actionDescriptorCollectionProvider.ActionDescriptors.Items.Cast(); + + if (excludeAnonymous) { + actionDescriptors = actionDescriptors.Where(x => x.EndpointMetadata.All(y => y is AllowAnonymousAttribute)); + } + + var actionGroup // + = actionDescriptors.GroupBy(x => x.ControllerTypeInfo); + + return actionGroup.Select(SelectQueryApiRsp); + + QueryApiRsp SelectQueryApiRsp(IGrouping group) + { + var first = group.First()!; + + var id = Regex.Replace( // + first.AttributeRouteInfo!.Template!, $"/{first.ActionName}$", string.Empty); + return new QueryApiRsp { + Summary = xmlCommentReader.GetComments(group.Key) + , Name = first.ControllerName + , Id = id + , Children = GetChildren(group) + , Namespace = regex.Match(group.Key.Namespace!).Groups[1].Value.ToLowerInvariant() + , PathCrc32 = id.Crc32() + }; + } + } + + /// + public async Task SyncAsync() + { + await using var locker = await GetLockerOnceAsync(nameof(SyncAsync)).ConfigureAwait(false); + _ = await Rpo.DeleteAsync(_ => true).ConfigureAwait(false); + + var list = ReflectionList(false); + + EnableCascadeSave = true; + foreach (var item in list) { + var entity = item.Adapt(); + _ = await Rpo.InsertAsync(entity).ConfigureAwait(false); + } + } + + private IEnumerable GetChildren(IEnumerable actionDescriptors) + { + return actionDescriptors // + .Select(x => { + var id = x.AttributeRouteInfo!.Template; + return new QueryApiRsp { + Summary = xmlCommentReader.GetComments(x.MethodInfo) + , Name = x.ActionName + , Id = id + , Method = x.ActionConstraints?.OfType().FirstOrDefault()?.HttpMethods.First() + , PathCrc32 = id.Crc32() + }; + }); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs new file mode 100644 index 00000000..9c3c39f5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CacheService.cs @@ -0,0 +1,105 @@ +using System.Collections.Concurrent; +using NetAdmin.SysComponent.Domain.Dto.Sys.Cache; +using StackExchange.Redis; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class CacheService(IConnectionMultiplexer connectionMultiplexer) // + : ServiceBase, ICacheService +{ + private readonly InstanceNode _redisInstance; + + /// + /// Initializes a new instance of the class. + /// + public CacheService(IConnectionMultiplexer connectionMultiplexer, IOptions redisOptions) // + : this(connectionMultiplexer) // + { + _redisInstance = redisOptions.Value.Instances.First(x => x.Name == Chars.FLG_REDIS_INSTANCE_DATA_CACHE); + } + + /// + public async Task BulkDeleteEntryAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteEntryAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public async Task CacheStatisticsAsync() + { + var database = connectionMultiplexer.GetDatabase(_redisInstance.Database); + return new CacheStatisticsRsp((string)await database.ExecuteAsync("info").ConfigureAwait(false)) { + DbSize = (long)await database.ExecuteAsync("dbSize").ConfigureAwait(false) + }; + } + + /// + public async Task DeleteEntryAsync(DelEntryReq req) + { + req.ThrowIfInvalid(); + #pragma warning disable VSTHRD103 + var database = connectionMultiplexer.GetDatabase(_redisInstance.Database); + var delSuccess = await database.KeyDeleteAsync(req.Key).ConfigureAwait(false); + return delSuccess ? 1 : 0; + } + + /// + public async Task> GetAllEntriesAsync(GetAllEntriesReq req) + { + req.ThrowIfInvalid(); + #pragma warning disable VSTHRD103 + var server = connectionMultiplexer.GetServers()[0]; + + var database = connectionMultiplexer.GetDatabase(_redisInstance.Database); + var keys = server.Keys(_redisInstance.Database, $"*{req.Keywords}*", Numbers.MAX_LIMIT_BULK_REQ).Take(Numbers.MAX_LIMIT_BULK_REQ).ToList(); + #pragma warning restore VSTHRD103 + + var dic = new ConcurrentDictionary(); + + await Parallel.ForEachAsync( + keys + , async (key, _) => + dic.TryAdd( + key + , (DateTime.Now + await database.KeyTimeToLiveAsync(key).ConfigureAwait(false) + , await database.KeyTypeAsync(key).ConfigureAwait(false)))) + .ConfigureAwait(false); + return dic.Select(x => new GetEntryRsp { Key = x.Key, ExpireTime = x.Value.Item1, Type = x.Value.Item2 }); + } + + /// + public async Task GetEntryAsync(GetEntriesReq req) + { + req.ThrowIfInvalid(); + var database = connectionMultiplexer.GetDatabase(_redisInstance.Database); + + var ret = new GetEntryRsp { + Type = await database.KeyTypeAsync(req.Key).ConfigureAwait(false) + , Key = req.Key + , ExpireTime = DateTime.Now + await database.KeyTimeToLiveAsync(req.Key).ConfigureAwait(false) + }; + + #pragma warning disable IDE0072 + ret.Data = ret.Type switch + #pragma warning restore IDE0072 + { + RedisType.String => await database.StringGetAsync(req.Key).ConfigureAwait(false) + , RedisType.List => string.Join(", ", await database.ListRangeAsync(req.Key).ConfigureAwait(false)) + , RedisType.Set => string.Join(", ", await database.SetMembersAsync(req.Key).ConfigureAwait(false)) + , RedisType.SortedSet => string.Join(", ", await database.SortedSetRangeByRankAsync(req.Key).ConfigureAwait(false)) + , RedisType.Hash => string.Join(", ", await database.HashGetAllAsync(req.Key).ConfigureAwait(false)) + , _ => "Unsupported key type" + }; + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs new file mode 100644 index 00000000..1a6600e3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/CaptchaService.cs @@ -0,0 +1,50 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; +using SixLabors.ImageSharp; +using Yitter.IdGenerator; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class CaptchaService : ServiceBase, ICaptchaService +{ + private static readonly Assembly _entryAsm = Assembly.GetEntryAssembly(); + private static readonly string _entryAsmName = _entryAsm.FullName![.._entryAsm.FullName.IndexOf(',')]; + + /// + /// Initializes a new instance of the class. + /// + public CaptchaService() { } + + /// + public async Task GetCaptchaImageAsync() + { + var (backgroundImage, sliderImage, offsetSaw) = await CaptchaImageHelper.CreateSawSliderImageAsync( + _entryAsm, $"{_entryAsmName}.Assets.Captcha.background" + , $"{_entryAsmName}.Assets.Captcha.template", (1, 101), (1, 7) + , new Size(50, 50)) + .ConfigureAwait(false); + + var id = $"{nameof(GetCaptchaImageAsync)}_{YitIdHelper.NextId()}"; + return new GetCaptchaRsp { Id = id, BackgroundImage = backgroundImage, SliderImage = sliderImage, SawOffsetX = offsetSaw.X }; + } + + /// + public Task VerifyCaptchaAsync(VerifyCaptchaReq req) + { + req.ThrowIfInvalid(); + if (req.SawOffsetX == null) { + return Task.FromResult(false); + } + + bool ret; + try { + var aesKey = req.Id.Aes(CaptchaOptions.SecretKey)[..32]; + ret = Math.Abs(req.SawOffsetX.Value - req.VerifyData.AesDe(aesKey).Float()) < 5f; + } + catch { + ret = false; + } + + return Task.FromResult(ret); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs new file mode 100644 index 00000000..f77ae523 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConfigService.cs @@ -0,0 +1,155 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class ConfigService(BasicRepository rpo) // + : RepositoryService(rpo), IConfigService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateConfigReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public async Task EditAsync(EditConfigReq req) + { + req.ThrowIfInvalid(); + #if DBTYPE_SQLSERVER + return (await UpdateReturnListAsync(req, null).ConfigureAwait(false)).FirstOrDefault()?.Adapt(); + #else + return await UpdateAsync(req, null).ConfigureAwait(false) > 0 + ? await GetAsync(new QueryConfigReq { Id = req.Id }).ConfigureAwait(false) + : null; + #endif + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.配置导出); + } + + /// + public async Task GetAsync(QueryConfigReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task GetLatestConfigAsync() + { + var ret = await QueryAsync(new QueryReq { Count = 1, Filter = new QueryConfigReq { Enabled = true } }).ConfigureAwait(false); + return ret.FirstOrDefault(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public Task SetEnabledAsync(SetConfigEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.Enabled)]); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.Include(a => a.UserRegisterDept) + .Include(a => a.UserRegisterRole) + .WhereDynamicFilter(req.DynamicFilter) + .WhereIf( // + req.Filter?.Enabled.HasValue ?? false, a => a.Enabled == req.Filter.Enabled.Value); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConstantService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConstantService.cs new file mode 100644 index 00000000..8a522256 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ConstantService.cs @@ -0,0 +1,66 @@ +using Microsoft.OpenApi.Extensions; +using NetAdmin.Domain.Attributes; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class ConstantService : ServiceBase, IConstantService +{ + /// + public IDictionary GetCharsDic() + { + return typeof(Chars).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.FieldType == typeof(string)) + .ToImmutableSortedDictionary( // + x => x.Name, x => x.GetValue(null)?.ToString()); + } + + /// + public IDictionary> GetEnums() + { + var ret = App.EffectiveTypes.Where(x => x.IsEnum && x.GetCustomAttribute(false) != null) + .ToDictionary(x => x.Name, x => // + x.GetEnumValues().Cast().ToDictionary(y => y.ToString(), GetDicValue)); + + var httpStatusCodes = Enum.GetNames().ToDictionary(x => x, GetHttpStatusCodeDicValue); + httpStatusCodes.Add( // + nameof(ErrorCodes.Unhandled) + , [Numbers.HTTP_STATUS_BIZ_FAIL.ToInvString(), nameof(ErrorCodes.Unhandled), nameof(Indicates.Danger).ToLowerInvariant()]); + ret.Add($"{nameof(HttpStatusCode)}s", httpStatusCodes); + return ret; + + static string[] GetDicValue(Enum y) + { + var ret = new[] { Convert.ToInt64(y, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture), y.ResDesc() }; + var indicate = y.GetAttributeOfType()?.Indicate.ToLowerInvariant(); + return indicate.NullOrEmpty() ? ret : [..ret, indicate]; + } + + static string[] GetHttpStatusCodeDicValue(string name) + { + var codeInt = Convert.ToInt64(Enum.Parse(name), CultureInfo.InvariantCulture); + return [ + codeInt.ToString(CultureInfo.InvariantCulture), name + , (codeInt switch { >= 200 and < 300 => nameof(Indicates.Success), < 400 => nameof(Indicates.Warning), _ => nameof(Indicates.Danger) }) + .ToLowerInvariant() + ]; + } + } + + /// + public IDictionary GetLocalizedStrings() + { + return typeof(Ln).GetProperties(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.PropertyType == typeof(string)) + .ToImmutableSortedDictionary(x => x.Name, x => x.GetValue(null)?.ToString()); + } + + /// + public IDictionary GetNumbersDic() + { + return typeof(Numbers).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.FieldType == typeof(int) || x.FieldType == typeof(long)) + .ToImmutableSortedDictionary( // + x => x.Name, x => Convert.ToInt64(x.GetValue(null), CultureInfo.InvariantCulture)); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IApiService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IApiService.cs new file mode 100644 index 00000000..acac3f75 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IApiService.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 接口服务 +/// +public interface IApiService : IService, IApiModule +{ + /// + /// 反射接口列表 + /// + IEnumerable ReflectionList(bool excludeAnonymous = true); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICacheService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICacheService.cs new file mode 100644 index 00000000..a4e7c12e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICacheService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 缓存服务 +/// +public interface ICacheService : IService, ICacheModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICaptchaService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICaptchaService.cs new file mode 100644 index 00000000..9fed2d81 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ICaptchaService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 人机验证服务 +/// +public interface ICaptchaService : IService, ICaptchaModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConfigService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConfigService.cs new file mode 100644 index 00000000..e11b541e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConfigService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 配置服务 +/// +public interface IConfigService : IService, IConfigModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConstantService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConstantService.cs new file mode 100644 index 00000000..6a7e52e4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IConstantService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 常量服务 +/// +public interface IConstantService : IService, IConstantModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDeptService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDeptService.cs new file mode 100644 index 00000000..df8e6770 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDeptService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 部门服务 +/// +public interface IDeptService : IService, IDeptModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDevService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDevService.cs new file mode 100644 index 00000000..a3b765e0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDevService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 开发服务 +/// +public interface IDevService : IService, IDevModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicCatalogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicCatalogService.cs new file mode 100644 index 00000000..440153ea --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicCatalogService.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 字典目录服务 +/// +public interface IDicCatalogService : IService, IDicCatalogModule +{ + /// + /// 编辑字典目录 + /// + Task EditAsync(EditDicCatalogReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicContentService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicContentService.cs new file mode 100644 index 00000000..e49ddb34 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicContentService.cs @@ -0,0 +1,19 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 字典内容服务 +/// +public interface IDicContentService : IService, IDicContentModule +{ + /// + /// 编辑字典内容 + /// + Task EditAsync(EditDicContentReq req); + + /// + /// 通过分类键查询字典内容 + /// + Task> QueryByCatalogCodeAsync(string catalogCode); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicService.cs new file mode 100644 index 00000000..4b078133 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IDicService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 字典服务 +/// +public interface IDicService : IService, IDicModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IFileService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IFileService.cs new file mode 100644 index 00000000..8e752ea9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IFileService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 文件服务 +/// +public interface IFileService : IService, IFileModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs new file mode 100644 index 00000000..02beb84e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobRecordService.cs @@ -0,0 +1,24 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 计划作业执行记录服务 +/// +public interface IJobRecordService : IService, IJobRecordModule +{ + /// + /// 获取条形图数据 + /// + Task> GetBarChartAsync(QueryReq req); + + /// + /// 状态码分组饼图数据 + /// + Task> GetPieChartByHttpStatusCodeAsync(QueryReq req); + + /// + /// 名称分组饼图数据 + /// + Task> GetPieChartByNameAsync(QueryReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs new file mode 100644 index 00000000..e3f65f13 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IJobService.cs @@ -0,0 +1,24 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 计划作业服务 +/// +public interface IJobService : IService, IJobModule +{ + /// + /// 完成计划作业 + /// + Task FinishJobAsync(FinishJobReq req); + + /// + /// 获取下一个要执行的计划作业 + /// + Task GetNextJobAsync(); + + /// + /// 释放卡死的任务 + /// + Task ReleaseStuckTaskAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ILoginLogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ILoginLogService.cs new file mode 100644 index 00000000..adb9f16d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ILoginLogService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 登录日志服务 +/// +public interface ILoginLogService : IService, ILoginLogModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IMenuService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IMenuService.cs new file mode 100644 index 00000000..40935931 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IMenuService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 菜单服务 +/// +public interface IMenuService : IService, IMenuModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogDetailService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogDetailService.cs new file mode 100644 index 00000000..8709e9a0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogDetailService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 请求日志明细服务 +/// +public interface IRequestLogDetailService : IService, IRequestLogDetailModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogService.cs new file mode 100644 index 00000000..76e04f9d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRequestLogService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 请求日志服务 +/// +public interface IRequestLogService : IService, IRequestLogModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRoleService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRoleService.cs new file mode 100644 index 00000000..6473bb79 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IRoleService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 角色服务 +/// +public interface IRoleService : IService, IRoleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgDeptService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgDeptService.cs new file mode 100644 index 00000000..6e691581 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgDeptService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 站内信-部门映射服务 +/// +public interface ISiteMsgDeptService : IService, ISiteMsgDeptModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgFlagService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgFlagService.cs new file mode 100644 index 00000000..dfc8c481 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgFlagService.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 站内信标记服务 +/// +public interface ISiteMsgFlagService : IService, ISiteMsgFlagModule +{ + /// + /// 设置用户站内信状态 + /// + Task SetUserSiteMsgStatusAsync(SetUserSiteMsgStatusReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgRoleService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgRoleService.cs new file mode 100644 index 00000000..eb17d051 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgRoleService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 站内信-角色映射服务 +/// +public interface ISiteMsgRoleService : IService, ISiteMsgRoleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgService.cs new file mode 100644 index 00000000..d59a82a6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 站内信服务 +/// +public interface ISiteMsgService : IService, ISiteMsgModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgUserService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgUserService.cs new file mode 100644 index 00000000..6613dcde --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/ISiteMsgUserService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 站内信-用户映射服务 +/// +public interface ISiteMsgUserService : IService, ISiteMsgUserModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IToolsService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IToolsService.cs new file mode 100644 index 00000000..a74ab32f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IToolsService.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 工具服务 +/// +public interface IToolsService : IService, IToolsModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserProfileService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserProfileService.cs new file mode 100644 index 00000000..6de23d56 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserProfileService.cs @@ -0,0 +1,24 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 用户档案服务 +/// +public interface IUserProfileService : IService, IUserProfileModule +{ + /// + /// 编辑用户档案 + /// + Task EditAsync(EditUserProfileReq req); + + /// + /// 获取当前用户配置 + /// + Task GetSessionUserAppConfigAsync(); + + /// + /// 设置当前用户配置 + /// + Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs new file mode 100644 index 00000000..e77b7219 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IUserService.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 用户服务 +/// +public interface IUserService : IService, IUserModule +{ + /// + /// 用户编号登录 + /// + Task LoginByUserIdAsync(long userId); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IVerifyCodeService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IVerifyCodeService.cs new file mode 100644 index 00000000..9026d4c2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/Dependency/IVerifyCodeService.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency; + +/// +/// 验证码服务 +/// +public interface IVerifyCodeService : IService, IVerifyCodeModule +{ + /// + /// 设置验证码状态 + /// + Task SetVerifyCodeStatusAsync(SetVerifyCodeStatusReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs new file mode 100644 index 00000000..6de64752 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DeptService.cs @@ -0,0 +1,166 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class DeptService(BasicRepository rpo) // + : RepositoryService(rpo), IDeptService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + /// Parent_department_does_not_exist + public async Task CreateAsync(CreateDeptReq req) + { + req.ThrowIfInvalid(); + if (req.ParentId != 0 && !await Rpo.Select.AnyAsync(a => a.Id == req.ParentId).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.父节点不存在); + } + + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + + return ret.Adapt(); + } + + /// + /// 该部门下存在用户 + /// 该部门下存在子部门 + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + if (await Rpo.Orm.Select().AnyAsync(a => a.DeptId == req.Id).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.该部门下存在用户); + } + + #pragma warning disable IDE0046 + if (await Rpo.Select.AnyAsync(a => a.ParentId == req.Id).ConfigureAwait(false)) { + #pragma warning restore IDE0046 + throw new NetAdminInvalidOperationException(Ln.该部门下存在子部门); + } + + return await Rpo.DeleteAsync(x => x.Id == req.Id).ConfigureAwait(false); + } + + /// + public async Task EditAsync(EditDeptReq req) + { + req.ThrowIfInvalid(); + #if DBTYPE_SQLSERVER + return (await UpdateReturnListAsync(req, null).ConfigureAwait(false)).FirstOrDefault()?.Adapt(); + #else + return await UpdateAsync(req, null).ConfigureAwait(false) > 0 ? await GetAsync(new QueryDeptReq { Id = req.Id }).ConfigureAwait(false) : null; + #endif + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.部门导出); + } + + /// + public async Task GetAsync(QueryDeptReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return (await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .ToTreeListAsync() + .ConfigureAwait(false)).Adapt>(); + } + + /// + public Task SetEnabledAsync(SetDeptEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.Enabled)]); + } + + private ISelect QueryInternal(QueryReq req) + { + return QueryInternal(req, false); + } + + private ISelect QueryInternal(QueryReq req, bool asTreeCte) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.Id == req.Keywords.Int64Try(0) || a.Name.Contains(req.Keywords) || a.Summary.Contains(req.Keywords)); + if (asTreeCte) { + ret = ret.AsTreeCte(); + } + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.Sort), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Sort); + } + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs new file mode 100644 index 00000000..656c5bdd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DevService.cs @@ -0,0 +1,200 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class DevService(IApiService apiService) : ServiceBase, IDevService +{ + private const string _REPLACE_TO_EMPTY = "//~"; + + private static readonly string _clientProjectPath = Path.Combine( // + Environment.CurrentDirectory, "../../frontend/admin"); + + private static readonly string[] _projectDirs = Directory.GetDirectories(Path.Combine(Environment.CurrentDirectory, "../")); + + private static readonly Regex _regex = new(@"\.(\w)"); + private static readonly Regex _regex2 = new("([a-zA-Z]+):"); + + /// + public async Task GenerateCsCodeAsync(GenerateCsCodeReq req) + { + req.ThrowIfInvalid(); + int index; + var typeAbbr = req.Type[(index = req.Type.LastIndexOf('.') + 1)..(index + 3)]; + + // 模板层目录 + var tplHostDir = GetDir("NetAdmin.Host"); + var tplCacheDir = GetDir("NetAdmin.Cache"); + var tplDataDir = GetDir("NetAdmin.Domain"); + var tplAppDir = GetDir("NetAdmin.Application"); + + // 主机层目录 + var hostControllerDir = Path.Combine(GetDir($"{req.Type}.Host"), "Controllers", typeAbbr); + + // 缓存层目录 + var cacheDir = Path.Combine(GetDir($"{req.Type}.Cache"), typeAbbr); + var cacheDependencyDir = Path.Combine(cacheDir, "Dependency"); + + // 业务逻辑层目录 + var appDir = GetDir($"{req.Type}.Application"); + var appModulesDir = Path.Combine(appDir, "Modules", typeAbbr); + var appServicesDir = Path.Combine(appDir, "Services", typeAbbr); + var appServicesDependencyDir = Path.Combine(appServicesDir, "Dependency"); + + // 数据契约层目录 + var dataDir = GetDir($"{req.Type}.Domain"); + var dtoDir = Path.Combine(dataDir, "Dto", typeAbbr, req.ModuleName); + var entityDir = Path.Combine(dataDir, "DbMaps", typeAbbr); + + // 创建缺少的目录 + CreateDir(hostControllerDir, cacheDir, cacheDependencyDir, appDir, appModulesDir, appServicesDir, appServicesDependencyDir, dataDir, dtoDir +, entityDir); + + // Controller + await WriteCodeFileAsync(req, Path.Combine(tplHostDir, "Controllers", "Tpl", "ExampleController.cs") + , Path.Combine(hostControllerDir, $"{req.ModuleName}Controller.cs"), typeAbbr) + .ConfigureAwait(false); + + // CreateReq + await WriteCodeFileAsync(req, Path.Combine(tplDataDir, "Dto", "Tpl", "Example", "CreateExampleReq.cs") + , Path.Combine(dtoDir, $"Create{req.ModuleName}Req.cs"), typeAbbr) + .ConfigureAwait(false); + + // QueryReq + await WriteCodeFileAsync(req, Path.Combine(tplDataDir, "Dto", "Tpl", "Example", "QueryExampleReq.cs") + , Path.Combine(dtoDir, $"Query{req.ModuleName}Req.cs"), typeAbbr) + .ConfigureAwait(false); + + // QueryRsp + await WriteCodeFileAsync(req, Path.Combine(tplDataDir, "Dto", "Tpl", "Example", "QueryExampleRsp.cs") + , Path.Combine(dtoDir, $"Query{req.ModuleName}Rsp.cs"), typeAbbr) + .ConfigureAwait(false); + + // ICache + await WriteCodeFileAsync(req, Path.Combine(tplCacheDir, "Tpl", "Dependency", "IExampleCache.cs") + , Path.Combine(cacheDependencyDir, $"I{req.ModuleName}Cache.cs"), typeAbbr) + .ConfigureAwait(false); + + // Cache + await WriteCodeFileAsync(req, Path.Combine(tplCacheDir, "Tpl", "ExampleCache.cs"), Path.Combine(cacheDir, $"{req.ModuleName}Cache.cs") + , typeAbbr) + .ConfigureAwait(false); + + // IModule + await WriteCodeFileAsync(req, Path.Combine(tplAppDir, "Modules", "Tpl", "IExampleModule.cs") + , Path.Combine(appModulesDir, $"I{req.ModuleName}Module.cs"), typeAbbr) + .ConfigureAwait(false); + + // IService + await WriteCodeFileAsync(req, Path.Combine(tplAppDir, "Services", "Tpl", "Dependency", "IExampleService.cs") + , Path.Combine(appServicesDependencyDir, $"I{req.ModuleName}Service.cs"), typeAbbr) + .ConfigureAwait(false); + + // Service + await WriteCodeFileAsync(req, Path.Combine(tplAppDir, "Services", "Tpl", "ExampleService.cs") + , Path.Combine(appServicesDir, $"{req.ModuleName}Service.cs"), typeAbbr) + .ConfigureAwait(false); + + // Entity + await WriteCodeFileAsync(req, Path.Combine(tplDataDir, "DbMaps", "Tpl", "Tpl_Example.cs") + , Path.Combine(entityDir, $"{typeAbbr}_{req.ModuleName}.cs"), typeAbbr) + .ConfigureAwait(false); + } + + /// + public async Task GenerateIconCodeAsync(GenerateIconCodeReq req) + { + req.ThrowIfInvalid(); + var tplSvg = await File.ReadAllTextAsync(Path.Combine(_clientProjectPath, "src", "assets", "icons", "tpl", "Svg.vue")).ConfigureAwait(false); + var tplExport = await File.ReadAllTextAsync(Path.Combine(_clientProjectPath, "src", "assets", "icons", "tpl", "export.js")) + .ConfigureAwait(false); + + var vueContent = tplSvg.Replace("$svgCode$", req.SvgCode).Replace(_REPLACE_TO_EMPTY, string.Empty); + + var dir = Path.Combine(_clientProjectPath, "src", "assets", "icons"); + if (!Directory.Exists(dir)) { + _ = Directory.CreateDirectory(dir); + } + + var vueFile = Path.Combine(dir, $"{req.IconName}.vue"); + await File.WriteAllTextAsync(vueFile, vueContent).ConfigureAwait(false); + + var indexJsFile = Path.Combine(dir, "index.js"); + + await File.AppendAllTextAsync( + indexJsFile, Environment.NewLine + tplExport.Replace("$iconName$", req.IconName).Replace(_REPLACE_TO_EMPTY, string.Empty)) + .ConfigureAwait(false); + + // 修改iconSelect.js + var iconSelectFile = Path.Combine(_clientProjectPath, "src", "config", "iconSelect.js"); + var iconSelectContent = await File.ReadAllTextAsync(iconSelectFile).ConfigureAwait(false); + iconSelectContent = iconSelectContent.Replace("export default", "exportDefault:").Replace("'", "\""); + iconSelectContent = _regex2.Replace(iconSelectContent, "\"$1\":"); + iconSelectContent = "{" + iconSelectContent + "}"; + var iconExportJsInfo = iconSelectContent.ToObject(); + iconExportJsInfo.ExportDefault.Icons.Last().Icons.Add($"sc-icon-{req.IconName}"); + var newContent = iconExportJsInfo.ToJson().TrimStart('{')[..^1].Replace("\"exportDefault\":", "export default"); + + await File.WriteAllTextAsync(iconSelectFile, newContent).ConfigureAwait(false); + } + + /// + public async Task GenerateJsCodeAsync() + { + // 模板文件 + var tplOuter = await File.ReadAllTextAsync(Path.Combine(_clientProjectPath, "src", "api", "tpl", "outer.js")).ConfigureAwait(false); + var tplInner = await File.ReadAllTextAsync(Path.Combine(_clientProjectPath, "src", "api", "tpl", "inner.js")).ConfigureAwait(false); + + foreach (var item in apiService.ReflectionList(false)) { + var dir = Path.Combine(_clientProjectPath, "src", "api", item.Namespace); + if (!Directory.Exists(dir)) { + _ = Directory.CreateDirectory(dir); + } + + var file = Path.Combine(dir, $"{item.Name.Replace(".", string.Empty)}.js"); + + var content = tplOuter.Replace("$controllerDesc$", item.Summary) + .Replace("$controllerPath$", item.Id) + .Replace( // + "$inner$", string.Join(Environment.NewLine + Environment.NewLine, Select(item))) + .Replace(_REPLACE_TO_EMPTY, string.Empty); + + await File.WriteAllTextAsync(file, content).ConfigureAwait(false); + } + + // ReSharper disable once SeparateLocalFunctionsWithJumpStatement + IEnumerable Select(QueryApiRsp item) + { + return item.Children.Select(x => tplInner.Replace("$actionDesc$", x.Summary) + .Replace( // + "$actionName$", _regex.Replace(x.Name, y => y.Groups[1].Value.ToUpperInvariant())) + .Replace("$actionPath$", x.Id) + .Replace( // + "$actionMethod$", x.Method?.ToLowerInvariant()) + .Replace(_REPLACE_TO_EMPTY, string.Empty)); // + } + } + + private static void CreateDir(params string[] dirs) + { + foreach (var dir in dirs) { + if (!Directory.Exists(dir)) { + _ = Directory.CreateDirectory(dir!); + } + } + } + + private static string GetDir(string key) + { + return _projectDirs.First(x => x.EndsWith(key, true, CultureInfo.InvariantCulture)); + } + + private static async Task WriteCodeFileAsync(GenerateCsCodeReq req, string tplFile, string writeFile, string moduleAbbr) + { + var tplContent = await File.ReadAllTextAsync(tplFile).ConfigureAwait(false); + tplContent = tplContent.Replace("Tpl", moduleAbbr).Replace("示例", req.ModuleRemark).Replace("Example", req.ModuleName); + + await File.WriteAllTextAsync(writeFile, tplContent).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs new file mode 100644 index 00000000..c838231f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicCatalogService.cs @@ -0,0 +1,149 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class DicCatalogService(BasicRepository rpo) // + : RepositoryService(rpo), IDicCatalogService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + /// The_parent_node_does_not_exist + public async Task CreateAsync(CreateDicCatalogReq req) + { + req.ThrowIfInvalid(); + if (req.ParentId != 0 && !await Rpo.Where(a => a.Id == req.ParentId) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync() + .ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.父节点不存在); + } + + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); + return ret.Count; + } + + /// + /// The_parent_node_does_not_exist + public async Task EditAsync(EditDicCatalogReq req) + { + req.ThrowIfInvalid(); + return req.ParentId == 0 || await Rpo.Where(a => a.Id == req.ParentId) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync() + .ConfigureAwait(false) + ? await UpdateAsync(req, null).ConfigureAwait(false) + : throw new NetAdminInvalidOperationException(Ln.父节点不存在); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QueryDicCatalogReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .ToTreeListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs new file mode 100644 index 00000000..bf2603a4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicContentService.cs @@ -0,0 +1,181 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class DicContentService(BasicRepository rpo) // + : RepositoryService(rpo), IDicContentService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + /// Dictionary_directory_does_not_exist + public async Task CreateAsync(CreateDicContentReq req) + { + req.ThrowIfInvalid(); + if (!await Rpo.Orm.Select() + .Where(a => a.Id == req.CatalogId) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync() + .ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.字典目录不存在); + } + + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + /// Dictionary_directory_does_not_exist + public async Task EditAsync(EditDicContentReq req) + { + req.ThrowIfInvalid(); + if (!await Rpo.Orm.Select() + .Where(a => a.Id == req.CatalogId) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync() + .ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.字典目录不存在); + } + + #if DBTYPE_SQLSERVER + return (await UpdateReturnListAsync(req, null).ConfigureAwait(false)).FirstOrDefault()?.Adapt(); + #else + return await UpdateAsync(req, null).ConfigureAwait(false) > 0 + ? await GetAsync(new QueryDicContentReq { Id = req.Id }).ConfigureAwait(false) + : null; + #endif + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.字典内容导出); + } + + /// + public async Task GetAsync(QueryDicContentReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task> QueryByCatalogCodeAsync(string catalogCode) + { + var ret = await Rpo.Orm.Select() + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Include(a => a.Catalog) + .Where(a => a.Catalog.Code == catalogCode) + .Where(a => a.Enabled) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public Task SetEnabledAsync(SetDicContentEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(Sys_DicContent.Enabled)]); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs new file mode 100644 index 00000000..e309cb9b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/DicService.cs @@ -0,0 +1,143 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class DicService(IDicCatalogService catalogService, IDicContentService contentService) // + : ServiceBase, IDicService +{ + /// + public Task BulkDeleteCatalogAsync(BulkReq req) + { + req.ThrowIfInvalid(); + return catalogService.BulkDeleteAsync(req); + } + + /// + public Task BulkDeleteContentAsync(BulkReq req) + { + req.ThrowIfInvalid(); + return contentService.BulkDeleteAsync(req); + } + + /// + public Task CreateCatalogAsync(CreateDicCatalogReq req) + { + req.ThrowIfInvalid(); + return catalogService.CreateAsync(req); + } + + /// + public Task CreateContentAsync(CreateDicContentReq req) + { + req.ThrowIfInvalid(); + return contentService.CreateAsync(req); + } + + /// + public Task DeleteCatalogAsync(DelReq req) + { + req.ThrowIfInvalid(); + return catalogService.DeleteAsync(req); + } + + /// + public Task DeleteContentAsync(DelReq req) + { + req.ThrowIfInvalid(); + return contentService.DeleteAsync(req); + } + + /// + public Task EditCatalogAsync(EditDicCatalogReq req) + { + req.ThrowIfInvalid(); + return catalogService.EditAsync(req); + } + + /// + public Task EditContentAsync(EditDicContentReq req) + { + req.ThrowIfInvalid(); + return contentService.EditAsync(req); + } + + /// + public Task ExportContentAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return contentService.ExportAsync(req); + } + + /// + public Task GetCatalogAsync(QueryDicCatalogReq req) + { + req.ThrowIfInvalid(); + return catalogService.GetAsync(req); + } + + /// + public Task GetContentAsync(QueryDicContentReq req) + { + req.ThrowIfInvalid(); + return contentService.GetAsync(req); + } + + /// + public async Task GetDicValueAsync(GetDicValueReq req) + { + req.ThrowIfInvalid(); + var df = new DynamicFilterInfo { + Filters = [ + new DynamicFilterInfo { + Field = nameof(QueryDicContentReq.CatalogId) + , Operator = DynamicFilterOperators.Eq + , Value = req.CatalogId + } + , new DynamicFilterInfo { + Field = nameof(QueryDicContentReq.Key) + , Operator = DynamicFilterOperators.Eq + , Value = req.Key + } + ] + }; + var queryParam = new QueryReq { Count = 1, DynamicFilter = df }; + return (await QueryContentAsync(queryParam).ConfigureAwait(false)).FirstOrDefault()?.Value; + } + + /// + public Task> PagedQueryCatalogAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + return catalogService.PagedQueryAsync(req); + } + + /// + public Task> PagedQueryContentAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + return contentService.PagedQueryAsync(req); + } + + /// + public Task> QueryCatalogAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return catalogService.QueryAsync(req); + } + + /// + public Task> QueryContentAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return contentService.QueryAsync(req); + } + + /// + public Task SetEnabledAsync(SetDicContentEnabledReq req) + { + req.ThrowIfInvalid(); + return contentService.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/FileService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/FileService.cs new file mode 100644 index 00000000..0a4da555 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/FileService.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class FileService(IOptions uploadOptions, MinioHelper minioHelper) // + : ServiceBase, IFileService +{ + /// + /// 文件不能为空 + /// 允许上传的文件格式 + /// 允许的文件大小 + public async Task UploadAsync(IFormFile file) + { + if (file == null || file.Length < 1) { + throw new NetAdminInvalidOperationException(Ln.文件不能为空); + } + + if (!uploadOptions.Value.ContentTypes.Contains(file.ContentType)) { + throw new NetAdminInvalidOperationException($"{Ln.允许的文件格式} {string.Join(",", uploadOptions.Value.ContentTypes)}"); + } + + if (file.Length > uploadOptions.Value.MaxSize) { + throw new NetAdminInvalidOperationException($"{Ln.允许的文件大小} {uploadOptions.Value.MaxSize}"); + } + + var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; + var objectName = $"{UserToken.Id}/{fileName}"; + await using var fs = file.OpenReadStream(); + return await minioHelper.UploadAsync(objectName, fs, file.ContentType, file.Length).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs new file mode 100644 index 00000000..1ab1841b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobRecordService.cs @@ -0,0 +1,180 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class JobRecordService(BasicRepository rpo) // + : RepositoryService(rpo), IJobRecordService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateJobRecordReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.计划作业执行记录导出); + } + + /// + public async Task GetAsync(QueryJobRecordReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> GetBarChartAsync(QueryReq req) + { + req.ThrowIfInvalid(); + + var ret = await QueryInternal(req with { Order = Orders.None }) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .GroupBy(a => new { a.CreatedTime.Year, a.CreatedTime.Month, a.CreatedTime.Day, a.CreatedTime.Hour }) + .ToListAsync(a => new GetBarChartRsp { + Timestamp = new DateTime(a.Key.Year, a.Key.Month, a.Key.Day, a.Key.Hour, 0, 0 + , DateTimeKind.Unspecified) + , Value = a.Count() + }) + .ConfigureAwait(false); + return ret.OrderBy(x => x.Timestamp); + } + + /// + public async Task> GetPieChartByHttpStatusCodeAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req with { Order = Orders.None }) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Include(a => a.Job) + .GroupBy(a => a.HttpStatusCode) + #pragma warning disable CA1305 + .ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key.ToString() }) + #pragma warning restore CA1305 + .ConfigureAwait(false); + return ret.Select(x => x with { Key = Enum.Parse(x.Key).ToString() }).OrderByDescending(x => x.Value); + } + + /// + public async Task> GetPieChartByNameAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req with { Order = Orders.None }) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Include(a => a.Job) + .GroupBy(a => a.Job.JobName) + .ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key }) + .ConfigureAwait(false); + return ret.OrderByDescending(x => x.Value); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.Include(a => a.Job) + .WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.JobId == req.Keywords.Int64Try(0) || a.Id == req.Keywords.Int64Try(0) || a.Job.JobName == req.Keywords); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs new file mode 100644 index 00000000..7b8edf0d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/JobService.cs @@ -0,0 +1,356 @@ +using Cronos; +using FreeSql.Internal; +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class JobService(BasicRepository rpo, IJobRecordService jobRecordService) // + : RepositoryService(rpo), IJobService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public Task CountRecordAsync(QueryReq req) + { + return jobRecordService.CountAsync(req); + } + + /// + public async Task CreateAsync(CreateJobReq req) + { + req.ThrowIfInvalid(); + var nextExecTime = GetNextExecTime(req.ExecutionCron); + var ret = await Rpo.InsertAsync(req with { + NextExecTime = nextExecTime + , NextTimeId = nextExecTime?.TimeUnixUtc() + , RequestHeader = req.RequestHeaders?.Json() + }) + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); + return ret.Count; + } + + /// + public async Task EditAsync(EditJobReq req) + { + req.ThrowIfInvalid(); + var update = Rpo.UpdateDiy.Set(a => a.ExecutionCron == req.ExecutionCron) + .Set(a => a.HttpMethod == req.HttpMethod) + .Set(a => a.JobName == req.JobName) + .SetIf(req.RequestHeaders == null, a => a.RequestHeader, null) + .SetIf(req.RequestHeaders != null, a => a.RequestHeader, req.RequestHeaders.Json()) + .Set(a => a.RequestBody == req.RequestBody) + .Set(a => a.RequestUrl == req.RequestUrl) + .Set(a => a.RandomDelayBegin == req.RandomDelayBegin) + .Set(a => a.RandomDelayEnd == req.RandomDelayEnd) + .Set(a => a.UserId == req.UserId) + .Set(a => a.Summary == req.Summary) + .Where(a => a.Id == req.Id); + + #if DBTYPE_SQLSERVER + return (await update.ExecuteUpdatedAsync().ConfigureAwait(false)).FirstOrDefault()?.Adapt(); + #else + return await update.ExecuteAffrowsAsync().ConfigureAwait(false) <= 0 + ? null + : await GetAsync(new QueryJobReq { Id = req.Id }).ConfigureAwait(false); + #endif + } + + /// + public async Task ExecuteAsync(QueryJobReq req) + { + req.ThrowIfInvalid(); + var df = new DynamicFilterInfo { + Filters = [ + new DynamicFilterInfo { + Field = nameof(QueryJobReq.Enabled) + , Operator = DynamicFilterOperators.Eq + , Value = true + } + , new DynamicFilterInfo { + Field = nameof(QueryJobReq.Status) + , Operator = DynamicFilterOperators.Eq + , Value = JobStatues.Idle + } + ] + }; + var job = await QueryInternal(new QueryReq { Count = 1, Filter = req, DynamicFilter = df, Order = Orders.None }) + .ToOneAsync() + .ConfigureAwait(false) ?? throw new NetAdminInvalidOperationException(Ln.未获取到待执行任务); + + var nextExecTime = GetNextExecTime(Chars.FLG_CRON_PER_SECS); + try { + _ = await UpdateAsync( // + job with { NextExecTime = nextExecTime, NextTimeId = nextExecTime?.TimeUnixUtc() } + , [nameof(job.NextExecTime), nameof(job.NextTimeId)]) + .ConfigureAwait(false); + } + catch (DbUpdateVersionException) { + throw new NetAdminInvalidOperationException(Ln.并发冲突_请稍后重试); + } + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.计划作业导出); + } + + /// + public Task ExportRecordAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.ExportAsync(req); + } + + /// + public async Task FinishJobAsync(FinishJobReq req) + { + req.ThrowIfInvalid(); + var nextExecTime = GetNextExecTime(req.ExecutionCron); + _ = await UpdateAsync( // + req with { Status = JobStatues.Idle, NextExecTime = nextExecTime, NextTimeId = nextExecTime?.TimeUnixUtc() } + , [nameof(req.Status), nameof(req.NextExecTime), nameof(req.NextTimeId), nameof(req.LastDuration), nameof(req.LastStatusCode)]) + .ConfigureAwait(false); + } + + /// + public async Task GetAsync(QueryJobReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task GetNextJobAsync() + { + var df = new DynamicFilterInfo { + Filters = [ + new DynamicFilterInfo { + Field = nameof(QueryJobReq.NextExecTime) + , Value = DateTime.Now + , Operator = DynamicFilterOperators.LessThan + } + , new DynamicFilterInfo { + Field = nameof(QueryJobReq.Status) + , Value = JobStatues.Idle + , Operator = DynamicFilterOperators.Eq + } + , new DynamicFilterInfo { + Field = nameof(QueryJobReq.Enabled) + , Value = true + , Operator = DynamicFilterOperators.Eq + } + ] + }; + var job = await QueryInternal(new QueryReq { DynamicFilter = df, Order = Orders.Random }, false) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Where(a => !Rpo.Orm.Select().As("b").Where(b => b.JobId == a.Id && b.TimeId == a.NextTimeId).Any()) + .ToOneAsync(a => new { + a.RequestUrl + , a.HttpMethod + , a.RequestHeader + , a.RequestBody + , a.RandomDelayBegin + , a.RandomDelayEnd + , a.UserId + , a.Id + , a.NextTimeId + , a.Version + }) + .ConfigureAwait(false); + if (job == null) { + return null; + } + + #if DBTYPE_SQLSERVER + var ret = await UpdateReturnListAsync( // + job.Adapt() with { Status = JobStatues.Running, LastExecTime = DateTime.Now } + , [nameof(Sys_Job.Status), nameof(Sys_Job.LastExecTime)]) + .ConfigureAwait(false); + + return ret.FirstOrDefault()?.Adapt(); + #else + return await UpdateAsync( // + job.Adapt() with { Status = JobStatues.Running, LastExecTime = DateTime.Now } + , [nameof(Sys_Job.Status), nameof(Sys_Job.LastExecTime)]) + .ConfigureAwait(false) > 0 + ? await GetAsync(new QueryJobReq { Id = job.Id }).ConfigureAwait(false) + : null; + #endif + } + + /// + public Task GetRecordAsync(QueryJobRecordReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.GetAsync(req); + } + + /// + public Task> GetRecordBarChartAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.GetBarChartAsync(req); + } + + /// + public Task> GetRecordPieChartByHttpStatusCodeAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.GetPieChartByHttpStatusCodeAsync(req); + } + + /// + public Task> GetRecordPieChartByNameAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.GetPieChartByNameAsync(req); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public Task> PagedQueryRecordAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + return jobRecordService.PagedQueryAsync(req); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task ReleaseStuckTaskAsync() + { + var ret1 = await UpdateAsync( // 运行中,运行时间超过超时设定;置为空闲状态 + new Sys_Job { Status = JobStatues.Idle }, [nameof(Sys_Job.Status)], null + , a => a.Status == JobStatues.Running && a.LastExecTime < DateTime.Now.AddSeconds(-SysNumbers.SECS_TIMEOUT_JOB), null, true) + .ConfigureAwait(false); + + var ret2 = await UpdateAsync( // 空闲中,下次执行时间在当前时间减去超时时间以前;将下次执行时间调整到现在 + new Sys_Job { NextExecTime = DateTime.Now, NextTimeId = DateTime.Now.TimeUnixUtc() } + , [nameof(Sys_Job.NextExecTime), nameof(Sys_Job.NextTimeId)], null + , a => a.Status == JobStatues.Idle && a.NextExecTime < DateTime.Now.AddSeconds(-SysNumbers.SECS_TIMEOUT_JOB), null, true) + .ConfigureAwait(false); + return ret1 + ret2; + } + + /// + public Task SetEnabledAsync(SetJobEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(Sys_Job.Enabled)]); + } + + private static DateTime? GetNextExecTime(string cron) + { + return CronExpression.Parse(cron, CronFormat.IncludeSeconds).GetNextOccurrence(DateTime.UtcNow, TimeZoneInfo.Local)?.ToLocalTime(); + } + + private ISelect QueryInternal(QueryReq req) + { + return QueryInternal(req, true); + } + + private ISelect QueryInternal(QueryReq req, bool includeUser) + { + var ret = Rpo.Select; + if (includeUser) { + ret = ret.Include(a => a.User); + } + + ret = ret.WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0, a => a.Id == req.Keywords.Int64Try(0) || a.JobName.Contains(req.Keywords)); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.LastExecTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.LastExecTime); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/LoginLogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/LoginLogService.cs new file mode 100644 index 00000000..8b41f4a1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/LoginLogService.cs @@ -0,0 +1,143 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class LoginLogService(BasicRepository rpo) // + : RepositoryService(rpo), ILoginLogService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateLoginLogReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.登录日志导出); + } + + /// + public async Task GetAsync(QueryLoginLogReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Include(a => a.Owner) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync(a => new { + a.CreatedClientIp + , a.CreatedTime + , a.CreatedUserAgent + , a.Duration + , a.ErrorCode + , a.HttpStatusCode + , a.Id + , a.LoginUserName + , Owner = new { a.Owner.Id, a.Owner.UserName } + , a.RequestUrl + , a.ServerIp + }) + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + if (req.Keywords?.Length > 0) { + ret = req.Keywords.IsIpV4() + ? ret.Where(a => a.CreatedClientIp == req.Keywords.IpV4ToInt32()) + : ret.Where(a => a.Id == req.Keywords.Int64Try(0) || a.OwnerId == req.Keywords.Int64Try(0) || a.LoginUserName == req.Keywords); + } + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs new file mode 100644 index 00000000..e43378be --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/MenuService.cs @@ -0,0 +1,146 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class MenuService(BasicRepository rpo, IUserService userService) // + : RepositoryService(rpo), IMenuService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateMenuReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.DeleteAsync(a => a.Id == req.Id).ConfigureAwait(false); + _ = await Rpo.Orm.Delete().Where(a => a.MenuId == req.Id).ExecuteAffrowsAsync().ConfigureAwait(false); + return ret; + } + + /// + public async Task EditAsync(EditMenuReq req) + { + req.ThrowIfInvalid(); + #if DBTYPE_SQLSERVER + return (await UpdateReturnListAsync(req, null).ConfigureAwait(false)).FirstOrDefault()?.Adapt(); + #else + return await UpdateAsync(req, null).ConfigureAwait(false) > 0 ? await GetAsync(new QueryMenuReq { Id = req.Id }).ConfigureAwait(false) : null; + #endif + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QueryMenuReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .ToTreeListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task> UserMenusAsync() + { + var userInfo = await userService.UserInfoAsync().ConfigureAwait(false); + Task> ret; + var req = new QueryReq(); + + if (userInfo.Roles.Any(x => x.IgnorePermissionControl)) { + // 忽略权限控制 + ret = QueryAsync(req); + } + else { + var ownedMenuIds = userInfo.Roles.SelectMany(x => x.MenuIds); + if (ownedMenuIds.NullOrEmpty()) { + ownedMenuIds = [0L]; + } + + var df = new DynamicFilterInfo { Field = nameof(QueryMenuReq.Id), Operator = DynamicFilterOperators.Any, Value = ownedMenuIds }; + ret = QueryAsync(req with { DynamicFilter = df }); + } + + return await ret.ConfigureAwait(false); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + #pragma warning disable IDE0072 + return req.Order switch { + Orders.None => ret + , Orders.Random => ret.OrderByRandom() + , _ => ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending) + .OrderByDescending(a => a.Sort) + .OrderBy(a => a.Name) + .OrderBy(a => a.Id) + }; + #pragma warning restore IDE0072 + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogDetailService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogDetailService.cs new file mode 100644 index 00000000..3ce68689 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogDetailService.cs @@ -0,0 +1,126 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class RequestLogDetailService(BasicRepository rpo) // + : RepositoryService(rpo), IRequestLogDetailService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateRequestLogDetailReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QueryRequestLogDetailReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }) + .ToOneAsync() + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs new file mode 100644 index 00000000..c5e5767e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RequestLogService.cs @@ -0,0 +1,236 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class RequestLogService(BasicRepository rpo, LoginLogService loginLogService) // + : RepositoryService(rpo), IRequestLogService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateRequestLogReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + + // 插入登录日志 + if (req.ApiPathCrc32 == SysChars.FLG_PATH_API_SYS_USER_LOGIN_BY_PWD.Crc32()) { + _ = await loginLogService.CreateAsync(req.Adapt()).ConfigureAwait(false); + } + + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync( // + QueryInternal, req, Ln.请求日志导出, a => new { + a.Id + , Api = new { a.Api.Id } + , a.CreatedClientIp + , a.CreatedTime + , a.Duration + , a.HttpMethod + , a.HttpStatusCode + , Owner = new { a.Owner.UserName } + }); + } + + /// + public async Task GetAsync(QueryRequestLogReq req) + { + req.ThrowIfInvalid(); + var df = new DynamicFilterInfo { + Field = nameof(QueryRequestLogReq.CreatedTime) + , Operator = DynamicFilterOperators.DateRange + , Value = new[] { + req.CreatedTime.AddHours(-1).yyyy_MM_dd_HH_mm_ss() + , req.CreatedTime.AddHours(1).yyyy_MM_dd_HH_mm_ss() + }.Json() + .Object() + }; + var ret = await QueryInternal(new QueryReq { Filter = req, DynamicFilter = df, Order = Orders.None }) + .Include(a => a.Detail) + .ToOneAsync() + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> GetBarChartAsync(QueryReq req) + { + req.ThrowIfInvalid(); + + var ret = await QueryInternal(req with { Order = Orders.None }, false) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .GroupBy(a => new { a.CreatedTime.Year, a.CreatedTime.Month, a.CreatedTime.Day, a.CreatedTime.Hour }) + .ToListAsync(a => new GetBarChartRsp { + Timestamp = new DateTime(a.Key.Year, a.Key.Month, a.Key.Day, a.Key.Hour, 0, 0 + , DateTimeKind.Unspecified) + , Value = a.Count() + }) + .ConfigureAwait(false); + return ret.OrderBy(x => x.Timestamp); + } + + /// + public async Task> GetPieChartByApiSummaryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req with { Order = Orders.None }) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .GroupBy(a => a.Api.Summary) + .ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key }) + .ConfigureAwait(false); + return ret.OrderByDescending(x => x.Value); + } + + /// + public async Task> GetPieChartByHttpStatusCodeAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req with { Order = Orders.None }, false) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .GroupBy(a => a.HttpStatusCode) + #pragma warning disable CA1305 + .ToListAsync(a => new GetPieChartRsp { Value = a.Count(), Key = a.Key.ToString() }) + #pragma warning restore CA1305 + .ConfigureAwait(false); + return ret.Select(x => x with { Key = Enum.Parse(x.Key).ToString() }).OrderByDescending(x => x.Value); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var select = QueryInternal(req with { Order = Orders.None }, false); + var selectTemp = select.WithTempQuery(a => new { temp = a }); + + if (req.Order == Orders.Random) { + selectTemp = selectTemp.OrderByRandom(); + } + else { + selectTemp = selectTemp.OrderBy( // + req.Prop?.Length > 0, $"{req.Prop} {(req.Order == Orders.Ascending ? "ASC" : "DESC")}"); + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + selectTemp = selectTemp.OrderByDescending(a => a.temp.CreatedTime); + } + } + + var ret = await selectTemp.Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync(a => a.temp) + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, ret.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + return QueryInternal(req, true); + } + + private ISelect QueryInternal(QueryReq req, bool include) + { + var ret = Rpo.Select; + if (include) { + ret = ret.Include(a => a.Api).Include(a => a.Owner); + } + + ret = ret.WhereDynamicFilter(req.DynamicFilter); + if (req.Filter?.Id is not 0) { + ret = ret.WhereDynamic(req.Filter); + } + + if (req.Keywords?.Length > 0) { + ret = req.Keywords.IsIpV4() + ? ret.Where(a => a.CreatedClientIp == req.Keywords.IpV4ToInt32()) + : ret.Where(a => a.Id == req.Keywords.Int64Try(0) || a.OwnerId == req.Keywords.Int64Try(0)); + } + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs new file mode 100644 index 00000000..802b448f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/RoleService.cs @@ -0,0 +1,184 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class RoleService(BasicRepository rpo) // + : RepositoryService(rpo), IRoleService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateRoleReq req) + { + req.ThrowIfInvalid(); + var entity = req.Adapt(); + var ret = await Rpo.InsertAsync(entity).ConfigureAwait(false); + + await Rpo.SaveManyAsync(entity, nameof(entity.Depts)).ConfigureAwait(false); + await Rpo.SaveManyAsync(entity, nameof(entity.Menus)).ConfigureAwait(false); + await Rpo.SaveManyAsync(entity, nameof(entity.Apis)).ConfigureAwait(false); + + entity = entity with { Id = ret.Id }; + return entity.Adapt(); + } + + /// + /// Users_exist_under_this_role_and_deletion_is_not_allowed + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return await Rpo.Orm.Select() + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(a => a.RoleId == req.Id) + .ConfigureAwait(false) + ? throw new NetAdminInvalidOperationException(Ln.该角色下存在用户) + : await Rpo.DeleteAsync(a => a.Id == req.Id).ConfigureAwait(false); + } + + /// + public async Task EditAsync(EditRoleReq req) + { + req.ThrowIfInvalid(); + var entity = req.Adapt(); + _ = await Rpo.UpdateAsync(entity).ConfigureAwait(false); + await Rpo.SaveManyAsync(entity, nameof(entity.Depts)).ConfigureAwait(false); + await Rpo.SaveManyAsync(entity, nameof(entity.Menus)).ConfigureAwait(false); + await Rpo.SaveManyAsync(entity, nameof(entity.Apis)).ConfigureAwait(false); + + return (await QueryAsync(new QueryReq { Filter = new QueryRoleReq { Id = req.Id } }).ConfigureAwait(false)).First(); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.角色导出); + } + + /// + public async Task GetAsync(QueryRoleReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public Task SetDisplayDashboardAsync(SetDisplayDashboardReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.DisplayDashboard)]); + } + + /// + public Task SetEnabledAsync(SetRoleEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.Enabled)]); + } + + /// + public Task SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.IgnorePermissionControl)]); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.IncludeMany(a => a.Depts.Select(b => new Sys_Dept { Id = b.Id })) + .IncludeMany(a => a.Menus.Select(b => new Sys_Menu { Id = b.Id })) + .IncludeMany(a => a.Apis.Select(b => new Sys_Api { Id = b.Id })) + .WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.Id == req.Keywords.Int64Try(0) || a.Name.Contains(req.Keywords) || a.Summary.Contains(req.Keywords)); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.Sort), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Sort); + } + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs new file mode 100644 index 00000000..4917580c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgDeptService.cs @@ -0,0 +1,124 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class SiteMsgDeptService(BasicRepository rpo) // + : RepositoryService(rpo), ISiteMsgDeptService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateSiteMsgDeptReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QuerySiteMsgDeptReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs new file mode 100644 index 00000000..d9ccd39b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgFlagService.cs @@ -0,0 +1,131 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class SiteMsgFlagService(BasicRepository rpo) // + : RepositoryService(rpo), ISiteMsgFlagService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateSiteMsgFlagReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QuerySiteMsgFlagReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public Task SetUserSiteMsgStatusAsync(SetUserSiteMsgStatusReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.UserSiteMsgStatus)], null, a => a.UserId == req.UserId && a.SiteMsgId == req.SiteMsgId); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs new file mode 100644 index 00000000..1bca97b8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgRoleService.cs @@ -0,0 +1,124 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class SiteMsgRoleService(BasicRepository rpo) // + : RepositoryService(rpo), ISiteMsgRoleService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateSiteMsgRoleReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QuerySiteMsgRoleReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs new file mode 100644 index 00000000..c99ccbef --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgService.cs @@ -0,0 +1,310 @@ +using NetAdmin.SysComponent.Domain.Contexts; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class SiteMsgService(BasicRepository rpo, ContextUserInfo contextUserInfo, ISiteMsgFlagService siteMsgFlagService) // + : RepositoryService(rpo), ISiteMsgService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateSiteMsgReq req) + { + req.ThrowIfInvalid(); + await CreateEditCheckAsync(req).ConfigureAwait(false); + + // 主表 + var entity = req.Adapt(); + var dbSiteMsg = await Rpo.InsertAsync(entity).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Roles)).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Users)).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Depts)).ConfigureAwait(false); + + var ret = await QueryAsync(new QueryReq { Filter = new QuerySiteMsgReq { Id = dbSiteMsg.Id } }).ConfigureAwait(false); + + return ret.Adapt(); + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.DeleteCascadeByDatabaseAsync(a => a.Id == req.Id).ConfigureAwait(false); + return ret.Count; + } + + /// + public async Task EditAsync(EditSiteMsgReq req) + { + req.ThrowIfInvalid(); + await CreateEditCheckAsync(req).ConfigureAwait(false); + + // 主表 + var entity = req.Adapt(); + _ = await UpdateAsync(entity, null).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Roles)).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Users)).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Depts)).ConfigureAwait(false); + + return (await QueryAsync(new QueryReq { Filter = new QuerySiteMsgReq { Id = req.Id } }).ConfigureAwait(false)).First(); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.站内信导出); + } + + /// + public async Task GetAsync(QuerySiteMsgReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }) + .IncludeMany(a => a.Roles) + .IncludeMany(a => a.Users) + .IncludeMany(a => a.Depts) + .ToOneAsync() + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task GetMineAsync(QuerySiteMsgReq req) + { + req.ThrowIfInvalid(); + var ret = await PagedQueryMineAsync( + new PagedQueryReq { + DynamicFilter = new DynamicFilterInfo { + Field = nameof(req.Id) + , Value = req.Id + , Operator = DynamicFilterOperators.Eq + } + }, true) + .ConfigureAwait(false); + return ret.Rows.FirstOrDefault(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync(a => new { + a.CreatedTime + , a.CreatedUserName + , a.Id + , a.MsgType + , a.Summary + , a.Title + , a.Version + }) + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public Task> PagedQueryMineAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + return PagedQueryMineAsync(req, false); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task SetSiteMsgStatusAsync(SetUserSiteMsgStatusReq req) + { + req.ThrowIfInvalid(); + if (!await ExistAsync(new QueryReq { Filter = new QuerySiteMsgReq { Id = req.SiteMsgId } }).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.站内信不存在); + } + + try { + _ = await siteMsgFlagService.CreateAsync(req with { UserId = contextUserInfo.Id }).ConfigureAwait(false); + } + catch { + await siteMsgFlagService.SetUserSiteMsgStatusAsync(req with { UserId = contextUserInfo.Id }).ConfigureAwait(false); + } + } + + /// + public async Task UnreadCountAsync() + { + // 减去标记已读的数量 + var subtract = await Rpo.Orm.Select() + .Where(a => a.UserId == contextUserInfo.Id && a.UserSiteMsgStatus == UserSiteMsgStatues.Read) + .CountAsync() + .ConfigureAwait(false); + + return await QueryMineInternal(new QueryReq()).CountAsync().ConfigureAwait(false) - subtract; + } + + private async Task CreateEditCheckAsync(CreateSiteMsgReq req) + { + // 检查角色是否存在 + if (!req.RoleIds.NullOrEmpty()) { + var roles = await Rpo.Orm.Select().Where(a => req.RoleIds.Contains(a.Id)).ToListAsync(a => a.Id).ConfigureAwait(false); + if (roles.Count != req.RoleIds.Count) { + throw new NetAdminInvalidOperationException(Ln.角色不存在); + } + } + + if (!req.DeptIds.NullOrEmpty()) { + // 检查部门是否存在 + var depts = await Rpo.Orm.Select().Where(a => req.DeptIds.Contains(a.Id)).ToListAsync(a => a.Id).ConfigureAwait(false); + if (depts.Count != req.DeptIds.Count) { + throw new NetAdminInvalidOperationException(Ln.部门不存在); + } + } + + if (!req.UserIds.NullOrEmpty()) { + // 检查用户是否存在 + var users = await Rpo.Orm.Select().Where(a => req.UserIds.Contains(a.Id)).ToListAsync(a => a.Id).ConfigureAwait(false); + if (users.Count != req.UserIds.Count) { + throw new NetAdminInvalidOperationException(Ln.用户不存在); + } + } + } + + private async Task> PagedQueryMineAsync(PagedQueryReq req, bool containsContent) + { + var list = await QueryMineInternal(req) + .Page(req.Page, req.PageSize) + .Count(out var total) + .OrderByDescending(a => a.Max(a.Value.Item1.CreatedTime)) + .ToListAsync(a => new QuerySiteMsgRsp { + Id = a.Max(a.Value.Item1.Id) + , Title = a.Max(a.Value.Item1.Title) + , Summary = a.Max(a.Value.Item1.Summary) + , Content = containsContent ? a.Max(a.Value.Item1.Content) : null + , CreatedTime = a.Max(a.Value.Item1.CreatedTime) + , MyFlags + = new QuerySiteMsgFlagRsp { + UserSiteMsgStatus + = a.Max(a.Value.Item6.UserSiteMsgStatus) + } + , Sender = new QueryUserRsp { + UserName = a.Max(a.Value.Item2.UserName) + , Avatar = a.Max(a.Value.Item2.Avatar) + } + }) + .ConfigureAwait(false); + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.Include(a => a.Creator) + .WhereDynamicFilter(req.DynamicFilter) + .WhereDynamic(req.Filter) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.Id == req.Keywords.Int64Try(0) || a.Title.Contains(req.Keywords) || a.Summary.Contains(req.Keywords)); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } + + private ISelectGrouping // + > QueryMineInternal( + QueryReq req) + { + var roleIds = contextUserInfo.Roles.Select(x => x.Id).ToList(); + + return Rpo.Orm.Select() + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .LeftJoin((a, b, _, _, _, _) => a.CreatedUserId == b.Id) + .LeftJoin((a, _, c, _, _, _) => a.Id == c.SiteMsgId) + .LeftJoin((a, _, _, d, _, _) => a.Id == d.SiteMsgId) + .LeftJoin((a, _, _, _, e, _) => a.Id == e.SiteMsgId) + .LeftJoin((a, _, _, _, _, f) => a.Id == f.SiteMsgId && f.UserId == contextUserInfo.Id) + .WhereDynamicFilter(req.DynamicFilter) + .Where((a, _, c, d, e, f) => (SqlExt.EqualIsNull(f.UserSiteMsgStatus) || f.UserSiteMsgStatus != UserSiteMsgStatues.Deleted) && + (a.MsgType == SiteMsgTypes.Public || c.DeptId == contextUserInfo.DeptId || + roleIds.Contains(d.RoleId) || e.UserId == contextUserInfo.Id)) + .GroupBy((a, _, _, _, _, _) => a.Id); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs new file mode 100644 index 00000000..5edf1b8f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/SiteMsgUserService.cs @@ -0,0 +1,124 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class SiteMsgUserService(BasicRepository rpo) // + : RepositoryService(rpo), ISiteMsgUserService +{ + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateSiteMsgUserReq req) + { + req.ThrowIfInvalid(); + var ret = await Rpo.InsertAsync(req).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QuerySiteMsgUserReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ToolsService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ToolsService.cs new file mode 100644 index 00000000..0a8f4ade --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/ToolsService.cs @@ -0,0 +1,54 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class ToolsService : ServiceBase, IToolsService +{ + /// + public string AesDecode(AesDecodeReq req) + { + req.ThrowIfInvalid(); + return req.CipherText.AesDe(GlobalStatic.SecretKey[..32]); + } + + /// + public Task ExecuteSqlAsync(ExecuteSqlReq req) + { + req.ThrowIfInvalid(); + return App.GetService().Ado.CommandFluent(req.Sql).CommandTimeout(req.TimeoutSecs).ExecuteArrayAsync(); + } + + /// + public async Task GetChangeLogAsync() + { + await using var stream = Assembly.GetEntryAssembly()!.GetManifestResourceStream("CHANGELOG.md"); + using var streamReader = new StreamReader(stream!); + return await streamReader.ReadToEndAsync().ConfigureAwait(false); + } + + /// + public Task> GetModulesAsync() + { + return Task.FromResult>( // + AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.FullName?.Contains('#') != true && a.FullName?.Contains("DynamicMethods") != true) + .Select(x => { + var asm = x.GetName(); + return new GetModulesRsp { Name = asm.Name, Version = asm.Version?.ToString() }; + }) + .OrderBy(x => x.Name)); + } + + /// + public Task GetServerUtcTimeAsync() + { + return Task.FromResult(DateTime.Now); + } + + /// + public Task GetVersionAsync() + { + return Task.FromResult(GlobalStatic.ProductVersion); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs new file mode 100644 index 00000000..5d607e25 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserProfileService.cs @@ -0,0 +1,197 @@ +using NetAdmin.SysComponent.Domain.Contexts; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class UserProfileService(BasicRepository rpo) // + : RepositoryService(rpo), IUserProfileService +{ + /// + /// 构建应用配置 + /// + public static string BuildAppConfig(Dictionary roles) + { + try { + return new string[][] { [ + Chars.FLG_FRONT_APP_SET_HOME_GRID + , new { content = roles.MaxBy(x => x.Key).Value.ToObject(), datetime = 0 }.ToJson() + ] + }.ToJson(); + } + catch { + return "[]"; + } + } + + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateUserProfileReq req) + { + req.ThrowIfInvalid(); + var entity = req.Adapt(); + var ret = await Rpo.InsertAsync(entity).ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task EditAsync(EditUserProfileReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req.Adapt(), null); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QueryUserProfileReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task GetSessionUserAppConfigAsync() + { + var ret = await Rpo.Select.Where(a => a.Id == UserToken.Id).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync((a, b, c, d, e) => new { + a + , b = new { b.Key, b.Value } + , c = new { c.Key, c.Value } + , d = new { d.Key, d.Value } + , e = new { e.Key, e.Value } + }) + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total + , list.ConvertAll( + x => x.a.Adapt() with { + NationArea = x.b.Adapt() + , CompanyArea = x.c.Adapt() + , HomeArea = x.d.Adapt() + , EmergencyContactArea = x.e.Adapt() + })); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync((a, b, c, d, e) => new { + a + , b = new { b.Key, b.Value } + , c = new { c.Key, c.Value } + , d = new { d.Key, d.Value } + , e = new { e.Key, e.Value } + }) + .ConfigureAwait(false); + return ret.ConvertAll(x => x.a.Adapt() with { + NationArea = x.b.Key == null ? null : x.b.Adapt() + , CompanyArea = x.c.Key == null ? null : x.c.Adapt() + , HomeArea = x.d.Key == null ? null : x.d.Adapt() + , EmergencyContactArea + = x.e.Key == null ? null : x.e.Adapt() + }); + } + + /// + public Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req) + { + req.ThrowIfInvalid(); + + // 默认仪表版 + if (req.AppConfig == "[]") { + req.AppConfig = BuildAppConfig(App.GetService().Roles.ToDictionary(x => x.Id, x => x.DashboardLayout)); + } + + return UpdateAsync(req, [nameof(req.AppConfig)], null, a => a.Id == UserToken.Id, null, true); + } + + private ISelect QueryInternal(QueryReq req) + { + #pragma warning disable CA1305,IDE0072 + var ret = Rpo.Orm.Select() + .LeftJoin((a, b, _, __, ___) => a.NationArea.ToString() == b.Value && b.CatalogId == SysNumbers.ID_DIC_CATALOG_GEO_AREA) + .LeftJoin((a, _, c, __, ___) => a.CompanyArea.ToString() == c.Value && c.CatalogId == SysNumbers.ID_DIC_CATALOG_GEO_AREA) + .LeftJoin((a, _, __, d, ___) => a.HomeArea.ToString() == d.Value && d.CatalogId == SysNumbers.ID_DIC_CATALOG_GEO_AREA) + .LeftJoin( + (a, _, __, ___, e) => a.EmergencyContactArea.ToString() == e.Value && e.CatalogId == SysNumbers.ID_DIC_CATALOG_GEO_AREA) + .WhereDynamicFilter(req.DynamicFilter); + + return req.Order switch { + Orders.None => ret + , Orders.Random => ret.OrderByRandom() + , _ => ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending) + .OrderByDescending((a, _, __, ___, ____) => a.Id) + }; + #pragma warning restore CA1305,IDE0072 + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs new file mode 100644 index 00000000..c9762199 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/UserService.cs @@ -0,0 +1,560 @@ +using NetAdmin.Domain.Attributes.DataValidation; +using NetAdmin.Domain.Contexts; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class UserService( + BasicRepository rpo // + , IUserProfileService userProfileService // + , IVerifyCodeService verifyCodeService // + , IEventPublisher eventPublisher) // + : RepositoryService(rpo), IUserService +{ + private readonly Expression> _listUserExp = a => new Sys_User { + Id = a.Id + , Avatar = a.Avatar + , Email = a.Email + , Mobile = a.Mobile + , Enabled = a.Enabled + , UserName = a.UserName + , Summary = a.Summary + , Version = a.Version + , CreatedTime = a.CreatedTime + , LastLoginTime = a.LastLoginTime + , Dept = new Sys_Dept { + Id = a.Dept.Id, Name = a.Dept.Name + } + , Roles = a.Roles + , CreatedUserId = a.CreatedUserId + , CreatedUserName = a.CreatedUserName + }; + + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public async Task CheckMobileAvailableAsync(CheckMobileAvailableReq req) + { + req.ThrowIfInvalid(); + return !await Rpo.Select.Where(a => a.Mobile == req.Mobile && a.Id != req.Id).AnyAsync().ConfigureAwait(false); + } + + /// + public async Task CheckUserNameAvailableAsync(CheckUserNameAvailableReq req) + { + req.ThrowIfInvalid(); + return !await Rpo.Select.Where(a => a.UserName == req.UserName && a.Id != req.Id).AnyAsync().ConfigureAwait(false); + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + #pragma warning disable VSTHRD103 + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + #pragma warning restore VSTHRD103 + } + + /// + public async Task CreateAsync(CreateUserReq req) + { + req.ThrowIfInvalid(); + var roles = await CreateEditCheckAsync(req).ConfigureAwait(false); + + // 主表 + var entity = req.Adapt(); + var dbUser = await Rpo.InsertAsync(entity).ConfigureAwait(false); + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Roles)).ConfigureAwait(false); + + // 档案表 + var appConfig = UserProfileService.BuildAppConfig(roles); + + _ = await userProfileService.CreateAsync((req.Profile ?? new CreateUserProfileReq()) with // + { + Id = dbUser.Id // + , AppConfig = appConfig + }) + .ConfigureAwait(false); + var userList = await QueryAsync(new QueryReq { Filter = new QueryUserReq { Id = dbUser.Id } }).ConfigureAwait(false); + + // 发布用户创建事件 + var ret = userList.First(); + await eventPublisher.PublishAsync(new UserCreatedEvent(ret.Adapt())).ConfigureAwait(false); + return ret; + } + + /// + public async Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + + // 删除主表 + var ret = await Rpo.DeleteAsync(req.Id).ConfigureAwait(false); + + // 删除分表 + _ = await Rpo.Orm.Delete(new { UserId = req.Id }).ExecuteAffrowsAsync().ConfigureAwait(false); + + // 删除档案表 + _ = await userProfileService.DeleteAsync(req).ConfigureAwait(false); + + return ret; + } + + /// + public async Task EditAsync(EditUserReq req) + { + req.ThrowIfInvalid(); + _ = await CreateEditCheckAsync(req).ConfigureAwait(false); + + // 主表 + var entity = req.Adapt(); + var ignoreCols = new List { nameof(Sys_User.Id), nameof(Sys_User.Token) }; + if (entity.Password == Guid.Empty) { + ignoreCols.Add(nameof(Sys_User.Password)); + } + + _ = await UpdateAsync(entity, null, ignoreCols.ToArray()).ConfigureAwait(false); + + // 档案表 + if (req.Profile != null) { + _ = await userProfileService.EditAsync(req.Profile).ConfigureAwait(false); + } + + // 分表 + await Rpo.SaveManyAsync(entity, nameof(entity.Roles)).ConfigureAwait(false); + + var ret = (await QueryAsync(new QueryReq { Filter = new QueryUserReq { Id = req.Id } }).ConfigureAwait(false)).First(); + + // 发布用户更新事件 + await eventPublisher.PublishAsync(new UserUpdatedEvent(ret.Adapt())).ConfigureAwait(false); + return ret; + } + + /// + public async Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return await (await QueryInternalAsync(req).ConfigureAwait(false)).AnyAsync().ConfigureAwait(false); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return ExportAsync(QueryInternal, req, Ln.用户导出); + } + + /// + public async Task GetAsync(QueryUserReq req) + { + req.ThrowIfInvalid(); + var ret = await (await QueryInternalAsync(new QueryReq { Filter = req, Order = Orders.None }).ConfigureAwait(false)) + .ToOneAsync() + .ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public Task GetSessionUserAppConfigAsync() + { + return userProfileService.GetSessionUserAppConfigAsync(); + } + + /// + /// 用户名或密码错误 + public async Task LoginByPwdAsync(LoginByPwdReq req) + { + req.ThrowIfInvalid(); + var pwd = req.Password.Pwd().Guid(); + + Sys_User dbUser; + #pragma warning disable IDE0045 + if (new MobileAttribute().IsValid(req.Account)) { + #pragma warning restore IDE0045 + dbUser = await Rpo.Where(a => a.Mobile == req.Account && a.Password == pwd).ToOneAsync().ConfigureAwait(false); + } + else { + dbUser = new EmailAddressAttribute().IsValid(req.Account) + ? await Rpo.Where(a => a.Email == req.Account && a.Password == pwd).ToOneAsync().ConfigureAwait(false) + : await Rpo.Where(a => a.UserName == req.Account && a.Password == pwd).ToOneAsync().ConfigureAwait(false); + } + + return dbUser == null ? throw new NetAdminInvalidOperationException(Ln.用户名或密码错误) : await LoginInternalAsync(dbUser).ConfigureAwait(false); + } + + /// + /// 验证码不正确 + /// 用户不存在 + public async Task LoginBySmsAsync(LoginBySmsReq req) + { + req.ThrowIfInvalid(); + if (!await verifyCodeService.VerifyAsync(req.Adapt()).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.验证码不正确); + } + + var dbUser = await Rpo.Where(a => a.Mobile == req.DestDevice).ToOneAsync().ConfigureAwait(false); + return dbUser == null ? throw new NetAdminInvalidOperationException(Ln.用户不存在) : await LoginInternalAsync(dbUser).ConfigureAwait(false); + } + + /// + public async Task LoginByUserIdAsync(long userId) + { + var dbUser = await Rpo.Where(a => a.Id == userId).ToOneAsync().ConfigureAwait(false); + + return await LoginInternalAsync(dbUser).ConfigureAwait(false); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var listUserExp = req.GetToListExp() ?? _listUserExp; + var select = await QueryInternalAsync(req, listUserExp == _listUserExp).ConfigureAwait(false); + var list = await select.Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync(listUserExp) + .ConfigureAwait(false); + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var list = await (await QueryInternalAsync(req, false).ConfigureAwait(false)) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync(a => new Sys_User { Id = a.Id, UserName = a.UserName }) + .ConfigureAwait(false); + return list.Adapt>(); + } + + /// + public Task> QueryProfileAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return userProfileService.QueryAsync(req); + } + + /// + /// 验证码不正确 + public async Task RegisterAsync(RegisterUserReq req) + { + req.ThrowIfInvalid(); + if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.验证码不正确); + } + + var createReq = req.Adapt() with { Profile = new CreateUserProfileReq() }; + return (await CreateAsync(createReq).ConfigureAwait(false)).Adapt(); + } + + /// + /// 验证码不正确 + /// 用户不存在 + public async Task ResetPasswordAsync(ResetPasswordReq req) + { + req.ThrowIfInvalid(); + if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.验证码不正确); + } + + var dto = (await Rpo.Where(a => a.Mobile == req.VerifySmsCodeReq.DestDevice).ToOneAsync(a => new { a.Version, a.Id }).ConfigureAwait(false)) + .Adapt() with { + Password = req.PasswordText.Pwd().Guid() + }; + return await UpdateAsync(dto, [nameof(Sys_User.Password)]).ConfigureAwait(false); + } + + /// + public async Task SetAvatarAsync(SetAvatarReq req) + { + req.ThrowIfInvalid(); + if (await UpdateAsync( + req with { + Id = UserToken.Id + , Version = await Rpo.Where(a => a.Id == UserToken.Id).ToOneAsync(a => a.Version).ConfigureAwait(false) + } + , [nameof(Sys_User.Avatar)]) + .ConfigureAwait(false) <= 0) { + return null; + } + + var ret = (await QueryAsync(new QueryReq { Filter = new QueryUserReq { Id = UserToken.Id } }).ConfigureAwait(false)).First() + .Adapt(); + + // 发布用户更新事件 + await eventPublisher.PublishAsync(new UserUpdatedEvent(ret)).ConfigureAwait(false); + return ret; + } + + /// + public async Task SetEmailAsync(SetEmailReq req) + { + req.ThrowIfInvalid(); + var user = await Rpo.Where(a => a.Id == UserToken.Id).ToOneAsync(a => new { a.Mobile, a.Version, a.Email }).ConfigureAwait(false); + + // 如果已绑定手机号码、需要手机安全验证 + if (!user.Mobile.NullOrEmpty()) { + if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException(Ln.验证码不正确); + } + + if (user.Mobile != req.VerifySmsCodeReq.DestDevice) { + throw new NetAdminInvalidOperationException($"{Ln.手机号码不正确}"); + } + } + + if (await UpdateAsync( // + new Sys_User { Email = req.DestDevice, Id = UserToken.Id, Version = user.Version }, [nameof(Sys_User.Email)]) + .ConfigureAwait(false) <= 0) { + return null; + } + + var ret = (await QueryAsync(new QueryReq { Filter = new QueryUserReq { Id = UserToken.Id } }).ConfigureAwait(false)).First() + .Adapt(); + + // 发布用户更新事件 + await eventPublisher.PublishAsync(new UserUpdatedEvent(ret)).ConfigureAwait(false); + return ret; + } + + /// + public Task SetEnabledAsync(SetUserEnabledReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.Enabled)]); + } + + /// + public async Task SetMobileAsync(SetMobileReq req) + { + req.ThrowIfInvalid(); + var user = await Rpo.Where(a => a.Id == UserToken.Id).ToOneAsync(a => new { a.Version, a.Mobile }).ConfigureAwait(false); + + if (!user.Mobile.NullOrEmpty()) { + // 已有手机号码,需验证旧手机 + if (!await verifyCodeService.VerifyAsync(req.OriginVerifySmsCodeReq).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException($"{Ln.旧手机号码验证码不正确}"); + } + + if (user.Mobile != req.OriginVerifySmsCodeReq.DestDevice) { + throw new NetAdminInvalidOperationException($"{Ln.旧手机号码不正确}"); + } + } + + // 验证新手机号码 + if (!await verifyCodeService.VerifyAsync(req.NewVerifySmsCodeReq).ConfigureAwait(false)) { + throw new NetAdminInvalidOperationException($"{Ln.新手机号码验证码不正确}"); + } + + if (await UpdateAsync( // + new Sys_User { Version = user.Version, Id = UserToken.Id, Mobile = req.NewVerifySmsCodeReq.DestDevice } + , [nameof(Sys_User.Mobile)]) + .ConfigureAwait(false) <= 0) { + return null; + } + + var ret = (await QueryAsync(new QueryReq { Filter = new QueryUserReq { Id = UserToken.Id } }).ConfigureAwait(false)).First() + .Adapt(); + + // 发布用户更新事件 + await eventPublisher.PublishAsync(new UserUpdatedEvent(ret)).ConfigureAwait(false); + return ret; + } + + /// + public async Task SetPasswordAsync(SetPasswordReq req) + { + req.ThrowIfInvalid(); + var version = await Rpo.Where(a => a.Id == UserToken.Id && a.Password == req.OldPassword.Pwd().Guid()) + .ToOneAsync(a => new long?(a.Version)) + .ConfigureAwait(false) ?? throw new NetAdminInvalidOperationException($"{Ln.旧密码不正确}"); + + return await UpdateAsync( // + new Sys_User { Id = UserToken.Id, Password = req.NewPassword.Pwd().Guid(), Version = version }, [nameof(Sys_User.Password)]) + .ConfigureAwait(false); + } + + /// + public Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req) + { + return userProfileService.SetSessionUserAppConfigAsync(req); + } + + /// + public async Task UserInfoAsync() + { + static void OtherIncludes(ISelect select) + { + select.Where(a => a.Enabled) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .IncludeMany( // + a => a.Menus + #if DBTYPE_SQLSERVER + #pragma warning disable SA1115 + , then => then.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #pragma warning restore SA1115 + #endif + #pragma warning disable SA1009, SA1111 + ) + #pragma warning restore SA1111, SA1009 + .IncludeMany( // + a => a.Depts + #if DBTYPE_SQLSERVER + #pragma warning disable SA1115 + , then => then.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #pragma warning restore SA1115 + #endif + #pragma warning disable SA1009, SA1111 + ) + #pragma warning restore SA1111, SA1009 + .IncludeMany( // + a => a.Apis + #if DBTYPE_SQLSERVER + #pragma warning disable SA1115 + , then => then.WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #pragma warning restore SA1115 + #endif + #pragma warning disable SA1009, SA1111 + ) + #pragma warning restore SA1111, SA1009 + ; + } + + var dbUser = await Rpo.Where(a => a.Token == UserToken.Token && a.Enabled) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Include(a => a.Dept) + .IncludeMany(a => a.Roles, OtherIncludes) + .ToOneAsync() + .ConfigureAwait(false); + return dbUser.Adapt(); + } + + private async Task> CreateEditCheckAsync(CreateEditUserReq req) + { + // 检查角色是否存在 + var roles = await Rpo.Orm.Select() + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Where(a => req.RoleIds.Contains(a.Id)) + .ToDictionaryAsync(a => a.Id, a => a.DashboardLayout) + .ConfigureAwait(false); + if (roles.Count != req.RoleIds.Count) { + throw new NetAdminInvalidOperationException(Ln.角色不存在); + } + + // 检查部门是否存在 + var dept = await Rpo.Orm.Select() + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Where(a => req.DeptId == a.Id) + .ToListAsync(a => a.Id) + .ConfigureAwait(false); + + return dept.Count != 1 ? throw new NetAdminInvalidOperationException(Ln.部门不存在) : roles; + } + + private async Task LoginInternalAsync(Sys_User dbUser) + { + if (!dbUser.Enabled) { + throw new NetAdminInvalidOperationException(Ln.请联系管理员激活账号); + } + + _ = await UpdateAsync(dbUser with { LastLoginTime = DateTime.Now }, [nameof(Sys_User.LastLoginTime)], ignoreVersion: true) + .ConfigureAwait(false); + + var tokenPayload = new Dictionary { { nameof(ContextUserToken), dbUser.Adapt() } }; + + var accessToken = JWTEncryption.Encrypt(tokenPayload); + return new LoginRsp { AccessToken = accessToken, RefreshToken = JWTEncryption.GenerateRefreshToken(accessToken) }; + } + + private ISelect QueryInternal(QueryReq req, IEnumerable deptIds, bool includeRoles = true) + { + var ret = Rpo.Select.Include(a => a.Dept); + if (includeRoles) { + ret = ret.IncludeMany(a => a.Roles.Select(b => new Sys_Role { Id = b.Id, Name = b.Name })); + } + + ret = ret.WhereDynamicFilter(req.DynamicFilter) + .WhereIf(deptIds != null, a => deptIds.Contains(a.DeptId)) + .WhereIf( // + req.Filter?.Id > 0, a => a.Id == req.Filter.Id) + .WhereIf( // + req.Filter?.RoleId > 0, a => a.Roles.Any(b => b.Id == req.Filter.RoleId)) + .WhereIf( // + req.Keywords?.Length > 0 + , a => a.Id == req.Keywords.Int64Try(0) || a.UserName == req.Keywords || a.Mobile == req.Keywords || a.Email == req.Keywords || + a.Summary.Contains(req.Keywords)); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + + if (!req.Prop?.Equals(nameof(req.Filter.CreatedTime), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.CreatedTime); + } + + return ret; + } + + private ISelect QueryInternal(QueryReq req) + { + IEnumerable deptIds = null; + if (req.Filter?.DeptId > 0) { + deptIds = Rpo.Orm.Select().Where(a => a.Id == req.Filter.DeptId).AsTreeCte().ToList(a => a.Id); + } + + return QueryInternal(req, deptIds); + } + + private async Task> QueryInternalAsync(QueryReq req, bool includeRoles = true) + { + IEnumerable deptIds = null; + if (req.Filter?.DeptId > 0) { + deptIds = await Rpo.Orm.Select().Where(a => a.Id == req.Filter.DeptId).AsTreeCte().ToListAsync(a => a.Id).ConfigureAwait(false); + } + + return QueryInternal(req, deptIds, includeRoles); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs new file mode 100644 index 00000000..67c8c51c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Application/Services/Sys/VerifyCodeService.cs @@ -0,0 +1,206 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Application.Services.Sys; + +/// +public sealed class VerifyCodeService(BasicRepository rpo, IEventPublisher eventPublisher) // + : RepositoryService(rpo), IVerifyCodeService +{ + private static readonly int[] _randRange = [0, 10000]; + + /// + public async Task BulkDeleteAsync(BulkReq req) + { + req.ThrowIfInvalid(); + var ret = 0; + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var item in req.Items) { + ret += await DeleteAsync(item).ConfigureAwait(false); + } + + return ret; + } + + /// + public Task CountAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .CountAsync(); + } + + /// + public async Task CreateAsync(CreateVerifyCodeReq req) + { + req.ThrowIfInvalid(); + var entity = await Rpo.InsertAsync(req).ConfigureAwait(false); + + var ret = entity.Adapt(); + + // 发布验证码创建事件 + await eventPublisher.PublishAsync(new VerifyCodeCreatedEvent(ret)).ConfigureAwait(false); + + return ret; + } + + /// + public Task DeleteAsync(DelReq req) + { + req.ThrowIfInvalid(); + return Rpo.DeleteAsync(a => a.Id == req.Id); + } + + /// + public Task ExistAsync(QueryReq req) + { + req.ThrowIfInvalid(); + return QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .AnyAsync(); + } + + /// + public Task ExportAsync(QueryReq req) + { + req.ThrowIfInvalid(); + throw new NotImplementedException(); + } + + /// + public async Task GetAsync(QueryVerifyCodeReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(new QueryReq { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false); + return ret.Adapt(); + } + + /// + public async Task> PagedQueryAsync(PagedQueryReq req) + { + req.ThrowIfInvalid(); + var list = await QueryInternal(req) + .Page(req.Page, req.PageSize) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Count(out var total) + .ToListAsync() + .ConfigureAwait(false); + + return new PagedQueryRsp(req.Page, req.PageSize, total, list.Adapt>()); + } + + /// + public async Task> QueryAsync(QueryReq req) + { + req.ThrowIfInvalid(); + var ret = await QueryInternal(req) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .Take(req.Count) + .ToListAsync() + .ConfigureAwait(false); + return ret.Adapt>(); + } + + /// + public async Task SendVerifyCodeAsync(SendVerifyCodeReq req) + { + req.ThrowIfInvalid(); + var lastSent = await GetLastSentAsync(req.DestDevice).ConfigureAwait(false); + + QueryVerifyCodeRsp ret; + + #if !DEBUG + // 有发送记录,且小于1分钟,不允许 + if (lastSent != null && (DateTime.Now - lastSent.CreatedTime).TotalMinutes < 1) { + throw new NetAdminInvalidOperationException(Ln._1分钟内只能发送1次); + } + #endif + + if (lastSent != null && lastSent.Status != VerifyCodeStatues.Verified) { // 上次发送未验证,生成相同code + ret = await CreateAsync(req.Adapt() with { Code = lastSent.Code }).ConfigureAwait(false); + } + else { // 生成新的code + var code = _randRange.Rand().ToString(CultureInfo.InvariantCulture).PadLeft(4, '0'); + ret = await CreateAsync(req.Adapt() with { Code = code }).ConfigureAwait(false); + } + + return ret.Adapt(); + } + + /// + public Task SetVerifyCodeStatusAsync(SetVerifyCodeStatusReq req) + { + req.ThrowIfInvalid(); + return UpdateAsync(req, [nameof(req.Status)]); + } + + /// + public async Task VerifyAsync(VerifyCodeReq req) + { + req.ThrowIfInvalid(); + #if DEBUG + if (req.Code == "8888") { + return true; + } + #endif + if (req.Code == GlobalStatic.SecretKey) { + return true; + } + + var lastSent = await GetLastSentAsync(req.DestDevice).ConfigureAwait(false); + + if (lastSent is not { Status: VerifyCodeStatues.Sent } || req.Code != lastSent.Code || + (DateTime.Now - lastSent.CreatedTime).TotalMinutes > 10) { + return false; + } + + _ = await UpdateAsync(lastSent with { Status = VerifyCodeStatues.Verified }, [nameof(lastSent.Status)]).ConfigureAwait(false); + + return true; + } + + private Task GetLastSentAsync(string destDevice) + { + return QueryInternal(new QueryReq { + DynamicFilter = new DynamicFilterInfo { + Field = nameof(Sys_VerifyCode.DestDevice) + , Operator = DynamicFilterOperators.Eq + , Value = destDevice + } + }) + #if DBTYPE_SQLSERVER + .WithLock(SqlServerLock.NoLock | SqlServerLock.NoWait) + #endif + .ToOneAsync(); + } + + private ISelect QueryInternal(QueryReq req) + { + var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter); + + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (req.Order) { + case Orders.None: + return ret; + case Orders.Random: + return ret.OrderByRandom(); + } + + ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending); + if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) { + ret = ret.OrderByDescending(a => a.Id); + } + + return ret; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/NetAdmin.SysComponent.Cache.csproj b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/NetAdmin.SysComponent.Cache.csproj new file mode 100644 index 00000000..88e0fbcc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/NetAdmin.SysComponent.Cache.csproj @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/ProjectUsings.cs new file mode 100644 index 00000000..fb6eb295 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/ProjectUsings.cs @@ -0,0 +1,6 @@ +global using NetAdmin.Cache; +global using NetAdmin.Domain.Dto.Dependency; +global using NetAdmin.SysComponent.Application.Modules.Sys; +global using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +global using NetAdmin.SysComponent.Cache.Sys.Dependency; +global using NetAdmin.SysComponent.Infrastructure.Constant; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ApiCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ApiCache.cs new file mode 100644 index 00000000..49d404b0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ApiCache.cs @@ -0,0 +1,68 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class ApiCache(IDistributedCache cache, IApiService service) // + : DistributedCache(cache, service), IScoped, IApiCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateApiReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryApiReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SyncAsync() + { + return Service.SyncAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CacheCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CacheCache.cs new file mode 100644 index 00000000..bc8de36c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CacheCache.cs @@ -0,0 +1,43 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class CacheCache(IDistributedCache cache, ICacheService service) // + : DistributedCache(cache, service), IScoped, ICacheCache +{ + /// + public Task BulkDeleteEntryAsync(BulkReq req) + { + return Service.BulkDeleteEntryAsync(req); + } + + /// + public Task CacheStatisticsAsync() + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(string.Empty), Service.CacheStatisticsAsync, TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)); + #else + return Service.CacheStatisticsAsync(); + #endif + } + + /// + public Task DeleteEntryAsync(DelEntryReq req) + { + return Service.DeleteEntryAsync(req); + } + + /// + public Task> GetAllEntriesAsync(GetAllEntriesReq req) + { + return Service.GetAllEntriesAsync(req); + } + + /// + public Task GetEntryAsync(GetEntriesReq req) + { + return Service.GetEntryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CaptchaCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CaptchaCache.cs new file mode 100644 index 00000000..0545b9d1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/CaptchaCache.cs @@ -0,0 +1,39 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class CaptchaCache(IDistributedCache cache, ICaptchaService service) // + : DistributedCache(cache, service), IScoped, ICaptchaCache +{ + /// + public async Task GetCaptchaImageAsync() + { + var captchaRsp = await Service.GetCaptchaImageAsync().ConfigureAwait(false); + await CreateAsync(GetCacheKey(captchaRsp.Id, nameof(CaptchaCache)), captchaRsp.SawOffsetX +, TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)) + .ConfigureAwait(false); + return captchaRsp; + } + + /// + /// 人机验证未通过 + public async Task VerifyCaptchaAndRemoveAsync(VerifyCaptchaReq req) + { + var ret = await VerifyCaptchaAsync(req).ConfigureAwait(false); + if (ret) { + // 人机验证通过,删除人机验证缓存 + await RemoveAsync(GetCacheKey(req.Id, nameof(CaptchaCache))).ConfigureAwait(false); + } + else { + throw new NetAdminInvalidOperationException(Ln.人机验证未通过); + } + } + + /// + public async Task VerifyCaptchaAsync(VerifyCaptchaReq req) + { + var val = await GetAsync(GetCacheKey(req.Id, nameof(CaptchaCache))).ConfigureAwait(false); + return await Service.VerifyCaptchaAsync(req with { SawOffsetX = val }).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConfigCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConfigCache.cs new file mode 100644 index 00000000..1a1a73c3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConfigCache.cs @@ -0,0 +1,80 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class ConfigCache(IDistributedCache cache, IConfigService service) // + : DistributedCache(cache, service), IScoped, IConfigCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateConfigReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditConfigReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryConfigReq req) + { + return Service.GetAsync(req); + } + + /// + public Task GetLatestConfigAsync() + { + return Service.GetLatestConfigAsync(); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetEnabledAsync(SetConfigEnabledReq req) + { + return Service.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConstantCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConstantCache.cs new file mode 100644 index 00000000..9235771e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ConstantCache.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class ConstantCache(IDistributedCache cache, IConstantService service) // + : DistributedCache(cache, service), IScoped, IConstantCache +{ + /// + public IDictionary GetCharsDic() + { + return Service.GetCharsDic(); + } + + /// + public IDictionary> GetEnums() + { + return Service.GetEnums(); + } + + /// + public IDictionary GetLocalizedStrings() + { + return Service.GetLocalizedStrings(); + } + + /// + public IDictionary GetNumbersDic() + { + return Service.GetNumbersDic(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IApiCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IApiCache.cs new file mode 100644 index 00000000..2a980a1f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IApiCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 接口缓存 +/// +public interface IApiCache : ICache, IApiModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICacheCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICacheCache.cs new file mode 100644 index 00000000..8d5d8638 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICacheCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 缓存缓存 +/// +public interface ICacheCache : ICache, ICacheModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICaptchaCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICaptchaCache.cs new file mode 100644 index 00000000..64b129a1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ICaptchaCache.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 人机验证缓存 +/// +public interface ICaptchaCache : ICache, ICaptchaModule +{ + /// + /// 完成人机校验 ,并删除缓存项 + /// + Task VerifyCaptchaAndRemoveAsync(VerifyCaptchaReq req); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConfigCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConfigCache.cs new file mode 100644 index 00000000..892021b8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConfigCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 配置缓存 +/// +public interface IConfigCache : ICache, IConfigModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConstantCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConstantCache.cs new file mode 100644 index 00000000..8338cbf9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IConstantCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 常量缓存 +/// +public interface IConstantCache : ICache, IConstantModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDeptCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDeptCache.cs new file mode 100644 index 00000000..e79a8da8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDeptCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 部门缓存 +/// +public interface IDeptCache : ICache, IDeptModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDevCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDevCache.cs new file mode 100644 index 00000000..c060f9d1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDevCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 开发缓存 +/// +public interface IDevCache : ICache, IDevModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCache.cs new file mode 100644 index 00000000..1891f6d4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 字典缓存 +/// +public interface IDicCache : ICache, IDicModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCatalogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCatalogCache.cs new file mode 100644 index 00000000..f1c2ed9b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicCatalogCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 字典目录缓存 +/// +public interface IDicCatalogCache : ICache, IDicCatalogModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicContentCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicContentCache.cs new file mode 100644 index 00000000..e5bb917d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IDicContentCache.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 字典内容缓存 +/// +public interface IDicContentCache : ICache, IDicContentModule +{ + /// + /// 通过分类键查询字典内容 + /// + Task> QueryByCatalogCodeAsync(string catalogCode); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IFileCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IFileCache.cs new file mode 100644 index 00000000..d51e8aec --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IFileCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 文件缓存 +/// +public interface IFileCache : ICache, IFileModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs new file mode 100644 index 00000000..a0f37cc7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 计划作业缓存 +/// +public interface IJobCache : ICache, IJobModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs new file mode 100644 index 00000000..dc665783 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IJobRecordCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 计划作业执行记录缓存 +/// +public interface IJobRecordCache : ICache, IJobRecordModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ILoginLogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ILoginLogCache.cs new file mode 100644 index 00000000..c073c1f6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ILoginLogCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 登录日志缓存 +/// +public interface ILoginLogCache : ICache, ILoginLogModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IMenuCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IMenuCache.cs new file mode 100644 index 00000000..e282a6e4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IMenuCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 菜单缓存 +/// +public interface IMenuCache : ICache, IMenuModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogCache.cs new file mode 100644 index 00000000..4655bab8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 请求日志缓存 +/// +public interface IRequestLogCache : ICache, IRequestLogModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogDetailCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogDetailCache.cs new file mode 100644 index 00000000..c2d9aa65 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRequestLogDetailCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 请求日志明细缓存 +/// +public interface IRequestLogDetailCache : ICache, IRequestLogDetailModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRoleCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRoleCache.cs new file mode 100644 index 00000000..e373f714 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IRoleCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 角色缓存 +/// +public interface IRoleCache : ICache, IRoleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgCache.cs new file mode 100644 index 00000000..57907be7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 站内信缓存 +/// +public interface ISiteMsgCache : ICache, ISiteMsgModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgDeptCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgDeptCache.cs new file mode 100644 index 00000000..f9a00d96 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgDeptCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 站内信-部门映射缓存 +/// +public interface ISiteMsgDeptCache : ICache, ISiteMsgDeptModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgFlagCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgFlagCache.cs new file mode 100644 index 00000000..d9db1e7a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgFlagCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 站内信标记缓存 +/// +public interface ISiteMsgFlagCache : ICache, ISiteMsgFlagModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgRoleCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgRoleCache.cs new file mode 100644 index 00000000..1f7d29bd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgRoleCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 站内信-角色映射缓存 +/// +public interface ISiteMsgRoleCache : ICache, ISiteMsgRoleModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgUserCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgUserCache.cs new file mode 100644 index 00000000..596b2570 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/ISiteMsgUserCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 站内信-用户映射缓存 +/// +public interface ISiteMsgUserCache : ICache, ISiteMsgUserModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IToolsCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IToolsCache.cs new file mode 100644 index 00000000..acdfff03 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IToolsCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 工具缓存 +/// +public interface IToolsCache : ICache, IToolsModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserCache.cs new file mode 100644 index 00000000..95dae0e7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserCache.cs @@ -0,0 +1,19 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 用户缓存 +/// +public interface IUserCache : ICache, IUserModule +{ + /// + /// 用户编号登录 + /// + Task LoginByUserIdAsync(long userId); + + /// + /// 删除缓存 UserInfoAsync + /// + Task RemoveUserInfoAsync(); +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserProfileCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserProfileCache.cs new file mode 100644 index 00000000..b1638909 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IUserProfileCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 用户档案缓存 +/// +public interface IUserProfileCache : ICache, IUserProfileModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IVerifyCodeCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IVerifyCodeCache.cs new file mode 100644 index 00000000..3af96fcf --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/Dependency/IVerifyCodeCache.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Cache.Sys.Dependency; + +/// +/// 验证码缓存 +/// +public interface IVerifyCodeCache : ICache, IVerifyCodeModule; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DeptCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DeptCache.cs new file mode 100644 index 00000000..207cff51 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DeptCache.cs @@ -0,0 +1,74 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class DeptCache(IDistributedCache cache, IDeptService service) // + : DistributedCache(cache, service), IScoped, IDeptCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateDeptReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditDeptReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryDeptReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetEnabledAsync(SetDeptEnabledReq req) + { + return Service.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DevCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DevCache.cs new file mode 100644 index 00000000..922f5d07 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DevCache.cs @@ -0,0 +1,26 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class DevCache(IDistributedCache cache, IDevService service) // + : DistributedCache(cache, service), IScoped, IDevCache +{ + /// + public Task GenerateCsCodeAsync(GenerateCsCodeReq req) + { + return Service.GenerateCsCodeAsync(req); + } + + /// + public Task GenerateIconCodeAsync(GenerateIconCodeReq req) + { + return Service.GenerateIconCodeAsync(req); + } + + /// + public Task GenerateJsCodeAsync() + { + return Service.GenerateJsCodeAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCache.cs new file mode 100644 index 00000000..246d12a4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCache.cs @@ -0,0 +1,117 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class DicCache(IDistributedCache cache, IDicService service) // + : DistributedCache(cache, service), IScoped, IDicCache +{ + /// + public Task BulkDeleteCatalogAsync(BulkReq req) + { + return Service.BulkDeleteCatalogAsync(req); + } + + /// + public Task BulkDeleteContentAsync(BulkReq req) + { + return Service.BulkDeleteContentAsync(req); + } + + /// + public Task CreateCatalogAsync(CreateDicCatalogReq req) + { + return Service.CreateCatalogAsync(req); + } + + /// + public Task CreateContentAsync(CreateDicContentReq req) + { + return Service.CreateContentAsync(req); + } + + /// + public Task DeleteCatalogAsync(DelReq req) + { + return Service.DeleteCatalogAsync(req); + } + + /// + public Task DeleteContentAsync(DelReq req) + { + return Service.DeleteContentAsync(req); + } + + /// + public Task EditCatalogAsync(EditDicCatalogReq req) + { + return Service.EditCatalogAsync(req); + } + + /// + public Task EditContentAsync(EditDicContentReq req) + { + return Service.EditContentAsync(req); + } + + /// + public Task ExportContentAsync(QueryReq req) + { + return Service.ExportContentAsync(req); + } + + /// + public Task GetCatalogAsync(QueryDicCatalogReq req) + { + return Service.GetCatalogAsync(req); + } + + /// + public Task GetContentAsync(QueryDicContentReq req) + { + return Service.GetContentAsync(req); + } + + /// + public Task GetDicValueAsync(GetDicValueReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetDicValueAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)); + #else + return Service.GetDicValueAsync(req); + #endif + } + + /// + public Task> PagedQueryCatalogAsync(PagedQueryReq req) + { + return Service.PagedQueryCatalogAsync(req); + } + + /// + public Task> PagedQueryContentAsync(PagedQueryReq req) + { + return Service.PagedQueryContentAsync(req); + } + + /// + public Task> QueryCatalogAsync(QueryReq req) + { + return Service.QueryCatalogAsync(req); + } + + /// + public Task> QueryContentAsync(QueryReq req) + { + return Service.QueryContentAsync(req); + } + + /// + public Task SetEnabledAsync(SetDicContentEnabledReq req) + { + return Service.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCatalogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCatalogCache.cs new file mode 100644 index 00000000..40c758b5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicCatalogCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class DicCatalogCache(IDistributedCache cache, IDicCatalogService service) // + : DistributedCache(cache, service), IScoped, IDicCatalogCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateDicCatalogReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryDicCatalogReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicContentCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicContentCache.cs new file mode 100644 index 00000000..ab143171 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/DicContentCache.cs @@ -0,0 +1,80 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class DicContentCache(IDistributedCache cache, IDicContentService service) // + : DistributedCache(cache, service), IScoped, IDicContentCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateDicContentReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryDicContentReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task> QueryByCatalogCodeAsync(string catalogCode) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(catalogCode), () => Service.QueryByCatalogCodeAsync(catalogCode) +, TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DIC_CATALOG_CODE)); + #else + return Service.QueryByCatalogCodeAsync(catalogCode); + #endif + } + + /// + public Task SetEnabledAsync(SetDicContentEnabledReq req) + { + return Service.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/FileCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/FileCache.cs new file mode 100644 index 00000000..525e6683 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/FileCache.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class FileCache(IDistributedCache cache, IFileService service) // + : DistributedCache(cache, service), IScoped, IFileCache +{ + /// + public Task UploadAsync(IFormFile file) + { + return Service.UploadAsync(file); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobCache.cs new file mode 100644 index 00000000..d514a5d8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobCache.cs @@ -0,0 +1,141 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys; +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class JobCache(IDistributedCache cache, IJobService service) : DistributedCache(cache, service), IScoped, IJobCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CountRecordAsync(QueryReq req) + { + return Service.CountRecordAsync(req); + } + + /// + public Task CreateAsync(CreateJobReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditJobReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExecuteAsync(QueryJobReq req) + { + return Service.ExecuteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task ExportRecordAsync(QueryReq req) + { + return Service.ExportRecordAsync(req); + } + + /// + public Task GetAsync(QueryJobReq req) + { + return Service.GetAsync(req); + } + + /// + public Task GetRecordAsync(QueryJobRecordReq req) + { + return Service.GetRecordAsync(req); + } + + /// + public Task> GetRecordBarChartAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetRecordBarChartAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_CHART)); + #else + return Service.GetRecordBarChartAsync(req); + #endif + } + + /// + public Task> GetRecordPieChartByHttpStatusCodeAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetRecordPieChartByHttpStatusCodeAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)); + #else + return Service.GetRecordPieChartByHttpStatusCodeAsync(req); + #endif + } + + /// + public Task> GetRecordPieChartByNameAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetRecordPieChartByNameAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_CHART)); + #else + return Service.GetRecordPieChartByNameAsync(req); + #endif + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> PagedQueryRecordAsync(PagedQueryReq req) + { + return Service.PagedQueryRecordAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetEnabledAsync(SetJobEnabledReq req) + { + return Service.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs new file mode 100644 index 00000000..31cb8cec --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/JobRecordCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class JobRecordCache(IDistributedCache cache, IJobRecordService service) + : DistributedCache(cache, service), IScoped, IJobRecordCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateJobRecordReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryJobRecordReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/LoginLogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/LoginLogCache.cs new file mode 100644 index 00000000..a7c35f2a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/LoginLogCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class LoginLogCache(IDistributedCache cache, ILoginLogService service) + : DistributedCache(cache, service), IScoped, ILoginLogCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateLoginLogReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryLoginLogReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/MenuCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/MenuCache.cs new file mode 100644 index 00000000..e848e162 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/MenuCache.cs @@ -0,0 +1,74 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class MenuCache(IDistributedCache cache, IMenuService service) // + : DistributedCache(cache, service), IScoped, IMenuCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateMenuReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditMenuReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryMenuReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task> UserMenusAsync() + { + return Service.UserMenusAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogCache.cs new file mode 100644 index 00000000..f22e701c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogCache.cs @@ -0,0 +1,111 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class RequestLogCache(IDistributedCache cache, IRequestLogService service) // + : DistributedCache(cache, service), IScoped, IRequestLogCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + #if !DEBUG + public async Task CountAsync(QueryReq req) + #else + public Task CountAsync(QueryReq req) + #endif + { + #if !DEBUG + var ret = await GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , async () => (long?)await Service.CountAsync(req).ConfigureAwait(false), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)) + .ConfigureAwait(false); + return ret ?? 0; + #else + return Service.CountAsync(req); + #endif + } + + /// + public Task CreateAsync(CreateRequestLogReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryRequestLogReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> GetBarChartAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetBarChartAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_CHART)); + #else + return Service.GetBarChartAsync(req); + #endif + } + + /// + public Task> GetPieChartByApiSummaryAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetPieChartByApiSummaryAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_CHART)); + #else + return Service.GetPieChartByApiSummaryAsync(req); + #endif + } + + /// + public Task> GetPieChartByHttpStatusCodeAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.GetPieChartByHttpStatusCodeAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_CHART)); + #else + return Service.GetPieChartByHttpStatusCodeAsync(req); + #endif + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogDetailCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogDetailCache.cs new file mode 100644 index 00000000..e6d336ed --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RequestLogDetailCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class RequestLogDetailCache(IDistributedCache cache, IRequestLogDetailService service) + : DistributedCache(cache, service), IScoped, IRequestLogDetailCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateRequestLogDetailReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryRequestLogDetailReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RoleCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RoleCache.cs new file mode 100644 index 00000000..2ca9e783 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/RoleCache.cs @@ -0,0 +1,86 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class RoleCache(IDistributedCache cache, IRoleService service) // + : DistributedCache(cache, service), IScoped, IRoleCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateRoleReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditRoleReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryRoleReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetDisplayDashboardAsync(SetDisplayDashboardReq req) + { + return Service.SetDisplayDashboardAsync(req); + } + + /// + public Task SetEnabledAsync(SetRoleEnabledReq req) + { + return Service.SetEnabledAsync(req); + } + + /// + public Task SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req) + { + return Service.SetIgnorePermissionControlAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgCache.cs new file mode 100644 index 00000000..60901ae5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgCache.cs @@ -0,0 +1,93 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class SiteMsgCache(IDistributedCache cache, ISiteMsgService service) + : DistributedCache(cache, service), IScoped, ISiteMsgCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateSiteMsgReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditSiteMsgReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QuerySiteMsgReq req) + { + return Service.GetAsync(req); + } + + /// + public Task GetMineAsync(QuerySiteMsgReq req) + { + return Service.GetMineAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> PagedQueryMineAsync(PagedQueryReq req) + { + return Service.PagedQueryMineAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SetSiteMsgStatusAsync(SetUserSiteMsgStatusReq req) + { + return Service.SetSiteMsgStatusAsync(req); + } + + /// + public Task UnreadCountAsync() + { + return Service.UnreadCountAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgDeptCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgDeptCache.cs new file mode 100644 index 00000000..53744bed --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgDeptCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class SiteMsgDeptCache(IDistributedCache cache, ISiteMsgDeptService service) + : DistributedCache(cache, service), IScoped, ISiteMsgDeptCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateSiteMsgDeptReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QuerySiteMsgDeptReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgFlagCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgFlagCache.cs new file mode 100644 index 00000000..6c061e00 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgFlagCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class SiteMsgFlagCache(IDistributedCache cache, ISiteMsgFlagService service) + : DistributedCache(cache, service), IScoped, ISiteMsgFlagCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateSiteMsgFlagReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QuerySiteMsgFlagReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgRoleCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgRoleCache.cs new file mode 100644 index 00000000..51fb7452 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgRoleCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class SiteMsgRoleCache(IDistributedCache cache, ISiteMsgRoleService service) + : DistributedCache(cache, service), IScoped, ISiteMsgRoleCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateSiteMsgRoleReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QuerySiteMsgRoleReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgUserCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgUserCache.cs new file mode 100644 index 00000000..7830b886 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/SiteMsgUserCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class SiteMsgUserCache(IDistributedCache cache, ISiteMsgUserService service) + : DistributedCache(cache, service), IScoped, ISiteMsgUserCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateSiteMsgUserReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QuerySiteMsgUserReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ToolsCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ToolsCache.cs new file mode 100644 index 00000000..f1fe1d79 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/ToolsCache.cs @@ -0,0 +1,44 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class ToolsCache(IDistributedCache cache, IToolsService service) // + : DistributedCache(cache, service), IScoped, IToolsCache +{ + /// + public string AesDecode(AesDecodeReq req) + { + return Service.AesDecode(req); + } + + /// + public Task ExecuteSqlAsync(ExecuteSqlReq req) + { + return Service.ExecuteSqlAsync(req); + } + + /// + public Task GetChangeLogAsync() + { + return Service.GetChangeLogAsync(); + } + + /// + public Task> GetModulesAsync() + { + return Service.GetModulesAsync(); + } + + /// + public Task GetServerUtcTimeAsync() + { + return Service.GetServerUtcTimeAsync(); + } + + /// + public Task GetVersionAsync() + { + return Service.GetVersionAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserCache.cs new file mode 100644 index 00000000..6b1a03b4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserCache.cs @@ -0,0 +1,192 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class UserCache(IDistributedCache cache, IUserService service, IVerifyCodeCache verifyCodeCache) // + : DistributedCache(cache, service), IScoped, IUserCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CheckMobileAvailableAsync(CheckMobileAvailableReq req) + { + return Service.CheckMobileAvailableAsync(req); + } + + /// + public Task CheckUserNameAvailableAsync(CheckUserNameAvailableReq req) + { + return Service.CheckUserNameAvailableAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateUserReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task EditAsync(EditUserReq req) + { + return Service.EditAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryUserReq req) + { + return Service.GetAsync(req); + } + + /// + public Task GetSessionUserAppConfigAsync() + { + return Service.GetSessionUserAppConfigAsync(); + } + + /// + public Task LoginByPwdAsync(LoginByPwdReq req) + { + return Service.LoginByPwdAsync(req); + } + + /// + public Task LoginBySmsAsync(LoginBySmsReq req) + { + return Service.LoginBySmsAsync(req); + } + + /// + public Task LoginByUserIdAsync(long userId) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(userId.ToInvString()) // + , () => Service.LoginByUserIdAsync(userId), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_LOGIN_BY_USER_ID)); + #else + return Service.LoginByUserIdAsync(userId); + #endif + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(req.Json().Crc32().ToString(CultureInfo.InvariantCulture)) // + , () => Service.QueryAsync(req), TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)); + #else + return Service.QueryAsync(req); + #endif + } + + /// + public Task> QueryProfileAsync(QueryReq req) + { + return Service.QueryProfileAsync(req); + } + + /// + public Task RegisterAsync(RegisterUserReq req) + { + return Service.RegisterAsync(req); + } + + /// + public Task RemoveUserInfoAsync() + { + return RemoveAsync(GetCacheKey( // + Service.UserToken.Id.ToString(CultureInfo.InvariantCulture), nameof(UserInfoAsync))); + } + + /// + public Task ResetPasswordAsync(ResetPasswordReq req) + { + return Service.ResetPasswordAsync(req); + } + + /// + public Task SetAvatarAsync(SetAvatarReq req) + { + return Service.SetAvatarAsync(req); + } + + /// + public async Task SetEmailAsync(SetEmailReq req) + { + return !await verifyCodeCache.VerifyAsync(req).ConfigureAwait(false) + ? throw new NetAdminInvalidOperationException(Ln.邮箱验证码不正确) + : await Service.SetEmailAsync(req).ConfigureAwait(false); + } + + /// + public Task SetEnabledAsync(SetUserEnabledReq req) + { + return Service.SetEnabledAsync(req); + } + + /// + public Task SetMobileAsync(SetMobileReq req) + { + return Service.SetMobileAsync(req); + } + + /// + public Task SetPasswordAsync(SetPasswordReq req) + { + return Service.SetPasswordAsync(req); + } + + /// + public Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req) + { + return Service.SetSessionUserAppConfigAsync(req); + } + + /// + public Task UserInfoAsync() + { + #if !DEBUG + return GetOrCreateAsync( // + GetCacheKey(Service.UserToken.Id.ToString(CultureInfo.InvariantCulture)), Service.UserInfoAsync +, TimeSpan.FromSeconds(SysNumbers.SECS_CACHE_DEFAULT)); + #else + return Service.UserInfoAsync(); + #endif + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserProfileCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserProfileCache.cs new file mode 100644 index 00000000..8f27e606 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/UserProfileCache.cs @@ -0,0 +1,62 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class UserProfileCache(IDistributedCache cache, IUserProfileService service) // + : DistributedCache(cache, service), IScoped, IUserProfileCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateUserProfileReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryUserProfileReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/VerifyCodeCache.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/VerifyCodeCache.cs new file mode 100644 index 00000000..b11444de --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Cache/Sys/VerifyCodeCache.cs @@ -0,0 +1,74 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Cache.Sys; + +/// +public sealed class VerifyCodeCache(IDistributedCache cache, IVerifyCodeService service) // + : DistributedCache(cache, service), IScoped, IVerifyCodeCache +{ + /// + public Task BulkDeleteAsync(BulkReq req) + { + return Service.BulkDeleteAsync(req); + } + + /// + public Task CountAsync(QueryReq req) + { + return Service.CountAsync(req); + } + + /// + public Task CreateAsync(CreateVerifyCodeReq req) + { + return Service.CreateAsync(req); + } + + /// + public Task DeleteAsync(DelReq req) + { + return Service.DeleteAsync(req); + } + + /// + public Task ExistAsync(QueryReq req) + { + return Service.ExistAsync(req); + } + + /// + public Task ExportAsync(QueryReq req) + { + return Service.ExportAsync(req); + } + + /// + public Task GetAsync(QueryVerifyCodeReq req) + { + return Service.GetAsync(req); + } + + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Service.PagedQueryAsync(req); + } + + /// + public Task> QueryAsync(QueryReq req) + { + return Service.QueryAsync(req); + } + + /// + public Task SendVerifyCodeAsync(SendVerifyCodeReq req) + { + return Service.SendVerifyCodeAsync(req); + } + + /// + public Task VerifyAsync(VerifyCodeReq req) + { + return Service.VerifyAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/ApiIdAttribute.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/ApiIdAttribute.cs new file mode 100644 index 00000000..b52b179d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/ApiIdAttribute.cs @@ -0,0 +1,22 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Domain.Attributes.DataValidation; + +/// +/// 接口编码验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class ApiIdAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var service = App.GetService(App.EffectiveTypes.Single(x => x.FullName == "NetAdmin.SysComponent.Cache.Sys.Dependency.IApiCache")); + + var req = new QueryReq { Filter = new QueryApiReq { Id = value as string } }; + + var method = service.GetType().GetMethod("ExistAsync"); + var exist = ((Task)method!.Invoke(service, [req]))!.ConfigureAwait(false).GetAwaiter().GetResult(); + return !exist ? new ValidationResult(Ln.接口编码不存在) : ValidationResult.Success; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/SpecificDeptAttribute.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/SpecificDeptAttribute.cs new file mode 100644 index 00000000..0f783588 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/SpecificDeptAttribute.cs @@ -0,0 +1,26 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Attributes.DataValidation; + +/// +/// 数据范围为特定部门的验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class SpecificDeptAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + if (validationContext.ObjectInstance is not CreateRoleReq { DataScope: DataScopes.SpecificDept }) { + return ValidationResult.Success; + } + #pragma warning disable IDE0046 + + if ((value as IEnumerable)?.Any() ?? false) { + #pragma warning restore IDE0046 + return ValidationResult.Success; + } + + return new ValidationResult(Ln.未指定部门); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/UserIdAttribute.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/UserIdAttribute.cs new file mode 100644 index 00000000..36229377 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Attributes/DataValidation/UserIdAttribute.cs @@ -0,0 +1,22 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Attributes.DataValidation; + +/// +/// 用户编号验证器 +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] +public sealed class UserIdAttribute : ValidationAttribute +{ + /// + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var service = App.GetService(App.EffectiveTypes.Single(x => x.FullName == "NetAdmin.SysComponent.Cache.Sys.Dependency.IUserCache")); + + var req = new QueryReq { Filter = new QueryUserReq { Id = (long)value! } }; + + var method = service.GetType().GetMethod("ExistAsync"); + var exist = ((Task)method!.Invoke(service, [req]))!.ConfigureAwait(false).GetAwaiter().GetResult(); + return !exist ? new ValidationResult(Ln.用户编号不存在) : ValidationResult.Success; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Contexts/ContextUserInfo.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Contexts/ContextUserInfo.cs new file mode 100644 index 00000000..b9cfbd33 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Contexts/ContextUserInfo.cs @@ -0,0 +1,26 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Contexts; + +/// +/// 上下文用户信息 +/// +public sealed record ContextUserInfo : QueryUserRsp +{ + /// + /// 从HttpContext 创建上下文用户信息 + /// + public static ContextUserInfo Create() + { + var ret = App.HttpContext?.Items[nameof(Chars.FLG_CONTEXT_USER_INFO)] as QueryUserRsp; + return ret?.Adapt(); + } + + /// + /// 是否存在于 HttpContext + /// + public static bool HasInContext() + { + return App.HttpContext?.Items.ContainsKey(Chars.FLG_CONTEXT_USER_INFO) ?? false; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Api.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Api.cs new file mode 100644 index 00000000..6fbc5e0f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Api.cs @@ -0,0 +1,83 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// Api接口表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Api))] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(PathCrc32), nameof(PathCrc32), true)] +public record Sys_Api : ImmutableEntity, IFieldSummary +{ + /// + /// 子节点 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(ParentId))] + public IEnumerable Children { get; init; } + + /// + /// 唯一编码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127, IsIdentity = false, IsPrimary = true, Position = 1)] + [CsvIgnore] + [JsonIgnore] + public override string Id { get; init; } + + /// + /// 请求方式 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_15)] + [CsvIgnore] + [JsonIgnore] + public virtual string Method { get; init; } + + /// + /// 服务名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Name { get; init; } + + /// + /// 命名空间 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + #pragma warning disable CA1716 + public virtual string Namespace { get; init; } + #pragma warning restore CA1716 + + /// + /// 父编号 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string ParentId { get; init; } + + /// + /// 路径CRC32 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int PathCrc32 { get; init; } + + /// + /// 角色集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleApi))] + public ICollection Roles { get; init; } + + /// + /// 服务描述 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Config.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Config.cs new file mode 100644 index 00000000..06da25f7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Config.cs @@ -0,0 +1,56 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 配置表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Config))] +public record Sys_Config : VersionEntity, IFieldEnabled +{ + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 用户注册是否需要人工确认 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool UserRegisterConfirm { get; init; } + + /// + /// 用户注册默认部门 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(UserRegisterDeptId))] + public Sys_Dept UserRegisterDept { get; init; } + + /// + /// 用户注册默认部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long UserRegisterDeptId { get; init; } + + /// + /// 用户注册默认角色 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(UserRegisterRoleId))] + public Sys_Role UserRegisterRole { get; init; } + + /// + /// 用户注册默认角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long UserRegisterRoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Dept.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Dept.cs new file mode 100644 index 00000000..75533fd9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Dept.cs @@ -0,0 +1,72 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 部门表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Dept))] +public record Sys_Dept : VersionEntity, IFieldEnabled, IFieldSummary, IFieldSort +{ + /// + /// 子节点 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(ParentId))] + public IEnumerable Children { get; init; } + + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 部门名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string Name { get; init; } + + /// + /// 父编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long ParentId { get; init; } + + /// + /// 角色集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleDept))] + public ICollection Roles { get; init; } + + /// + /// 发送给此部门的站内信集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgDept))] + public ICollection SiteMsgs { get; init; } + + /// + /// 排序值,越大越前 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long Sort { get; init; } + + /// + /// 部门备注 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicCatalog.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicCatalog.cs new file mode 100644 index 00000000..50f4d592 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicCatalog.cs @@ -0,0 +1,49 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 字典目录表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Code), nameof(Code), true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_DicCatalog))] +public record Sys_DicCatalog : VersionEntity +{ + /// + /// 子节点 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(ParentId))] + public IEnumerable Children { get; init; } + + /// + /// 字典编码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Code { get; init; } + + /// + /// 字典内容集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(Sys_DicContent.CatalogId))] + public ICollection Contents { get; init; } + + /// + /// 字典名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Name { get; init; } + + /// + /// 父编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long ParentId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicContent.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicContent.cs new file mode 100644 index 00000000..a629a2d8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_DicContent.cs @@ -0,0 +1,57 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 字典内容表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(CatalogId)}_{nameof(Key)}", $"{nameof(CatalogId)},{nameof(Key)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_DicContent))] +public record Sys_DicContent : VersionEntity, IFieldEnabled, IFieldSummary +{ + /// + /// 字典目录 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(CatalogId))] + public Sys_DicCatalog Catalog { get; init; } + + /// + /// 字典目录编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long CatalogId { get; init; } + + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 键名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Key { get; init; } + + /// + /// 备注 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } + + /// + /// 键值 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Value { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Job.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Job.cs new file mode 100644 index 00000000..f7800e90 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Job.cs @@ -0,0 +1,168 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 计划作业表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Job))] +public record Sys_Job : VersionEntity, IFieldEnabled, IFieldSummary +{ + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 执行时间计划 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string ExecutionCron { get; init; } + + /// + /// 请求方法 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual HttpMethods HttpMethod { get; init; } + + /// + /// 作业名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string JobName { get; init; } + + /// + /// 上次执行耗时 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? LastDuration { get; init; } + + /// + /// 上次执行时间 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime? LastExecTime { get; init; } + + /// + /// 上次执行状态 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public HttpStatusCode? LastStatusCode { get; init; } + + /// + /// 下次执行时间 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime? NextExecTime { get; init; } + + /// + /// 下次执行时间编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? NextTimeId { get; init; } + + /// + /// 随机延时起始值(毫秒) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? RandomDelayBegin { get; init; } + + /// + /// 随机延时结束值(毫秒) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? RandomDelayEnd { get; init; } + + /// + /// 请求体 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestBody { get; init; } + + /// + /// 请求头 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestHeader { get; init; } + + /// + /// 请求的网络地址 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestUrl { get; init; } + + /// + /// 作业状态 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual JobStatues Status { get; init; } + + /// + /// 备注 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } + + /// + /// 执行用户 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(UserId))] + public Sys_User User { get; init; } + + /// + /// 执行用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_JobRecord.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_JobRecord.cs new file mode 100644 index 00000000..72653c9f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_JobRecord.cs @@ -0,0 +1,116 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 计划作业执行记录表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(JobId)}_{nameof(TimeId)}", $"{nameof(JobId)},{nameof(TimeId)}", true)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(JobId), nameof(JobId), false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_JobRecord))] +public record Sys_JobRecord : LiteImmutableEntity +{ + /// + /// 执行耗时(毫秒) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long Duration { get; init; } + + /// + /// 请求方法 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual HttpMethods HttpMethod { get; init; } + + /// + /// HTTP 状态码 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public int HttpStatusCode { get; init; } + + /// + /// 拥有者信息 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(JobId))] + public Sys_Job Job { get; init; } + + /// + /// 作业编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long JobId { get; init; } + + /// + /// 请求体 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestBody { get; init; } + + /// + /// 请求头 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestHeader { get; init; } + + /// + /// 请求的网络地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string RequestUrl { get; init; } + + /// + /// 响应体 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseBody { get; init; } + + /// + /// 响应头 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseHeader { get; init; } + + /// + /// 执行时间编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long TimeId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_LoginLog.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_LoginLog.cs new file mode 100644 index 00000000..2ac76050 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_LoginLog.cs @@ -0,0 +1,159 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 登录日志表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(OwnerId), nameof(OwnerId), false)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_LoginLog))] +public record Sys_LoginLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFieldCreatedClientIp, IFieldCreatedClientUserAgent +{ + /// + /// 创建者客户端IP + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? CreatedClientIp { get; init; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime CreatedTime { get; init; } + + /// + /// 创建者客户端用户代理 + /// + #if DBTYPE_SQLSERVER + [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)] + #else + [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string CreatedUserAgent { get; init; } + + /// + /// 执行耗时(毫秒) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int Duration { get; protected init; } + + /// + /// 程序响应码 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual ErrorCodes ErrorCode { get; protected init; } + + /// + /// HTTP状态码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_SMALL_INT)] + [CsvIgnore] + [JsonIgnore] + public virtual int HttpStatusCode { get; init; } + + /// + /// 登录用户名 + /// + [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string LoginUserName { get; protected init; } + + /// + /// 拥有者 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(OwnerId))] + public Sys_User Owner { get; init; } + + /// + /// 拥有者部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? OwnerDeptId { get; init; } + + /// + /// 拥有者用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? OwnerId { get; init; } + + /// + /// 请求内容 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestBody { get; protected init; } + + /// + /// 请求头信息 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestHeaders { get; protected init; } + + /// + /// 请求地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string RequestUrl { get; protected init; } + + /// + /// 响应内容 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseBody { get; protected init; } + + /// + /// 响应头 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseHeaders { get; protected init; } + + /// + /// 服务器IP + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? ServerIp { get; protected init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Menu.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Menu.cs new file mode 100644 index 00000000..8b00b05f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Menu.cs @@ -0,0 +1,145 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 菜单表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Name), nameof(Name), true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Menu))] +public record Sys_Menu : VersionEntity, IFieldSort +{ + /// + /// 子节点或详情页需要高亮的上级菜单路由地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string Active { get; init; } + + /// + /// 子节点 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(ParentId))] + public IEnumerable Children { get; init; } + + /// + /// 背景颜色 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_7)] + [CsvIgnore] + [JsonIgnore] + public virtual string Color { get; init; } + + /// + /// 组件 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Component { get; init; } + + /// + /// 是否整页路由 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool FullPageRouting { get; init; } + + /// + /// 是否隐藏菜单 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Hidden { get; init; } + + /// + /// 是否隐藏面包屑 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool HiddenBreadCrumb { get; init; } + + /// + /// 图标 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string Icon { get; init; } + + /// + /// 菜单名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Name { get; init; } + + /// + /// 父编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long ParentId { get; init; } + + /// + /// 菜单路径 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string Path { get; init; } + + /// + /// 重定向地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string Redirect { get; init; } + + /// + /// 拥有此菜单的角色集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleMenu))] + public ICollection Roles { get; init; } + + /// + /// 排序值,越大越前 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long Sort { get; init; } + + /// + /// 标签 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string Tag { get; init; } + + /// + /// 菜单标题 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Title { get; init; } + + /// + /// 菜单类型 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual MenuTypes Type { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLog.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLog.cs new file mode 100644 index 00000000..f0c3b625 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLog.cs @@ -0,0 +1,109 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 请求日志表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(ApiPathCrc32), nameof(ApiPathCrc32), false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(OwnerId), nameof(OwnerId), false)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(HttpStatusCode), nameof(HttpStatusCode), false)] +[Table( // + Name = $"{Chars.FLG_DB_TABLE_NAME_PREFIX}{nameof(Sys_RequestLog)}_{{yyyyMMdd}}", AsTable = $"{nameof(CreatedTime)}=2024-5-1(1 day)")] +public record Sys_RequestLog : SimpleEntity, IFieldCreatedTime, IFieldOwner, IFieldCreatedClientIp +{ + /// + /// 接口 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(ApiPathCrc32), TempPrimary = nameof(Sys_Api.PathCrc32))] + public Sys_Api Api { get; init; } + + /// + /// 接口路径CRC32 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int ApiPathCrc32 { get; init; } + + /// + /// 创建者客户端IP + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? CreatedClientIp { get; init; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime CreatedTime { get; init; } + + /// + /// 明细 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(Id))] + public Sys_RequestLogDetail Detail { get; init; } + + /// + /// 执行耗时(毫秒) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int Duration { get; init; } + + /// + /// 请求方法 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_TINY_INT)] + [CsvIgnore] + [JsonIgnore] + public virtual HttpMethods HttpMethod { get; init; } + + /// + /// HTTP状态码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_SMALL_INT)] + [CsvIgnore] + [JsonIgnore] + public virtual int HttpStatusCode { get; init; } + + /// + /// 拥有者 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(OwnerId))] + public Sys_User Owner { get; init; } + + /// + /// 拥有者部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? OwnerDeptId { get; init; } + + /// + /// 拥有者用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long? OwnerId { get; init; } + + /// + /// 请求跟踪标识 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual Guid TraceId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLogDetail.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLogDetail.cs new file mode 100644 index 00000000..397d22f6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RequestLogDetail.cs @@ -0,0 +1,129 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 请求日志明细表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RequestLogDetail))] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(CreatedTime), $"{nameof(CreatedTime)} DESC", false)] +public record Sys_RequestLogDetail : SimpleEntity, IFieldCreatedTime, IFieldCreatedClientUserAgent +{ + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false, Position = -1)] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime CreatedTime { get; init; } + + /// + /// 创建者客户端用户代理 + /// + #if DBTYPE_SQLSERVER + [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_1022)] + #else + [Column(Position = -1, DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string CreatedUserAgent { get; init; } + + /// + /// 程序响应码 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual ErrorCodes ErrorCode { get; init; } + + /// + /// 异常信息 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string Exception { get; init; } + + /// + /// 请求内容 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestBody { get; init; } + + /// + /// 请求content-type + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string RequestContentType { get; init; } + + /// + /// 请求头信息 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string RequestHeaders { get; init; } + + /// + /// 请求地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string RequestUrl { get; init; } + + /// + /// 响应内容 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseBody { get; init; } + + /// + /// 响应content-type + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseContentType { get; init; } + + /// + /// 响应头 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string ResponseHeaders { get; init; } + + /// + /// 服务器IP + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? ServerIp { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Role.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Role.cs new file mode 100644 index 00000000..1231a3fa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_Role.cs @@ -0,0 +1,134 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 角色表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Name), nameof(Name), true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_Role))] +public record Sys_Role : VersionEntity, IFieldSort, IFieldEnabled, IFieldSummary, IRegister +{ + /// + /// 角色-接口映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleApi))] + public ICollection Apis { get; init; } + + /// + /// 仪表板布局 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string DashboardLayout { get; set; } + + /// + /// 数据范围 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual DataScopes DataScope { get; init; } + + /// + /// 角色-部门映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleDept))] + public ICollection Depts { get; init; } + + /// + /// 是否显示仪表板 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool DisplayDashboard { get; init; } + + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 是否忽略权限控制 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool IgnorePermissionControl { get; init; } + + /// + /// 角色-菜单映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_RoleMenu))] + public ICollection Menus { get; init; } + + /// + /// 角色名称 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string Name { get; init; } + + /// + /// 发送给此角色的站内信集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgRole))] + public ICollection SiteMsgs { get; init; } + + /// + /// 排序值,越大越前 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long Sort { get; init; } + + /// + /// 备注 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } + + /// + /// 此角色下的用户集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_UserRole))] + public ICollection Users { get; init; } + + /// + public virtual void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map( // + d => d.Depts, s => s.DeptIds.NullOrEmpty() ? Array.Empty() : s.DeptIds.Select(x => new Sys_Dept { Id = x })) + .Map( // + d => d.Menus, s => s.MenuIds.NullOrEmpty() ? Array.Empty() : s.MenuIds.Select(x => new Sys_Menu { Id = x })) + .Map( // + d => d.Apis, s => s.ApiIds.NullOrEmpty() ? Array.Empty() : s.ApiIds.Select(x => new Sys_Api { Id = x })) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleApi.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleApi.cs new file mode 100644 index 00000000..5ec80fb4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleApi.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 角色-接口映射表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RoleApi))] +public record Sys_RoleApi : ImmutableEntity +{ + /// + /// 关联的接口 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Api Api { get; init; } + + /// + /// 接口编号 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public string ApiId { get; init; } + + /// + /// 关联的角色 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Role Role { get; init; } + + /// + /// 角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleDept.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleDept.cs new file mode 100644 index 00000000..eb5c9717 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleDept.cs @@ -0,0 +1,39 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 角色-部门映射表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(RoleId)}_{nameof(DeptId)}", $"{nameof(RoleId)},{nameof(DeptId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RoleDept))] +public record Sys_RoleDept : ImmutableEntity +{ + /// + /// 关联的部门 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Dept Dept { get; init; } + + /// + /// 可访问的部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long DeptId { get; init; } + + /// + /// 关联的角色 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Role Role { get; init; } + + /// + /// 角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleMenu.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleMenu.cs new file mode 100644 index 00000000..cff50425 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_RoleMenu.cs @@ -0,0 +1,39 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 角色-菜单映射表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(RoleId)}_{nameof(MenuId)}", $"{nameof(RoleId)},{nameof(MenuId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_RoleMenu))] +public record Sys_RoleMenu : ImmutableEntity +{ + /// + /// 关联的菜单 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Menu Menu { get; init; } + + /// + /// 菜单编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long MenuId { get; init; } + + /// + /// 关联的角色 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Role Role { get; init; } + + /// + /// 角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsg.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsg.cs new file mode 100644 index 00000000..53d6bdd6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsg.cs @@ -0,0 +1,102 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 站内信表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_SiteMsg))] +public record Sys_SiteMsg : VersionEntity, IRegister, IFieldSummary +{ + /// + /// 消息内容 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string Content { get; init; } + + /// + /// 消息-创建者映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(CreatedUserId))] + public Sys_User Creator { get; init; } + + /// + /// 消息-部门映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgDept))] + public ICollection Depts { get; init; } + + /// + /// 消息-标记映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(Sys_SiteMsgFlag.SiteMsgId))] + public ICollection Flags { get; init; } + + /// + /// 消息类型 + /// + [CsvIgnore] + [JsonIgnore] + public virtual SiteMsgTypes MsgType { get; init; } + + /// + /// 消息-角色映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgRole))] + public ICollection Roles { get; init; } + + /// + /// 消息摘要 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } + + /// + /// 消息主题 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Title { get; init; } + + /// + /// 消息-用户映射 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgUser))] + public ICollection Users { get; init; } + + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map( // + d => d.Summary, s => s.Content.RemoveHtmlTag().HtmlDe().Sub(0, 100)) + .Map( // + d => d.Roles, s => s.RoleIds.NullOrEmpty() ? Array.Empty() : s.RoleIds.Select(x => new Sys_Role { Id = x })) + .Map( // + d => d.Users, s => s.UserIds.NullOrEmpty() ? Array.Empty() : s.UserIds.Select(x => new Sys_User { Id = x })) + .Map( // + d => d.Depts, s => s.DeptIds.NullOrEmpty() ? Array.Empty() : s.DeptIds.Select(x => new Sys_Dept { Id = x })) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgDept.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgDept.cs new file mode 100644 index 00000000..af545896 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgDept.cs @@ -0,0 +1,39 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 站内信-部门映射表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(DeptId)}_{nameof(SiteMsgId)}", $"{nameof(DeptId)},{nameof(SiteMsgId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_SiteMsgDept))] +public record Sys_SiteMsgDept : ImmutableEntity +{ + /// + /// 关联的部门 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Dept Dept { get; init; } + + /// + /// 部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long DeptId { get; init; } + + /// + /// 关联的站内信 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_SiteMsg SiteMsg { get; init; } + + /// + /// 站内信编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long SiteMsgId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgFlag.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgFlag.cs new file mode 100644 index 00000000..a04de438 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgFlag.cs @@ -0,0 +1,33 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 站内信标记表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(SiteMsgId)}_{nameof(UserId)}", $"{nameof(SiteMsgId)},{nameof(UserId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_SiteMsgFlag))] +public record Sys_SiteMsgFlag : MutableEntity +{ + /// + /// 站内信编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long SiteMsgId { get; init; } + + /// + /// 用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long UserId { get; init; } + + /// + /// 用户站内信状态 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual UserSiteMsgStatues UserSiteMsgStatus { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgRole.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgRole.cs new file mode 100644 index 00000000..05d3ee7c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgRole.cs @@ -0,0 +1,39 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 站内信-角色映射表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(RoleId)}_{nameof(SiteMsgId)}", $"{nameof(RoleId)},{nameof(SiteMsgId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_SiteMsgRole))] +public record Sys_SiteMsgRole : ImmutableEntity +{ + /// + /// 关联的角色 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Role Role { get; init; } + + /// + /// 角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long RoleId { get; init; } + + /// + /// 关联的站内信 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_SiteMsg SiteMsg { get; init; } + + /// + /// 站内信编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long SiteMsgId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgUser.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgUser.cs new file mode 100644 index 00000000..26c4feee --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_SiteMsgUser.cs @@ -0,0 +1,39 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 站内信-用户映射表 +/// +[SqlIndex($"{Chars.FLG_DB_INDEX_PREFIX}{nameof(UserId)}_{nameof(SiteMsgId)}", $"{nameof(UserId)},{nameof(SiteMsgId)}", true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_SiteMsgUser))] +public record Sys_SiteMsgUser : ImmutableEntity +{ + /// + /// 关联的站内信 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_SiteMsg SiteMsg { get; init; } + + /// + /// 站内信编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long SiteMsgId { get; init; } + + /// + /// 关联的用户 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_User User { get; init; } + + /// + /// 用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_User.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_User.cs new file mode 100644 index 00000000..cab3df75 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_User.cs @@ -0,0 +1,141 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 用户基本信息表 +/// +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Email), nameof(Email), true)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Mobile), nameof(Mobile), true)] +[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(UserName), nameof(UserName), true)] +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_User))] +public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister +{ + /// + /// 头像链接 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string Avatar { get; init; } + + /// + /// 所属部门 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(nameof(DeptId))] + public Sys_Dept Dept { get; init; } + + /// + /// 部门编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual long DeptId { get; init; } + + /// + /// 邮箱 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string Email { get; init; } + + /// + /// 是否启用 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual bool Enabled { get; init; } + + /// + /// 最后登录时间 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime? LastLoginTime { get; init; } + + /// + /// 手机号码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_15)] + [CsvIgnore] + [JsonIgnore] + public virtual string Mobile { get; init; } + + /// + /// 密码 + /// + [Column] + [CsvIgnore] + [DangerField] + [JsonIgnore] + public Guid Password { get; init; } + + /// + /// 用户档案 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_UserProfile Profile { get; init; } + + /// + /// 所属角色 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_UserRole))] + public ICollection Roles { get; init; } + + /// + /// 发送给此用户的站内信集合 + /// + [CsvIgnore] + [JsonIgnore] + [Navigate(ManyToMany = typeof(Sys_SiteMsgUser))] + public ICollection SiteMsgs { get; init; } + + /// + /// 备注 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public virtual string Summary { get; init; } + + /// + /// 授权验证Token,全局唯一,可以随时重置(强制下线) + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public Guid Token { get; init; } + + /// + /// 用户名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string UserName { get; init; } + + /// + public virtual void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map(d => d.Password, s => s.PasswordText.Pwd().Guid()) + .Map(d => d.Token, _ => Guid.NewGuid()) + .Map( // + d => d.Roles, s => s.RoleIds.NullOrEmpty() ? Array.Empty() : s.RoleIds.Select(x => new Sys_Role { Id = x })); + + _ = config.ForType() + .Map( // + d => d.Password, s => s.PasswordText.NullOrEmpty() ? Guid.Empty : s.PasswordText.Pwd().Guid()) + .Map( // + d => d.Roles, s => s.RoleIds.NullOrEmpty() ? Array.Empty() : s.RoleIds.Select(x => new Sys_Role { Id = x })); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserProfile.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserProfile.cs new file mode 100644 index 00000000..417ba5b3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserProfile.cs @@ -0,0 +1,233 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 用户档案表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_UserProfile))] +public record Sys_UserProfile : VersionEntity, IRegister +{ + /// + /// 应用配置 + /// + #if DBTYPE_SQLSERVER + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_MAX)] + #else + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + #endif + [CsvIgnore] + [JsonIgnore] + public virtual string AppConfig { get; set; } + + /// + /// 出生日期 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual DateTime? BornDate { get; init; } + + /// + /// 证件号码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string CertificateNumber { get; init; } + + /// + /// 证件类型 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual CertificateTypes? CertificateType { get; init; } + + /// + /// 工作地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string CompanyAddress { get; init; } + + /// + /// 工作地区 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public int? CompanyArea { get; init; } + + /// + /// 工作单位 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string CompanyName { get; init; } + + /// + /// 工作电话 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string CompanyTelephone { get; init; } + + /// + /// 文化程度 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual Educations? Education { get; init; } + + /// + /// 紧急联系地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string EmergencyContactAddress { get; init; } + + /// + /// 紧急联系地区 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public int? EmergencyContactArea { get; init; } + + /// + /// 紧急联系人手机号 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_15)] + [CsvIgnore] + [JsonIgnore] + public virtual string EmergencyContactMobile { get; init; } + + /// + /// 紧急联系人 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string EmergencyContactName { get; init; } + + /// + /// 毕业学校 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string GraduateSchool { get; init; } + + /// + /// 身高 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual int? Height { get; init; } + + /// + /// 住宅地址 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_127)] + [CsvIgnore] + [JsonIgnore] + public virtual string HomeAddress { get; init; } + + /// + /// 住宅地区 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public int? HomeArea { get; init; } + + /// + /// 住宅电话 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string HomeTelephone { get; init; } + + /// + /// 婚姻状况 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual MarriageStatues? MarriageStatus { get; init; } + + /// + /// 民族 + /// + /// 7 + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual Nations? Nation { get; init; } + + /// + /// 籍贯 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public int? NationArea { get; init; } + + /// + /// 政治面貌 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual PoliticalStatues? PoliticalStatus { get; init; } + + /// + /// 职业 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string Profession { get; init; } + + /// + /// 真实姓名 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_31)] + [CsvIgnore] + [JsonIgnore] + public virtual string RealName { get; init; } + + /// + /// 性别 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual Sexes? Sex { get; init; } + + /// + /// 用户基本信息 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_User User { get; init; } + + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map(d => d.NationArea, s => s.NationArea == null ? null : s.NationArea.Value) + .Map(d => d.CompanyArea, s => s.CompanyArea == null ? null : s.CompanyArea.Value) + .Map(d => d.HomeArea, s => s.HomeArea == null ? null : s.HomeArea.Value) + .Map( // + d => d.EmergencyContactArea, s => s.EmergencyContactArea == null ? null : s.EmergencyContactArea.Value); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserRole.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserRole.cs new file mode 100644 index 00000000..c251ae87 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_UserRole.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 用户-角色映射表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_UserRole))] +public record Sys_UserRole : VersionEntity +{ + /// + /// 关联的角色 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_Role Role { get; init; } + + /// + /// 角色编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long RoleId { get; init; } + + /// + /// 关联的用户 + /// + [CsvIgnore] + [JsonIgnore] + public Sys_User User { get; init; } + + /// + /// 用户编号 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_VerifyCode.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_VerifyCode.cs new file mode 100644 index 00000000..f311303e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/DbMaps/Sys/Sys_VerifyCode.cs @@ -0,0 +1,56 @@ +namespace NetAdmin.SysComponent.Domain.DbMaps.Sys; + +/// +/// 验证码表 +/// +[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_VerifyCode))] +public record Sys_VerifyCode : VersionEntity +{ + /// + /// 验证码 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_7)] + [CsvIgnore] + [JsonIgnore] + public virtual string Code { get; init; } + + /// + /// 目标设备 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_63)] + [CsvIgnore] + [JsonIgnore] + public virtual string DestDevice { get; init; } + + /// + /// 设备类型 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual VerifyCodeDeviceTypes DeviceType { get; init; } + + /// + /// 发送报告 + /// + [Column(DbType = Chars.FLG_DB_FIELD_TYPE_VARCHAR_255)] + [CsvIgnore] + [JsonIgnore] + public string Report { get; init; } + + /// + /// 验证码状态 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual VerifyCodeStatues Status { get; init; } + + /// + /// 验证码类型 + /// + [Column] + [CsvIgnore] + [JsonIgnore] + public virtual VerifyCodeTypes Type { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/CreateApiReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/CreateApiReq.cs new file mode 100644 index 00000000..29383823 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/CreateApiReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +/// +/// 请求:创建接口 +/// +public sealed record CreateApiReq : Sys_Api; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/ExportApiRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/ExportApiRsp.cs new file mode 100644 index 00000000..7ea6ebd1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/ExportApiRsp.cs @@ -0,0 +1,35 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +/// +/// 响应:导出接口 +/// +public sealed record ExportApiRsp : QueryApiRsp +{ + /// + [CsvIgnore] + public override IEnumerable Children { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.接口路径))] + public override string Id { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.请求方式))] + public override string Method { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.接口名称))] + public override string Name { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.接口描述))] + public override string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiReq.cs new file mode 100644 index 00000000..6d411819 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +/// +/// 请求:查询接口 +/// +public sealed record QueryApiReq : Sys_Api; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiRsp.cs new file mode 100644 index 00000000..88db6709 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Api/QueryApiRsp.cs @@ -0,0 +1,37 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +/// +/// 响应:查询接口 +/// +public record QueryApiRsp : Sys_Api +{ + /// + public new virtual IEnumerable Children { get; init; } + + /// + public override string Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Method { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Namespace { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int PathCrc32 { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/CacheStatisticsRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/CacheStatisticsRsp.cs new file mode 100644 index 00000000..12640e93 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/CacheStatisticsRsp.cs @@ -0,0 +1,70 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +/// +/// 响应:缓存统计 +/// +public sealed record CacheStatisticsRsp : DataAbstraction +{ + private static readonly Regex[] _regexes = [ + new(@"keyspace_hits:(\d+)", RegexOptions.Compiled) // + , new(@"keyspace_misses:(\d+)", RegexOptions.Compiled) // + , new(@"uptime_in_seconds:(\d+)", RegexOptions.Compiled) // + , new(@"used_cpu_sys:([\d\\.]+)", RegexOptions.Compiled) // + , new(@"used_cpu_user:([\d\\.]+)", RegexOptions.Compiled) // + , new(@"used_memory:(\d+)", RegexOptions.Compiled) // + , new("redis_version:(.+)", RegexOptions.Compiled) // + ]; + + /// + /// Initializes a new instance of the class. + /// + public CacheStatisticsRsp() { } + + /// + /// Initializes a new instance of the class. + /// + public CacheStatisticsRsp(string redisResult) + { + KeyspaceHits = _regexes[0].Match(redisResult).Groups[1].Value.Trim().Int64Try(0); + KeyspaceMisses = _regexes[1].Match(redisResult).Groups[1].Value.Trim().Int64Try(0); + UpTime = _regexes[2].Match(redisResult).Groups[1].Value.Trim().Int64Try(0); + UsedCpu = _regexes[3].Match(redisResult).Groups[1].Value.Trim().DecTry(0) + _regexes[4].Match(redisResult).Groups[1].Value.Trim().DecTry(0); + UsedMemory = _regexes[5].Match(redisResult).Groups[1].Value.Trim().Int64Try(0); + Version = _regexes[6].Match(redisResult).Groups[1].Value.Trim(); + } + + /// + /// 键总数 + /// + public long DbSize { get; init; } + + /// + /// 命中键的数量 + /// + public long KeyspaceHits { get; init; } + + /// + /// 未命中键的数量 + /// + public long KeyspaceMisses { get; init; } + + /// + /// Redis运行时间(秒) + /// + public long UpTime { get; init; } + + /// + /// 使用的CPU时间(秒) + /// + public decimal UsedCpu { get; init; } + + /// + /// 使用的内存量(字节) + /// + public long UsedMemory { get; init; } + + /// + /// Redis版本号 + /// + public string Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/DelEntryReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/DelEntryReq.cs new file mode 100644 index 00000000..e6e9bdef --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/DelEntryReq.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +/// +/// 请求:删除缓存项 +/// +public sealed record DelEntryReq : DataAbstraction +{ + /// + /// 缓存键 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.缓存键不能为空))] + public string Key { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetAllEntriesReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetAllEntriesReq.cs new file mode 100644 index 00000000..e3ec64d7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetAllEntriesReq.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +/// +/// 请求:获取所有缓存项 +/// +public sealed record GetAllEntriesReq : DataAbstraction +{ + /// + /// 关键词 + /// + public string Keywords { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntriesReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntriesReq.cs new file mode 100644 index 00000000..d86b4334 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntriesReq.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +/// +/// 请求:获取缓存项 +/// +public sealed record GetEntriesReq : DataAbstraction +{ + /// + /// 缓存键 + /// + public string Key { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntryRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntryRsp.cs new file mode 100644 index 00000000..bf6fa9e8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Cache/GetEntryRsp.cs @@ -0,0 +1,34 @@ +using StackExchange.Redis; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +/// +/// 响应:获取所有缓存项 +/// +public sealed record GetEntryRsp : DataAbstraction +{ + /// + /// Initializes a new instance of the class. + /// + public GetEntryRsp() { } + + /// + /// 缓存值 + /// + public string Data { get; set; } + + /// + /// 过期时间 + /// + public DateTime? ExpireTime { get; init; } + + /// + /// 缓存键 + /// + public string Key { get; init; } + + /// + /// 数据类型 + /// + public RedisType Type { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/GetCaptchaRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/GetCaptchaRsp.cs new file mode 100644 index 00000000..962cab00 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/GetCaptchaRsp.cs @@ -0,0 +1,28 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +/// +/// 响应:获取人机校验图 +/// +public sealed record GetCaptchaRsp : DataAbstraction +{ + /// + /// 背景图(base64) + /// + public string BackgroundImage { get; init; } + + /// + /// 唯一编码 + /// + public string Id { get; init; } + + /// + /// 缺口x坐标 + /// + [JsonIgnore] + public int SawOffsetX { get; init; } + + /// + /// 滑块图(base64) + /// + public string SliderImage { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/VerifyCaptchaReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/VerifyCaptchaReq.cs new file mode 100644 index 00000000..7e47100d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Captcha/VerifyCaptchaReq.cs @@ -0,0 +1,25 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +/// +/// 请求:完成人机验证 +/// +public sealed record VerifyCaptchaReq : DataAbstraction +{ + /// + /// 唯一编码 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.唯一编码不能为空))] + public string Id { get; init; } + + /// + /// 缺口x坐标 + /// + [JsonIgnore] + public int? SawOffsetX { get; init; } + + /// + /// 验证数据 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.验证数据不能为空))] + public string VerifyData { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/CreateConfigReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/CreateConfigReq.cs new file mode 100644 index 00000000..fc5467ea --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/CreateConfigReq.cs @@ -0,0 +1,23 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 请求:创建配置 +/// +public record CreateConfigReq : Sys_Config +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool UserRegisterConfirm { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserRegisterDeptId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserRegisterRoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/EditConfigReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/EditConfigReq.cs new file mode 100644 index 00000000..8aeed6f9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/EditConfigReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 请求:编辑配置 +/// +public sealed record EditConfigReq : CreateConfigReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/ExportConfigRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/ExportConfigRsp.cs new file mode 100644 index 00000000..bc6d75c6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/ExportConfigRsp.cs @@ -0,0 +1,60 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 响应:导出配置 +/// +public sealed record ExportConfigRsp : QueryConfigRsp, IRegister +{ + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.人工审核))] + public override bool UserRegisterConfirm { get; init; } + + /// + [CsvIgnore] + public override QueryDeptRsp UserRegisterDept { get; init; } + + /// + /// 默认部门 + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.默认部门))] + public string UserRegisterDeptName { get; init; } + + /// + [CsvIgnore] + public override QueryRoleRsp UserRegisterRole { get; init; } + + /// + /// 默认角色 + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.默认角色))] + public string UserRegisterRoleName { get; init; } + + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map(d => d.UserRegisterDeptName, s => s.UserRegisterDept.Name) + .Map(d => d.UserRegisterRoleName, s => s.UserRegisterRole.Name); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigReq.cs new file mode 100644 index 00000000..bbde4e52 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 请求:查询配置 +/// +public sealed record QueryConfigReq : Sys_Config +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public new bool? Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigRsp.cs new file mode 100644 index 00000000..e2ef1416 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/QueryConfigRsp.cs @@ -0,0 +1,44 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 响应:查询配置 +/// +public record QueryConfigRsp : Sys_Config +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool UserRegisterConfirm { get; init; } + + /// + public new virtual QueryDeptRsp UserRegisterDept { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserRegisterDeptId { get; init; } + + /// + public new virtual QueryRoleRsp UserRegisterRole { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserRegisterRoleId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/SetConfigEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/SetConfigEnabledReq.cs new file mode 100644 index 00000000..88660fcd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Config/SetConfigEnabledReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +/// +/// 请求:启用/禁用配置 +/// +public sealed record SetConfigEnabledReq : Sys_Config +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/CreateDeptReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/CreateDeptReq.cs new file mode 100644 index 00000000..1e37c2a0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/CreateDeptReq.cs @@ -0,0 +1,30 @@ +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 请求:创建部门 +/// +public record CreateDeptReq : Sys_Dept +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.部门名称不能为空))] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } = SysNumbers.DEF_SORT_VAL; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/EditDeptReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/EditDeptReq.cs new file mode 100644 index 00000000..df4b915b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/EditDeptReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 请求:编辑部门 +/// +public sealed record EditDeptReq : CreateDeptReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/ExportDeptRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/ExportDeptRsp.cs new file mode 100644 index 00000000..f45824e6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/ExportDeptRsp.cs @@ -0,0 +1,47 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 响应:导出部门 +/// +public sealed record ExportDeptRsp : QueryDeptRsp +{ + /// + [CsvIgnore] + public override IEnumerable Children { get; init; } + + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.部门名称))] + public override string Name { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.排序))] + public override long Sort { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.备注))] + public override string Summary { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptReq.cs new file mode 100644 index 00000000..a0c38716 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 请求:查询部门 +/// +public sealed record QueryDeptReq : Sys_Dept +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptRsp.cs new file mode 100644 index 00000000..ccf1f43f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/QueryDeptRsp.cs @@ -0,0 +1,44 @@ +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 响应:查询部门 +/// +public record QueryDeptRsp : Sys_Dept +{ + /// + public new virtual IEnumerable Children { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } = SysNumbers.DEF_SORT_VAL; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/SetDeptEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/SetDeptEnabledReq.cs new file mode 100644 index 00000000..2078f026 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dept/SetDeptEnabledReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +/// +/// 请求:启用/禁用部门 +/// +public sealed record SetDeptEnabledReq : Sys_Dept +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateCsCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateCsCodeReq.cs new file mode 100644 index 00000000..6e95b548 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateCsCodeReq.cs @@ -0,0 +1,25 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +/// +/// 请求:生成后端代码 +/// +public sealed record GenerateCsCodeReq : DataAbstraction +{ + /// + /// 模块名称 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块名称不能为空))] + public string ModuleName { get; init; } + + /// + /// 模块说明 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块说明不能为空))] + public string ModuleRemark { get; init; } + + /// + /// 模块类型 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.模块类型不能为空))] + public string Type { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateIconCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateIconCodeReq.cs new file mode 100644 index 00000000..e2ac4ab0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/GenerateIconCodeReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +/// +/// 请求:生成图标代码 +/// +public sealed record GenerateIconCodeReq : DataAbstraction +{ + /// + /// 图标名称 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.图标名称不能为空))] + public string IconName { get; init; } + + /// + /// 图标代码 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.图标代码不能为空))] + public string SvgCode { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/IconExportJsInfo.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/IconExportJsInfo.cs new file mode 100644 index 00000000..a47b35da --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dev/IconExportJsInfo.cs @@ -0,0 +1,40 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +/// +/// IconExportJsInfo +/// +public sealed record IconExportJsInfo : DataAbstraction +{ + /// + /// ExportDefault + /// + public ExportDefaultRecord ExportDefault { get; init; } + + /// + /// ExportDefaultRecord + /// + public sealed record ExportDefaultRecord + { + /// + /// Icons + /// + public IEnumerable Icons { get; init; } + } + + /// + /// IconsItem + /// + public sealed record IconsItem + { + /// + /// Icons + /// + [JsonInclude] + public ICollection Icons { get; init; } + + /// + /// Name + /// + public string Name { get; init; } + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/CreateDicCatalogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/CreateDicCatalogReq.cs new file mode 100644 index 00000000..13913a8f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/CreateDicCatalogReq.cs @@ -0,0 +1,21 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +/// +/// 请求:创建字典目录 +/// +public record CreateDicCatalogReq : Sys_DicCatalog +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.字典编码不能为空))] + public override string Code { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.字典名称不能为空))] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/EditDicCatalogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/EditDicCatalogReq.cs new file mode 100644 index 00000000..67615fbf --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/EditDicCatalogReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +/// +/// 请求:编辑字典目录 +/// +public sealed record EditDicCatalogReq : CreateDicCatalogReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogReq.cs new file mode 100644 index 00000000..d7abee76 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +/// +/// 请求:查询字典目录 +/// +public sealed record QueryDicCatalogReq : Sys_DicCatalog; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogRsp.cs new file mode 100644 index 00000000..724f42aa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Catalog/QueryDicCatalogRsp.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; + +/// +/// 响应:查询字典目录 +/// +public sealed record QueryDicCatalogRsp : Sys_DicCatalog +{ + /// + public new IEnumerable Children { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Code { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/CreateDicContentReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/CreateDicContentReq.cs new file mode 100644 index 00000000..3852119b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/CreateDicContentReq.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 请求:创建字典内容 +/// +public record CreateDicContentReq : Sys_DicContent +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.字典目录编号不能为空))] + public override long CatalogId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } = true; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.键名称不能为空))] + public override string Key { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.键值不能为空))] + public override string Value { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/EditDicContentReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/EditDicContentReq.cs new file mode 100644 index 00000000..bb8b8443 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/EditDicContentReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 请求:编辑字典内容 +/// +public sealed record EditDicContentReq : CreateDicContentReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/ExportDicContentRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/ExportDicContentRsp.cs new file mode 100644 index 00000000..3a32f31b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/ExportDicContentRsp.cs @@ -0,0 +1,37 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 响应:导出字典内容 +/// +public sealed record ExportDicContentRsp : QueryDicContentRsp +{ + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.项名))] + public override string Key { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.备注))] + public override string Summary { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.项值))] + public override string Value { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/GetDicValueReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/GetDicValueReq.cs new file mode 100644 index 00000000..ce05dc5d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/GetDicValueReq.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 请求:获取字典值 +/// +public sealed record GetDicValueReq : Sys_DicContent +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.字典目录编号不能为空))] + public override long CatalogId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.键名称不能为空))] + public override string Key { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentReq.cs new file mode 100644 index 00000000..04de6f72 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 请求:查询字典内容 +/// +public sealed record QueryDicContentReq : Sys_DicContent; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentRsp.cs new file mode 100644 index 00000000..29909c48 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/QueryDicContentRsp.cs @@ -0,0 +1,35 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 响应:查询字典内容 +/// +public record QueryDicContentRsp : Sys_DicContent +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long CatalogId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Key { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Value { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/SetDicContentEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/SetDicContentEnabledReq.cs new file mode 100644 index 00000000..ab4246a7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Dic/Content/SetDicContentEnabledReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +/// +/// 请求:设置字典内容启用状态 +/// +public sealed record SetDicContentEnabledReq : Sys_DicContent +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetBarChartRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetBarChartRsp.cs new file mode 100644 index 00000000..83e89d5a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetBarChartRsp.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys; + +/// +/// 响应:获取条形图数据 +/// +public sealed record GetBarChartRsp : DataAbstraction +{ + /// + /// 时间戳 + /// + public DateTime Timestamp { get; init; } + + /// + /// 值 + /// + public int Value { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetPieChartRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetPieChartRsp.cs new file mode 100644 index 00000000..7477d981 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/GetPieChartRsp.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys; + +/// +/// 响应:获取饼图数据 +/// +public sealed record GetPieChartRsp : DataAbstraction +{ + /// + /// 键名 + /// + public string Key { get; init; } + + /// + /// 键值 + /// + public int Value { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/CreateJobReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/CreateJobReq.cs new file mode 100644 index 00000000..a481716c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/CreateJobReq.cs @@ -0,0 +1,68 @@ +using NetAdmin.SysComponent.Domain.Attributes.DataValidation; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 请求:创建计划作业 +/// +public record CreateJobReq : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } = true; + + /// + [Cron] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.时间计划不能为空))] + public override string ExecutionCron { get; init; } + + /// + [EnumDataType(typeof(HttpMethods), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.请求方法不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.作业名称不能为空))] + public override string JobName { get; init; } + + /// + public override long? NextTimeId { get; init; } + + /// + [Range(1, int.MaxValue, ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.随机延时起始时间不正确))] + public override int? RandomDelayBegin { get; init; } + + /// + [Range(1, int.MaxValue, ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.随机延时结束时间不正确))] + public override int? RandomDelayEnd { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + /// 请求头 + /// + public Dictionary RequestHeaders { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.请求地址不能为空))] + [Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))] + public override string RequestUrl { get; init; } + + /// + public override JobStatues Status { get; init; } = JobStatues.Idle; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Range(1, long.MaxValue, ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户编号不存在))] + [UserId] + public override long UserId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/EditJobReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/EditJobReq.cs new file mode 100644 index 00000000..e6526af0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/EditJobReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 请求:编辑计划作业 +/// +public sealed record EditJobReq : CreateJobReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/ExportJobRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/ExportJobRsp.cs new file mode 100644 index 00000000..a8818930 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/ExportJobRsp.cs @@ -0,0 +1,79 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 响应:导出计划作业 +/// +public sealed record ExportJobRsp : QueryJobRsp +{ + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.上次执行状态))] + public override string LastStatusCode => base.LastStatusCode; + + /// + [CsvIndex(10)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(9)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.执行计划))] + public override string ExecutionCron { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.请求方式))] + public override HttpMethods HttpMethod { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.作业名称))] + public override string JobName { get; init; } + + /// + [CsvIndex(7)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.上次执行耗时))] + public override long? LastDuration { get; init; } + + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.上次执行时间))] + public override DateTime? LastExecTime { get; init; } + + /// + [CsvIndex(8)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.下次执行时间))] + public override DateTime? NextExecTime { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.作业状态))] + public override JobStatues Status { get; init; } + + /// + [CsvIgnore] + public override QueryUserRsp User { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/FinishJobReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/FinishJobReq.cs new file mode 100644 index 00000000..b894fe2e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/FinishJobReq.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 请求:完成计划作业 +/// +public sealed record FinishJobReq : Sys_Job, IRegister +{ + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType().Map(d => d.LastStatusCode, s => ((Sys_Job)s).LastStatusCode); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobReq.cs new file mode 100644 index 00000000..ccabc317 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 请求:查询计划作业 +/// +public sealed record QueryJobReq : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobRsp.cs new file mode 100644 index 00000000..2c3b0464 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/QueryJobRsp.cs @@ -0,0 +1,128 @@ +using CronExpressionDescriptor; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; +using Options = CronExpressionDescriptor.Options; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 响应:查询计划作业 +/// +public record QueryJobRsp : Sys_Job +{ + /// + /// Cron 表达式描述 + /// + [JsonInclude] + public string CronDescription => ExpressionDescriptor.GetDescription(ExecutionCron, new Options { Locale = "zh-CN" }); + + /// + [JsonInclude] + public new virtual string LastStatusCode => + #pragma warning disable IDE0072 + base.LastStatusCode switch { + #pragma warning restore IDE0072 + null => null + , _ => (int)base.LastStatusCode.Value == Numbers.HTTP_STATUS_BIZ_FAIL + ? nameof(ErrorCodes.Unhandled).ToLowerCamelCase() + : base.LastStatusCode.Value.ToString().ToLowerCamelCase() + }; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? CreatedUserId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CreatedUserName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ExecutionCron { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string JobName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? LastDuration { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? LastExecTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? ModifiedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? ModifiedUserId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ModifiedUserName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? NextExecTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? NextTimeId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override int? RandomDelayBegin { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override int? RandomDelayEnd { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestHeader { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestUrl { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override JobStatues Status { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + public new virtual QueryUserRsp User { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long UserId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/SetJobEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/SetJobEnabledReq.cs new file mode 100644 index 00000000..b01e3f5c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Job/SetJobEnabledReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +/// +/// 请求:设置计划作业启用状态 +/// +public sealed record SetJobEnabledReq : Sys_Job +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs new file mode 100644 index 00000000..064b1e71 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/CreateJobRecordReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +/// +/// 请求:创建计划作业执行记录 +/// +public sealed record CreateJobRecordReq : Sys_JobRecord; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/ExportJobRecordRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/ExportJobRecordRsp.cs new file mode 100644 index 00000000..63bd4193 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/ExportJobRecordRsp.cs @@ -0,0 +1,57 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +/// +/// 响应:导出计划作业执行记录 +/// +public sealed record ExportJobRecordRsp : QueryJobRecordRsp, IRegister +{ + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.响应状态码))] + public override string HttpStatusCode => base.HttpStatusCode; + + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.执行耗时))] + public override long Duration { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIgnore] + public override QueryJobRsp Job { get; init; } + + /// + /// 作业名称 + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.作业名称))] + public string JobName { get; set; } + + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.响应体))] + public override string ResponseBody { get; init; } + + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType().Map(d => d.JobName, s => s.Job.JobName); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs new file mode 100644 index 00000000..96eabd6a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +/// +/// 请求:查询计划作业执行记录 +/// +public sealed record QueryJobRecordReq : Sys_JobRecord +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs new file mode 100644 index 00000000..2564386b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/JobRecord/QueryJobRecordRsp.cs @@ -0,0 +1,65 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +/// +/// 响应:查询计划作业执行记录 +/// +public record QueryJobRecordRsp : Sys_JobRecord +{ + /// + [JsonInclude] + public new virtual string HttpStatusCode => + base.HttpStatusCode == Numbers.HTTP_STATUS_BIZ_FAIL + ? nameof(ErrorCodes.Unhandled).ToLowerCamelCase() + : ((HttpStatusCode)base.HttpStatusCode).ToString().ToLowerCamelCase(); + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Duration { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + /// 作业信息 + /// + public new virtual QueryJobRsp Job { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long JobId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestHeader { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestUrl { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseHeader { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long TimeId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/CreateLoginLogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/CreateLoginLogReq.cs new file mode 100644 index 00000000..4c6ce4a6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/CreateLoginLogReq.cs @@ -0,0 +1,53 @@ +using NetAdmin.Domain.Contexts; +using NetAdmin.Domain.Dto; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +/// +/// 请求:创建登录日志 +/// +public sealed record CreateLoginLogReq : Sys_LoginLog, IRegister +{ + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType().MapWith(x => Convert(x)); + } + + private static CreateLoginLogReq Convert(CreateRequestLogReq s) + { + var body = s.Detail.ResponseBody.ToObject>(); + ContextUserToken userToken = null; + + // ReSharper disable once InvertIf + if (body.Data?.AccessToken != null) { + try { + userToken = ContextUserToken.Create(body.Data.AccessToken); + } + catch { + // ignored + } + } + + return new CreateLoginLogReq { + Id = s.Id + , CreatedClientIp = s.CreatedClientIp + , CreatedTime = s.CreatedTime + , Duration = s.Duration + , HttpStatusCode = s.HttpStatusCode + , ErrorCode = s.Detail.ErrorCode + , RequestBody = s.Detail.RequestBody + , RequestHeaders = s.Detail.RequestHeaders + , RequestUrl = s.Detail.RequestUrl + , ResponseBody = s.Detail.ResponseBody + , ResponseHeaders = s.Detail.ResponseHeaders + , ServerIp = s.Detail.ServerIp + , CreatedUserAgent = s.Detail.CreatedUserAgent + , OwnerId = userToken?.Id + , OwnerDeptId = userToken?.DeptId + , LoginUserName = s.Detail.RequestBody?.ToObject()?.Account + }; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/ExportLoginLogRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/ExportLoginLogRsp.cs new file mode 100644 index 00000000..39643ec2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/ExportLoginLogRsp.cs @@ -0,0 +1,55 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +/// +/// 响应:导出登录日志 +/// +public sealed record ExportLoginLogRsp : QueryLoginLogRsp +{ + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.客户端IP))] + public override string CreatedClientIp => base.CreatedClientIp; + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.操作系统))] + public override string Os => base.Os; + + /// + [CsvIgnore(false)] + [CsvIndex(6)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.用户代理))] + public override string CreatedUserAgent { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.响应状态码))] + public override int HttpStatusCode { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIgnore(false)] + [CsvIndex(2)] + [CsvName(nameof(Ln.登录名))] + public override string LoginUserName { get; protected init; } + + /// + [CsvIgnore] + public override QueryUserRsp Owner { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogReq.cs new file mode 100644 index 00000000..c1771cc5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +/// +/// 请求:查询登录日志 +/// +public sealed record QueryLoginLogReq : Sys_LoginLog +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogRsp.cs new file mode 100644 index 00000000..a1e6c803 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/LoginLog/QueryLoginLogRsp.cs @@ -0,0 +1,76 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +/// +/// 响应:查询登录日志 +/// +public record QueryLoginLogRsp : Sys_LoginLog +{ + /// + /// 创建者客户端IP + /// + [JsonInclude] + public new virtual string CreatedClientIp => base.CreatedClientIp?.ToIpV4(); + + /// + /// 操作系统 + /// + [JsonInclude] + public virtual string Os => UserAgentParser.Create(CreatedUserAgent)?.Platform; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CreatedUserAgent { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int Duration { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override ErrorCodes ErrorCode { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int HttpStatusCode { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string LoginUserName { get; protected init; } + + /// + public new virtual QueryUserRsp Owner { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestHeaders { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestUrl { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseBody { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseHeaders { get; protected init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override int? ServerIp { get; protected init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/CreateMenuReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/CreateMenuReq.cs new file mode 100644 index 00000000..6709bea7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/CreateMenuReq.cs @@ -0,0 +1,69 @@ +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +/// +/// 请求:创建菜单 +/// +public record CreateMenuReq : Sys_Menu +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Active { get; init; } + + /// + public override string Color => Meta.Color; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Component { get; init; } + + /// + public override bool FullPageRouting => Meta.FullPage; + + /// + public override bool Hidden => Meta.Hidden; + + /// + public override bool HiddenBreadCrumb => Meta.HiddenBreadCrumb; + + /// + public override string Icon => Meta.Icon; + + /// + /// 元数据 + /// + public MetaInfo Meta { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.菜单名称不能为空))] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Path { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Redirect { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } = SysNumbers.DEF_SORT_VAL; + + /// + public override string Tag => Meta.Tag; + + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.菜单标题不能为空))] + public override string Title => Meta.Title; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override MenuTypes Type => Meta.Type; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/EditMenuReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/EditMenuReq.cs new file mode 100644 index 00000000..d745504c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/EditMenuReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +/// +/// 请求:更新菜单 +/// +public sealed record EditMenuReq : CreateMenuReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/MetaInfo.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/MetaInfo.cs new file mode 100644 index 00000000..db0c4ac3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/MetaInfo.cs @@ -0,0 +1,76 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +/// +/// 信息:元数据 +/// +public sealed record MetaInfo : DataAbstraction +{ + /// + /// Initializes a new instance of the class. + /// + public MetaInfo() { } + + /// + /// Initializes a new instance of the class. + /// + public MetaInfo(string color, bool fullPage, bool hidden, bool hiddenBreadCrumb, string icon, string tag, string title, MenuTypes type) + { + Color = color; + FullPage = fullPage; + Hidden = hidden; + HiddenBreadCrumb = hiddenBreadCrumb; + Icon = icon; + Tag = tag; + Title = title; + Type = type; + } + + /// + /// 背景颜色 + /// + [JsonInclude] + public string Color { get; init; } + + /// + /// 是否整页路由 + /// + [JsonInclude] + public bool FullPage { get; init; } + + /// + /// 是否隐藏 + /// + [JsonInclude] + public bool Hidden { get; init; } + + /// + /// 是否隐藏面包屑 + /// + [JsonInclude] + public bool HiddenBreadCrumb { get; init; } + + /// + /// 图标 + /// + [JsonInclude] + public string Icon { get; init; } + + /// + /// 标签 + /// + [JsonInclude] + public string Tag { get; init; } + + /// + /// 标题 + /// + [JsonInclude] + public string Title { get; init; } + + /// + /// 类型 + /// + [EnumDataType(typeof(MenuTypes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.菜单类型不正确))] + [JsonInclude] + public MenuTypes Type { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuReq.cs new file mode 100644 index 00000000..b9485e5f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +/// +/// 请求:查询菜单 +/// +public sealed record QueryMenuReq : Sys_Menu +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuRsp.cs new file mode 100644 index 00000000..0dfd238e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Menu/QueryMenuRsp.cs @@ -0,0 +1,73 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +/// +/// 信息:菜单 +/// +public sealed record QueryMenuRsp : Sys_Menu, IRegister +{ + /// + /// 元数据 + /// + public MetaInfo Meta => new(Color, FullPageRouting, Hidden, HiddenBreadCrumb, Icon, Tag, Title, Type); + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Active { get; init; } + + /// + public new IEnumerable Children { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Component { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool FullPageRouting { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Hidden { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool HiddenBreadCrumb { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long ParentId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Path { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Redirect { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } + + /// + public void Register(TypeAdapterConfig config) + { + _ = config.ForType() // + .Map(d => d.Path, s => s.Path ?? string.Empty) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/CreateRequestLogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/CreateRequestLogReq.cs new file mode 100644 index 00000000..04d6c52d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/CreateRequestLogReq.cs @@ -0,0 +1,12 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +/// +/// 请求:创建请求日志 +/// +public sealed record CreateRequestLogReq : Sys_RequestLog +{ + /// + public new CreateRequestLogDetailReq Detail { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/ExportRequestLogRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/ExportRequestLogRsp.cs new file mode 100644 index 00000000..31542243 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/ExportRequestLogRsp.cs @@ -0,0 +1,81 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +/// +/// 响应:导出请求日志 +/// +public sealed record ExportRequestLogRsp : QueryRequestLogRsp +{ + /// + /// 接口路径 + /// + [CsvIndex(2)] + [JsonInclude] + [CsvName(nameof(Ln.接口路径))] + public string ApiId => Api.Id; + + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.客户端IP))] + public override string CreatedClientIp => base.CreatedClientIp; + + /// + /// 用户名 + /// + [CsvIndex(5)] + [JsonInclude] + [CsvName(nameof(Ln.用户名))] + public string UserName => Owner?.UserName; + + /// + [CsvIgnore] + public override QueryApiRsp Api { get; init; } + + /// + [CsvIgnore(false)] + [CsvIndex(8)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIgnore] + public override QueryRequestLogDetailRsp Detail { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.执行耗时))] + public override int Duration { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.请求方式))] + public override HttpMethods HttpMethod { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.响应状态码))] + public override int HttpStatusCode { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIgnore] + public override QueryUserRsp Owner { get; init; } + + /// + [CsvIndex(7)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.跟踪标识))] + public override Guid TraceId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogReq.cs new file mode 100644 index 00000000..3d051737 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +/// +/// 请求:查询请求日志 +/// +public sealed record QueryRequestLogReq : Sys_RequestLog +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogRsp.cs new file mode 100644 index 00000000..9dc3aa8c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLog/QueryRequestLogRsp.cs @@ -0,0 +1,54 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +/// +/// 响应:查询请求日志 +/// +public record QueryRequestLogRsp : Sys_RequestLog +{ + /// + /// 创建者客户端IP + /// + [JsonInclude] + public new virtual string CreatedClientIp => base.CreatedClientIp?.ToIpV4(); + + /// + public new virtual QueryApiRsp Api { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int ApiPathCrc32 { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + public new virtual QueryRequestLogDetailRsp Detail { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int Duration { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override HttpMethods HttpMethod { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override int HttpStatusCode { get; init; } + + /// + public new virtual QueryUserRsp Owner { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? OwnerId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override Guid TraceId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/CreateRequestLogDetailReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/CreateRequestLogDetailReq.cs new file mode 100644 index 00000000..003ca697 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/CreateRequestLogDetailReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +/// +/// 请求:创建请求日志明细 +/// +public sealed record CreateRequestLogDetailReq : Sys_RequestLogDetail; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailReq.cs new file mode 100644 index 00000000..d60643e7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +/// +/// 请求:查询请求日志明细 +/// +public sealed record QueryRequestLogDetailReq : Sys_RequestLogDetail +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailRsp.cs new file mode 100644 index 00000000..0ff35620 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/RequestLogDetail/QueryRequestLogDetailRsp.cs @@ -0,0 +1,69 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; + +/// +/// 响应:查询请求日志明细 +/// +public sealed record QueryRequestLogDetailRsp : Sys_RequestLogDetail +{ + /// + /// 登录名 + /// + [JsonInclude] + public string LoginName => RequestBody?.ToObject()?.Account; + + /// + /// 操作系统 + /// + [JsonInclude] + public string Os => UserAgentParser.Create(CreatedUserAgent)?.Platform; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CreatedUserAgent { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override ErrorCodes ErrorCode { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Exception { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestContentType { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestHeaders { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RequestUrl { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseBody { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseContentType { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string ResponseHeaders { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override int? ServerIp { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/CreateRoleReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/CreateRoleReq.cs new file mode 100644 index 00000000..e2b164fc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/CreateRoleReq.cs @@ -0,0 +1,70 @@ +using NetAdmin.SysComponent.Domain.Attributes.DataValidation; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:创建角色 +/// +public record CreateRoleReq : Sys_Role, IValidatableObject +{ + /// + /// 角色-接口映射 + /// + public IReadOnlyCollection ApiIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonString] + public override string DashboardLayout { get; set; } + + /// + [EnumDataType(typeof(DataScopes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.角色数据范围不正确))] + public override DataScopes DataScope { get; init; } = DataScopes.All; + + /// + /// 当 DataScope = SpecificDept ,此参数指定部门编号 + /// + [SpecificDept] + public IReadOnlyCollection DeptIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool DisplayDashboard { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool IgnorePermissionControl { get; init; } + + /// + /// 角色-菜单映射 + /// + public IReadOnlyCollection MenuIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.角色名称不能为空))] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } = SysNumbers.DEF_SORT_VAL; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + public IEnumerable Validate(ValidationContext validationContext) + { + if (validationContext.MemberName != null) { + DashboardLayout = JsonSerializer.Serialize(JsonDocument.Parse(DashboardLayout)); + } + + yield break; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/EditRoleReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/EditRoleReq.cs new file mode 100644 index 00000000..86c97c75 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/EditRoleReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:编辑角色 +/// +public sealed record EditRoleReq : CreateRoleReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/ExportRoleRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/ExportRoleRsp.cs new file mode 100644 index 00000000..dd2813c3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/ExportRoleRsp.cs @@ -0,0 +1,55 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 响应:导出角色 +/// +public sealed record ExportRoleRsp : QueryRoleRsp +{ + /// + [CsvIndex(7)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.数据范围))] + public override DataScopes DataScope { get; init; } + + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.显示仪表板))] + public override bool DisplayDashboard { get; init; } + + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.无限权限))] + public override bool IgnorePermissionControl { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.角色名称))] + public override string Name { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.排序))] + public override long Sort { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/GetMenusRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/GetMenusRsp.cs new file mode 100644 index 00000000..995da42e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/GetMenusRsp.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 响应: 获取角色菜单 +/// +public sealed record GetMenusRsp : Sys_RoleMenu +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long MenuId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/MapMenusReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/MapMenusReq.cs new file mode 100644 index 00000000..2f437c09 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/MapMenusReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:角色-菜单映射 +/// +public sealed record MapMenusReq : DataAbstraction +{ + /// + /// 菜单编号 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.菜单编号不能为空))] + public IReadOnlyCollection MenuIds { get; init; } + + /// + /// 角色编号 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.角色编号不能为空))] + public long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleReq.cs new file mode 100644 index 00000000..e4aba47f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:查询角色 +/// +public sealed record QueryRoleReq : Sys_Role +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleRsp.cs new file mode 100644 index 00000000..445fd027 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/QueryRoleRsp.cs @@ -0,0 +1,86 @@ +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 响应:查询角色 +/// +public record QueryRoleRsp : Sys_Role +{ + /// + /// 角色-接口映射 + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyCollection ApiIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string DashboardLayout { get; set; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DataScopes DataScope { get; init; } + + /// + /// 角色-部门映射 + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IEnumerable DeptIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool DisplayDashboard { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool IgnorePermissionControl { get; init; } + + /// + /// 角色-菜单映射 + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IReadOnlyCollection MenuIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Name { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Sort { get; init; } = SysNumbers.DEF_SORT_VAL; + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } + + /// + public override void Register(TypeAdapterConfig config) + { + _ = config.ForType() // + .IgnoreIf((s, _) => s.Depts == null, d => d.DeptIds) + .IgnoreIf((s, _) => s.Menus == null, d => d.MenuIds) + .IgnoreIf((s, _) => s.Apis == null, d => d.ApiIds) + .Map(d => d.DeptIds, s => s.Depts.Select(x => x.Id)) + .Map(d => d.ApiIds, s => s.Apis.Select(x => x.Id)) + .Map(d => d.MenuIds, s => s.Menus.Select(x => x.Id)) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetDisplayDashboardReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetDisplayDashboardReq.cs new file mode 100644 index 00000000..b75a3201 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetDisplayDashboardReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:设置是否显示仪表板 +/// +public sealed record SetDisplayDashboardReq : Sys_Role +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool DisplayDashboard { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetIgnorePermissionControlReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetIgnorePermissionControlReq.cs new file mode 100644 index 00000000..13906b84 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetIgnorePermissionControlReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:设置是否忽略权限控制 +/// +public sealed record SetIgnorePermissionControlReq : Sys_Role +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool IgnorePermissionControl { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetRoleEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetRoleEnabledReq.cs new file mode 100644 index 00000000..b18a7a1a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Role/SetRoleEnabledReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +/// +/// 请求:启用/禁用角色 +/// +public sealed record SetRoleEnabledReq : Sys_Role +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/CreateSiteMsgReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/CreateSiteMsgReq.cs new file mode 100644 index 00000000..827bbe48 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/CreateSiteMsgReq.cs @@ -0,0 +1,43 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +/// +/// 请求:创建站内信 +/// +public record CreateSiteMsgReq : Sys_SiteMsg +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.消息内容不能为空))] + public override string Content { get; init; } + + /// + /// 部门编号列表 + /// + [MaxLength(Numbers.MAX_LIMIT_BULK_REQ)] + [MinLength(1)] + public IReadOnlyCollection DeptIds { get; init; } + + /// + [EnumDataType(typeof(SiteMsgTypes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.站内信类型不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override SiteMsgTypes MsgType { get; init; } + + /// + /// 角色编号列表 + /// + [MaxLength(Numbers.MAX_LIMIT_BULK_REQ)] + [MinLength(1)] + public IReadOnlyCollection RoleIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.消息主题不能为空))] + public override string Title { get; init; } + + /// + /// 用户编号列表 + /// + [MaxLength(Numbers.MAX_LIMIT_BULK_REQ)] + [MinLength(1)] + public IReadOnlyCollection UserIds { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/ExportSiteMsgRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/ExportSiteMsgRsp.cs new file mode 100644 index 00000000..af10106c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/ExportSiteMsgRsp.cs @@ -0,0 +1,59 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +/// +/// 响应:导出站内信 +/// +public sealed record ExportSiteMsgRsp : QuerySiteMsgRsp +{ + /// + [CsvIndex(5)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.用户名))] + public override string CreatedUserName { get; init; } + + /// + [CsvIgnore] + public override IEnumerable Depts { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.消息类型))] + public override SiteMsgTypes MsgType { get; init; } + + /// + [CsvIgnore] + public override IEnumerable Roles { get; init; } + + /// + [CsvIndex(4)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.消息摘要))] + public override string Summary { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.消息主题))] + public override string Title { get; init; } + + /// + [CsvIgnore] + public override IEnumerable Users { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgReq.cs new file mode 100644 index 00000000..f35b5b52 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +/// +/// 请求:查询站内信 +/// +public sealed record QuerySiteMsgReq : Sys_SiteMsg +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgRsp.cs new file mode 100644 index 00000000..a12f50d6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsg/QuerySiteMsgRsp.cs @@ -0,0 +1,67 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +/// +/// 响应:查询站内信 +/// +public record QuerySiteMsgRsp : Sys_SiteMsg +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Content { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CreatedUserName { get; init; } + + /// + public new virtual IEnumerable Depts { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? ModifiedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override SiteMsgTypes MsgType { get; init; } + + /// + /// 我的标记 + /// + public QuerySiteMsgFlagRsp MyFlags { get; init; } + + /// + public new virtual IEnumerable Roles { get; init; } + + /// + /// 消息发送者 + /// + public QueryUserRsp Sender { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Title { get; init; } + + /// + public new virtual IEnumerable Users { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/CreateSiteMsgDeptReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/CreateSiteMsgDeptReq.cs new file mode 100644 index 00000000..87dfb17d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/CreateSiteMsgDeptReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +/// +/// 请求:创建站内信-部门映射 +/// +public sealed record CreateSiteMsgDeptReq : Sys_SiteMsgDept; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptReq.cs new file mode 100644 index 00000000..c1282420 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +/// +/// 请求:查询站内信-部门映射 +/// +public sealed record QuerySiteMsgDeptReq : Sys_SiteMsgDept +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptRsp.cs new file mode 100644 index 00000000..1401811e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgDept/QuerySiteMsgDeptRsp.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgDept; + +/// +/// 响应:查询站内信-部门映射 +/// +public sealed record QuerySiteMsgDeptRsp : Sys_SiteMsgDept +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/CreateSiteMsgFlagReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/CreateSiteMsgFlagReq.cs new file mode 100644 index 00000000..67ff6463 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/CreateSiteMsgFlagReq.cs @@ -0,0 +1,16 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +/// +/// 请求:创建站内信标记 +/// +public record CreateSiteMsgFlagReq : Sys_SiteMsgFlag +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long SiteMsgId { get; init; } + + /// + [EnumDataType(typeof(UserSiteMsgStatues), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.站内信状态不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override UserSiteMsgStatues UserSiteMsgStatus { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/EditSiteMsgReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/EditSiteMsgReq.cs new file mode 100644 index 00000000..b147e38d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/EditSiteMsgReq.cs @@ -0,0 +1,13 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +/// +/// 请求:编辑站内信 +/// +public sealed record EditSiteMsgReq : CreateSiteMsgReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagReq.cs new file mode 100644 index 00000000..2904c280 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +/// +/// 请求:查询站内信标记 +/// +public sealed record QuerySiteMsgFlagReq : Sys_SiteMsgFlag +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagRsp.cs new file mode 100644 index 00000000..4f49404f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/QuerySiteMsgFlagRsp.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +/// +/// 响应:查询站内信标记 +/// +public sealed record QuerySiteMsgFlagRsp : Sys_SiteMsgFlag +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override UserSiteMsgStatues UserSiteMsgStatus { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/SetUserSiteMsgStatusReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/SetUserSiteMsgStatusReq.cs new file mode 100644 index 00000000..c6d6e823 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgFlag/SetUserSiteMsgStatusReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +/// +/// 请求:设置用户站内信状态 +/// +public sealed record SetUserSiteMsgStatusReq : CreateSiteMsgFlagReq; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/CreateSiteMsgRoleReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/CreateSiteMsgRoleReq.cs new file mode 100644 index 00000000..25c2fe30 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/CreateSiteMsgRoleReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +/// +/// 请求:创建站内信-角色映射 +/// +public sealed record CreateSiteMsgRoleReq : Sys_SiteMsgRole; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleReq.cs new file mode 100644 index 00000000..45e4d677 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +/// +/// 请求:查询站内信-角色映射 +/// +public sealed record QuerySiteMsgRoleReq : Sys_SiteMsgRole +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleRsp.cs new file mode 100644 index 00000000..96a384ae --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgRole/QuerySiteMsgRoleRsp.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgRole; + +/// +/// 响应:查询站内信-角色映射 +/// +public sealed record QuerySiteMsgRoleRsp : Sys_SiteMsgRole +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/CreateSiteMsgUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/CreateSiteMsgUserReq.cs new file mode 100644 index 00000000..ee2b2a98 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/CreateSiteMsgUserReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +/// +/// 请求:创建站内信-用户映射 +/// +public sealed record CreateSiteMsgUserReq : Sys_SiteMsgUser; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserReq.cs new file mode 100644 index 00000000..32d96c95 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +/// +/// 请求:查询站内信-用户映射 +/// +public sealed record QuerySiteMsgUserReq : Sys_SiteMsgUser +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserRsp.cs new file mode 100644 index 00000000..7ed61962 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/SiteMsgUser/QuerySiteMsgUserRsp.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgUser; + +/// +/// 响应:查询站内信-用户映射 +/// +public sealed record QuerySiteMsgUserRsp : Sys_SiteMsgUser +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/AesDecodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/AesDecodeReq.cs new file mode 100644 index 00000000..331993e8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/AesDecodeReq.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +/// +/// 请求:Aes解密 +/// +public record AesDecodeReq : DataAbstraction +{ + /// + /// 密文 + /// + public string CipherText { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/ExecuteSqlReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/ExecuteSqlReq.cs new file mode 100644 index 00000000..f310ebc5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/ExecuteSqlReq.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +/// +/// 请求:执行SQL +/// +public record ExecuteSqlReq : DataAbstraction +{ + /// + /// SQL 语句 + /// + public string Sql { get; init; } + + /// + /// 超时时间(秒) + /// + public int TimeoutSecs { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/GetModulesRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/GetModulesRsp.cs new file mode 100644 index 00000000..54b462ca --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/Tool/GetModulesRsp.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +/// +/// 响应:获取模块信息 +/// +public sealed record GetModulesRsp : DataAbstraction +{ + /// + /// 模块名称 + /// + public string Name { get; init; } + + /// + /// 模块版本 + /// + public string Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckMobileAvailableReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckMobileAvailableReq.cs new file mode 100644 index 00000000..123c9136 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckMobileAvailableReq.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:检查手机号码是否可用 +/// +public sealed record CheckMobileAvailableReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Mobile] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.手机号码不能为空))] + public override string Mobile { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckUserNameAvailableReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckUserNameAvailableReq.cs new file mode 100644 index 00000000..619fc5d7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CheckUserNameAvailableReq.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:检查用户名是否可用 +/// +public sealed record CheckUserNameAvailableReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户名不能为空))] + [UserName] + public override string UserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateEditUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateEditUserReq.cs new file mode 100644 index 00000000..fcaa66f3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateEditUserReq.cs @@ -0,0 +1,54 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:创建编辑用户 +/// +public abstract record CreateEditUserReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))] + public override string Avatar { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long DeptId { get; init; } + + /// + [EmailAddress] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Email { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Mobile] + public override string Mobile { get; init; } + + /// + /// 登录密码 + /// + [Password] + public virtual string PasswordText { get; init; } + + /// + /// 角色编号列表 + /// + [MaxLength(Numbers.MAX_LIMIT_BULK_REQ)] + [MinLength(1)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.角色编号列表不能为空))] + public IReadOnlyCollection RoleIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户名不能为空))] + [UserName] + public override string UserName { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateUserReq.cs new file mode 100644 index 00000000..9781cd6c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/CreateUserReq.cs @@ -0,0 +1,26 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:创建用户 +/// +public sealed record CreateUserReq : CreateEditUserReq +{ + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.密码不能为空))] + public override string PasswordText { get; init; } + + /// + public new CreateUserProfileReq Profile { get; init; } + + /// + public override void Register(TypeAdapterConfig config) + { + _ = config.ForType() // + .Map(d => d.Mobile, s => s.VerifySmsCodeReq.DestDevice) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/EditUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/EditUserReq.cs new file mode 100644 index 00000000..325eddaa --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/EditUserReq.cs @@ -0,0 +1,20 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:编辑用户 +/// +public sealed record EditUserReq : CreateEditUserReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + public new EditUserProfileReq Profile { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ExportUserRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ExportUserRsp.cs new file mode 100644 index 00000000..34b263f5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ExportUserRsp.cs @@ -0,0 +1,82 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 响应:导出用户 +/// +public sealed record ExportUserRsp : QueryUserRsp +{ + /// + [CsvIndex(7)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.创建时间))] + public override DateTime CreatedTime { get; init; } + + /// + [CsvIgnore] + public override QueryDeptRsp Dept { get; init; } + + /// + /// 所属部门 + /// + [CsvIndex(5)] + [CsvName(nameof(Ln.所属部门))] + public string DeptName { get; init; } + + /// + [CsvIndex(3)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.邮箱号))] + public override string Email { get; init; } + + /// + [CsvIndex(6)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.是否启用))] + public override bool Enabled { get; init; } + + /// + [CsvIndex(0)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.唯一编码))] + public override long Id { get; init; } + + /// + [CsvIndex(8)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.最后登录时间))] + public override DateTime? LastLoginTime { get; init; } + + /// + [CsvIndex(2)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.手机号))] + public override string Mobile { get; init; } + + /// + /// 所属角色 + /// + [CsvIndex(4)] + [CsvName(nameof(Ln.所属角色))] + public string RoleNames { get; init; } + + /// + [CsvIgnore] + public override IEnumerable Roles { get; init; } + + /// + [CsvIndex(1)] + [CsvIgnore(false)] + [CsvName(nameof(Ln.用户名))] + public override string UserName { get; init; } + + /// + public override void Register(TypeAdapterConfig config) + { + _ = config.ForType() + .Map(d => d.DeptName, s => s.Dept.Name) + .Map(d => d.RoleNames, s => string.Join(',', s.Roles.Select(x => x.Name))); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginByPwdReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginByPwdReq.cs new file mode 100644 index 00000000..822b259a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginByPwdReq.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:密码登录 +/// +public sealed record LoginByPwdReq : DataAbstraction +{ + /// + /// 用户名、手机号、邮箱 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.账号不能为空))] + public string Account { get; init; } + + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.密码不能为空))] + public string Password { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginBySmsReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginBySmsReq.cs new file mode 100644 index 00000000..a00ccc5d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginBySmsReq.cs @@ -0,0 +1,8 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:短信登录 +/// +public sealed record LoginBySmsReq : VerifySmsCodeReq; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginRsp.cs new file mode 100644 index 00000000..e64566f5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/LoginRsp.cs @@ -0,0 +1,27 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 响应:密码登录 +/// +public sealed record LoginRsp : DataAbstraction +{ + /// + /// 访问令牌 + /// + public string AccessToken { get; init; } + + /// + /// 刷新令牌 + /// + public string RefreshToken { get; init; } + + /// + /// 设置到响应头 + /// + public void SetToRspHeader() + { + // 设置响应报文头 + App.HttpContext.Response.Headers[Chars.FLG_HTTP_HEADER_KEY_ACCESS_TOKEN] = AccessToken; + App.HttpContext.Response.Headers[Chars.FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN] = RefreshToken; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserReq.cs new file mode 100644 index 00000000..f9b83cc6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserReq.cs @@ -0,0 +1,20 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:查询用户 +/// +public sealed record QueryUserReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long DeptId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + /// 角色编号 + /// + public long RoleId { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserRsp.cs new file mode 100644 index 00000000..a5c9d15b --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/QueryUserRsp.cs @@ -0,0 +1,64 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 响应:查询用户 +/// +public record QueryUserRsp : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Avatar { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime CreatedTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override long? CreatedUserId { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CreatedUserName { get; init; } + + /// + public new virtual QueryDeptRsp Dept { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Email { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? LastLoginTime { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Mobile { get; init; } + + /// + public new virtual IEnumerable Roles { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Summary { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string UserName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/RegisterUserReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/RegisterUserReq.cs new file mode 100644 index 00000000..afdee157 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/RegisterUserReq.cs @@ -0,0 +1,35 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:注册用户 +/// +public sealed record RegisterUserReq : Sys_User +{ + /// + /// 密码 + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Password] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.密码不能为空))] + public string PasswordText { get; init; } + + /// + /// 角色编号列表 + /// + [JsonIgnore] + public IReadOnlyCollection RoleIds { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户名不能为空))] + [UserName] + public override string UserName { get; init; } + + /// + /// 短信验证请求 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.短信验证请求不能为空))] + public VerifySmsCodeReq VerifySmsCodeReq { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ResetPasswordReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ResetPasswordReq.cs new file mode 100644 index 00000000..c2c22686 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/ResetPasswordReq.cs @@ -0,0 +1,22 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:重置密码 +/// +public sealed record ResetPasswordReq : DataAbstraction +{ + /// + /// 密码 + /// + [Password] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.密码不能为空))] + public string PasswordText { get; init; } + + /// + /// 短信验证请求 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.短信验证请求不能为空))] + public VerifySmsCodeReq VerifySmsCodeReq { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetAvatarReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetAvatarReq.cs new file mode 100644 index 00000000..7bdb6cff --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetAvatarReq.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:设置用户头像 +/// +public sealed record SetAvatarReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.用户头像不能为空))] + [Url(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.网络地址不正确))] + public override string Avatar { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetEmailReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetEmailReq.cs new file mode 100644 index 00000000..f4caef17 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetEmailReq.cs @@ -0,0 +1,14 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:设置邮箱 +/// +public sealed record SetEmailReq : VerifyEmailCodeReq +{ + /// + /// 短信验证请求 + /// + public VerifySmsCodeReq VerifySmsCodeReq { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetMobileReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetMobileReq.cs new file mode 100644 index 00000000..4cea3e0a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetMobileReq.cs @@ -0,0 +1,20 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:设置手机号 +/// +public sealed record SetMobileReq : DataAbstraction +{ + /// + /// 新手机短信验证请求 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.短信验证请求不能为空))] + public VerifySmsCodeReq NewVerifySmsCodeReq { get; init; } + + /// + /// 原手机短信验证请求 + /// + public VerifySmsCodeReq OriginVerifySmsCodeReq { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetPasswordReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetPasswordReq.cs new file mode 100644 index 00000000..9a92a9ea --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetPasswordReq.cs @@ -0,0 +1,21 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:设置密码 +/// +public sealed record SetPasswordReq : DataAbstraction +{ + /// + /// 新密码 + /// + [Password] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.新密码不能为空))] + public string NewPassword { get; init; } + + /// + /// 旧密码 + /// + [Password] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.旧密码不能为空))] + public string OldPassword { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetUserEnabledReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetUserEnabledReq.cs new file mode 100644 index 00000000..db0c2eda --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/SetUserEnabledReq.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 请求:启用/禁用用户 +/// +public sealed record SetUserEnabledReq : Sys_User +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override bool Enabled { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/UserInfoRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/UserInfoRsp.cs new file mode 100644 index 00000000..94a955c8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/User/UserInfoRsp.cs @@ -0,0 +1,27 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.User; + +/// +/// 响应:当前用户信息 +/// +public sealed record UserInfoRsp : QueryUserRsp +{ + /// + public override QueryDeptRsp Dept { get; init; } + + /// + public override IEnumerable Roles { get; init; } + + /// + public override void Register(TypeAdapterConfig config) + { + _ = config.ForType() // + .IgnoreIf((s, _) => s.Mobile == null, d => d.Mobile) + .Map(d => d.Mobile, s => s.Mobile.MaskMobile()) + + // + ; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/CreateUserProfileReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/CreateUserProfileReq.cs new file mode 100644 index 00000000..580a39b4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/CreateUserProfileReq.cs @@ -0,0 +1,112 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 请求:创建用户档案 +/// +public record CreateUserProfileReq : Sys_UserProfile +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override DateTime? BornDate { get; init; } + + /// + [Certificate] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CertificateNumber { get; init; } + + /// + [EnumDataType(typeof(CertificateTypes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.证件类型不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override CertificateTypes? CertificateType { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CompanyAddress { get; init; } + + /// + public new QueryDicContentRsp CompanyArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CompanyName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Telephone] + public override string CompanyTelephone { get; init; } + + /// + [EnumDataType(typeof(Educations), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.学历不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override Educations? Education { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string EmergencyContactAddress { get; init; } + + /// + public new QueryDicContentRsp EmergencyContactArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Mobile] + public override string EmergencyContactMobile { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string EmergencyContactName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string GraduateSchool { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Range(100, 250)] + public override int? Height { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string HomeAddress { get; init; } + + /// + public new QueryDicContentRsp HomeArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Telephone] + public override string HomeTelephone { get; init; } + + /// + [EnumDataType(typeof(MarriageStatues), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.婚姻状况不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override MarriageStatues? MarriageStatus { get; init; } + + /// + [EnumDataType(typeof(Nations), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.民族不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override Nations? Nation { get; init; } + + /// + public new CreateDicContentReq NationArea { get; init; } + + /// + [EnumDataType(typeof(PoliticalStatues), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.政治面貌不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override PoliticalStatues? PoliticalStatus { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Profession { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RealName { get; init; } + + /// + [EnumDataType(typeof(Sexes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.性别不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override Sexes? Sex { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/EditUserProfileReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/EditUserProfileReq.cs new file mode 100644 index 00000000..68a8be81 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/EditUserProfileReq.cs @@ -0,0 +1,16 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 请求:编辑用户档案 +/// +public sealed record EditUserProfileReq : CreateUserProfileReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonString] + public override string AppConfig { get; set; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/GetSessionUserAppConfigRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/GetSessionUserAppConfigRsp.cs new file mode 100644 index 00000000..961808e9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/GetSessionUserAppConfigRsp.cs @@ -0,0 +1,19 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 响应:获取当前用户应用配置 +/// +public sealed record GetSessionUserAppConfigRsp : Sys_UserProfile +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string AppConfig { get; set; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileReq.cs new file mode 100644 index 00000000..07ae5c68 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 请求:查询用户档案 +/// +public sealed record QueryUserProfileReq : Sys_UserProfile +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileRsp.cs new file mode 100644 index 00000000..701ef790 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/QueryUserProfileRsp.cs @@ -0,0 +1,113 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 响应:查询用户档案 +/// +public sealed record QueryUserProfileRsp : Sys_UserProfile +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string AppConfig { get; set; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override DateTime? BornDate { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CertificateNumber { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override CertificateTypes? CertificateType { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CompanyAddress { get; init; } + + /// + public new QueryDicContentRsp CompanyArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CompanyName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string CompanyTelephone { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override Educations? Education { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string EmergencyContactAddress { get; init; } + + /// + public new QueryDicContentRsp EmergencyContactArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string EmergencyContactMobile { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string EmergencyContactName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string GraduateSchool { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override int? Height { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string HomeAddress { get; init; } + + /// + public new QueryDicContentRsp HomeArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string HomeTelephone { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override MarriageStatues? MarriageStatus { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override Nations? Nation { get; init; } + + /// + public new QueryDicContentRsp NationArea { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override PoliticalStatues? PoliticalStatus { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Profession { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string RealName { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override Sexes? Sex { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/SetSessionUserAppConfigReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/SetSessionUserAppConfigReq.cs new file mode 100644 index 00000000..cf97f047 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/UserProfile/SetSessionUserAppConfigReq.cs @@ -0,0 +1,12 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +/// +/// 请求:设置当前用户应用配置 +/// +public sealed record SetSessionUserAppConfigReq : Sys_UserProfile +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonString] + public override string AppConfig { get; set; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/CreateVerifyCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/CreateVerifyCodeReq.cs new file mode 100644 index 00000000..c13a50c9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/CreateVerifyCodeReq.cs @@ -0,0 +1,6 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:创建验证码 +/// +public record CreateVerifyCodeReq : Sys_VerifyCode; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeReq.cs new file mode 100644 index 00000000..8d625d56 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:查询验证码 +/// +public sealed record QueryVerifyCodeReq : Sys_VerifyCode +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeRsp.cs new file mode 100644 index 00000000..7ae35741 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/QueryVerifyCodeRsp.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 响应:查询验证码 +/// +public sealed record QueryVerifyCodeRsp : Sys_VerifyCode +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Id { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeReq.cs new file mode 100644 index 00000000..3f11a7c7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeReq.cs @@ -0,0 +1,59 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:发送验证码 +/// +public sealed record SendVerifyCodeReq : Sys_VerifyCode, IValidatableObject +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.目标设备不能为空))] + public override string DestDevice { get; init; } + + /// + [EnumDataType(typeof(VerifyCodeDeviceTypes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.验证码目标设备类型不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.设备类型不能为空))] + public override VerifyCodeDeviceTypes DeviceType { get; init; } + + /// + public override VerifyCodeStatues Status => VerifyCodeStatues.Waiting; + + /// + [EnumDataType(typeof(VerifyCodeTypes), ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.验证码类型不正确))] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.验证码类型不能为空))] + public override VerifyCodeTypes Type { get; init; } + + /// + /// 人机校验请求 + /// + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.人机校验请求不能为空))] + public VerifyCaptchaReq VerifyCaptchaReq { get; init; } + + /// + public IEnumerable Validate(ValidationContext validationContext) + { + ValidationResult validationResult; + switch (DeviceType) { + case VerifyCodeDeviceTypes.Email: + validationResult = new EmailAttribute().GetValidationResult(DestDevice, validationContext); + + break; + case VerifyCodeDeviceTypes.Mobile: + validationResult = new MobileAttribute().GetValidationResult(DestDevice, validationContext); + + break; + default: + yield break; + } + + if (validationResult == null) { + yield break; + } + + yield return new ValidationResult(validationResult.ErrorMessage, [nameof(DestDevice)]); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeRsp.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeRsp.cs new file mode 100644 index 00000000..4c33af26 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SendVerifyCodeRsp.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 响应:发送验证码 +/// +public sealed record SendVerifyCodeRsp : Sys_VerifyCode +{ + #if DEBUG + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public override string Code { get; init; } + #endif +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SetVerifyCodeStatusReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SetVerifyCodeStatusReq.cs new file mode 100644 index 00000000..7d00ac33 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/SetVerifyCodeStatusReq.cs @@ -0,0 +1,11 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:设置验证码状态 +/// +public sealed record SetVerifyCodeStatusReq : CreateVerifyCodeReq +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public override long Version { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyCodeReq.cs new file mode 100644 index 00000000..b478868f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyCodeReq.cs @@ -0,0 +1,18 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:核实验证码 +/// +public abstract record VerifyCodeReq : Sys_VerifyCode +{ + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.验证码不能为空))] + [VerifyCode] + public override string Code { get; init; } + + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + [Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.目标设备不能为空))] + public override string DestDevice { get; init; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyEmailCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyEmailCodeReq.cs new file mode 100644 index 00000000..44459459 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifyEmailCodeReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:核实邮箱验证码 +/// +public record VerifyEmailCodeReq : VerifyCodeReq +{ + /// + [Email] + public override string DestDevice { get; init; } + + /// + [JsonIgnore] + public override VerifyCodeDeviceTypes DeviceType { get; init; } = VerifyCodeDeviceTypes.Email; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifySmsCodeReq.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifySmsCodeReq.cs new file mode 100644 index 00000000..1ab17813 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Dto/Sys/VerifyCode/VerifySmsCodeReq.cs @@ -0,0 +1,15 @@ +namespace NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +/// +/// 请求:核实短信验证码 +/// +public record VerifySmsCodeReq : VerifyCodeReq +{ + /// + [Mobile] + public override string DestDevice { get; init; } + + /// + [JsonIgnore] + public override VerifyCodeDeviceTypes DeviceType { get; init; } = VerifyCodeDeviceTypes.Mobile; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/DataScopes.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/DataScopes.cs new file mode 100644 index 00000000..d53ff19f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/DataScopes.cs @@ -0,0 +1,46 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 角色数据范围 +/// +[Export] +public enum DataScopes +{ + /// + /// 全部数据 + /// + [ResourceDescription(nameof(Ln.全部数据))] + All = 1 + + , + + /// + /// 本部门和下级部门数据 + /// + [ResourceDescription(nameof(Ln.本部门和下级部门数据))] + DeptWithChild = 2 + + , + + /// + /// 本部门数据 + /// + [ResourceDescription(nameof(Ln.本部门数据))] + Dept = 3 + + , + + /// + /// 本人数据 + /// + [ResourceDescription(nameof(Ln.本人数据))] + Self = 4 + + , + + /// + /// 指定部门数据 + /// + [ResourceDescription(nameof(Ln.指定部门数据))] + SpecificDept = 5 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/JobStatues.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/JobStatues.cs new file mode 100644 index 00000000..9019e1e6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/JobStatues.cs @@ -0,0 +1,24 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 计划作业状态 +/// +[Export] +public enum JobStatues +{ + /// + /// 空闲 + /// + [Indicator(nameof(Indicates.Success))] + [ResourceDescription(nameof(Ln.空闲))] + Idle = 1 + + , + + /// + /// 运行 + /// + [Indicator(nameof(Indicates.Warning))] + [ResourceDescription(nameof(Ln.运行))] + Running = 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/MenuTypes.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/MenuTypes.cs new file mode 100644 index 00000000..2e70c60e --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/MenuTypes.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 菜单类型 +/// +[Export] +public enum MenuTypes +{ + /// + /// 菜单 + /// + [ResourceDescription(nameof(Ln.菜单))] + Menu = 1 + + , + + /// + /// 链接 + /// + [ResourceDescription(nameof(Ln.链接))] + Link = 2 + + , + + /// + /// 框架 + /// + [ResourceDescription(nameof(Ln.框架))] + Iframe = 3 + + , + + /// + /// 按钮 + /// + [ResourceDescription(nameof(Ln.按钮))] + Button = 4 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/SiteMsgTypes.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/SiteMsgTypes.cs new file mode 100644 index 00000000..6e970372 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/SiteMsgTypes.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 站内信类型 +/// +[Export] +public enum SiteMsgTypes +{ + /// + /// 通知 + /// + [ResourceDescription(nameof(Ln.通知))] + Private = 1 + + , + + /// + /// 公告 + /// + [ResourceDescription(nameof(Ln.公告))] + Public = 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/UserSiteMsgStatues.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/UserSiteMsgStatues.cs new file mode 100644 index 00000000..498e4a97 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/UserSiteMsgStatues.cs @@ -0,0 +1,30 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 站内信状态 +/// +[Export] +public enum UserSiteMsgStatues +{ + /// + /// 未读 + /// + [ResourceDescription(nameof(Ln.未读))] + Unread = 1 + + , + + /// + /// 已读 + /// + [ResourceDescription(nameof(Ln.已读))] + Read = 2 + + , + + /// + /// 删除 + /// + [ResourceDescription(nameof(Ln.删除))] + Deleted = 3 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeDeviceTypes.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeDeviceTypes.cs new file mode 100644 index 00000000..8b6bb799 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeDeviceTypes.cs @@ -0,0 +1,22 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 验证码目标设备类型 +/// +[Export] +public enum VerifyCodeDeviceTypes +{ + /// + /// 手机 + /// + [ResourceDescription(nameof(Ln.手机))] + Mobile = 1 + + , + + /// + /// 电子邮箱 + /// + [ResourceDescription(nameof(Ln.电子邮箱))] + Email = 2 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeStatues.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeStatues.cs new file mode 100644 index 00000000..b6d8d190 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeStatues.cs @@ -0,0 +1,38 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 验证码状态 +/// +[Export] +public enum VerifyCodeStatues +{ + /// + /// 等待发送 + /// + [ResourceDescription(nameof(Ln.等待发送))] + Waiting = 1 + + , + + /// + /// 已发送 + /// + [ResourceDescription(nameof(Ln.已发送))] + Sent = 2 + + , + + /// + /// 发送失败 + /// + [ResourceDescription(nameof(Ln.发送失败))] + Failed = 3 + + , + + /// + /// 已校验 + /// + [ResourceDescription(nameof(Ln.已校验))] + Verified = 4 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeTypes.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeTypes.cs new file mode 100644 index 00000000..83786c1c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Enums/Sys/VerifyCodeTypes.cs @@ -0,0 +1,46 @@ +namespace NetAdmin.SysComponent.Domain.Enums.Sys; + +/// +/// 验证码类型 +/// +[Export] +public enum VerifyCodeTypes +{ + /// + /// 绑定手机号码 + /// + [ResourceDescription(nameof(Ln.绑定手机号码))] + LinkMobile = 1 + + , + + /// + /// 登录 + /// + [ResourceDescription(nameof(Ln.登录))] + Login = 2 + + , + + /// + /// 解绑手机号码 + /// + [ResourceDescription(nameof(Ln.解绑手机号码))] + UnlinkMobile = 3 + + , + + /// + /// 注册 + /// + [ResourceDescription(nameof(Ln.注册))] + Register = 4 + + , + + /// + /// 重设密码 + /// + [ResourceDescription(nameof(Ln.重设密码))] + ResetPassword = 5 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/RequestLogEvent.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/RequestLogEvent.cs new file mode 100644 index 00000000..cf0db030 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/RequestLogEvent.cs @@ -0,0 +1,41 @@ +using NetAdmin.Domain.Events; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +namespace NetAdmin.SysComponent.Domain.Events.Sys; + +/// +/// 请求日志事件 +/// +public sealed record RequestLogEvent : DataAbstraction, IEventSourceGeneric +{ + /// + /// Initializes a new instance of the class. + /// + public RequestLogEvent(CreateRequestLogReq data, bool isConsumOnce = false, object payload = default, DateTime createdTime = default + , CancellationToken cancellationToken = default) + { + Data = data; + IsConsumOnce = isConsumOnce; + Payload = payload; + CreatedTime = createdTime; + CancellationToken = cancellationToken; + } + + /// + public CancellationToken CancellationToken { get; } + + /// + public DateTime CreatedTime { get; } + + /// + public CreateRequestLogReq Data { get; } + + /// + public string EventId => nameof(RequestLogEvent); + + /// + public bool IsConsumOnce { get; } + + /// + public object Payload { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserCreatedEvent.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserCreatedEvent.cs new file mode 100644 index 00000000..b2953f78 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserCreatedEvent.cs @@ -0,0 +1,41 @@ +using NetAdmin.Domain.Events; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Events.Sys; + +/// +/// 用户被创建事件 +/// +public sealed record UserCreatedEvent : DataAbstraction, IEventSourceGeneric +{ + /// + /// Initializes a new instance of the class. + /// + public UserCreatedEvent(UserInfoRsp data, DateTime createdTime = default, bool isConsumOnce = false, object payload = default + , CancellationToken cancellationToken = default) + { + Data = data; + CancellationToken = cancellationToken; + CreatedTime = createdTime; + IsConsumOnce = isConsumOnce; + Payload = payload; + } + + /// + public CancellationToken CancellationToken { get; } + + /// + public DateTime CreatedTime { get; } + + /// + public UserInfoRsp Data { get; } + + /// + public string EventId => nameof(UserCreatedEvent); + + /// + public bool IsConsumOnce { get; } + + /// + public object Payload { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserUpdatedEvent.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserUpdatedEvent.cs new file mode 100644 index 00000000..ae45dfb9 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/UserUpdatedEvent.cs @@ -0,0 +1,41 @@ +using NetAdmin.Domain.Events; +using NetAdmin.SysComponent.Domain.Dto.Sys.User; + +namespace NetAdmin.SysComponent.Domain.Events.Sys; + +/// +/// 用户被更新事件 +/// +public sealed record UserUpdatedEvent : DataAbstraction, IEventSourceGeneric +{ + /// + /// Initializes a new instance of the class. + /// + public UserUpdatedEvent(UserInfoRsp data, DateTime createdTime = default, bool isConsumOnce = false, object payload = default + , CancellationToken cancellationToken = default) + { + Data = data; + CancellationToken = cancellationToken; + CreatedTime = createdTime; + IsConsumOnce = isConsumOnce; + Payload = payload; + } + + /// + public CancellationToken CancellationToken { get; } + + /// + public DateTime CreatedTime { get; } + + /// + public UserInfoRsp Data { get; } + + /// + public string EventId => nameof(UserUpdatedEvent); + + /// + public bool IsConsumOnce { get; } + + /// + public object Payload { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/VerifyCodeCreatedEvent.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/VerifyCodeCreatedEvent.cs new file mode 100644 index 00000000..0e6c4650 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/Events/Sys/VerifyCodeCreatedEvent.cs @@ -0,0 +1,41 @@ +using NetAdmin.Domain.Events; +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Domain.Events.Sys; + +/// +/// 验证码创建事件 +/// +public sealed record VerifyCodeCreatedEvent : DataAbstraction, IEventSourceGeneric +{ + /// + /// Initializes a new instance of the class. + /// + public VerifyCodeCreatedEvent(QueryVerifyCodeRsp data, DateTime createdTime = default, bool isConsumOnce = false, object payload = default + , CancellationToken cancellationToken = default) + { + Data = data; + CancellationToken = cancellationToken; + CreatedTime = createdTime; + IsConsumOnce = isConsumOnce; + Payload = payload; + } + + /// + public CancellationToken CancellationToken { get; } + + /// + public DateTime CreatedTime { get; } + + /// + public QueryVerifyCodeRsp Data { get; } + + /// + public string EventId => nameof(VerifyCodeCreatedEvent); + + /// + public bool IsConsumOnce { get; } + + /// + public object Payload { get; } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/NetAdmin.SysComponent.Domain.csproj b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/NetAdmin.SysComponent.Domain.csproj new file mode 100644 index 00000000..080c5908 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/NetAdmin.SysComponent.Domain.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/ProjectUsings.cs new file mode 100644 index 00000000..dcf84574 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Domain/ProjectUsings.cs @@ -0,0 +1,13 @@ +global using NetAdmin.Domain; +global using NetAdmin.Domain.Attributes; +global using NetAdmin.Domain.Attributes.DataValidation; +global using NetAdmin.Domain.DbMaps.Dependency; +global using NetAdmin.Domain.DbMaps.Dependency.Fields; +global using NetAdmin.Domain.Dto.Dependency; +global using NetAdmin.SysComponent.Domain.DbMaps.Sys; +global using NetAdmin.SysComponent.Domain.Enums.Sys; +global using CsvIgnore = CsvHelper.Configuration.Attributes.IgnoreAttribute; +global using CsvIndex = CsvHelper.Configuration.Attributes.IndexAttribute; +global using CsvName = CsvHelper.Configuration.Attributes.NameAttribute; +global using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; +global using SqlIndex = FreeSql.DataAnnotations.IndexAttribute; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ApiController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ApiController.cs new file mode 100644 index 00000000..146481a0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ApiController.cs @@ -0,0 +1,101 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Api; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 接口服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ApiController(IApiCache cache) : ControllerBase(cache), IApiModule +{ + /// + /// 批量删除接口 + /// + [NonAction] + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 接口计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建接口 + /// + [NonAction] + [Transaction] + public Task CreateAsync(CreateApiReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除接口 + /// + [NonAction] + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 接口是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出接口 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个接口 + /// + [NonAction] + public Task GetAsync(QueryApiReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询接口 + /// + [NonAction] + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询接口 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 同步接口 + /// + [Transaction] + public Task SyncAsync() + { + return Cache.SyncAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CacheController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CacheController.cs new file mode 100644 index 00000000..ce540d95 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CacheController.cs @@ -0,0 +1,51 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Cache; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 缓存服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class CacheController(ICacheCache cache) : ControllerBase(cache), ICacheModule +{ + /// + /// 批量删除缓存项 + /// + public Task BulkDeleteEntryAsync(BulkReq req) + { + return Cache.BulkDeleteEntryAsync(req); + } + + /// + /// 缓存统计 + /// + public Task CacheStatisticsAsync() + { + return Cache.CacheStatisticsAsync(); + } + + /// + /// 删除缓存项 + /// + public Task DeleteEntryAsync(DelEntryReq req) + { + return Cache.DeleteEntryAsync(req); + } + + /// + /// 获取所有缓存项 + /// + public Task> GetAllEntriesAsync(GetAllEntriesReq req) + { + return Cache.GetAllEntriesAsync(req); + } + + /// + /// 获取缓存项 + /// + public Task GetEntryAsync(GetEntriesReq req) + { + return Cache.GetEntryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CaptchaController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CaptchaController.cs new file mode 100644 index 00000000..bfc255f7 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/CaptchaController.cs @@ -0,0 +1,29 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Captcha; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 人机验证服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class CaptchaController(ICaptchaCache cache) : ControllerBase(cache), ICaptchaModule +{ + /// + /// 获取人机校验图 + /// + [AllowAnonymous] + public Task GetCaptchaImageAsync() + { + return Cache.GetCaptchaImageAsync(); + } + + /// + /// 完成人机校验 + /// + [AllowAnonymous] + public Task VerifyCaptchaAsync(VerifyCaptchaReq req) + { + return Cache.VerifyCaptchaAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConfigController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConfigController.cs new file mode 100644 index 00000000..b68e544d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConfigController.cs @@ -0,0 +1,112 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Config; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 配置服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ConfigController(IConfigCache cache) : ControllerBase(cache), IConfigModule +{ + /// + /// 批量删除配置 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 配置计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建配置 + /// + [Transaction] + public Task CreateAsync(CreateConfigReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除配置 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑配置 + /// + [Transaction] + public Task EditAsync(EditConfigReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 配置是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出配置 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个配置 + /// + public Task GetAsync(QueryConfigReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 获取最新有效配置 + /// + public Task GetLatestConfigAsync() + { + return Cache.GetLatestConfigAsync(); + } + + /// + /// 分页查询配置 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询配置 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 设置配置启用状态 + /// + public Task SetEnabledAsync(SetConfigEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConstantController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConstantController.cs new file mode 100644 index 00000000..3f270787 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ConstantController.cs @@ -0,0 +1,74 @@ +using NetAdmin.Domain.Dto; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 常量服务 +/// +[AllowAnonymous] +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ConstantController(IConstantCache cache, IOptions jsonOptions) + : ControllerBase(cache), IConstantModule +{ + /// + /// 获得常量字符串 + /// + [NonUnify] + public IActionResult GetChars() + { + var ret = GetCharsDic(); + return OriginNamingResult(ret); + } + + /// + /// 获得常量字符串 + /// + [NonAction] + public IDictionary GetCharsDic() + { + return Cache.GetCharsDic(); + } + + /// + /// 获得公共枚举值 + /// + public IDictionary> GetEnums() + { + return Cache.GetEnums(); + } + + /// + /// 获得本地化字符串 + /// + public IDictionary GetLocalizedStrings() + { + return Cache.GetLocalizedStrings(); + } + + /// + /// 获得数字常量表 + /// + [NonUnify] + public IActionResult GetNumbers() + { + var ret = GetNumbersDic(); + return OriginNamingResult(ret); + } + + /// + /// 获得数字常量表 + /// + [NonAction] + public IDictionary GetNumbersDic() + { + return Cache.GetNumbersDic(); + } + + private JsonResult OriginNamingResult(T data) + { + return new JsonResult( // + new RestfulInfo { Code = 0, Data = data } + , new JsonSerializerOptions(jsonOptions.Value.JsonSerializerOptions) { DictionaryKeyPolicy = null }); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DeptController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DeptController.cs new file mode 100644 index 00000000..5758fdca --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DeptController.cs @@ -0,0 +1,105 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dept; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 部门服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class DeptController(IDeptCache cache) : ControllerBase(cache), IDeptModule +{ + /// + /// 批量删除部门 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 部门计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建部门 + /// + [Transaction] + public Task CreateAsync(CreateDeptReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除部门 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑部门 + /// + [Transaction] + public Task EditAsync(EditDeptReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 部门是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出部门 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个部门 + /// + public Task GetAsync(QueryDeptReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询部门 + /// + [NonAction] + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询部门 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 启用/禁用部门 + /// + public Task SetEnabledAsync(SetDeptEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DevController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DevController.cs new file mode 100644 index 00000000..dbe4702c --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DevController.cs @@ -0,0 +1,35 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dev; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 开发服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class DevController(IDevCache cache) : ControllerBase(cache), IDevModule +{ + /// + /// 生成后端代码 + /// + public Task GenerateCsCodeAsync(GenerateCsCodeReq req) + { + return Cache.GenerateCsCodeAsync(req); + } + + /// + /// 生成图标代码 + /// + public Task GenerateIconCodeAsync(GenerateIconCodeReq req) + { + return Cache.GenerateIconCodeAsync(req); + } + + /// + /// 生成接口代码 + /// + public Task GenerateJsCodeAsync() + { + return Cache.GenerateJsCodeAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DicController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DicController.cs new file mode 100644 index 00000000..1ad06fc5 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/DicController.cs @@ -0,0 +1,156 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Catalog; +using NetAdmin.SysComponent.Domain.Dto.Sys.Dic.Content; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 字典服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class DicController(IDicCache cache) : ControllerBase(cache), IDicModule +{ + /// + /// 批量删除字典目录 + /// + [Transaction] + public Task BulkDeleteCatalogAsync(BulkReq req) + { + return Cache.BulkDeleteCatalogAsync(req); + } + + /// + /// 批量删除字典内容 + /// + [Transaction] + public Task BulkDeleteContentAsync(BulkReq req) + { + return Cache.BulkDeleteContentAsync(req); + } + + /// + /// 创建字典目录 + /// + [Transaction] + public Task CreateCatalogAsync(CreateDicCatalogReq req) + { + return Cache.CreateCatalogAsync(req); + } + + /// + /// 创建字典内容 + /// + [Transaction] + public Task CreateContentAsync(CreateDicContentReq req) + { + return Cache.CreateContentAsync(req); + } + + /// + /// 删除字典目录 + /// + [Transaction] + public Task DeleteCatalogAsync(DelReq req) + { + return Cache.DeleteCatalogAsync(req); + } + + /// + /// 删除字典内容 + /// + [Transaction] + public Task DeleteContentAsync(DelReq req) + { + return Cache.DeleteContentAsync(req); + } + + /// + /// 编辑字典目录 + /// + [Transaction] + public Task EditCatalogAsync(EditDicCatalogReq req) + { + return Cache.EditCatalogAsync(req); + } + + /// + /// 编辑字典内容 + /// + [Transaction] + public Task EditContentAsync(EditDicContentReq req) + { + return Cache.EditContentAsync(req); + } + + /// + /// 导出字典内容 + /// + public Task ExportContentAsync(QueryReq req) + { + return Cache.ExportContentAsync(req); + } + + /// + /// 获取单个字典目录 + /// + public Task GetCatalogAsync(QueryDicCatalogReq req) + { + return Cache.GetCatalogAsync(req); + } + + /// + /// 获取单个字典内容 + /// + public Task GetContentAsync(QueryDicContentReq req) + { + return Cache.GetContentAsync(req); + } + + /// + /// 获取字典值 + /// + public Task GetDicValueAsync(GetDicValueReq req) + { + return Cache.GetDicValueAsync(req); + } + + /// + /// 分页查询字典目录 + /// + public Task> PagedQueryCatalogAsync(PagedQueryReq req) + { + return Cache.PagedQueryCatalogAsync(req); + } + + /// + /// 分页查询字典内容 + /// + public Task> PagedQueryContentAsync(PagedQueryReq req) + { + return Cache.PagedQueryContentAsync(req); + } + + /// + /// 查询字典目录 + /// + public Task> QueryCatalogAsync(QueryReq req) + { + return Cache.QueryCatalogAsync(req); + } + + /// + /// 查询字典内容 + /// + public Task> QueryContentAsync(QueryReq req) + { + return Cache.QueryContentAsync(req); + } + + /// + /// 启用/禁用字典内容 + /// + public Task SetEnabledAsync(SetDicContentEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/FileController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/FileController.cs new file mode 100644 index 00000000..5098e813 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/FileController.cs @@ -0,0 +1,17 @@ +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 文件服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class FileController(IFileCache cache) : ControllerBase(cache), IFileModule +{ + /// + /// 文件上传 + /// + public Task UploadAsync(IFormFile file) + { + return Cache.UploadAsync(file); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs new file mode 100644 index 00000000..c3688611 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/JobController.cs @@ -0,0 +1,169 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys; +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 计划作业服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class JobController(IJobCache cache) : ControllerBase(cache), IJobModule +{ + /// + /// 批量删除计划作业 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 计划作业计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 作业记录计数 + /// + public Task CountRecordAsync(QueryReq req) + { + return Cache.CountRecordAsync(req); + } + + /// + /// 创建计划作业 + /// + [Transaction] + public Task CreateAsync(CreateJobReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除计划作业 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑作业 + /// + [Transaction] + public Task EditAsync(EditJobReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 执行作业 + /// + public Task ExecuteAsync(QueryJobReq req) + { + return Cache.ExecuteAsync(req); + } + + /// + /// 计划作业是否存在 + /// + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出计划作业 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 导出作业记录 + /// + public Task ExportRecordAsync(QueryReq req) + { + return Cache.ExportRecordAsync(req); + } + + /// + /// 获取单个计划作业 + /// + public Task GetAsync(QueryJobReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 获取单个作业记录 + /// + public Task GetRecordAsync(QueryJobRecordReq req) + { + return Cache.GetRecordAsync(req); + } + + /// + /// 获取作业记录条形图数据 + /// + public Task> GetRecordBarChartAsync(QueryReq req) + { + return Cache.GetRecordBarChartAsync(req); + } + + /// + /// 状态码分组作业记录饼图数据 + /// + public Task> GetRecordPieChartByHttpStatusCodeAsync(QueryReq req) + { + return Cache.GetRecordPieChartByHttpStatusCodeAsync(req); + } + + /// + /// 名称分组作业记录饼图数据 + /// + public Task> GetRecordPieChartByNameAsync(QueryReq req) + { + return Cache.GetRecordPieChartByNameAsync(req); + } + + /// + /// 分页查询计划作业 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 分页查询作业记录 + /// + public Task> PagedQueryRecordAsync(PagedQueryReq req) + { + return Cache.PagedQueryRecordAsync(req); + } + + /// + /// 查询计划作业 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 启用/禁用作业 + /// + public Task SetEnabledAsync(SetJobEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/LoginLogController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/LoginLogController.cs new file mode 100644 index 00000000..dc6052f1 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/LoginLogController.cs @@ -0,0 +1,86 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.LoginLog; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 登录日志服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class LoginLogController(ILoginLogCache cache) : ControllerBase(cache), ILoginLogModule +{ + /// + /// 批量删除登录日志 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 登录日志计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建登录日志 + /// + [Transaction] + public Task CreateAsync(CreateLoginLogReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除登录日志 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 登录日志是否存在 + /// + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出登录日志 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个登录日志 + /// + public Task GetAsync(QueryLoginLogReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询登录日志 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询登录日志 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/MenuController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/MenuController.cs new file mode 100644 index 00000000..cb9450bd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/MenuController.cs @@ -0,0 +1,106 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Menu; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 菜单服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class MenuController(IMenuCache cache) : ControllerBase(cache), IMenuModule +{ + /// + /// 批量删除菜单 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 菜单计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建菜单 + /// + [Transaction] + public Task CreateAsync(CreateMenuReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除菜单 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑菜单 + /// + [Transaction] + public Task EditAsync(EditMenuReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 菜单是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出菜单 + /// + [NonAction] + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个菜单 + /// + public Task GetAsync(QueryMenuReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询菜单 + /// + [NonAction] + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询菜单 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 当前用户菜单 + /// + public Task> UserMenusAsync() + { + return Cache.UserMenusAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RequestLogController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RequestLogController.cs new file mode 100644 index 00000000..cd0cb3b0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RequestLogController.cs @@ -0,0 +1,115 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 请求日志服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class RequestLogController(IRequestLogCache cache) : ControllerBase(cache), IRequestLogModule +{ + /// + /// 批量删除请求日志 + /// + [NonAction] + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 请求日志计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建请求日志 + /// + [NonAction] + [Transaction] + public Task CreateAsync(CreateRequestLogReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除请求日志 + /// + [NonAction] + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 请求日志是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出请求日志 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个请求日志 + /// + public Task GetAsync(QueryRequestLogReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 获取条形图数据 + /// + public Task> GetBarChartAsync(QueryReq req) + { + return Cache.GetBarChartAsync(req); + } + + /// + /// 描述分组饼图数据 + /// + public Task> GetPieChartByApiSummaryAsync(QueryReq req) + { + return Cache.GetPieChartByApiSummaryAsync(req); + } + + /// + /// 状态码分组饼图数据 + /// + public Task> GetPieChartByHttpStatusCodeAsync(QueryReq req) + { + return Cache.GetPieChartByHttpStatusCodeAsync(req); + } + + /// + /// 分页查询请求日志 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询请求日志 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RoleController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RoleController.cs new file mode 100644 index 00000000..69be1de6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/RoleController.cs @@ -0,0 +1,120 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Role; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 角色服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class RoleController(IRoleCache cache) : ControllerBase(cache), IRoleModule +{ + /// + /// 批量删除角色 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 角色计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建角色 + /// + [Transaction] + public Task CreateAsync(CreateRoleReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除角色 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑角色 + /// + [Transaction] + public Task EditAsync(EditRoleReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 角色是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出角色 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个角色 + /// + public Task GetAsync(QueryRoleReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询角色 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询角色 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 设置是否显示仪表板 + /// + public Task SetDisplayDashboardAsync(SetDisplayDashboardReq req) + { + return Cache.SetDisplayDashboardAsync(req); + } + + /// + /// 启用/禁用角色 + /// + public Task SetEnabledAsync(SetRoleEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } + + /// + /// 设置是否忽略权限控制 + /// + public Task SetIgnorePermissionControlAsync(SetIgnorePermissionControlReq req) + { + return Cache.SetIgnorePermissionControlAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/SiteMsgController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/SiteMsgController.cs new file mode 100644 index 00000000..2e7d2933 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/SiteMsgController.cs @@ -0,0 +1,128 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsg; +using NetAdmin.SysComponent.Domain.Dto.Sys.SiteMsgFlag; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 站内信服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class SiteMsgController(ISiteMsgCache cache) : ControllerBase(cache), ISiteMsgModule +{ + /// + /// 批量删除站内信 + /// + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 站内信计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建站内信 + /// + [Transaction] + public Task CreateAsync(CreateSiteMsgReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除站内信 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑站内信 + /// + [Transaction] + public Task EditAsync(EditSiteMsgReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 站内信是否存在 + /// + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出站内信 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个站内信 + /// + public Task GetAsync(QuerySiteMsgReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 获取单个我的站内信 + /// + public Task GetMineAsync(QuerySiteMsgReq req) + { + return Cache.GetMineAsync(req); + } + + /// + /// 分页查询站内信 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 分页查询我的站内信 + /// + public Task> PagedQueryMineAsync(PagedQueryReq req) + { + return Cache.PagedQueryMineAsync(req); + } + + /// + /// 查询站内信 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 设置站内信状态 + /// + public Task SetSiteMsgStatusAsync(SetUserSiteMsgStatusReq req) + { + return Cache.SetSiteMsgStatusAsync(req); + } + + /// + /// 未读数量 + /// + public Task UnreadCountAsync() + { + return Cache.UnreadCountAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ToolsController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ToolsController.cs new file mode 100644 index 00000000..04893e94 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/ToolsController.cs @@ -0,0 +1,63 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.Tool; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 工具服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class ToolsController(IToolsCache cache) : ControllerBase(cache), IToolsModule +{ + /// + /// Aes解密 + /// + public string AesDecode(AesDecodeReq req) + { + return Cache.AesDecode(req); + } + + /// + /// 执行SQL语句 + /// + public Task ExecuteSqlAsync(ExecuteSqlReq req) + { + return Cache.ExecuteSqlAsync(req); + } + + /// + /// 获取更新日志 + /// + [AllowAnonymous] + public Task GetChangeLogAsync() + { + return Cache.GetChangeLogAsync(); + } + + /// + /// 获取模块信息 + /// + [AllowAnonymous] + public Task> GetModulesAsync() + { + return Cache.GetModulesAsync(); + } + + /// + /// 获取服务器时间 + /// + [AllowAnonymous] + public Task GetServerUtcTimeAsync() + { + return Cache.GetServerUtcTimeAsync(); + } + + /// + /// 获取版本信息 + /// + [AllowAnonymous] + public Task GetVersionAsync() + { + return Cache.GetVersionAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/UserController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/UserController.cs new file mode 100644 index 00000000..5e1603b6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/UserController.cs @@ -0,0 +1,246 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.User; +using NetAdmin.SysComponent.Domain.Dto.Sys.UserProfile; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 用户服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class UserController(IUserCache cache, IConfigCache configCache) : ControllerBase(cache), IUserModule +{ + /// + /// 批量删除用户 + /// + [NonAction] + [Transaction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 检查手机号码是否可用 + /// + [AllowAnonymous] + public Task CheckMobileAvailableAsync(CheckMobileAvailableReq req) + { + return Cache.CheckMobileAvailableAsync(req); + } + + /// + /// 检查用户名是否可用 + /// + [AllowAnonymous] + public Task CheckUserNameAvailableAsync(CheckUserNameAvailableReq req) + { + return Cache.CheckUserNameAvailableAsync(req); + } + + /// + /// 用户计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建用户 + /// + [Transaction] + public Task CreateAsync(CreateUserReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除用户 + /// + [Transaction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 编辑用户 + /// + [Transaction] + public Task EditAsync(EditUserReq req) + { + return Cache.EditAsync(req); + } + + /// + /// 用户是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出用户 + /// + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个用户 + /// + public Task GetAsync(QueryUserReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 获取当前用户应用配置 + /// + public Task GetSessionUserAppConfigAsync() + { + return Cache.GetSessionUserAppConfigAsync(); + } + + /// + /// 密码登录 + /// + [AllowAnonymous] + [Transaction] + public async Task LoginByPwdAsync(LoginByPwdReq req) + { + var ret = await Cache.LoginByPwdAsync(req).ConfigureAwait(false); + ret.SetToRspHeader(); + return ret; + } + + /// + /// 短信登录 + /// + [AllowAnonymous] + [Transaction] + public async Task LoginBySmsAsync(LoginBySmsReq req) + { + var ret = await Cache.LoginBySmsAsync(req).ConfigureAwait(false); + ret.SetToRspHeader(); + return ret; + } + + /// + /// 分页查询用户 + /// + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询用户 + /// + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 查询用户档案 + /// + public Task> QueryProfileAsync(QueryReq req) + { + return Cache.QueryProfileAsync(req); + } + + /// + /// 注册用户 + /// + [AllowAnonymous] + [Transaction] + public async Task RegisterAsync(RegisterUserReq req) + { + var config = await configCache.GetLatestConfigAsync().ConfigureAwait(false); + + return await Cache.RegisterAsync(req with { + DeptId = config.UserRegisterDeptId + , RoleIds = [config.UserRegisterRoleId] + , Profile = new CreateUserProfileReq() + , Enabled = !config.UserRegisterConfirm + , Mobile = req.VerifySmsCodeReq.DestDevice + }) + .ConfigureAwait(false); + } + + /// + /// 重设密码 + /// + [AllowAnonymous] + [Transaction] + public Task ResetPasswordAsync(ResetPasswordReq req) + { + return Cache.ResetPasswordAsync(req); + } + + /// + /// 设置用户头像 + /// + [Transaction] + public Task SetAvatarAsync(SetAvatarReq req) + { + return Cache.SetAvatarAsync(req); + } + + /// + /// 设置邮箱 + /// + [Transaction] + public Task SetEmailAsync(SetEmailReq req) + { + return Cache.SetEmailAsync(req); + } + + /// + /// 启用/禁用用户 + /// + [Transaction] + public Task SetEnabledAsync(SetUserEnabledReq req) + { + return Cache.SetEnabledAsync(req); + } + + /// + /// 设置手机号码 + /// + [Transaction] + public Task SetMobileAsync(SetMobileReq req) + { + return Cache.SetMobileAsync(req); + } + + /// + /// 设置密码 + /// + [Transaction] + public Task SetPasswordAsync(SetPasswordReq req) + { + return Cache.SetPasswordAsync(req); + } + + /// + /// 设置当前用户应用配置 + /// + public Task SetSessionUserAppConfigAsync(SetSessionUserAppConfigReq req) + { + return Cache.SetSessionUserAppConfigAsync(req); + } + + /// + /// 当前用户信息 + /// + public Task UserInfoAsync() + { + return Cache.UserInfoAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/VerifyCodeController.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/VerifyCodeController.cs new file mode 100644 index 00000000..cb9f1ce8 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Controllers/Sys/VerifyCodeController.cs @@ -0,0 +1,113 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; + +namespace NetAdmin.SysComponent.Host.Controllers.Sys; + +/// +/// 验证码服务 +/// +[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))] +[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)] +public sealed class VerifyCodeController(IVerifyCodeCache cache, ICaptchaCache captchaCache) + : ControllerBase(cache), IVerifyCodeModule +{ + /// + /// 批量删除验证码 + /// + [NonAction] + public Task BulkDeleteAsync(BulkReq req) + { + return Cache.BulkDeleteAsync(req); + } + + /// + /// 验证码计数 + /// + public Task CountAsync(QueryReq req) + { + return Cache.CountAsync(req); + } + + /// + /// 创建验证码 + /// + [NonAction] + public Task CreateAsync(CreateVerifyCodeReq req) + { + return Cache.CreateAsync(req); + } + + /// + /// 删除验证码 + /// + [NonAction] + public Task DeleteAsync(DelReq req) + { + return Cache.DeleteAsync(req); + } + + /// + /// 判断验证码是否存在 + /// + [NonAction] + public Task ExistAsync(QueryReq req) + { + return Cache.ExistAsync(req); + } + + /// + /// 导出验证码 + /// + [NonAction] + public Task ExportAsync(QueryReq req) + { + return Cache.ExportAsync(req); + } + + /// + /// 获取单个验证码 + /// + [NonAction] + public Task GetAsync(QueryVerifyCodeReq req) + { + return Cache.GetAsync(req); + } + + /// + /// 分页查询验证码 + /// + [NonAction] + public Task> PagedQueryAsync(PagedQueryReq req) + { + return Cache.PagedQueryAsync(req); + } + + /// + /// 查询验证码 + /// + [NonAction] + public Task> QueryAsync(QueryReq req) + { + return Cache.QueryAsync(req); + } + + /// + /// 发送验证码 + /// + [AllowAnonymous] + [Transaction] + public async Task SendVerifyCodeAsync(SendVerifyCodeReq req) + { + await captchaCache.VerifyCaptchaAndRemoveAsync(req.VerifyCaptchaReq).ConfigureAwait(false); + return await Cache.SendVerifyCodeAsync(req).ConfigureAwait(false); + } + + /// + /// 完成验证 + /// + [AllowAnonymous] + [Transaction] + public Task VerifyAsync(VerifyCodeReq req) + { + return Cache.VerifyAsync(req); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..1e47132f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,74 @@ +using Gurion.Schedule; +using NetAdmin.Domain.Events; +using NetAdmin.Host.Filters; +using NetAdmin.SysComponent.Domain.Contexts; +using NetAdmin.SysComponent.Host.Jobs; +using NetAdmin.SysComponent.Host.Utils; +using FreeSqlBuilder = NetAdmin.Infrastructure.Utils.FreeSqlBuilder; + +namespace NetAdmin.SysComponent.Host.Extensions; + +/// +/// ServiceCollection 扩展方法 +/// +[SuppressSniffer] +public static class ServiceCollectionExtensions +{ + /// + /// 添加上下文用户信息 + /// + public static IServiceCollection AddContextUserInfo(this IServiceCollection me) + { + return me.AddScoped(typeof(ContextUserInfo), _ => ContextUserInfo.Create()); + } + + /// + /// 添加 freeSql orm工具 + /// + public static IServiceCollection AddFreeSql( // + this IServiceCollection me, FreeSqlInitMethods initMethods = FreeSqlInitMethods.None, Action freeSqlConfig = null) + { + // // 非调试模式下禁止同步数据库 + // #if !DEBUG + // initOptions = FreeSqlInitOptions.None; + // #endif + var dbOptions = App.GetOptions(); + var eventPublisher = App.GetService(); + + var fSql = new FreeSqlBuilder(dbOptions).Build(initMethods, count => eventPublisher.PublishAsync(new SeedDataInsertedEvent(count))); + _ = me.AddSingleton(fSql); + + fSql.Aop.AuditValue += SqlAuditor.DataAuditHandler; // Insert/Update自动值处理 + + #pragma warning disable VSTHRD110 + + // AOP事件发布(异步) + fSql.Aop.CommandBefore += (_, e) => eventPublisher.PublishAsync(new SqlCommandBeforeEvent(e)); // 增删查改,执行命令之前触发 + fSql.Aop.CommandAfter += (_, e) => eventPublisher.PublishAsync(new SqlCommandAfterEvent(e)); // 增删查改,执行命令完成后触发 + + fSql.Aop.SyncStructureBefore += (_, e) => eventPublisher.PublishAsync(new SyncStructureBeforeEvent(e)); // CodeFirst迁移,执行之前触发 + + fSql.Aop.SyncStructureAfter += (_, e) => eventPublisher.PublishAsync(new SyncStructureAfterEvent(e)); // CodeFirst迁移,执行完成触发 + #pragma warning restore VSTHRD110 + + // 全局过滤器设置 + freeSqlConfig?.Invoke(fSql); + + return me.AddScoped() // 注入工作单元管理器 + .AddFreeRepository(null, App.Assemblies.ToArray()) // 批量注入 Repository + .AddMvcFilter(); // 注入事务拦截器 + } + + /// + /// 添加定时任务 + /// + public static IServiceCollection AddSchedules(this IServiceCollection me) + { + return App.WebHostEnvironment.IsProduction() + ? me.AddSchedule( // + builder => builder // + .AddJob(true, Triggers.PeriodSeconds(1).SetRunOnStart(true)) + .AddJob(true, Triggers.PeriodMinutes(1).SetRunOnStart(true))) + : me; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/FreeScheduledJob.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/FreeScheduledJob.cs new file mode 100644 index 00000000..61456dd6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/FreeScheduledJob.cs @@ -0,0 +1,47 @@ +using Gurion.Schedule; +using NetAdmin.Host.BackgroundRunning; +using NetAdmin.Host.Middlewares; + +namespace NetAdmin.SysComponent.Host.Jobs; + +/// +/// 释放计划作业 +/// +public sealed class FreeScheduledJob : WorkBase, IJob +{ + private readonly IJobService _jobService; + + /// + /// Initializes a new instance of the class. + /// + public FreeScheduledJob() + { + _jobService = ServiceProvider.GetService(); + } + + /// + /// 具体处理逻辑 + /// + /// 作业执行前上下文 + /// 取消任务 Token + /// 加锁失败异常 + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + if (SafetyShopHostMiddleware.IsShutdown) { + Console.WriteLine(Ln.此节点已下线); + return; + } + + await WorkflowAsync(true, stoppingToken).ConfigureAwait(false); + } + + /// + /// 通用工作流 + /// + /// NotImplementedException + /// ArgumentOutOfRangeException + protected override async ValueTask WorkflowAsync(CancellationToken cancelToken) + { + _ = await _jobService.ReleaseStuckTaskAsync().ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs new file mode 100644 index 00000000..272e7871 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Jobs/ScheduledJob.cs @@ -0,0 +1,155 @@ +using FreeSql.Internal; +using Gurion.RemoteRequest; +using Gurion.RemoteRequest.Extensions; +using Gurion.Schedule; +using NetAdmin.Application.Extensions; +using NetAdmin.Host.BackgroundRunning; +using NetAdmin.Host.Middlewares; +using NetAdmin.SysComponent.Domain.Dto.Sys.Job; +using NetAdmin.SysComponent.Domain.Dto.Sys.JobRecord; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Host.Jobs; + +/// +/// 计划作业 +/// +public sealed class ScheduledJob : WorkBase, IJob +{ + private static string _accessToken; + private static string _refreshToken; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + public ScheduledJob() + { + _logger = ServiceProvider.GetService>(); + } + + /// + /// 具体处理逻辑 + /// + /// 作业执行前上下文 + /// 取消任务 Token + /// 加锁失败异常 + public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken) + { + if (SafetyShopHostMiddleware.IsShutdown) { + Console.WriteLine(Ln.此节点已下线); + return; + } + + // ReSharper disable once MethodSupportsCancellation + await Parallel.ForAsync(0, SysNumbers.SCHEDULED_JOB_PARALLEL_NUM, async (_, _) => await WorkflowAsync(stoppingToken).ConfigureAwait(false)) + .ConfigureAwait(false); + } + + /// + /// 通用工作流 + /// + /// NotImplementedException + /// ArgumentOutOfRangeException + protected override async ValueTask WorkflowAsync(CancellationToken cancelToken) + { + QueryJobRsp job = null; + await using var scope = ServiceProvider.CreateAsyncScope(); + var jobService = scope.ServiceProvider.GetService(); + var jobRecordService = scope.ServiceProvider.GetService(); + var userService = scope.ServiceProvider.GetService(); + var unitOfWorkManager = scope.ServiceProvider.GetService(); + + try { + job = await jobService.GetNextJobAsync().ConfigureAwait(false); + } + catch (DbUpdateVersionException) { + // ignore + } + + if (job == null) { + _logger.Info(Ln.未获取到待执行任务); + return; + } + + var request = BuildRequest(job); + + // 随机延时 + if (job.RandomDelayBegin is > 0 && job.RandomDelayEnd is > 0) { + var (start, end) = (job.RandomDelayBegin.Value, job.RandomDelayEnd.Value); + if (start > end) { + (start, end) = (end, start); + } + + await Task.Delay(new[] { start, end }.Rand(), CancellationToken.None).ConfigureAwait(false); + } + + var sw = new Stopwatch(); + sw.Start(); + var rsp = await request.SendAsync(CancellationToken.None).ConfigureAwait(false); + if (rsp.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { + var loginRsp = await userService.LoginByUserIdAsync(job.UserId).ConfigureAwait(false); + #pragma warning disable S2696 + _accessToken = loginRsp.AccessToken; + _refreshToken = loginRsp.RefreshToken; + #pragma warning restore S2696 + request = BuildRequest(job); + rsp = await request.SendAsync(CancellationToken.None).ConfigureAwait(false); + } + + sw.Stop(); + + await unitOfWorkManager.AtomicOperateAsync(async () => { + var rspBody = await rsp.Content.ReadAsStringAsync(CancellationToken.None).ConfigureAwait(false); + var jobRecord = new CreateJobRecordReq // + { + Duration = sw.ElapsedMilliseconds + , HttpMethod = job.HttpMethod + , HttpStatusCode = (int)rsp.StatusCode + , JobId = job.Id + , RequestBody = job.RequestBody + , RequestHeader = rsp!.RequestMessage!.Headers.Json() + , RequestUrl = job.RequestUrl + , ResponseBody = rspBody + , ResponseHeader = rsp.Headers.Json() + , TimeId = job.NextTimeId!.Value + }; + + _ = await jobRecordService.CreateAsync(jobRecord).ConfigureAwait(false); + await jobService.FinishJobAsync(job.Adapt() with // + { + LastStatusCode = rsp.StatusCode // + , LastDuration = jobRecord.Duration + }) + .ConfigureAwait(false); + }) + .ConfigureAwait(false); + } + + private HttpRequestPart BuildRequest(QueryJobRsp job) + { + var ret = job.RequestUrl.SetHttpMethod(new HttpMethod(job.HttpMethod.ToString())); + var headers = new Dictionary(); + + if (!_accessToken.NullOrEmpty()) { + headers.Add( // + Chars.FLG_HTTP_HEADER_KEY_AUTHORIZATION, $"{Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA} {_accessToken}"); + } + + if (!_refreshToken.NullOrEmpty()) { + headers.Add( // + Chars.FLG_HTTP_HEADER_KEY_X_ACCESS_TOKEN_HEADER_KEY, $"{Chars.FLG_HTTP_HEADER_VALUE_AUTH_SCHEMA} {_refreshToken}"); + } + + if (!job.RequestHeader.NullOrEmpty()) { + // ReSharper disable once UsageOfDefaultStructEquality + ret = ret.SetHeaders(headers.Union(job.RequestHeader.Object>()).ToDictionary(x => x.Key, x => x.Value)); + } + + if (!job.RequestBody.NullOrEmpty()) { + ret = ret.SetBody(job.RequestBody); + } + + return ret.SetLog(_logger); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/RequestAuditMiddleware.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/RequestAuditMiddleware.cs new file mode 100644 index 00000000..b25f0bb6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/RequestAuditMiddleware.cs @@ -0,0 +1,60 @@ +using NetAdmin.SysComponent.Host.Utils; + +namespace NetAdmin.SysComponent.Host.Middlewares; + +/// +/// 请求审计中间件 +/// +public sealed class RequestAuditMiddleware( + RequestDelegate next + , IOptions dynamicApiControllerSettingsOptions + , RequestLogger requestLogger) +{ + private readonly PathString _defaultRoutePrefix = new($"/{dynamicApiControllerSettingsOptions.Value.DefaultRoutePrefix}"); + + private readonly PathString _healthCheckRoutePrefix = new($"/{dynamicApiControllerSettingsOptions.Value.DefaultRoutePrefix}/probe/health.check"); + + /// + /// 主函数 + /// + public async Task InvokeAsync(HttpContext context) + { + // 跳过处理的情况: + if (!context.Request.Path.StartsWithSegments(_defaultRoutePrefix) // 非api请求 + || context.Request.Path.StartsWithSegments(_healthCheckRoutePrefix) // 健康检查 + || context.Request.Method == Chars.FLG_HTTP_METHOD_OPTIONS // is options 请求 + || (context.Request.ContentType?.StartsWith("multipart/form-data", true, CultureInfo.InvariantCulture) ?? false) // 文件上传 + #pragma warning disable SA1009 + ) { + #pragma warning restore SA1009 + await next(context).ConfigureAwait(false); + return; + } + + // Response.Body流默认是不可读的,将Response.Body流替换成MemoryStream 使其可读 + await using var ms = new MemoryStream(); + var stream = context.Response.Body; + context.Response.Body = ms; + + // 在控制台上输出分割线,区分不同请求 + + // 调用下一个中间件 + var sw = Stopwatch.StartNew(); + await next(context).ConfigureAwait(false); + sw.Stop(); + + _ = ms.Seek(0, SeekOrigin.Begin); + using var sr = new StreamReader(ms); + var responseBody = await sr.ReadToEndAsync().ConfigureAwait(false); + _ = ms.Seek(0, SeekOrigin.Begin); + await ms.CopyToAsync(stream).ConfigureAwait(false); + context.Response.Body = stream; + + var exception = context.Features.Get(); + var errorCode = context.Response.Headers[nameof(ErrorCodes)] // + .FirstOrDefault() + ?.Enum() ?? 0; + + _ = await requestLogger.LogAsync(context, (long)sw.Elapsed.TotalMilliseconds, responseBody, errorCode, exception).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/VersionCheckerMiddleware.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/VersionCheckerMiddleware.cs new file mode 100644 index 00000000..a499b739 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Middlewares/VersionCheckerMiddleware.cs @@ -0,0 +1,45 @@ +using System.Net.WebSockets; + +namespace NetAdmin.SysComponent.Host.Middlewares; + +/// +/// 版本更新检查中间件 +/// +public sealed class VersionCheckerMiddleware(RequestDelegate next) +{ + /// + /// 主函数 + /// + public async Task InvokeAsync(HttpContext context) + { + if (context.Request.Path == "/ws/version") { + if (context.WebSockets.IsWebSocketRequest) { + var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); + await ConnectionAsync(webSocket).ConfigureAwait(false); + } + else { + context.Response.StatusCode = 400; + } + } + else { + await next(context).ConfigureAwait(false); + } + } + + private static async Task ConnectionAsync(WebSocket webSocket) + { + var buffer = new byte[1024]; + while (webSocket.State == WebSocketState.Open) { + var receiveResult = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None).ConfigureAwait(false); + if (receiveResult.MessageType != WebSocketMessageType.Text) { + continue; + } + + var ver = await App.GetService().GetVersionAsync().ConfigureAwait(false); + await webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(ver)), WebSocketMessageType.Text, true, CancellationToken.None) + .ConfigureAwait(false); + } + + await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/NetAdmin.SysComponent.Host.csproj b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/NetAdmin.SysComponent.Host.csproj new file mode 100644 index 00000000..500bc68a --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/NetAdmin.SysComponent.Host.csproj @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/ProjectUsings.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/ProjectUsings.cs new file mode 100644 index 00000000..44b39df6 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/ProjectUsings.cs @@ -0,0 +1,6 @@ +global using NetAdmin.Domain.Dto.Dependency; +global using NetAdmin.Host.Attributes; +global using NetAdmin.Host.Controllers; +global using NetAdmin.SysComponent.Application.Modules.Sys; +global using NetAdmin.SysComponent.Application.Services.Sys.Dependency; +global using NetAdmin.SysComponent.Cache.Sys.Dependency; \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/ApiSynchronizer.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/ApiSynchronizer.cs new file mode 100644 index 00000000..9e632179 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/ApiSynchronizer.cs @@ -0,0 +1,20 @@ +using NetAdmin.Domain.Events; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// Api接口同步器 +/// +public sealed class ApiSynchronizer(ILogger logger) : IEventSubscriber +{ + /// + /// 同步Api接口 + /// + [EventSubscribe(nameof(SyncStructureAfterEvent))] + public async Task SyncApiAsync(EventHandlerExecutingContext _) + { + var logService = App.GetService(); + await logService.SyncAsync().ConfigureAwait(false); + logger.Info($"{nameof(IApiService)}.{nameof(IApiService.SyncAsync)} {Ln.已处理完毕}"); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/CacheCleaner.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/CacheCleaner.cs new file mode 100644 index 00000000..7c8e0e99 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/CacheCleaner.cs @@ -0,0 +1,31 @@ +using NetAdmin.Domain.Contexts; +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// 缓存清理器 +/// +public sealed class CacheCleaner : IEventSubscriber +{ + /// + /// Initializes a new instance of the class. + /// + public CacheCleaner() { } + + /// + /// 用户缓存清理 + /// + [EventSubscribe(nameof(UserUpdatedEvent))] + public Task RemoveUserInfoAsync(EventHandlerExecutingContext context) + { + if (context.Source is not UserUpdatedEvent userUpdatedEvent) { + return Task.CompletedTask; + } + + var cache = App.GetService(); + cache.Service.UserToken = ContextUserToken.Create(userUpdatedEvent.Data.Id, userUpdatedEvent.Data.Token, userUpdatedEvent.Data.UserName + , userUpdatedEvent.Data.DeptId); + return cache.RemoveUserInfoAsync(); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/EmailCodeSender.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/EmailCodeSender.cs new file mode 100644 index 00000000..77243cb3 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/EmailCodeSender.cs @@ -0,0 +1,30 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; +using NetAdmin.SysComponent.Domain.Enums.Sys; +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// 邮件验证码发送器 +/// +public sealed class EmailCodeSender(ILogger logger) : IEventSubscriber +{ + /// + /// 发送邮件 + /// + [EventSubscribe(nameof(VerifyCodeCreatedEvent))] + public async Task SendEmailAsync(EventHandlerExecutingContext context) + { + if (context.Source is not VerifyCodeCreatedEvent verifyCodeCreatedEvent || + verifyCodeCreatedEvent.Data.DeviceType != VerifyCodeDeviceTypes.Email) { + return; + } + + // 发送... + var verifyCodeService = App.GetService(); + _ = await verifyCodeService.SetVerifyCodeStatusAsync( + verifyCodeCreatedEvent.Data.Adapt() with { Status = VerifyCodeStatues.Sent }) + .ConfigureAwait(false); + logger.Info($"{nameof(IVerifyCodeService)}.{nameof(IVerifyCodeService.SetVerifyCodeStatusAsync)} {Ln.已处理完毕}"); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/OperationLogger.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/OperationLogger.cs new file mode 100644 index 00000000..ee69c291 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/OperationLogger.cs @@ -0,0 +1,88 @@ +#if !DEBUG && DBTYPE_SQLSERVER +using System.Collections.Concurrent; +using NetAdmin.SysComponent.Domain.DbMaps.Sys; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; +using NetAdmin.SysComponent.Domain.Events.Sys; +using NetAdmin.SysComponent.Infrastructure.Constant; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// 操作日志记录 +/// +public sealed class OperationLogger : IEventSubscriber +{ + private static readonly ConcurrentQueue _requestLogs = new(); + + /// + /// 保存请求日志到数据库 + /// + [EventSubscribe(nameof(RequestLogEvent))] + public async Task OperationEventDbRecordAsync(EventHandlerExecutingContext context) + { + if (context.Source is not RequestLogEvent operationEvent) { + return; + } + + if (_requestLogs.Count > SysNumbers.REQUEST_LOG_BUFF_SIZE) { + await WriteToDbAsync().ConfigureAwait(false); + } + else { + _requestLogs.Enqueue(operationEvent.Data); + } + } + + private static async Task WriteToDbAsync() + { + var inserts = new List(SysNumbers.REQUEST_LOG_BUFF_SIZE); + + // 批量入库 + for (var i = 0; i != SysNumbers.REQUEST_LOG_BUFF_SIZE; ++i) { + if (!_requestLogs.TryDequeue(out var log)) { + continue; + } + + inserts.Add(log); + } + + // 如果首尾日期不一致,要分别插入不同的日期分表 + if (inserts[0].CreatedTime.Date != inserts[^1].CreatedTime.Date) { + foreach (var dayInserts in inserts.GroupBy(x => x.CreatedTime.Date)) { + await App.GetService() + .Insert(dayInserts.Select(x => x)) + .ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{dayInserts.Key:yyyyMMdd}") + .ConfigureAwait(false); + } + } + else { + await App.GetService() + .Insert(inserts) + .ExecuteSqlBulkCopyAsync(tableName: $"{nameof(Sys_RequestLog)}_{inserts[0].CreatedTime:yyyyMMdd}") + .ConfigureAwait(false); + } + } +} +#else +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// 操作日志记录 +/// +public sealed class OperationLogger : IEventSubscriber +{ + /// + /// 保存请求日志到数据库 + /// + [EventSubscribe(nameof(RequestLogEvent))] + public async Task OperationEventDbRecordAsync(EventHandlerExecutingContext context) + { + if (context.Source is not RequestLogEvent operationEvent) { + return; + } + + _ = await App.GetService().CreateAsync(operationEvent.Data).ConfigureAwait(false); + } +} +#endif \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/SmsCodeSender.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/SmsCodeSender.cs new file mode 100644 index 00000000..3cfe88bc --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Subscribers/SmsCodeSender.cs @@ -0,0 +1,30 @@ +using NetAdmin.SysComponent.Domain.Dto.Sys.VerifyCode; +using NetAdmin.SysComponent.Domain.Enums.Sys; +using NetAdmin.SysComponent.Domain.Events.Sys; + +namespace NetAdmin.SysComponent.Host.Subscribers; + +/// +/// 短信验证码发送器 +/// +public sealed class SmsCodeSender(ILogger logger) : IEventSubscriber +{ + /// + /// 发送短信 + /// + [EventSubscribe(nameof(VerifyCodeCreatedEvent))] + public async Task SendSmsAsync(EventHandlerExecutingContext context) + { + if (context.Source is not VerifyCodeCreatedEvent verifyCodeCreatedEvent || + verifyCodeCreatedEvent.Data.DeviceType != VerifyCodeDeviceTypes.Mobile) { + return; + } + + // 发送... + var verifyCodeService = App.GetService(); + _ = await verifyCodeService.SetVerifyCodeStatusAsync( + verifyCodeCreatedEvent.Data.Adapt() with { Status = VerifyCodeStatues.Sent }) + .ConfigureAwait(false); + logger.Info($"{nameof(IVerifyCodeService)}.{nameof(IVerifyCodeService.SetVerifyCodeStatusAsync)} {Ln.已处理完毕}"); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/RequestLogger.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/RequestLogger.cs new file mode 100644 index 00000000..a4e760e0 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/RequestLogger.cs @@ -0,0 +1,93 @@ +using NetAdmin.Domain.Contexts; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLog; +using NetAdmin.SysComponent.Domain.Dto.Sys.RequestLogDetail; +using NetAdmin.SysComponent.Domain.Events.Sys; +using Yitter.IdGenerator; +using HttpMethods = NetAdmin.Domain.Enums.HttpMethods; + +namespace NetAdmin.SysComponent.Host.Utils; + +/// +/// 请求日志记录器 +/// +public sealed class RequestLogger(ILogger logger, IEventPublisher eventPublisher) : ISingleton +{ + private static readonly string[] _loggerIgnoreApiIds + = ["api/adm/tools/query.es.log", "api/probe/health.check", "api/probe/is.system.safety.stopped"]; + + private static readonly string[] _textContentTypes = ["text", "json", "xml", "urlencoded"]; + + /// + /// 生成审计数据 + /// + public async Task LogAsync(HttpContext context, long duration, string responseBody, ErrorCodes errorCode + , IExceptionHandlerFeature exception) + { + // 从请求头中读取用户信息 + var associatedUser = GetAssociatedUser(context); + var id = YitIdHelper.NextId(); + var requestBody = Array.Exists( // + _textContentTypes, x => context.Request.ContentType?.Contains(x, StringComparison.OrdinalIgnoreCase) ?? false) + ? await context.ReadBodyContentAsync().ConfigureAwait(false) + : string.Empty; + var apiId = context.Request.Path.Value!.TrimStart('/'); + var auditData = new CreateRequestLogReq // + { + Detail = new CreateRequestLogDetailReq // + { + Id = id + , CreatedUserAgent = context.Request.Headers.UserAgent.ToString() + , ErrorCode = errorCode + , Exception = exception?.Error.ToString() + , RequestBody = requestBody + , RequestContentType = context.Request.ContentType + , RequestHeaders = context.Request.Headers.Json() + , RequestUrl = context.Request.GetRequestUrlAddress() + , ResponseBody = responseBody + , ResponseContentType = context.Response.ContentType + , ResponseHeaders = context.Response.Headers.Json() + , ServerIp = context.GetLocalIpAddressToIPv4()?.IpV4ToInt32() + , CreatedTime = DateTime.Now + } + , Duration = (int)duration + , HttpMethod = Enum.Parse(context.Request.Method, true) + , ApiPathCrc32 = apiId.Crc32() + , HttpStatusCode = context.Response.StatusCode + , CreatedClientIp = context.GetRealIpAddress()?.MapToIPv4().ToString().IpV4ToInt32() + , OwnerId = associatedUser?.UserId + , OwnerDeptId = associatedUser?.DeptId + , Id = id + , TraceId = context.GetTraceId() + , CreatedTime = DateTime.Now + }; + + // ReSharper disable once InvertIf + if (!_loggerIgnoreApiIds.Contains(apiId)) { + // 打印日志 + logger.Info(auditData); + + // 发布请求日志事件 + await eventPublisher.PublishAsync(new RequestLogEvent(auditData)).ConfigureAwait(false); + } + + return auditData; + } + + private (long UserId, long DeptId, string UserName)? GetAssociatedUser(HttpContext context) + { + var token = context.Request.Headers.Authorization.FirstOrDefault(); + if (token == null) { + return null; + } + + ContextUserToken userToken = null; + try { + userToken = ContextUserToken.Create(token); + } + catch (Exception ex) { + logger.Warn($"{Ln.读取用户令牌出错} {ex}"); + } + + return userToken == null ? null : (userToken.Id, userToken.DeptId, userToken.UserName); + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/SqlAuditor.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/SqlAuditor.cs new file mode 100644 index 00000000..29ef37cd --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Host/Utils/SqlAuditor.cs @@ -0,0 +1,194 @@ +using NetAdmin.Domain.Attributes; +using NetAdmin.Domain.DbMaps.Dependency.Fields; +using NetAdmin.SysComponent.Domain.Contexts; +using Yitter.IdGenerator; + +namespace NetAdmin.SysComponent.Host.Utils; + +/// +/// Sql审核器 +/// +public sealed class SqlAuditor : ISingleton +{ + /// + /// 数据库服务器时钟偏移 + /// + #pragma warning disable S1450 + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + private readonly TimeSpan _timeOffset; + #pragma warning restore S1450 + + /// + /// Initializes a new instance of the class. + /// + public SqlAuditor(ILogger logger) + { + // 设置服务器时间偏差 + _timeOffset = DateTime.Now.Subtract(App.GetService().Ado.QuerySingle(() => DateTime.Now)); + + logger.Info($"{Ln.数据库服务器时钟偏移} {_timeOffset}"); + } + + /// + /// 对Insert/Update的数据加工 + /// + /// e + public static void DataAuditHandler(object sender, AuditValueEventArgs e) + { + // SetServerTime(e); + SetSnowflake(e); + + // 设置创建者、修改者信息 + var user = App.GetService(); + switch (e.AuditValueType) { + case AuditValueType.Insert: + SetCreator(e, user); + SetOwner(e, user); + break; + case AuditValueType.Update: + SetModificator(e, user); + break; + case AuditValueType.InsertOrUpdate: + break; + default: + throw new ArgumentOutOfRangeException(nameof(e)); + } + } + + private static void SetCreatedClientIp(AuditValueEventArgs e) + { + if (e.Value is null or 0) { + e.Value = App.HttpContext?.GetRealIpAddress()?.MapToIPv4().ToString().IpV4ToInt32(); + } + } + + private static void SetCreatedTime(AuditValueEventArgs e) + { + if (e.Value == null || (e.Value is DateTime val && val == default)) { + e.Value = DateTime.Now; + } + } + + private static void SetCreatedUserAgent(AuditValueEventArgs e) + { + if (e.Value is null or "") { + e.Value = App.HttpContext?.Request.Headers[Chars.FLG_HTTP_HEADER_KEY_USER_AGENT].ToString(); + } + } + + private static void SetCreatedUserId(AuditValueEventArgs e, ContextUserInfo userInfo) + { + if (userInfo != null && e.Value is null or (long and 0)) { + e.Value = userInfo.Id; + } + } + + private static void SetCreatedUserName(AuditValueEventArgs e, ContextUserInfo userInfo) + { + if (userInfo != null && e.Value is null or "") { + e.Value = userInfo.UserName; + } + } + + /// + /// 设置创建者 + /// + private static void SetCreator(AuditValueEventArgs e, ContextUserInfo userInfo) + { + switch (e.Property.Name) { + case nameof(IFieldCreatedTime.CreatedTime): + SetCreatedTime(e); + break; + case nameof(IFieldCreatedUser.CreatedUserId): + SetCreatedUserId(e, userInfo); + break; + case nameof(IFieldCreatedUser.CreatedUserName): + SetCreatedUserName(e, userInfo); + break; + case nameof(IFieldCreatedClientIp.CreatedClientIp): + SetCreatedClientIp(e); + break; + case nameof(IFieldCreatedClientUserAgent.CreatedUserAgent): + SetCreatedUserAgent(e); + break; + default: + return; + } + } + + /// + /// 设置更新人 + /// + private static void SetModificator(AuditValueEventArgs e, ContextUserInfo userInfo) + { + switch (e.Property.Name) { + // case nameof(IFieldModifiedTime.ModifiedTime): + // e.Value = DateTime.Now; + // break; + case nameof(IFieldModifiedUser.ModifiedUserId): + if (userInfo != null && e.Value is null or (long and 0)) { + e.Value = userInfo.Id; + } + + break; + + case nameof(IFieldModifiedUser.ModifiedUserName): + if (userInfo != null && e.Value is null or "") { + e.Value = userInfo.UserName; + } + + break; + } + } + + /// + /// 设置拥有者 + /// + private static void SetOwner(AuditValueEventArgs e, ContextUserInfo userInfo) + { + switch (e.Property.Name) { + case nameof(IFieldOwner.OwnerId): + if (userInfo != null && e.Value is null or (long and 0)) { + e.Value = userInfo.Id; + } + + break; + case nameof(IFieldOwner.OwnerDeptId): + if (userInfo != null && e.Value is null or "") { + e.Value = userInfo.Dept.Id; + } + + break; + default: + return; + } + } + + /// + /// 设置雪花编号字段 + /// + private static void SetSnowflake(AuditValueEventArgs e) + { + var isSnowflake = e.Property.GetCustomAttribute(false) != null; + var isLongType = e.Column.CsType == typeof(long); + var isNoValue = e.Value is null or (long and 0); + if (isSnowflake && isLongType && isNoValue) { + e.Value = YitIdHelper.NextId(); + } + } + + // /// + // /// 设置服务器时间字段 + // /// + // private void SetServerTime(AuditValueEventArgs e) + // #pragma warning restore RCS1213, IDE0051 + // { + // var isServerTime = e.Property.GetCustomAttribute(false) is { Enable: true }; + // var isDateType = e.Column.CsType == typeof(DateTime) || e.Column.CsType == typeof(DateTime?); + // var hasValue = e.Value is DateTime val && val != default; + // if (isServerTime && isDateType && hasValue) { + // e.Value = ((DateTime)e.Value).Subtract(_timeOffset); + // } + // } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysChars.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysChars.cs new file mode 100644 index 00000000..052dc400 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysChars.cs @@ -0,0 +1,14 @@ +#pragma warning disable CS1591 + +namespace NetAdmin.SysComponent.Infrastructure.Constant; + +/// +/// 字符串常量表 +/// +/// +/// public类型会通过接口暴露给前端 +/// +public static class SysChars +{ + public const string FLG_PATH_API_SYS_USER_LOGIN_BY_PWD = "api/sys/user/login.by.pwd"; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysNumbers.cs b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysNumbers.cs new file mode 100644 index 00000000..9577a037 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/Constant/SysNumbers.cs @@ -0,0 +1,22 @@ +#pragma warning disable CS1591 + +namespace NetAdmin.SysComponent.Infrastructure.Constant; + +/// +/// 数字常量表 +/// +/// +/// public类型会通过接口暴露给前端 +/// +public static class SysNumbers +{ + public const long DEF_SORT_VAL = 100; // 默认值:排序字段 + public const long ID_DIC_CATALOG_GEO_AREA = 379794295185413; // 唯一编号:字典目录-行政区划字典 + public const int REQUEST_LOG_BUFF_SIZE = 1000; // 请求日志缓冲区大小 + public const int SCHEDULED_JOB_PARALLEL_NUM = 5; // 计划作业并发数 + public const int SECS_CACHE_CHART = 300; // 缓存时间(秒):仪表 + public const int SECS_CACHE_DEFAULT = 60; // 缓存时间(秒):默认 + public const int SECS_CACHE_DIC_CATALOG_CODE = 300; // 缓存时间(秒):字典配置-目录代码 + public const int SECS_CACHE_LOGIN_BY_USER_ID = 3600 * 24 * 30; // 缓存时间(秒):通过用户编号登录的用户信息 + public const int SECS_TIMEOUT_JOB = 180; // 超时时间(秒):作业 +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/NetAdmin.SysComponent.Infrastructure.csproj b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/NetAdmin.SysComponent.Infrastructure.csproj new file mode 100644 index 00000000..c0f1eca4 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.SysComponent.Infrastructure/NetAdmin.SysComponent.Infrastructure.csproj @@ -0,0 +1,14 @@ + + + + + + + + Languages/NetAdmin.SysComponent.Statements.ln + + + Languages/NetAdmin.SysComponent.Fields.ln + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Tests/Attributes/TestPriorityAttribute.cs b/src/backend/NetAdmin/NetAdmin.Tests/Attributes/TestPriorityAttribute.cs new file mode 100644 index 00000000..cd28231d --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Tests/Attributes/TestPriorityAttribute.cs @@ -0,0 +1,13 @@ +namespace NetAdmin.Tests.Attributes; + +/// +/// 测试用例优先级 +/// +[AttributeUsage(AttributeTargets.Method)] +public class TestPriorityAttribute(int priority) : Attribute +{ + /// + /// 优先级 + /// + public int Priority { get; private set; } = priority; +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Tests/NetAdmin.Tests.csproj b/src/backend/NetAdmin/NetAdmin.Tests/NetAdmin.Tests.csproj new file mode 100644 index 00000000..c9bab2c2 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Tests/NetAdmin.Tests.csproj @@ -0,0 +1,17 @@ + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Tests/PriorityOrderer.cs b/src/backend/NetAdmin/NetAdmin.Tests/PriorityOrderer.cs new file mode 100644 index 00000000..14ecca1f --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Tests/PriorityOrderer.cs @@ -0,0 +1,50 @@ +using NetAdmin.Tests.Attributes; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace NetAdmin.Tests; + +/// +/// 优先级排序 +/// +public class PriorityOrderer : ITestCaseOrderer +{ + /// + /// 排序测试用例 + /// + public IEnumerable OrderTestCases(IEnumerable testCases) + where TTestCase : ITestCase + { + var sortedMethods = new SortedDictionary>(); + + foreach (var testCase in testCases) { + var priority = 0; + + foreach (var attr in testCase.TestMethod.Method.GetCustomAttributes(typeof(TestPriorityAttribute).AssemblyQualifiedName)) { + priority = attr.GetNamedArgument("Priority"); + } + + GetOrCreate(sortedMethods, priority).Add(testCase); + } + + foreach (var list in sortedMethods.Keys.Select(priority => sortedMethods[priority])) { + list.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name)); + foreach (var testCase in list) { + yield return testCase; + } + } + } + + private static TValue GetOrCreate(SortedDictionary dictionary, TKey key) + where TValue : new() + { + if (dictionary.TryGetValue(key, out var result)) { + return result; + } + + result = new TValue(); + dictionary[key] = result; + + return result; + } +} \ No newline at end of file diff --git a/src/backend/NetAdmin/NetAdmin.Tests/WebTestApplicationFactory.cs b/src/backend/NetAdmin/NetAdmin.Tests/WebTestApplicationFactory.cs new file mode 100644 index 00000000..3f1cc335 --- /dev/null +++ b/src/backend/NetAdmin/NetAdmin.Tests/WebTestApplicationFactory.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc.Testing; + +namespace NetAdmin.Tests; + +/// +/// Web 测试应用程序工厂 +/// +/// +public class WebTestApplicationFactory : WebApplicationFactory + where T : class +{ + /// + /// 测试用户 + /// + public const string TEST_ACCOUNT = "root"; + + /// + /// 测试密码 + /// + public const string TEST_PASSWORD = "1234qwer"; +} \ No newline at end of file