diff --git a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj index 40a42f83..0781edd6 100644 --- a/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj +++ b/Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj @@ -17,27 +17,27 @@ - - - - + + + + - - + + - + - + @@ -45,7 +45,7 @@ - + @@ -56,9 +56,9 @@ - - - + + + diff --git a/Admin.NET/Admin.NET.Core/Attribute/OwnerOrgAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/OwnerOrgAttribute.cs new file mode 100644 index 00000000..468272cb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/OwnerOrgAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 所属机构数据权限 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] +public class OwnerOrgAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Attribute/OwnerUserAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/OwnerUserAttribute.cs new file mode 100644 index 00000000..4fc7c69f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Attribute/OwnerUserAttribute.cs @@ -0,0 +1,16 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 所属用户数据权限 +/// +[SuppressSniffer] +[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)] +public class OwnerUserAttribute : Attribute +{ +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs index 758e8ce0..6114a8d7 100644 --- a/Admin.NET/Admin.NET.Core/Const/CacheConst.cs +++ b/Admin.NET/Admin.NET.Core/Const/CacheConst.cs @@ -100,4 +100,9 @@ public class CacheConst /// 系统字典缓存 /// public const string KeyDict = "sys_dict:"; + + /// + /// Excel临时文件缓存 + /// + public const string KeyExcelTemp = "sys_excel_temp:"; } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Const/CommonConst.cs b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs index dd0ba083..bfecf5e7 100644 --- a/Admin.NET/Admin.NET.Core/Const/CommonConst.cs +++ b/Admin.NET/Admin.NET.Core/Const/CommonConst.cs @@ -87,6 +87,11 @@ public class CommonConst /// public const string SysValidationLog = "sys_validation_log"; + /// + /// 行政区域同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级 + /// + public const string SysRegionSyncLevel = "sys_region_sync_level"; + /// /// 日志保留天数 /// diff --git a/Admin.NET/Admin.NET.Core/Const/ConfigConst.cs b/Admin.NET/Admin.NET.Core/Const/ConfigConst.cs new file mode 100644 index 00000000..ec33dabb --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Const/ConfigConst.cs @@ -0,0 +1,58 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 配置常量 +/// +public class ConfigConst +{ + /// + /// WebConfig 分组 + /// + public const string SysWebConfigGroup = "WebConfig"; + + /// + /// 系统图标 + /// + public const string SysWebLogo = "sys_web_logo"; + + /// + /// 系统主标题 + /// + public const string SysWebTitle = "sys_web_title"; + + /// + /// 系统副标题 + /// + public const string SysWebViceTitle = "sys_web_viceTitle"; + + /// + /// 系统描述 + /// + public const string SysWebViceDesc = "sys_web_viceDesc"; + + /// + /// 水印内容 + /// + public const string SysWebWatermark = "sys_web_watermark"; + + /// + /// 版权说明 + /// + public const string SysWebCopyright = "sys_web_copyright"; + + /// + /// ICP备案号 + /// + public const string SysWebIcp = "sys_web_icp"; + + /// + /// ICP地址 + /// + public const string SysWebIcpUrl = "sys_web_icpUrl"; +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs b/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs index d000be13..56c2fa70 100644 --- a/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs +++ b/Admin.NET/Admin.NET.Core/Entity/EntityBase.cs @@ -40,6 +40,7 @@ public abstract class EntityBase : EntityBaseId, IDeletedFilter /// 创建者Id /// [SugarColumn(ColumnDescription = "创建者Id", IsOnlyIgnoreUpdate = true)] + [OwnerUser] public virtual long? CreateUserId { get; set; } ///// @@ -92,6 +93,7 @@ public abstract class EntityBaseData : EntityBase, IOrgIdFilter /// 创建者部门Id /// [SugarColumn(ColumnDescription = "创建者部门Id", IsOnlyIgnoreUpdate = true)] + [OwnerOrg] public virtual long? CreateOrgId { get; set; } /// diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs index ba23d162..8ec86d27 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs @@ -1,125 +1,131 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core; - -/// -/// 代码生成表 -/// -[SugarTable(null, "代码生成表")] -[SysTable] -[SugarIndex("index_{table}_B", nameof(BusName), OrderByType.Asc)] -[SugarIndex("index_{table}_T", nameof(TableName), OrderByType.Asc)] -public partial class SysCodeGen : EntityBase -{ - /// - /// 作者姓名 - /// - [SugarColumn(ColumnDescription = "作者姓名", Length = 32)] - [MaxLength(32)] - public string? AuthorName { get; set; } - - /// - /// 是否移除表前缀 - /// - [SugarColumn(ColumnDescription = "是否移除表前缀", Length = 8)] - [MaxLength(8)] - public string? TablePrefix { get; set; } - - /// - /// 生成方式 - /// - [SugarColumn(ColumnDescription = "生成方式", Length = 32)] - [MaxLength(32)] - public string? GenerateType { get; set; } - - /// - /// 库定位器名 - /// - [SugarColumn(ColumnDescription = "库定位器名", Length = 64)] - [MaxLength(64)] - public string? ConfigId { get; set; } - - /// - /// 数据库名(保留字段) - /// - [SugarColumn(ColumnDescription = "数据库库名", Length = 64)] - [MaxLength(64)] - public string? DbName { get; set; } - - /// - /// 数据库类型 - /// - [SugarColumn(ColumnDescription = "数据库类型", Length = 64)] - [MaxLength(64)] - public string? DbType { get; set; } - - /// - /// 数据库链接 - /// - [SugarColumn(ColumnDescription = "数据库链接", Length = 256)] - [MaxLength(256)] - public string? ConnectionString { get; set; } - - /// - /// 数据库表名 - /// - [SugarColumn(ColumnDescription = "数据库表名", Length = 128)] - [MaxLength(128)] - public string? TableName { get; set; } - - /// - /// 命名空间 - /// - [SugarColumn(ColumnDescription = "命名空间", Length = 128)] - [MaxLength(128)] - public string? NameSpace { get; set; } - - /// - /// 业务名 - /// - [SugarColumn(ColumnDescription = "业务名", Length = 128)] - [MaxLength(128)] - public string? BusName { get; set; } - - /// - /// 是否生成菜单 - /// - [SugarColumn(ColumnDescription = "是否生成菜单")] - public bool GenerateMenu { get; set; } = true; - - /// - /// 菜单图标 - /// - [SugarColumn(ColumnDescription = "菜单图标", Length = 32)] - public string? MenuIcon { get; set; } = "ele-Menu"; - - /// - /// 菜单编码 - /// - [SugarColumn(ColumnDescription = "菜单编码")] - public long? MenuPid { get; set; } - - /// - /// 页面目录 - /// - [SugarColumn(ColumnDescription = "页面目录", Length = 32)] - public string? PagePath { get; set; } - - /// - /// 支持打印类型 - /// - [SugarColumn(ColumnDescription = "支持打印类型", Length = 32)] - [MaxLength(32)] - public string? PrintType { get; set; } - - /// - /// 打印模版名称 - /// - [SugarColumn(ColumnDescription = "打印模版名称", Length = 32)] - [MaxLength(32)] - public string? PrintName { get; set; } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 代码生成表 +/// +[SugarTable(null, "代码生成表")] +[SysTable] +[SugarIndex("index_{table}_B", nameof(BusName), OrderByType.Asc)] +[SugarIndex("index_{table}_T", nameof(TableName), OrderByType.Asc)] +public partial class SysCodeGen : EntityBase +{ + /// + /// 作者姓名 + /// + [SugarColumn(ColumnDescription = "作者姓名", Length = 32)] + [MaxLength(32)] + public string? AuthorName { get; set; } + + /// + /// 是否移除表前缀 + /// + [SugarColumn(ColumnDescription = "是否移除表前缀", Length = 8)] + [MaxLength(8)] + public string? TablePrefix { get; set; } + + /// + /// 生成方式 + /// + [SugarColumn(ColumnDescription = "生成方式", Length = 32)] + [MaxLength(32)] + public string? GenerateType { get; set; } + + /// + /// 库定位器名 + /// + [SugarColumn(ColumnDescription = "库定位器名", Length = 64)] + [MaxLength(64)] + public string? ConfigId { get; set; } + + /// + /// 数据库名(保留字段) + /// + [SugarColumn(ColumnDescription = "数据库库名", Length = 64)] + [MaxLength(64)] + public string? DbName { get; set; } + + /// + /// 数据库类型 + /// + [SugarColumn(ColumnDescription = "数据库类型", Length = 64)] + [MaxLength(64)] + public string? DbType { get; set; } + + /// + /// 数据库链接 + /// + [SugarColumn(ColumnDescription = "数据库链接", Length = 256)] + [MaxLength(256)] + public string? ConnectionString { get; set; } + + /// + /// 数据库表名 + /// + [SugarColumn(ColumnDescription = "数据库表名", Length = 128)] + [MaxLength(128)] + public string? TableName { get; set; } + + /// + /// 命名空间 + /// + [SugarColumn(ColumnDescription = "命名空间", Length = 128)] + [MaxLength(128)] + public string? NameSpace { get; set; } + + /// + /// 业务名 + /// + [SugarColumn(ColumnDescription = "业务名", Length = 128)] + [MaxLength(128)] + public string? BusName { get; set; } + + /// + /// 是否生成菜单 + /// + [SugarColumn(ColumnDescription = "是否生成菜单")] + public bool GenerateMenu { get; set; } = true; + + /// + /// 菜单图标 + /// + [SugarColumn(ColumnDescription = "菜单图标", Length = 32)] + public string? MenuIcon { get; set; } = "ele-Menu"; + + /// + /// 菜单编码 + /// + [SugarColumn(ColumnDescription = "菜单编码")] + public long? MenuPid { get; set; } + + /// + /// 页面目录 + /// + [SugarColumn(ColumnDescription = "页面目录", Length = 32)] + public string? PagePath { get; set; } + + /// + /// 支持打印类型 + /// + [SugarColumn(ColumnDescription = "支持打印类型", Length = 32)] + [MaxLength(32)] + public string? PrintType { get; set; } + + /// + /// 打印模版名称 + /// + [SugarColumn(ColumnDescription = "打印模版名称", Length = 32)] + [MaxLength(32)] + public string? PrintName { get; set; } + + /// + /// 是否使用 Api Service + /// + [SugarColumn(ColumnDescription = "是否使用 Api Service")] + public bool IsApiService { get; set; } = false; } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs index 94b33c53..d4ef92a3 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs @@ -63,7 +63,7 @@ public class SysWechatRefund : EntityBase /// 退款资金来源, 可不传,默认使用未结算资金退款(仅对老资金流商户适用) /// [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)] - public string FundsAccount { get; set; } + public string? FundsAccount { get; set; } /// /// 关联的商户订单号 diff --git a/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs index 3bf74c86..61e5016b 100644 --- a/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs +++ b/Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs @@ -679,6 +679,24 @@ public enum ErrorCodeEnum [ErrorCodeItemMetadata("已有相同编码或名称")] R2002, + /// + /// 行政区代码只能为6、9或12位 + /// + [ErrorCodeItemMetadata("行政区代码只能为6、9或12位")] + R2003, + + /// + /// 父节点不能为自己的子节点 + /// + [ErrorCodeItemMetadata("父节点不能为自己的子节点")] + R2004, + + /// + /// 同步国家统计局数据异常,请稍后重试 + /// + [ErrorCodeItemMetadata("同步国家统计局数据异常,请稍后重试")] + R2005, + /// /// 默认租户状态禁止修改 /// diff --git a/Admin.NET/Admin.NET.Core/Enum/FilterLogicEnum.cs b/Admin.NET/Admin.NET.Core/Enum/FilterLogicEnum.cs new file mode 100644 index 00000000..5e898f2f --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/FilterLogicEnum.cs @@ -0,0 +1,32 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 过滤条件 +/// +[Description("过滤条件")] +public enum FilterLogicEnum +{ + /// + /// 并且 + /// + [Description("并且")] + And, + + /// + /// 或者 + /// + [Description("或者")] + Or, + + /// + /// 异或 + /// + [Description("异或")] + Xor +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/FilterOperatorEnum.cs b/Admin.NET/Admin.NET.Core/Enum/FilterOperatorEnum.cs new file mode 100644 index 00000000..892619b8 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Enum/FilterOperatorEnum.cs @@ -0,0 +1,68 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 过滤逻辑运算符 +/// +[Description("过滤逻辑运算符")] +public enum FilterOperatorEnum +{ + /// + /// 等于(=) + /// + [Description("等于")] + EQ, + + /// + /// 不等于(!=) + /// + [Description("不等于")] + NEQ, + + /// + /// 小于(<) + /// + [Description("小于")] + LT, + + /// + /// 小于等于(<=) + /// + [Description("小于等于")] + LTE, + + /// + /// 大于(>) + /// + [Description("大于")] + GT, + + /// + /// 大于等于(>=) + /// + [Description("大于等于")] + GTE, + + /// + /// 开始包含 + /// + [Description("开始包含")] + StartsWith, + + /// + /// 末尾包含 + /// + [Description("末尾包含")] + EndsWith, + + /// + /// 包含 + /// + [Description("包含")] + Contains +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs index 3284532c..5b5a7904 100644 --- a/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs +++ b/Admin.NET/Admin.NET.Core/Enum/GenderEnum.cs @@ -7,26 +7,32 @@ namespace Admin.NET.Core; /// -/// 性别枚举 +/// 性别枚举(GB/T 2261.1-2003) /// [Description("性别枚举")] public enum GenderEnum { /// - /// 男 + /// 未知的性别 /// - [Description("男")] + [Description("未知的性别")] + Unknown = 0, + + /// + /// 男性 + /// + [Description("男性")] Male = 1, /// - /// 女 + /// 女性 /// - [Description("女")] + [Description("女性")] Female = 2, /// - /// 其他 + /// 未说明的性别 /// - [Description("其他")] - Other = 3 + [Description("未说明的性别")] + Unspecified = 9 } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs b/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs index 2a438056..92f27f51 100644 --- a/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs +++ b/Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs @@ -42,7 +42,8 @@ public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable //var mailTemp = File.ReadAllText(mailTempPath); //var mail = await _serviceScope.ServiceProvider.GetRequiredService().RunCompileFromCachedAsync(mailTemp, ); - var title = "Admin.NET 系统异常"; + var webTitle = await _serviceScope.ServiceProvider.GetRequiredService().GetConfigValue(ConfigConst.SysWebTitle); + var title = $"{webTitle} 系统异常"; await _serviceScope.ServiceProvider.GetRequiredService().SendEmail(JSON.Serialize(context.Source.Payload), title); } diff --git a/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs b/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs new file mode 100644 index 00000000..88074a71 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/HttpContextExtension.cs @@ -0,0 +1,94 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using Microsoft.AspNetCore.Authentication; + +namespace Admin.NET.Core; + +public static class HttpContextExtension +{ + public static async Task GetExternalProvidersAsync(this HttpContext context) + { + ArgumentNullException.ThrowIfNull(context); + + var schemes = context.RequestServices.GetRequiredService(); + + return (from scheme in await schemes.GetAllSchemesAsync() + where !string.IsNullOrEmpty(scheme.DisplayName) + select scheme).ToArray(); + } + + public static async Task IsProviderSupportedAsync(this HttpContext context, string provider) + { + ArgumentNullException.ThrowIfNull(context); + + return (from scheme in await context.GetExternalProvidersAsync() + where string.Equals(scheme.Name, provider, StringComparison.OrdinalIgnoreCase) + select scheme).Any(); + } + + /// + /// 获取设备信息 + /// + /// + /// + public static string GetClientDeviceInfo(this HttpContext context) + { + ArgumentNullException.ThrowIfNull(context); + + return CommonUtil.GetClientDeviceInfo(context.Request.Headers.UserAgent); + } + + /// + /// 获取浏览器信息 + /// + /// + /// + public static string GetClientBrowser(this HttpContext context) + { + ArgumentNullException.ThrowIfNull(context); + + string userAgent = context.Request.Headers.UserAgent; + try + { + if (userAgent != null) + { + var client = Parser.GetDefault().Parse(userAgent); + if (client.Device.IsSpider) + return "爬虫"; + return $"{client.UA.Family} {client.UA.Major}.{client.UA.Minor} / {client.Device.Family}"; + } + } + catch + { } + return "未知"; + } + + /// + /// 获取操作系统信息 + /// + /// + /// + public static string GetClientOs(this HttpContext context) + { + ArgumentNullException.ThrowIfNull(context); + + string userAgent = context.Request.Headers.UserAgent; + try + { + if (userAgent != null) + { + var client = Parser.GetDefault().Parse(userAgent); + if (client.Device.IsSpider) + return "爬虫"; + return $"{client.OS.Family} {client.OS.Major} {client.OS.Minor}"; + } + } + catch + { } + return "未知"; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs b/Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs new file mode 100644 index 00000000..7b9a7af5 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs @@ -0,0 +1,294 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.Text.Json; + +namespace Admin.NET.Core; + +/// +/// Sqlsugar 动态查询扩展方法 +/// +public static class SqlSugarExtension +{ + public static ISugarQueryable SearchBy(this ISugarQueryable queryable, BaseFilter filter) + { + return queryable.SearchByKeyword(filter.Keyword) + .AdvancedSearch(filter.Search) + .AdvancedFilter(filter.Filter); + } + + public static ISugarQueryable SearchByKeyword(this ISugarQueryable queryable, string keyword) + { + return queryable.AdvancedSearch(new Search { Keyword = keyword }); + } + + public static ISugarQueryable AdvancedSearch(this ISugarQueryable queryable, Search search) + { + if (!string.IsNullOrWhiteSpace(search?.Keyword)) + { + var paramExpr = Expression.Parameter(typeof(T)); + + Expression right = Expression.Constant(false); + + if (search.Fields?.Any() is true) + { + foreach (string field in search.Fields) + { + MemberExpression propertyExpr = GetPropertyExpression(field, paramExpr); + + var left = AddSearchPropertyByKeyword(propertyExpr, search.Keyword); + + right = Expression.Or(left, right); + } + } + else + { + var properties = typeof(T).GetProperties() + .Where(prop => Nullable.GetUnderlyingType(prop.PropertyType) == null + && !prop.PropertyType.IsEnum + && Type.GetTypeCode(prop.PropertyType) != TypeCode.Object); + + foreach (var property in properties) + { + var propertyExpr = Expression.Property(paramExpr, property); + + var left = AddSearchPropertyByKeyword(propertyExpr, search.Keyword); + + right = Expression.Or(left, right); + } + } + + var lambda = Expression.Lambda>(right, paramExpr); + + return queryable.Where(lambda); + } + + return queryable; + } + + public static ISugarQueryable AdvancedFilter(this ISugarQueryable queryable, Filter filter) + { + if (filter is not null) + { + var parameter = Expression.Parameter(typeof(T)); + + Expression binaryExpresioFilter; + + if (filter.Logic.HasValue) + { + if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic"); + binaryExpresioFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter); + } + else + { + var filterValid = GetValidFilter(filter); + binaryExpresioFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter); + } + + var lambda = Expression.Lambda>(binaryExpresioFilter, parameter); + + return queryable.Where(lambda); + } + return queryable; + } + + private static Expression CombineFilter( + FilterLogicEnum filterLogic, + Expression bExpresionBase, + Expression bExpresion) + { + return filterLogic switch + { + FilterLogicEnum.And => Expression.And(bExpresionBase, bExpresion), + FilterLogicEnum.Or => Expression.Or(bExpresionBase, bExpresion), + FilterLogicEnum.Xor => Expression.ExclusiveOr(bExpresionBase, bExpresion), + _ => throw new ArgumentException("FilterLogic is not valid.", nameof(filterLogic)), + }; + } + + private static Filter GetValidFilter(Filter filter) + { + if (string.IsNullOrEmpty(filter.Field)) throw new ArgumentException("The field attribute is required when declaring a filter"); + if (filter.Operator.IsNullOrEmpty()) throw new ArgumentException("The Operator attribute is required when declaring a filter"); + return filter; + } + + private static Expression CreateFilterExpression( + FilterLogicEnum filterLogic, + IEnumerable filters, + ParameterExpression parameter) + { + Expression filterExpression = default!; + + foreach (var filter in filters) + { + Expression bExpresionFilter; + + if (filter.Logic.HasValue) + { + if (filter.Filters is null) throw new ArgumentException("The Filters attribute is required when declaring a logic"); + bExpresionFilter = CreateFilterExpression(filter.Logic.Value, filter.Filters, parameter); + } + else + { + var filterValid = GetValidFilter(filter); + bExpresionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator.Value, filterValid.Value, parameter); + } + + filterExpression = filterExpression is null ? bExpresionFilter : CombineFilter(filterLogic, filterExpression, bExpresionFilter); + } + + return filterExpression; + } + + private static Expression CreateFilterExpression( + string field, + FilterOperatorEnum filterOperator, + object? value, + ParameterExpression parameter) + { + var propertyExpresion = GetPropertyExpression(field, parameter); + var valueExpresion = GeValuetExpression(field, value, propertyExpresion.Type); + return CreateFilterExpression(propertyExpresion, valueExpresion, filterOperator); + } + + private static Expression CreateFilterExpression( + MemberExpression memberExpression, + ConstantExpression constantExpression, + FilterOperatorEnum filterOperator) + { + return filterOperator switch + { + FilterOperatorEnum.EQ => Expression.Equal(memberExpression, constantExpression), + FilterOperatorEnum.NEQ => Expression.NotEqual(memberExpression, constantExpression), + FilterOperatorEnum.LT => Expression.LessThan(memberExpression, constantExpression), + FilterOperatorEnum.LTE => Expression.LessThanOrEqual(memberExpression, constantExpression), + FilterOperatorEnum.GT => Expression.GreaterThan(memberExpression, constantExpression), + FilterOperatorEnum.GTE => Expression.GreaterThanOrEqual(memberExpression, constantExpression), + FilterOperatorEnum.Contains => Expression.Call(memberExpression, nameof(FilterOperatorEnum.Contains), null, constantExpression), + FilterOperatorEnum.StartsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.StartsWith), null, constantExpression), + FilterOperatorEnum.EndsWith => Expression.Call(memberExpression, nameof(FilterOperatorEnum.EndsWith), null, constantExpression), + _ => throw new ArgumentException("Filter Operator is not valid."), + }; + } + + private static string GetStringFromJsonElement(object value) + { + if (value is JsonElement) return ((JsonElement)value).GetString()!; + if (value is string) return (string)value; + return value?.ToString(); + } + + private static ConstantExpression GeValuetExpression( + string field, + object? value, + Type propertyType) + { + if (value == null) return Expression.Constant(null, propertyType); + + if (propertyType.IsEnum) + { + string? stringEnum = GetStringFromJsonElement(value); + + if (!Enum.TryParse(propertyType, stringEnum, true, out object? valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field)); + + return Expression.Constant(valueparsed, propertyType); + } + if (propertyType == typeof(long)) + { + string? stringLong = GetStringFromJsonElement(value); + + if (!long.TryParse(stringLong, out long valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field)); + + return Expression.Constant(valueparsed, propertyType); + } + + if (propertyType == typeof(Guid)) + { + string? stringGuid = GetStringFromJsonElement(value); + + if (!Guid.TryParse(stringGuid, out Guid valueparsed)) throw new ArgumentException(string.Format("Value {0} is not valid for {1}", value, field)); + + return Expression.Constant(valueparsed, propertyType); + } + + if (propertyType == typeof(string)) + { + string? text = GetStringFromJsonElement(value); + + return Expression.Constant(text, propertyType); + } + + if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)) + { + string? text = GetStringFromJsonElement(value); + return Expression.Constant(ChangeType(text, propertyType), propertyType); + } + + return Expression.Constant(ChangeType(((JsonElement)value).GetRawText(), propertyType), propertyType); + } + + private static dynamic? ChangeType(object value, Type conversion) + { + var t = conversion; + + if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) + { + if (value == null) + { + return null; + } + + t = Nullable.GetUnderlyingType(t); + } + + return Convert.ChangeType(value, t!); + } + + private static MemberExpression GetPropertyExpression( + string propertyName, + ParameterExpression parameter) + { + Expression propertyExpression = parameter; + foreach (string member in propertyName.Split('.')) + { + propertyExpression = Expression.PropertyOrField(propertyExpression, member); + } + + return (MemberExpression)propertyExpression; + } + + private static Expression AddSearchPropertyByKeyword( + Expression propertyExpr, + string keyword, + FilterOperatorEnum operatorSearch = FilterOperatorEnum.Contains) + { + if (propertyExpr is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo property) + { + throw new ArgumentException("propertyExpr must be a property expression.", nameof(propertyExpr)); + } + + ConstantExpression constant = Expression.Constant(keyword); + + MethodInfo method = operatorSearch switch + { + FilterOperatorEnum.Contains => typeof(string).GetMethod(nameof(FilterOperatorEnum.Contains), new Type[] { typeof(string) }), + FilterOperatorEnum.StartsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.StartsWith), new Type[] { typeof(string) }), + FilterOperatorEnum.EndsWith => typeof(string).GetMethod(nameof(FilterOperatorEnum.EndsWith), new Type[] { typeof(string) }), + _ => throw new ArgumentException("Filter Operator is not valid."), + }; + + Expression selectorExpr = + property.PropertyType == typeof(string) + ? propertyExpr + : Expression.Condition( + Expression.Equal(Expression.Convert(propertyExpr, typeof(object)), Expression.Constant(null, typeof(object))), + Expression.Constant(null, typeof(string)), + Expression.Call(propertyExpr, "ToString", null, null)); + + return Expression.Call(selectorExpr, method, constant); + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Extension/SqlSugarFilterExtension.cs b/Admin.NET/Admin.NET.Core/Extension/SqlSugarFilterExtension.cs new file mode 100644 index 00000000..4018db49 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Extension/SqlSugarFilterExtension.cs @@ -0,0 +1,55 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +public static class SqlSugarFilterExtension +{ + /// + /// 根据指定Attribute获取属性 + /// + /// + /// + /// + private static List GetPropertyNames(this Type type) + where T : Attribute + { + var allProperties = type.GetProperties(); + + var properties = allProperties.Where(x => x.CustomAttributes.Any(a => a.AttributeType == typeof(T))); + + return properties.Select(x => x.Name).ToList(); + } + + /// + /// 获取过滤表达式 + /// + /// + /// + /// + /// + public static LambdaExpression GetConditionExpression(this Type type, List owners) + where T : Attribute + { + var fieldNames = type.GetPropertyNames(); + + ParameterExpression parameter = Expression.Parameter(type, "c"); + Expression right = Expression.Constant(false); + fieldNames.ForEach(filedName => + { + owners.ForEach(owner => + { + Expression left = Expression.Equal( + Expression.Property(parameter, type.GetProperty(filedName)), + Expression.Constant(owner) + ); + right = Expression.Or(left, right); + }); + }); + var finalExpression = Expression.Lambda(right, new ParameterExpression[] { parameter }); + return finalExpression; + } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs b/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs index 7973e694..6e1d583e 100644 --- a/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs +++ b/Admin.NET/Admin.NET.Core/Hub/OnlineUserHub.cs @@ -17,6 +17,7 @@ public class OnlineUserHub : Hub { private const string GROUP_ONLINE = "GROUP_ONLINE_"; // 租户分组前缀 + private readonly UserManager _userManager; private readonly SqlSugarRepository _sysOnlineUerRep; private readonly SysMessageService _sysMessageService; private readonly IHubContext _onlineUserHubContext; @@ -27,13 +28,15 @@ public class OnlineUserHub : Hub SysMessageService sysMessageService, IHubContext onlineUserHubContext, SysCacheService sysCacheService, - SysConfigService sysConfigService) + SysConfigService sysConfigService, + UserManager userManager) { _sysOnlineUerRep = sysOnlineUerRep; _sysMessageService = sysMessageService; _onlineUserHubContext = onlineUserHubContext; _sysCacheService = sysCacheService; _sysConfigService = sysConfigService; + _userManager = userManager; } /// @@ -43,23 +46,17 @@ public class OnlineUserHub : Hub public override async Task OnConnectedAsync() { var httpContext = Context.GetHttpContext(); - var token = httpContext.Request.Query["access_token"]; - var claims = JWTEncryption.ReadJwtToken(token)?.Claims; - var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]); - - var userId = claims?.FirstOrDefault(u => u.Type == ClaimConst.UserId)?.Value; - var tenantId = claims?.FirstOrDefault(u => u.Type == ClaimConst.TenantId)?.Value; var user = new SysOnlineUser { ConnectionId = Context.ConnectionId, - UserId = string.IsNullOrWhiteSpace(userId) ? 0 : long.Parse(userId), - UserName = claims?.FirstOrDefault(u => u.Type == ClaimConst.Account)?.Value, - RealName = claims?.FirstOrDefault(u => u.Type == ClaimConst.RealName)?.Value, + UserId = _userManager.UserId, + UserName = _userManager.Account, + RealName = _userManager.RealName, Time = DateTime.Now, - Ip = httpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(), - Browser = client.UA.Family + client.UA.Major, - Os = client.OS.Family + client.OS.Major, - TenantId = string.IsNullOrWhiteSpace(tenantId) ? 0 : Convert.ToInt64(tenantId), + Ip = httpContext.GetRemoteIpAddressToIPv4(true), + Browser = httpContext.GetClientBrowser(), + Os = httpContext.GetClientOs(), + TenantId = _userManager.TenantId, }; await _sysOnlineUerRep.InsertAsync(user); @@ -70,7 +67,7 @@ public class OnlineUserHub : Hub } else { - var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim(); + var device = httpContext.GetClientDeviceInfo().Trim(); _sysCacheService.Set(CacheConst.KeyUserOnline + user.UserId + device, user); } @@ -98,7 +95,6 @@ public class OnlineUserHub : Hub if (string.IsNullOrEmpty(Context.ConnectionId)) return; var httpContext = Context.GetHttpContext(); - var client = Parser.GetDefault().Parse(httpContext.Request.Headers["User-Agent"]); var user = await _sysOnlineUerRep.AsQueryable().Filter("", true).FirstAsync(u => u.ConnectionId == Context.ConnectionId); if (user == null) return; @@ -112,7 +108,7 @@ public class OnlineUserHub : Hub } else { - var device = (client.UA.Family + client.UA.Major + client.OS.Family + client.OS.Major).Trim(); + var device = httpContext.GetClientDeviceInfo().Trim(); _sysCacheService.Remove(CacheConst.KeyUserOnline + user.UserId + device); } diff --git a/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs index 95848948..3e0d5661 100644 --- a/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs +++ b/Admin.NET/Admin.NET.Core/Job/EnumToDictJob.cs @@ -70,7 +70,7 @@ public class EnumToDictJob : IJob }); try { - db.Ado.BeginTran(); + db.BeginTran(); if (uSysDictType.Count > 0) await db.Updateable(uSysDictType).ExecuteCommandAsync(stoppingToken); @@ -78,11 +78,11 @@ public class EnumToDictJob : IJob if (uSysDictData.Count > 0) await db.Updateable(uSysDictData).ExecuteCommandAsync(stoppingToken); - db.Ado.CommitTran(); + db.CommitTran(); } catch (Exception error) { - db.Ado.RollbackTran(); + db.RollbackTran(); Log.Error($"{context.Trigger.Description}更新枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); throw new Exception($"{context.Trigger.Description}更新枚举转换字典入库错误"); } @@ -123,7 +123,7 @@ public class EnumToDictJob : IJob }); try { - db.Ado.BeginTran(); + db.BeginTran(); if (iDictType.Count > 0) await db.Insertable(iDictType).ExecuteCommandAsync(stoppingToken); @@ -131,11 +131,11 @@ public class EnumToDictJob : IJob if (iDictData.Count > 0) await db.Insertable(iDictData).ExecuteCommandAsync(stoppingToken); - db.Ado.CommitTran(); + db.CommitTran(); } catch (Exception error) { - db.Ado.RollbackTran(); + db.RollbackTran(); Log.Error($"{context.Trigger.Description}新增枚举转换字典入库错误:" + _jsonSerializer.Serialize(error)); throw new Exception($"{context.Trigger.Description}新增枚举转换字典入库错误"); } diff --git a/Admin.NET/Admin.NET.Core/Job/LogJob.cs b/Admin.NET/Admin.NET.Core/Job/LogJob.cs index 752791e0..f29036d2 100644 --- a/Admin.NET/Admin.NET.Core/Job/LogJob.cs +++ b/Admin.NET/Admin.NET.Core/Job/LogJob.cs @@ -36,12 +36,13 @@ public class LogJob : IJob await logOpRep.CopyNew().AsDeleteable().Where(u => (DateTime)u.CreateTime < DateTime.Now.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除操作日志 await logDiffRep.CopyNew().AsDeleteable().Where(u => (DateTime)u.CreateTime < DateTime.Now.AddDays(-daysAgo)).ExecuteCommandAsync(stoppingToken); // 删除差异日志 + string msg = $"【{DateTime.Now}】清理系统日志成功,删除 {daysAgo} 天前的日志数据!"; var originColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"【{DateTime.Now}】清理系统日志({daysAgo}天前)"); + Console.WriteLine(msg); Console.ForegroundColor = originColor; // 自定义日志 - _logger.LogInformation($"【{DateTime.Now}】清理系统日志..."); + _logger.LogInformation(msg); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs b/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs index e1e929ea..e5c333c2 100644 --- a/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs +++ b/Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs @@ -31,15 +31,16 @@ public class OnlineUserJob : IJob var rep = serviceScope.ServiceProvider.GetRequiredService>(); await rep.CopyNew().AsDeleteable().ExecuteCommandAsync(stoppingToken); - var originColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"【{DateTime.Now}】清空在线用户列表"); - Console.ForegroundColor = originColor; - // 缓存租户列表 await serviceScope.ServiceProvider.GetRequiredService().CacheTenant(); + string msg = $"【{DateTime.Now}】清理在线用户成功!服务已重启..."; + var originColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(msg); + Console.ForegroundColor = originColor; + // 自定义日志 - _logger.LogInformation($"【{DateTime.Now}】服务已重启..."); + _logger.LogInformation(msg); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysBaseApiSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysBaseApiSeedData.cs index 24055450..0f4b7152 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysBaseApiSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysBaseApiSeedData.cs @@ -19,33 +19,34 @@ public class SysBaseApiSeedData : ISqlSugarEntitySeedData { return new[] { - new SysBaseApi { Id = 1300000000001, Route = "sysAuth/login" }, - new SysBaseApi { Id = 1300000000002, Route = "sysAuth/unLockScreen" }, - new SysBaseApi { Id = 1300000000003, Route = "sysAuth/userInfo" }, - new SysBaseApi { Id = 1300000000004, Route = "sysAuth/refreshToken" }, - new SysBaseApi { Id = 1300000000005, Route = "sysAuth/loginConfig" }, - new SysBaseApi { Id = 1300000000006, Route = "sysAuth/watermarkConfig" }, - new SysBaseApi { Id = 1300000000007, Route = "sysAuth/captcha" }, - new SysBaseApi { Id = 1300000000008, Route = "sysMenu/loginMenuTree" }, - new SysBaseApi { Id = 1300000000009, Route = "sysOAuth/signIn" }, - new SysBaseApi { Id = 1300000000010, Route = "sysOAuth/signInCallback" }, - new SysBaseApi { Id = 1300000000011, Route = "sysOnlineUser/page" }, - new SysBaseApi { Id = 1300000000012, Route = "sysOrg/list" }, - new SysBaseApi { Id = 1300000000013, Route = "sysPos/list" }, - new SysBaseApi { Id = 1300000000014, Route = "sysRole/page" }, - new SysBaseApi { Id = 1300000000015, Route = "sysRole/list" }, - new SysBaseApi { Id = 1300000000016, Route = "sysFile/uploadAvatar" }, - new SysBaseApi { Id = 1300000000017, Route = "sysFile/uploadSignature" }, - new SysBaseApi { Id = 1300000000018, Route = "sysUser/baseInfo" }, - new SysBaseApi { Id = 1300000000019, Route = "sysUser/changePwd" }, - new SysBaseApi { Id = 1300000000020, Route = "sysNotice/page" }, - new SysBaseApi { Id = 1300000000021, Route = "sysNotice/add" }, - new SysBaseApi { Id = 1300000000022, Route = "sysNotice/update" }, - new SysBaseApi { Id = 1300000000023, Route = "sysNotice/delete" }, - new SysBaseApi { Id = 1300000000024, Route = "sysNotice/public" }, - new SysBaseApi { Id = 1300000000025, Route = "sysNotice/setRead" }, - new SysBaseApi { Id = 1300000000026, Route = "sysNotice/pageReceived" }, - new SysBaseApi { Id = 1300000000027, Route = "sysNotice/unReadList" }, + new SysBaseApi { Id = 1300000000010, Route = "sysAuth/login" }, + new SysBaseApi { Id = 1300000000020, Route = "sysAuth/unLockScreen" }, + new SysBaseApi { Id = 1300000000030, Route = "sysAuth/userInfo" }, + new SysBaseApi { Id = 1300000000040, Route = "sysAuth/refreshToken" }, + new SysBaseApi { Id = 1300000000050, Route = "sysAuth/loginConfig" }, + new SysBaseApi { Id = 1300000000060, Route = "sysAuth/watermarkConfig" }, + new SysBaseApi { Id = 1300000000070, Route = "sysAuth/captcha" }, + new SysBaseApi { Id = 1300000000080, Route = "sysAuth/logout" }, + new SysBaseApi { Id = 1300000000090, Route = "sysMenu/loginMenuTree" }, + new SysBaseApi { Id = 1300000000100, Route = "sysOAuth/signIn" }, + new SysBaseApi { Id = 1300000000110, Route = "sysOAuth/signInCallback" }, + new SysBaseApi { Id = 1300000000120, Route = "sysOnlineUser/page" }, + new SysBaseApi { Id = 1300000000130, Route = "sysOrg/list" }, + new SysBaseApi { Id = 1300000000140, Route = "sysPos/list" }, + new SysBaseApi { Id = 1300000000150, Route = "sysRole/page" }, + new SysBaseApi { Id = 1300000000160, Route = "sysRole/list" }, + new SysBaseApi { Id = 1300000000170, Route = "sysFile/uploadAvatar" }, + new SysBaseApi { Id = 1300000000180, Route = "sysFile/uploadSignature" }, + new SysBaseApi { Id = 1300000000190, Route = "sysUser/baseInfo" }, + new SysBaseApi { Id = 1300000000200, Route = "sysUser/changePwd" }, + new SysBaseApi { Id = 1300000000210, Route = "sysNotice/page" }, + new SysBaseApi { Id = 1300000000220, Route = "sysNotice/add" }, + new SysBaseApi { Id = 1300000000230, Route = "sysNotice/update" }, + new SysBaseApi { Id = 1300000000240, Route = "sysNotice/delete" }, + new SysBaseApi { Id = 1300000000250, Route = "sysNotice/public" }, + new SysBaseApi { Id = 1300000000260, Route = "sysNotice/setRead" }, + new SysBaseApi { Id = 1300000000270, Route = "sysNotice/pageReceived" }, + new SysBaseApi { Id = 1300000000280, Route = "sysNotice/unReadList" }, }; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs index a0928390..53b97a0e 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs @@ -32,6 +32,7 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData new SysConfig{ Id=1300000000201, Name="发送异常日志邮件", Code="sys_error_mail", Value="True", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000211, Name="开启域登录验证", Code="sys_domain_login", Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000221, Name="开启数据校验日志", Code="sys_validation_log", Value="True", SysFlag=YesNoEnum.Y, Remark="是否数据校验日志", OrderNo=130, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, + new SysConfig{ Id=1300000000231, Name="行政区域同步层级", Code="sys_region_sync_level", Value="3", SysFlag=YesNoEnum.Y, Remark="行政区域同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=140, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000301, Name="系统主标题", Code="sys_web_title", Value="Admin.NET.Pro", SysFlag=YesNoEnum.Y, Remark="系统主标题", OrderNo=300, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000311, Name="系统副标题", Code="sys_web_viceTitle", Value="Admin.NET.Pro", SysFlag=YesNoEnum.Y, Remark="系统副标题", OrderNo=310, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, new SysConfig{ Id=1300000000321, Name="系统描述", Code="sys_web_viceDesc", Value="站在巨人肩膀上的 .NET 通用权限开发框架", SysFlag=YesNoEnum.Y, Remark="系统描述", OrderNo=320, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") }, diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs index 009000b2..b4d45642 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/Dto/SysLdapInput.cs @@ -9,7 +9,7 @@ namespace Admin.NET.Core.Service; /// /// 系统域登录信息配置输入参数 /// -public class SysLdapInput : BasePageInput +public class PageSysLdapInput : BasePageInput { /// /// 关键字查询 diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs index 4d4af392..38013046 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs @@ -275,7 +275,7 @@ public class SysAuthService : IDynamicApiController, ITransient var roleIds = await _sysUserRep.ChangeRepository>().AsQueryable() .Where(u => u.UserId == user.Id).Select(u => u.RoleId).ToListAsync(); // 获取水印文字(若系统水印为空则全局为空) - var watermarkText = await _sysConfigService.GetConfigValue("sys_web_watermark"); + var watermarkText = await _sysConfigService.GetConfigValue(ConfigConst.SysWebWatermark); if (!string.IsNullOrWhiteSpace(watermarkText)) watermarkText += $"-{user.RealName}"; // $"-{user.RealName}-{_httpContextAccessor.HttpContext.GetRemoteIp()}-{DateTime.Now}"; diff --git a/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs index 2514891f..fd6679e8 100644 --- a/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Auth/SysLdapService.cs @@ -27,7 +27,7 @@ public class SysLdapService : IDynamicApiController, ITransient /// /// [DisplayName("获取系统域登录配置分页列表")] - public async Task> Page(SysLdapInput input) + public async Task> Page(PageSysLdapInput input) { return await _sysLdapRep.AsQueryable() .WhereIF(!string.IsNullOrWhiteSpace(input.SearchKey), u => u.Host.Contains(input.SearchKey.Trim())) diff --git a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs index 89d79784..46c79d2a 100644 --- a/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs @@ -62,6 +62,17 @@ public class SysCacheService : IDynamicApiController, ISingleton return _cacheProvider.Cache.Set($"{_cacheOptions.Prefix}{key}", value, expire); } + /// + /// 获取缓存的剩余生存时间 + /// + /// + /// + [NonAction] + public TimeSpan GetExpire(string key) + { + return _cacheProvider.Cache.GetExpire(key); + } + /// /// 获取缓存 /// @@ -133,6 +144,10 @@ public class SysCacheService : IDynamicApiController, ISingleton [DisplayName("获取缓存值")] public object GetValue(string key) { + // 若Key经过URL编码则进行解码 + if (Regex.IsMatch(key, @"%[0-9a-fA-F]{2}")) + key = HttpUtility.UrlDecode(key); + return _cacheProvider.Cache == Cache.Default ? _cacheProvider.Cache.Get($"{_cacheOptions.Prefix}{key}") : _cacheProvider.Cache.Get($"{_cacheOptions.Prefix}{key}"); diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs index d202e728..a2f2449e 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs @@ -1,100 +1,102 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core.Service; - -public class CustomViewEngine : ViewEngineModel -{ - private readonly ISqlSugarClient _db; - - public CustomViewEngine() - { - } - - public CustomViewEngine(ISqlSugarClient db) - { - _db = db; - } - - /// - /// 库定位器 - /// - public string ConfigId { get; set; } = SqlSugarConst.MainConfigId; - - public string AuthorName { get; set; } - - public string BusName { get; set; } - - public string NameSpace { get; set; } - - public string ClassName { get; set; } - - public string ProjectLastName { get; set; } - - public string LowerClassName - { - get - { - return ClassName[..1].ToLower() + ClassName[1..]; // 首字母小写 - } - } - - public string PagePath { get; set; } = "main"; - - public bool IsJoinTable { get; set; } - - public bool IsUpload { get; set; } - - public string PrintType { get; set; } - - public string PrintName { get; set; } - - public List QueryWhetherList { get; set; } - - public List TableField { get; set; } - - private List ColumnList { get; set; } - - public string GetColumnNetType(object tbName, object colName) - { - if (tbName == null || colName == null) return null; - - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == ConfigId); - ColumnList = GetColumnListByTableName(tbName.ToString()); - var col = ColumnList.Where(c => (config.DbSettings.EnableUnderLine - ? CodeGenUtil.CamelColumnName(c.ColumnName, Array.Empty()) - : c.ColumnName) == colName.ToString()).FirstOrDefault(); - return col.NetType; - } - - public List GetColumnListByTableName(string tableName) - { - // 多库代码生成切换库 - var provider = _db.AsTenant().GetConnectionScope(ConfigId != SqlSugarConst.MainConfigId ? ConfigId : SqlSugarConst.MainConfigId); - - // 获取实体类型属性 - var entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); - - // 因为ConfigId的表通常也会用到主库的表来做连接,所以这里如果在ConfigId中找不到实体也尝试一下在主库中查找 - if (ConfigId == SqlSugarConst.MainConfigId && entityType == null) return null; - if (ConfigId != SqlSugarConst.MainConfigId) - { - provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId); - entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); - if (entityType == null) return null; - } - - // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) - return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput - { - ColumnName = u.DbColumnName, - ColumnKey = u.IsPrimarykey.ToString(), - DataType = u.DataType.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), - ColumnComment = u.ColumnDescription - }).ToList(); - } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +public class CustomViewEngine : ViewEngineModel +{ + private readonly ISqlSugarClient _db; + + public CustomViewEngine() + { + } + + public CustomViewEngine(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 库定位器 + /// + public string ConfigId { get; set; } = SqlSugarConst.MainConfigId; + + public string AuthorName { get; set; } + + public string BusName { get; set; } + + public string NameSpace { get; set; } + + public string ClassName { get; set; } + + public string ProjectLastName { get; set; } + + public string LowerClassName + { + get + { + return ClassName[..1].ToLower() + ClassName[1..]; // 首字母小写 + } + } + + public string PagePath { get; set; } = "main"; + + public bool IsJoinTable { get; set; } + + public bool IsUpload { get; set; } + + public string PrintType { get; set; } + + public string PrintName { get; set; } + + public bool IsApiService { get; set; } + + public List QueryWhetherList { get; set; } + + public List TableField { get; set; } + + private List ColumnList { get; set; } + + public string GetColumnNetType(object tbName, object colName) + { + if (tbName == null || colName == null) return null; + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == ConfigId); + ColumnList = GetColumnListByTableName(tbName.ToString()); + var col = ColumnList.Where(c => (config.DbSettings.EnableUnderLine + ? CodeGenUtil.CamelColumnName(c.ColumnName, Array.Empty()) + : c.ColumnName) == colName.ToString()).FirstOrDefault(); + return col.NetType; + } + + public List GetColumnListByTableName(string tableName) + { + // 多库代码生成切换库 + var provider = _db.AsTenant().GetConnectionScope(ConfigId != SqlSugarConst.MainConfigId ? ConfigId : SqlSugarConst.MainConfigId); + + // 获取实体类型属性 + var entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); + + // 因为ConfigId的表通常也会用到主库的表来做连接,所以这里如果在ConfigId中找不到实体也尝试一下在主库中查找 + if (ConfigId == SqlSugarConst.MainConfigId && entityType == null) return null; + if (ConfigId != SqlSugarConst.MainConfigId) + { + provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId); + entityType = provider.DbMaintenance.GetTableInfoList().FirstOrDefault(u => u.Name == tableName); + if (entityType == null) return null; + } + + // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) + return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput + { + ColumnName = u.DbColumnName, + ColumnKey = u.IsPrimarykey.ToString(), + DataType = u.DataType.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = u.ColumnDescription + }).ToList(); + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs index aee96f67..3b90760d 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenOutput.cs @@ -1,83 +1,88 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -namespace Admin.NET.Core.Service; - -/// -/// 代码生成参数类 -/// -public class CodeGenOutput -{ - /// - /// 代码生成器Id - /// - public long Id { get; set; } - - /// - /// 作者姓名 - /// - public string AuthorName { get; set; } - - /// - /// 类名 - /// - public string ClassName { get; set; } - - /// - /// 是否移除表前缀 - /// - public string TablePrefix { get; set; } - - /// - /// 生成方式 - /// - public string GenerateType { get; set; } - - /// - /// 数据库表名 - /// - public string TableName { get; set; } - - /// - /// 包名 - /// - public string PackageName { get; set; } - - /// - /// 业务名(业务代码包名称) - /// - public string BusName { get; set; } - - /// - /// 功能名(数据库表名称) - /// - public string TableComment { get; set; } - - /// - /// 菜单应用分类(应用编码) - /// - public string MenuApplication { get; set; } - - /// - /// 是否生成菜单 - /// - public bool GenerateMenu { get; set; } - - /// - /// 菜单父级 - /// - public long? MenuPid { get; set; } - - /// - /// 支持打印类型 - /// - public string PrintType { get; set; } - - /// - /// 打印模版名称 - /// - public string PrintName { get; set; } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core.Service; + +/// +/// 代码生成参数类 +/// +public class CodeGenOutput +{ + /// + /// 代码生成器Id + /// + public long Id { get; set; } + + /// + /// 作者姓名 + /// + public string AuthorName { get; set; } + + /// + /// 类名 + /// + public string ClassName { get; set; } + + /// + /// 是否移除表前缀 + /// + public string TablePrefix { get; set; } + + /// + /// 生成方式 + /// + public string GenerateType { get; set; } + + /// + /// 数据库表名 + /// + public string TableName { get; set; } + + /// + /// 包名 + /// + public string PackageName { get; set; } + + /// + /// 业务名(业务代码包名称) + /// + public string BusName { get; set; } + + /// + /// 功能名(数据库表名称) + /// + public string TableComment { get; set; } + + /// + /// 菜单应用分类(应用编码) + /// + public string MenuApplication { get; set; } + + /// + /// 是否生成菜单 + /// + public bool GenerateMenu { get; set; } + + /// + /// 菜单父级 + /// + public long? MenuPid { get; set; } + + /// + /// 支持打印类型 + /// + public string PrintType { get; set; } + + /// + /// 打印模版名称 + /// + public string PrintName { get; set; } + + /// + /// 是否使用 Api Service + /// + public bool IsApiService { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/PageCodeGenInput.cs similarity index 92% rename from Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs rename to Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/PageCodeGenInput.cs index f44ff273..2e51e331 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/PageCodeGenInput.cs @@ -9,7 +9,7 @@ namespace Admin.NET.Core.Service; /// /// 代码生成参数类 /// -public class CodeGenInput : BasePageInput +public class PageCodeGenInput : BasePageInput { /// /// 作者姓名 @@ -100,9 +100,14 @@ public class CodeGenInput : BasePageInput /// 打印模版名称 /// public virtual string PrintName { get; set; } + + /// + /// 是否使用 Api Service + /// + public virtual bool IsApiService { get; set; } } -public class AddCodeGenInput : CodeGenInput +public class AddCodeGenInput : PageCodeGenInput { /// /// 数据库表名 @@ -157,6 +162,11 @@ public class AddCodeGenInput : CodeGenInput /// [Required(ErrorMessage = "是否生成菜单不能为空")] public override bool GenerateMenu { get; set; } + + /// + /// 是否使用 Api Service + /// + public override bool IsApiService { get; set; } } public class DeleteCodeGenInput @@ -168,7 +178,7 @@ public class DeleteCodeGenInput public long Id { get; set; } } -public class UpdateCodeGenInput : CodeGenInput +public class UpdateCodeGenInput : PageCodeGenInput { /// /// 代码生成器Id diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs index 6f06463a..a6ab9946 100644 --- a/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs @@ -1,811 +1,814 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -using System.IO.Compression; - -namespace Admin.NET.Core.Service; - -/// -/// 系统代码生成器服务 🧩 -/// -[ApiDescriptionSettings(Order = 270)] -public class SysCodeGenService : IDynamicApiController, ITransient -{ - private readonly ISqlSugarClient _db; - - private readonly SysCodeGenConfigService _codeGenConfigService; - private readonly IViewEngine _viewEngine; - private readonly CodeGenOptions _codeGenOptions; - - public SysCodeGenService(ISqlSugarClient db, - SysCodeGenConfigService codeGenConfigService, - IViewEngine viewEngine, - IOptions codeGenOptions) - { - _db = db; - _codeGenConfigService = codeGenConfigService; - _viewEngine = viewEngine; - _codeGenOptions = codeGenOptions.Value; - } - - /// - /// 获取代码生成分页列表 🔖 - /// - /// - /// - [DisplayName("获取代码生成分页列表")] - public async Task> Page(CodeGenInput input) - { - return await _db.Queryable() - .WhereIF(!string.IsNullOrWhiteSpace(input.TableName), u => u.TableName.Contains(input.TableName.Trim())) - .WhereIF(!string.IsNullOrWhiteSpace(input.BusName), u => u.BusName.Contains(input.BusName.Trim())) - .ToPagedListAsync(input.Page, input.PageSize); - } - - /// - /// 增加代码生成 🔖 - /// - /// - /// - [ApiDescriptionSettings(Name = "Add"), HttpPost] - [DisplayName("增加代码生成")] - public async Task AddCodeGen(AddCodeGenInput input) - { - var isExist = await _db.Queryable().Where(u => u.TableName == input.TableName).AnyAsync(); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D1400); - - var codeGen = input.Adapt(); - var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync(); - // 加入配置表中 - _codeGenConfigService.AddList(GetColumnList(input), newCodeGen); - } - - /// - /// 更新代码生成 🔖 - /// - /// - /// - [ApiDescriptionSettings(Name = "Update"), HttpPost] - [DisplayName("更新代码生成")] - public async Task UpdateCodeGen(UpdateCodeGenInput input) - { - var isExist = await _db.Queryable().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D1400); - - await _db.Updateable(input.Adapt()).ExecuteCommandAsync(); - } - - /// - /// 删除代码生成 🔖 - /// - /// - /// - [ApiDescriptionSettings(Name = "Delete"), HttpPost] - [DisplayName("删除代码生成")] - public async Task DeleteCodeGen(List inputs) - { - if (inputs == null || inputs.Count < 1) return; - - var codeGenConfigTaskList = new List(); - inputs.ForEach(u => - { - _db.Deleteable().In(u.Id).ExecuteCommand(); - - // 删除配置表中 - codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id)); - }); - await Task.WhenAll(codeGenConfigTaskList); - } - - /// - /// 获取代码生成详情 🔖 - /// - /// - /// - [DisplayName("获取代码生成详情")] - public async Task GetDetail([FromQuery] QueryCodeGenInput input) - { - return await _db.Queryable().SingleAsync(u => u.Id == input.Id); - } - - /// - /// 获取数据库库集合 🔖 - /// - /// - [DisplayName("获取数据库库集合")] - public async Task> GetDatabaseList() - { - var dbConfigs = App.GetOptions().ConnectionConfigs; - return await Task.FromResult(dbConfigs.Adapt>()); - } - - /// - /// 获取数据库表(实体)集合 🔖 - /// - /// - [DisplayName("获取数据库表(实体)集合")] - public async Task> GetTableList(string configId = SqlSugarConst.MainConfigId) - { - var provider = _db.AsTenant().GetConnectionScope(configId); - var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用 - - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId)); - - var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList(); - IEnumerable entityInfos = await GetEntityInfos(); - - var tableOutputList = new List(); - foreach (var item in entityInfos) - { - var table = dbTableInfos.FirstOrDefault(u => u.Name.ToLower() == (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()); - if (table == null) continue; - tableOutputList.Add(new TableOutput - { - ConfigId = configId, - EntityName = item.EntityName, - TableName = table.Name, - TableComment = item.TableDescription - }); - } - return tableOutputList; - } - - /// - /// 根据表名获取列集合 🔖 - /// - /// - [DisplayName("根据表名获取列集合")] - public List GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId) - { - // 切库---多库代码生成用 - var provider = _db.AsTenant().GetConnectionScope(configId); - - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId); - // 获取实体类型属性 - var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName); - if (entityType == null) return null; - var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; - // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) - return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput - { - ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, - ColumnKey = u.IsPrimarykey.ToString(), - DataType = u.DataType.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), - ColumnComment = u.ColumnDescription - }).ToList(); - } - - /// - /// 获取数据表列(实体属性)集合 - /// - /// - private List GetColumnList([FromQuery] AddCodeGenInput input) - { - var entityType = GetEntityInfos().GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName); - if (entityType == null) - return null; - var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); - var dbTableName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(entityType.DbTableName) : entityType.DbTableName; - - // 切库---多库代码生成用 - var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId); - - var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; - var columnInfos = provider.DbMaintenance.GetColumnInfosByTableName(dbTableName, false); - var result = columnInfos.Select(u => new ColumnOuput - { - // 转下划线后的列名需要再转回来(暂时不转) - //ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, - ColumnName = u.DbColumnName, - ColumnLength = u.Length, - IsPrimarykey = u.IsPrimarykey, - IsNullable = u.IsNullable, - ColumnKey = u.IsPrimarykey.ToString(), - NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), - DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), - ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? u.DbColumnName : u.ColumnDescription - }).ToList(); - - // 获取实体的属性信息,赋值给PropertyName属性(CodeFirst模式应以PropertyName为实际使用名称) - var entityProperties = entityType.Type.GetProperties(); - - for (int i = result.Count - 1; i >= 0; i--) - { - var columnOutput = result[i]; - // 先找自定义字段名的,如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性) - var propertyInfo = entityProperties.FirstOrDefault(u => (u.GetCustomAttribute()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower()) ?? - entityProperties.FirstOrDefault(u => u.GetCustomAttribute() != null && u.Name.ToLower() == (config.DbSettings.EnableUnderLine - ? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower() - : columnOutput.ColumnName.ToLower())); - if (propertyInfo != null) - { - columnOutput.PropertyName = propertyInfo.Name; - columnOutput.ColumnComment = propertyInfo.GetCustomAttribute().ColumnDescription; - } - else - { - result.RemoveAt(i); // 移除没有定义此属性的字段 - } - } - return result; - } - - /// - /// 获取库表信息 - /// - /// - private async Task> GetEntityInfos() - { - var entityInfos = new List(); - - var type = typeof(SugarTable); - var types = new List(); - if (_codeGenOptions.EntityAssemblyNames != null) - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in assemblies) - { - var assemblyName = assembly.GetName().Name; - if (_codeGenOptions.EntityAssemblyNames.Contains(assemblyName) || _codeGenOptions.EntityAssemblyNames.Any(name => assemblyName.Contains(name))) - { - Assembly asm = Assembly.Load(assemblyName); - types.AddRange(asm.GetExportedTypes().ToList()); - } - } - } - bool IsMyAttribute(Attribute[] o) - { - foreach (Attribute a in o) - { - if (a.GetType() == type) - return true; - } - return false; - } - Type[] cosType = types.Where(o => - { - return IsMyAttribute(Attribute.GetCustomAttributes(o, true)); - } - ).ToArray(); - - foreach (var ct in cosType) - { - var sugarAttribute = ct.GetCustomAttributes(type, true)?.FirstOrDefault(); - - var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), true); - var description = ""; - if (des.Length > 0) - { - description = ((DescriptionAttribute)des[0]).Description; - } - entityInfos.Add(new EntityInfo() - { - EntityName = ct.Name, - DbTableName = sugarAttribute == null ? ct.Name : ((SugarTable)sugarAttribute).TableName, - TableDescription = sugarAttribute == null ? description : ((SugarTable)sugarAttribute).TableDescription, - Type = ct - }); - } - return await Task.FromResult(entityInfos); - } - - /// - /// 获取程序保存位置 🔖 - /// - /// - [DisplayName("获取程序保存位置")] - public List GetApplicationNamespaces() - { - return _codeGenOptions.BackendApplicationNamespaces; - } - - /// - /// 代码生成到本地 🔖 - /// - /// - [DisplayName("代码生成到本地")] - public async Task RunLocal(SysCodeGen input) - { - if (string.IsNullOrEmpty(input.GenerateType)) - input.GenerateType = "200"; - - // 先删除该表已生成的菜单列表 - List targetPathList; - var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); - if (input.GenerateType.StartsWith('1')) - { - targetPathList = GetZipPathList(input); - if (Directory.Exists(zipPath)) - Directory.Delete(zipPath, true); - } - else - targetPathList = GetTargetPathList(input); - - var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 - var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 - var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 - (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 - - var data = new CustomViewEngine(_db) - { - ConfigId = input.ConfigId, - AuthorName = input.AuthorName, - BusName = input.BusName, - NameSpace = input.NameSpace, - ClassName = input.TableName, - PagePath = input.PagePath, - ProjectLastName = input.NameSpace.Split('.').Last(), - QueryWhetherList = queryWhetherList, - TableField = tableFieldList, - IsJoinTable = joinTableList.Count > 0, - IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), - PrintType = input.PrintType, - PrintName = input.PrintName, - }; - // 模板目录 - var templatePathList = GetTemplatePathList(input); - var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); - - for (var i = 0; i < templatePathList.Count; i++) - { - var templateFilePath = Path.Combine(templatePath, templatePathList[i]); - if (!File.Exists(templateFilePath)) continue; - var tContent = File.ReadAllText(templateFilePath); - var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => - { - builder.AddAssemblyReferenceByName("System.Linq"); - builder.AddAssemblyReferenceByName("System.Collections"); - builder.AddUsing("System.Collections.Generic"); - builder.AddUsing("System.Linq"); - }); - var dirPath = new DirectoryInfo(targetPathList[i]).Parent.FullName; - if (!Directory.Exists(dirPath)) - Directory.CreateDirectory(dirPath); - File.WriteAllText(targetPathList[i], tResult, Encoding.UTF8); - } - if (input.GenerateMenu) - await AddMenu(input.TableName, input.BusName, input.MenuPid ?? 0, input.MenuIcon, input.PagePath, tableFieldList); - // 非ZIP压缩返回空 - if (!input.GenerateType.StartsWith('1')) - return null; - else - { - string downloadPath = zipPath + ".zip"; - // 判断是否存在同名称文件 - if (File.Exists(downloadPath)) - File.Delete(downloadPath); - ZipFile.CreateFromDirectory(zipPath, downloadPath); - return new { url = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}/CodeGen/{input.TableName}.zip" }; - } - } - - /// - /// 获取代码生成预览 🔖 - /// - /// - [DisplayName("获取代码生成预览")] - public async Task> Preview(SysCodeGen input) - { - var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 - var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 - var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 - (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 - - var data = new CustomViewEngine(_db) - { - ConfigId = input.ConfigId, - AuthorName = input.AuthorName, - BusName = input.BusName, - NameSpace = input.NameSpace, - ClassName = input.TableName, - PagePath = input.PagePath, - ProjectLastName = input.NameSpace.Split('.').Last(), - QueryWhetherList = queryWhetherList, - TableField = tableFieldList, - IsJoinTable = joinTableList.Count > 0, - IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), - PrintType = input.PrintType, - PrintName = input.PrintName, - }; - - // 获取模板文件并替换 - var templatePathList = GetTemplatePathList(); - var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); - - var result = new Dictionary(); - for (var i = 0; i < templatePathList.Count; i++) - { - var templateFilePath = Path.Combine(templatePath, templatePathList[i]); - if (!File.Exists(templateFilePath)) continue; - var tContent = File.ReadAllText(templateFilePath); - var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => - { - builder.AddAssemblyReferenceByName("System.Linq"); - builder.AddAssemblyReferenceByName("System.Collections"); - builder.AddUsing("System.Collections.Generic"); - builder.AddUsing("System.Linq"); - }); - result.Add(templatePathList[i]?.TrimEnd(".vm"), tResult); - } - - return result; - } - - /// - /// 获取连表的实体名和别名 - /// - /// - /// - private static (string, string) GetJoinTableStr(List configs) - { - var uploads = configs.Where(u => u.EffectType == "Upload").ToList(); - var fks = configs.Where(u => u.EffectType == "fk").ToList(); - string str = ""; // - string lowerStr = ""; // (o, i, c) - foreach (var item in uploads) - { - lowerStr += "sysFile_FK_" + item.LowerPropertyName + ","; - str += "SysFile,"; - } - foreach (var item in fks) - { - lowerStr += item.LowerFkEntityName + "_FK_" + item.LowerFkColumnName + ","; - str += item.FkEntityName + ","; - } - return (str.TrimEnd(','), lowerStr.TrimEnd(',')); - } - - /// - /// 增加菜单 - /// - /// - /// - /// - /// - /// - /// - /// - private async Task AddMenu(string className, string busName, long pid, string menuIcon, string pagePath, List tableFieldList) - { - var pPath = string.Empty; - // 若 pid=0 为顶级则创建菜单目录 - if (pid == 0) - { - // 目录 - var menuType0 = new SysMenu - { - Pid = 0, - Title = busName + "管理", - Type = MenuTypeEnum.Dir, - Icon = "robot", - Path = "/" + className.ToLower(), - Component = "Layout", - }; - // 若先前存在则删除本级和下级 - var menuList0 = await _db.Queryable().Where(u => u.Title == menuType0.Title && u.Type == menuType0.Type).ToListAsync(); - if (menuList0.Count > 0) - { - var listIds = menuList0.Select(u => u.Id).ToList(); - var childlistIds = new List(); - foreach (var item in listIds) - { - var childlist = await _db.Queryable().ToChildListAsync(u => u.Pid, item); - childlistIds.AddRange(childlist.Select(u => u.Id).ToList()); - } - listIds.AddRange(childlistIds); - await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); - await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); - } - pid = (await _db.Insertable(menuType0).ExecuteReturnEntityAsync()).Id; - } - else - { - var pMenu = await _db.Queryable().FirstAsync(u => u.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505); - pPath = pMenu.Path; - } - - // 菜单 - var menuType = new SysMenu - { - Pid = pid, - Title = busName + "管理", - Name = className[..1].ToLower() + className[1..], - Type = MenuTypeEnum.Menu, - Icon = menuIcon, - Path = pPath + "/" + className.ToLower(), - Component = "/" + pagePath + "/" + className[..1].ToLower() + className[1..] + "/index", - }; - // 若先前存在则删除本级和下级 - var menuListCurrent = await _db.Queryable().Where(u => u.Title == menuType.Title && u.Type == menuType.Type).ToListAsync(); - if (menuListCurrent.Count > 0) - { - var listIds = menuListCurrent.Select(u => u.Id).ToList(); - var childListIds = new List(); - foreach (var item in listIds) - { - var childList = await _db.Queryable().ToChildListAsync(u => u.Pid, item); - childListIds.AddRange(childList.Select(u => u.Id).ToList()); - } - listIds.AddRange(childListIds); - await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); - await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); - } - - var menuPid = (await _db.Insertable(menuType).ExecuteReturnEntityAsync()).Id; - int menuOrder = 100; - // 按钮-page - var menuTypePage = new SysMenu - { - Pid = menuPid, - Title = "查询", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":page", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-detail - var menuTypeDetail = new SysMenu - { - Pid = menuPid, - Title = "详情", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":detail", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-add - var menuTypeAdd = new SysMenu - { - Pid = menuPid, - Title = "增加", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":add", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-delete - var menuTypeDelete = new SysMenu - { - Pid = menuPid, - Title = "删除", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":delete", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-update - var menuTypeUpdate = new SysMenu - { - Pid = menuPid, - Title = "编辑", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":update", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-print - var menuTypePrint = new SysMenu - { - Pid = menuPid, - Title = "打印", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":print", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-import - var menuTypeImport = new SysMenu - { - Pid = menuPid, - Title = "导入", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":import", - OrderNo = menuOrder - }; - menuOrder += 10; - - // 按钮-export - var menuTypeExport = new SysMenu - { - Pid = menuPid, - Title = "导出", - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":export", - OrderNo = menuOrder - }; - menuOrder += 10; - - var menuList = new List() { menuTypePage, menuTypeDetail, menuTypeAdd, menuTypeDelete, menuTypeUpdate, menuTypePrint, menuTypeImport, menuTypeExport }; - // 加入fk、Upload、ApiTreeSelect 等接口的权限 - // 在生成表格时,有些字段只是查询时显示,不需要填写(WhetherAddUpdate),所以这些字段没必要生成相应接口 - var fkTableList = tableFieldList.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList(); - foreach (var @column in fkTableList) - { - var menuType1 = new SysMenu - { - Pid = menuPid, - Title = "外键" + @column.ColumnName, - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + column.ColumnName + "Dropdown", - OrderNo = menuOrder - }; - menuOrder += 10; - menuList.Add(menuType1); - } - var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelect").ToList(); - foreach (var @column in treeSelectTableList) - { - var menuType1 = new SysMenu - { - Pid = menuPid, - Title = "树型" + @column.ColumnName, - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + "Tree", - OrderNo = menuOrder - }; - menuOrder += 10; - menuList.Add(menuType1); - } - var uploadTableList = tableFieldList.Where(u => u.EffectType == "Upload").ToList(); - foreach (var @column in uploadTableList) - { - var menuType1 = new SysMenu - { - Pid = menuPid, - Title = "上传" + @column.ColumnName, - Type = MenuTypeEnum.Btn, - Permission = className[..1].ToLower() + className[1..] + ":Upload" + column.ColumnName, - OrderNo = menuOrder - }; - menuOrder += 10; - menuList.Add(menuType1); - } - await _db.Insertable(menuList).ExecuteCommandAsync(); - } - - /// - /// 获取模板文件路径集合 - /// - /// - private static List GetTemplatePathList(SysCodeGen input) - { - if (input.GenerateType.Substring(1, 1).Contains('1')) - { - return new() { "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; - } - else if (input.GenerateType.Substring(1, 1).Contains('2')) - { - return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm" }; - } - else - { - return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; - } - } - - /// - /// 获取模板文件路径集合 - /// - /// - private static List GetTemplatePathList() => new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; - - /// - /// 设置生成文件路径 - /// - /// - /// - private List GetTargetPathList(SysCodeGen input) - { - //var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); - var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Service", input.TableName); - var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); - var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); - var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); - var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); - var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); - var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");// - var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); - var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); - - if (input.GenerateType.Substring(1, 1).Contains('1')) - { - // 生成到本项目(前端) - return new List() - { - indexPath, - formModalPath, - apiJsPath - }; - } - else if (input.GenerateType.Substring(1, 1).Contains('2')) - { - // 生成到本项目(后端) - return new List() - { - servicePath, - inputPath, - outputPath, - viewPath, - }; - } - else - { - // 前后端同时生成到本项目 - return new List() - { - servicePath, - inputPath, - outputPath, - viewPath, - indexPath, - formModalPath, - apiJsPath - }; - } - } - - /// - /// 设置生成文件路径 - /// - /// - /// - private List GetZipPathList(SysCodeGen input) - { - var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); - - //var backendPath = Path.Combine(zipPath, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); - var backendPath = Path.Combine(zipPath, input.NameSpace, "Service", input.TableName); - var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); - var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); - var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); - var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); - var frontendPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); - var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue"); - var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); - var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); - if (input.GenerateType.StartsWith("11")) - { - return new List() - { - indexPath, - formModalPath, - apiJsPath - }; - } - else if (input.GenerateType.StartsWith("12")) - { - return new List() - { - servicePath, - inputPath, - outputPath, - viewPath - }; - } - else - { - return new List() - { - servicePath, - inputPath, - outputPath, - viewPath, - indexPath, - formModalPath, - apiJsPath - }; - } - } +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +using System.IO.Compression; + +namespace Admin.NET.Core.Service; + +/// +/// 系统代码生成器服务 🧩 +/// +[ApiDescriptionSettings(Order = 270)] +public class SysCodeGenService : IDynamicApiController, ITransient +{ + private readonly ISqlSugarClient _db; + + private readonly SysCodeGenConfigService _codeGenConfigService; + private readonly IViewEngine _viewEngine; + private readonly CodeGenOptions _codeGenOptions; + + public SysCodeGenService(ISqlSugarClient db, + SysCodeGenConfigService codeGenConfigService, + IViewEngine viewEngine, + IOptions codeGenOptions) + { + _db = db; + _codeGenConfigService = codeGenConfigService; + _viewEngine = viewEngine; + _codeGenOptions = codeGenOptions.Value; + } + + /// + /// 获取代码生成分页列表 🔖 + /// + /// + /// + [DisplayName("获取代码生成分页列表")] + public async Task> Page(PageCodeGenInput input) + { + return await _db.Queryable() + .WhereIF(!string.IsNullOrWhiteSpace(input.TableName), u => u.TableName.Contains(input.TableName.Trim())) + .WhereIF(!string.IsNullOrWhiteSpace(input.BusName), u => u.BusName.Contains(input.BusName.Trim())) + .OrderBy(u => u.Id, OrderByType.Desc) + .ToPagedListAsync(input.Page, input.PageSize); + } + + /// + /// 增加代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Add"), HttpPost] + [DisplayName("增加代码生成")] + public async Task AddCodeGen(AddCodeGenInput input) + { + var isExist = await _db.Queryable().Where(u => u.TableName == input.TableName).AnyAsync(); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1400); + + var codeGen = input.Adapt(); + var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync(); + // 加入配置表中 + _codeGenConfigService.AddList(GetColumnList(input), newCodeGen); + } + + /// + /// 更新代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Update"), HttpPost] + [DisplayName("更新代码生成")] + public async Task UpdateCodeGen(UpdateCodeGenInput input) + { + var isExist = await _db.Queryable().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id); + if (isExist) + throw Oops.Oh(ErrorCodeEnum.D1400); + + await _db.Updateable(input.Adapt()).ExecuteCommandAsync(); + } + + /// + /// 删除代码生成 🔖 + /// + /// + /// + [ApiDescriptionSettings(Name = "Delete"), HttpPost] + [DisplayName("删除代码生成")] + public async Task DeleteCodeGen(List inputs) + { + if (inputs == null || inputs.Count < 1) return; + + var codeGenConfigTaskList = new List(); + inputs.ForEach(u => + { + _db.Deleteable().In(u.Id).ExecuteCommand(); + + // 删除配置表中 + codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id)); + }); + await Task.WhenAll(codeGenConfigTaskList); + } + + /// + /// 获取代码生成详情 🔖 + /// + /// + /// + [DisplayName("获取代码生成详情")] + public async Task GetDetail([FromQuery] QueryCodeGenInput input) + { + return await _db.Queryable().SingleAsync(u => u.Id == input.Id); + } + + /// + /// 获取数据库库集合 🔖 + /// + /// + [DisplayName("获取数据库库集合")] + public async Task> GetDatabaseList() + { + var dbConfigs = App.GetOptions().ConnectionConfigs; + return await Task.FromResult(dbConfigs.Adapt>()); + } + + /// + /// 获取数据库表(实体)集合 🔖 + /// + /// + [DisplayName("获取数据库表(实体)集合")] + public async Task> GetTableList(string configId = SqlSugarConst.MainConfigId) + { + var provider = _db.AsTenant().GetConnectionScope(configId); + var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用 + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId)); + + var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList(); + IEnumerable entityInfos = await GetEntityInfos(); + + var tableOutputList = new List(); + foreach (var item in entityInfos) + { + var table = dbTableInfos.FirstOrDefault(u => u.Name.ToLower() == (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()); + if (table == null) continue; + tableOutputList.Add(new TableOutput + { + ConfigId = configId, + EntityName = item.EntityName, + TableName = table.Name, + TableComment = item.TableDescription + }); + } + return tableOutputList; + } + + /// + /// 根据表名获取列集合 🔖 + /// + /// + [DisplayName("根据表名获取列集合")] + public List GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId) + { + // 切库---多库代码生成用 + var provider = _db.AsTenant().GetConnectionScope(configId); + + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId); + // 获取实体类型属性 + var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName); + if (entityType == null) return null; + var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; + // 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null) + return provider.DbMaintenance.GetColumnInfosByTableName(entityType.Name).Select(u => new ColumnOuput + { + ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, + ColumnKey = u.IsPrimarykey.ToString(), + DataType = u.DataType.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = u.ColumnDescription + }).ToList(); + } + + /// + /// 获取数据表列(实体属性)集合 + /// + /// + private List GetColumnList([FromQuery] AddCodeGenInput input) + { + var entityType = GetEntityInfos().GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName); + if (entityType == null) + return null; + var config = App.GetOptions().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId); + var dbTableName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(entityType.DbTableName) : entityType.DbTableName; + + // 切库---多库代码生成用 + var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId); + + var entityBasePropertyNames = _codeGenOptions.EntityBaseColumn[nameof(EntityTenant)]; + var columnInfos = provider.DbMaintenance.GetColumnInfosByTableName(dbTableName, false); + var result = columnInfos.Select(u => new ColumnOuput + { + // 转下划线后的列名需要再转回来(暂时不转) + //ColumnName = config.DbSettings.EnableUnderLine ? CodeGenUtil.CamelColumnName(u.DbColumnName, entityBasePropertyNames) : u.DbColumnName, + ColumnName = u.DbColumnName, + ColumnLength = u.Length, + IsPrimarykey = u.IsPrimarykey, + IsNullable = u.IsNullable, + ColumnKey = u.IsPrimarykey.ToString(), + NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + DataType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType), + ColumnComment = string.IsNullOrWhiteSpace(u.ColumnDescription) ? u.DbColumnName : u.ColumnDescription + }).ToList(); + + // 获取实体的属性信息,赋值给PropertyName属性(CodeFirst模式应以PropertyName为实际使用名称) + var entityProperties = entityType.Type.GetProperties(); + + for (int i = result.Count - 1; i >= 0; i--) + { + var columnOutput = result[i]; + // 先找自定义字段名的,如果找不到就再找自动生成字段名的(并且过滤掉没有SugarColumn的属性) + var propertyInfo = entityProperties.FirstOrDefault(u => (u.GetCustomAttribute()?.ColumnName ?? "").ToLower() == columnOutput.ColumnName.ToLower()) ?? + entityProperties.FirstOrDefault(u => u.GetCustomAttribute() != null && u.Name.ToLower() == (config.DbSettings.EnableUnderLine + ? CodeGenUtil.CamelColumnName(columnOutput.ColumnName, entityBasePropertyNames).ToLower() + : columnOutput.ColumnName.ToLower())); + if (propertyInfo != null) + { + columnOutput.PropertyName = propertyInfo.Name; + columnOutput.ColumnComment = propertyInfo.GetCustomAttribute().ColumnDescription; + } + else + { + result.RemoveAt(i); // 移除没有定义此属性的字段 + } + } + return result; + } + + /// + /// 获取库表信息 + /// + /// + private async Task> GetEntityInfos() + { + var entityInfos = new List(); + + var type = typeof(SugarTable); + var types = new List(); + if (_codeGenOptions.EntityAssemblyNames != null) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + var assemblyName = assembly.GetName().Name; + if (_codeGenOptions.EntityAssemblyNames.Contains(assemblyName) || _codeGenOptions.EntityAssemblyNames.Any(name => assemblyName.Contains(name))) + { + Assembly asm = Assembly.Load(assemblyName); + types.AddRange(asm.GetExportedTypes().ToList()); + } + } + } + bool IsMyAttribute(Attribute[] o) + { + foreach (Attribute a in o) + { + if (a.GetType() == type) + return true; + } + return false; + } + Type[] cosType = types.Where(o => + { + return IsMyAttribute(Attribute.GetCustomAttributes(o, true)); + } + ).ToArray(); + + foreach (var ct in cosType) + { + var sugarAttribute = ct.GetCustomAttributes(type, true)?.FirstOrDefault(); + + var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), true); + var description = ""; + if (des.Length > 0) + { + description = ((DescriptionAttribute)des[0]).Description; + } + entityInfos.Add(new EntityInfo() + { + EntityName = ct.Name, + DbTableName = sugarAttribute == null ? ct.Name : ((SugarTable)sugarAttribute).TableName, + TableDescription = sugarAttribute == null ? description : ((SugarTable)sugarAttribute).TableDescription, + Type = ct + }); + } + return await Task.FromResult(entityInfos); + } + + /// + /// 获取程序保存位置 🔖 + /// + /// + [DisplayName("获取程序保存位置")] + public List GetApplicationNamespaces() + { + return _codeGenOptions.BackendApplicationNamespaces; + } + + /// + /// 代码生成到本地 🔖 + /// + /// + [DisplayName("代码生成到本地")] + public async Task RunLocal(SysCodeGen input) + { + if (string.IsNullOrEmpty(input.GenerateType)) + input.GenerateType = "200"; + + // 先删除该表已生成的菜单列表 + List targetPathList; + var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); + if (input.GenerateType.StartsWith('1')) + { + targetPathList = GetZipPathList(input); + if (Directory.Exists(zipPath)) + Directory.Delete(zipPath, true); + } + else + targetPathList = GetTargetPathList(input); + + var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 + var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 + var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 + (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 + + var data = new CustomViewEngine(_db) + { + ConfigId = input.ConfigId, + AuthorName = input.AuthorName, + BusName = input.BusName, + NameSpace = input.NameSpace, + ClassName = input.TableName, + PagePath = input.PagePath, + ProjectLastName = input.NameSpace.Split('.').Last(), + QueryWhetherList = queryWhetherList, + TableField = tableFieldList, + IsJoinTable = joinTableList.Count > 0, + IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), + PrintType = input.PrintType, + PrintName = input.PrintName, + IsApiService = input.IsApiService + }; + // 模板目录 + var templatePathList = GetTemplatePathList(input); + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + + for (var i = 0; i < templatePathList.Count; i++) + { + var templateFilePath = Path.Combine(templatePath, templatePathList[i]); + if (!File.Exists(templateFilePath)) continue; + var tContent = File.ReadAllText(templateFilePath); + var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => + { + builder.AddAssemblyReferenceByName("System.Linq"); + builder.AddAssemblyReferenceByName("System.Collections"); + builder.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + var dirPath = new DirectoryInfo(targetPathList[i]).Parent.FullName; + if (!Directory.Exists(dirPath)) + Directory.CreateDirectory(dirPath); + File.WriteAllText(targetPathList[i], tResult, Encoding.UTF8); + } + if (input.GenerateMenu) + await AddMenu(input.TableName, input.BusName, input.MenuPid ?? 0, input.MenuIcon, input.PagePath, tableFieldList); + // 非ZIP压缩返回空 + if (!input.GenerateType.StartsWith('1')) + return null; + else + { + string downloadPath = zipPath + ".zip"; + // 判断是否存在同名称文件 + if (File.Exists(downloadPath)) + File.Delete(downloadPath); + ZipFile.CreateFromDirectory(zipPath, downloadPath); + return new { url = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}/CodeGen/{input.TableName}.zip" }; + } + } + + /// + /// 获取代码生成预览 🔖 + /// + /// + [DisplayName("获取代码生成预览")] + public async Task> Preview(SysCodeGen input) + { + var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig() { CodeGenId = input.Id }); // 字段集合 + var queryWhetherList = tableFieldList.Where(u => u.QueryWhether == YesNoEnum.Y.ToString()).ToList(); // 前端查询集合 + var joinTableList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList(); // 需要连表查询的字段 + (string joinTableNames, string lowerJoinTableNames) = GetJoinTableStr(joinTableList); // 获取连表的实体名和别名 + + var data = new CustomViewEngine(_db) + { + ConfigId = input.ConfigId, + AuthorName = input.AuthorName, + BusName = input.BusName, + NameSpace = input.NameSpace, + ClassName = input.TableName, + PagePath = input.PagePath, + ProjectLastName = input.NameSpace.Split('.').Last(), + QueryWhetherList = queryWhetherList, + TableField = tableFieldList, + IsJoinTable = joinTableList.Count > 0, + IsUpload = joinTableList.Where(u => u.EffectType == "Upload").Any(), + PrintType = input.PrintType, + PrintName = input.PrintName, + IsApiService = input.IsApiService + }; + + // 获取模板文件并替换 + var templatePathList = GetTemplatePathList(); + var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Template"); + + var result = new Dictionary(); + for (var i = 0; i < templatePathList.Count; i++) + { + var templateFilePath = Path.Combine(templatePath, templatePathList[i]); + if (!File.Exists(templateFilePath)) continue; + var tContent = File.ReadAllText(templateFilePath); + var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: builder => + { + builder.AddAssemblyReferenceByName("System.Linq"); + builder.AddAssemblyReferenceByName("System.Collections"); + builder.AddUsing("System.Collections.Generic"); + builder.AddUsing("System.Linq"); + }); + result.Add(templatePathList[i]?.TrimEnd(".vm"), tResult); + } + + return result; + } + + /// + /// 获取连表的实体名和别名 + /// + /// + /// + private static (string, string) GetJoinTableStr(List configs) + { + var uploads = configs.Where(u => u.EffectType == "Upload").ToList(); + var fks = configs.Where(u => u.EffectType == "fk").ToList(); + string str = ""; // + string lowerStr = ""; // (o, i, c) + foreach (var item in uploads) + { + lowerStr += "sysFile_FK_" + item.LowerPropertyName + ","; + str += "SysFile,"; + } + foreach (var item in fks) + { + lowerStr += item.LowerFkEntityName + "_FK_" + item.LowerFkColumnName + ","; + str += item.FkEntityName + ","; + } + return (str.TrimEnd(','), lowerStr.TrimEnd(',')); + } + + /// + /// 增加菜单 + /// + /// + /// + /// + /// + /// + /// + /// + private async Task AddMenu(string className, string busName, long pid, string menuIcon, string pagePath, List tableFieldList) + { + var pPath = string.Empty; + // 若 pid=0 为顶级则创建菜单目录 + if (pid == 0) + { + // 目录 + var menuType0 = new SysMenu + { + Pid = 0, + Title = busName + "管理", + Type = MenuTypeEnum.Dir, + Icon = "robot", + Path = "/" + className.ToLower(), + Component = "Layout", + }; + // 若先前存在则删除本级和下级 + var menuList0 = await _db.Queryable().Where(u => u.Title == menuType0.Title && u.Type == menuType0.Type).ToListAsync(); + if (menuList0.Count > 0) + { + var listIds = menuList0.Select(u => u.Id).ToList(); + var childlistIds = new List(); + foreach (var item in listIds) + { + var childlist = await _db.Queryable().ToChildListAsync(u => u.Pid, item); + childlistIds.AddRange(childlist.Select(u => u.Id).ToList()); + } + listIds.AddRange(childlistIds); + await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); + await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); + } + pid = (await _db.Insertable(menuType0).ExecuteReturnEntityAsync()).Id; + } + else + { + var pMenu = await _db.Queryable().FirstAsync(u => u.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505); + pPath = pMenu.Path; + } + + // 菜单 + var menuType = new SysMenu + { + Pid = pid, + Title = busName + "管理", + Name = className[..1].ToLower() + className[1..], + Type = MenuTypeEnum.Menu, + Icon = menuIcon, + Path = pPath + "/" + className.ToLower(), + Component = "/" + pagePath + "/" + className[..1].ToLower() + className[1..] + "/index", + }; + // 若先前存在则删除本级和下级 + var menuListCurrent = await _db.Queryable().Where(u => u.Title == menuType.Title && u.Type == menuType.Type).ToListAsync(); + if (menuListCurrent.Count > 0) + { + var listIds = menuListCurrent.Select(u => u.Id).ToList(); + var childListIds = new List(); + foreach (var item in listIds) + { + var childList = await _db.Queryable().ToChildListAsync(u => u.Pid, item); + childListIds.AddRange(childList.Select(u => u.Id).ToList()); + } + listIds.AddRange(childListIds); + await _db.Deleteable().Where(u => listIds.Contains(u.Id)).ExecuteCommandAsync(); + await _db.Deleteable().Where(u => listIds.Contains(u.MenuId)).ExecuteCommandAsync(); + } + + var menuPid = (await _db.Insertable(menuType).ExecuteReturnEntityAsync()).Id; + int menuOrder = 100; + // 按钮-page + var menuTypePage = new SysMenu + { + Pid = menuPid, + Title = "查询", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":page", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-detail + var menuTypeDetail = new SysMenu + { + Pid = menuPid, + Title = "详情", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":detail", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-add + var menuTypeAdd = new SysMenu + { + Pid = menuPid, + Title = "增加", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":add", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-delete + var menuTypeDelete = new SysMenu + { + Pid = menuPid, + Title = "删除", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":delete", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-update + var menuTypeUpdate = new SysMenu + { + Pid = menuPid, + Title = "编辑", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":update", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-print + var menuTypePrint = new SysMenu + { + Pid = menuPid, + Title = "打印", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":print", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-import + var menuTypeImport = new SysMenu + { + Pid = menuPid, + Title = "导入", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":import", + OrderNo = menuOrder + }; + menuOrder += 10; + + // 按钮-export + var menuTypeExport = new SysMenu + { + Pid = menuPid, + Title = "导出", + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":export", + OrderNo = menuOrder + }; + menuOrder += 10; + + var menuList = new List() { menuTypePage, menuTypeDetail, menuTypeAdd, menuTypeDelete, menuTypeUpdate, menuTypePrint, menuTypeImport, menuTypeExport }; + // 加入fk、Upload、ApiTreeSelect 等接口的权限 + // 在生成表格时,有些字段只是查询时显示,不需要填写(WhetherAddUpdate),所以这些字段没必要生成相应接口 + var fkTableList = tableFieldList.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y")).ToList(); + foreach (var @column in fkTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "外键" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + column.ColumnName + "Dropdown", + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + var treeSelectTableList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelect").ToList(); + foreach (var @column in treeSelectTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "树型" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":" + column.FkEntityName + "Tree", + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + var uploadTableList = tableFieldList.Where(u => u.EffectType == "Upload").ToList(); + foreach (var @column in uploadTableList) + { + var menuType1 = new SysMenu + { + Pid = menuPid, + Title = "上传" + @column.ColumnName, + Type = MenuTypeEnum.Btn, + Permission = className[..1].ToLower() + className[1..] + ":Upload" + column.ColumnName, + OrderNo = menuOrder + }; + menuOrder += 10; + menuList.Add(menuType1); + } + await _db.Insertable(menuList).ExecuteCommandAsync(); + } + + /// + /// 获取模板文件路径集合 + /// + /// + private static List GetTemplatePathList(SysCodeGen input) + { + if (input.GenerateType.Substring(1, 1).Contains('1')) + { + return new() { "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + } + else if (input.GenerateType.Substring(1, 1).Contains('2')) + { + return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm" }; + } + else + { + return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + } + } + + /// + /// 获取模板文件路径集合 + /// + /// + private static List GetTemplatePathList() => new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "manage.js.vm" }; + + /// + /// 设置生成文件路径 + /// + /// + /// + private List GetTargetPathList(SysCodeGen input) + { + //var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); + var backendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName, input.NameSpace, "Service", input.TableName); + var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); + var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); + var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); + var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); + var frontendPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); + var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue");// + var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); + var apiJsPath = Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.Parent.FullName, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); + + if (input.GenerateType.Substring(1, 1).Contains('1')) + { + // 生成到本项目(前端) + return new List() + { + indexPath, + formModalPath, + apiJsPath + }; + } + else if (input.GenerateType.Substring(1, 1).Contains('2')) + { + // 生成到本项目(后端) + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + }; + } + else + { + // 前后端同时生成到本项目 + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + indexPath, + formModalPath, + apiJsPath + }; + } + } + + /// + /// 设置生成文件路径 + /// + /// + /// + private List GetZipPathList(SysCodeGen input) + { + var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName); + + //var backendPath = Path.Combine(zipPath, _codeGenOptions.BackendApplicationNamespace, "Service", input.TableName); + var backendPath = Path.Combine(zipPath, input.NameSpace, "Service", input.TableName); + var servicePath = Path.Combine(backendPath, input.TableName + "Service.cs"); + var inputPath = Path.Combine(backendPath, "Dto", input.TableName + "Input.cs"); + var outputPath = Path.Combine(backendPath, "Dto", input.TableName + "Output.cs"); + var viewPath = Path.Combine(backendPath, "Dto", input.TableName + "Dto.cs"); + var frontendPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "views", input.PagePath); + var indexPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "index.vue"); + var formModalPath = Path.Combine(frontendPath, input.TableName[..1].ToLower() + input.TableName[1..], "component", "editDialog.vue"); + var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, input.TableName[..1].ToLower() + input.TableName[1..] + ".ts"); + if (input.GenerateType.StartsWith("11")) + { + return new List() + { + indexPath, + formModalPath, + apiJsPath + }; + } + else if (input.GenerateType.StartsWith("12")) + { + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath + }; + } + else + { + return new List() + { + servicePath, + inputPath, + outputPath, + viewPath, + indexPath, + formModalPath, + apiJsPath + }; + } + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs index 91813ae2..792fc444 100644 --- a/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Common/SysCommonService.cs @@ -121,4 +121,23 @@ public class SysCommonService : IDynamicApiController, ITransient } return apiList; } + + /// + /// 下载标记错误的临时 Excel(全局) + /// + /// + [DisplayName("下载标记错误的临时 Excel")] + public async Task DownloadErrorExcelTemp([FromQuery] string fileName = null) + { + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + var resultStream = App.GetRequiredService().Get(CacheConst.KeyExcelTemp + userId); + + if (resultStream == null) + throw Oops.Oh("错误标记文件已过期。"); + + return await Task.FromResult(new FileStreamResult(resultStream, "application/octet-stream") + { + FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? "错误标记_" + DateTime.Now.ToString("yyyyMMddhhmmss") : fileName)}.xlsx" + }); + } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs b/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs index 83813159..32df0fdf 100644 --- a/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs @@ -16,6 +16,11 @@ public class InfoSaveInput /// public string SysLogoBase64 { get; set; } + /// + /// 系统图标文件名 + /// + public string SysLogoFileName { get; set; } + /// /// 系统主标题 /// diff --git a/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs b/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs index c6640026..ecb4f26b 100644 --- a/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs @@ -31,7 +31,7 @@ public class SysConfigService : IDynamicApiController, ITransient public async Task> Page(PageConfigInput input) { return await _sysConfigRep.AsQueryable() - .Where(u => u.GroupCode != "WebConfig") // 不显示 WebConfig 分组 + .Where(u => u.GroupCode != ConfigConst.SysWebConfigGroup) // 不显示 WebConfig 分组 .WhereIF(!string.IsNullOrWhiteSpace(input.Name?.Trim()), u => u.Name.Contains(input.Name)) .WhereIF(!string.IsNullOrWhiteSpace(input.Code?.Trim()), u => u.Code.Contains(input.Code)) .WhereIF(!string.IsNullOrWhiteSpace(input.GroupCode?.Trim()), u => u.GroupCode.Equals(input.GroupCode)) @@ -183,7 +183,7 @@ public class SysConfigService : IDynamicApiController, ITransient public async Task> GetGroupList() { return await _sysConfigRep.AsQueryable() - .Where(u => u.GroupCode != "WebConfig") // 不显示 WebConfig 分组 + .Where(u => u.GroupCode != ConfigConst.SysWebConfigGroup) // 不显示 WebConfig 分组 .GroupBy(u => u.GroupCode) .Select(u => u.GroupCode).ToListAsync(); } @@ -237,14 +237,14 @@ public class SysConfigService : IDynamicApiController, ITransient [DisplayName("获取系统信息")] public async Task GetSysInfo() { - var sysLogo = await GetConfigValue("sys_web_logo"); - var sysTitle = await GetConfigValue("sys_web_title"); - var sysViceTitle = await GetConfigValue("sys_web_viceTitle"); - var sysViceDesc = await GetConfigValue("sys_web_viceDesc"); - var sysWatermark = await GetConfigValue("sys_web_watermark"); - var sysCopyright = await GetConfigValue("sys_web_copyright"); - var sysIcp = await GetConfigValue("sys_web_icp"); - var sysIcpUrl = await GetConfigValue("sys_web_icpUrl"); + var sysLogo = await GetConfigValue(ConfigConst.SysWebLogo); + var sysTitle = await GetConfigValue(ConfigConst.SysWebTitle); + var sysViceTitle = await GetConfigValue(ConfigConst.SysWebViceTitle); + var sysViceDesc = await GetConfigValue(ConfigConst.SysWebViceDesc); + var sysWatermark = await GetConfigValue(ConfigConst.SysWebWatermark); + var sysCopyright = await GetConfigValue(ConfigConst.SysWebCopyright); + var sysIcp = await GetConfigValue(ConfigConst.SysWebIcp); + var sysIcpUrl = await GetConfigValue(ConfigConst.SysWebIcpUrl); return new { @@ -263,6 +263,7 @@ public class SysConfigService : IDynamicApiController, ITransient /// 保存系统信息 🔖 /// /// + [UnitOfWork] [DisplayName("保存系统信息")] public async Task SaveSysInfo(InfoSaveInput input) { @@ -270,17 +271,18 @@ public class SysConfigService : IDynamicApiController, ITransient if (!string.IsNullOrEmpty(input.SysLogoBase64)) { // 旧图标文件相对路径 - var oldSysLogoRelativeFilePath = await GetConfigValue("sys_web_logo") ?? ""; + var oldSysLogoRelativeFilePath = await GetConfigValue(ConfigConst.SysWebLogo) ?? ""; var oldSysLogoAbsoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, oldSysLogoRelativeFilePath.TrimStart('/')); var groups = Regex.Match(input.SysLogoBase64, @"data:image/(?.+?);base64,(?.+)").Groups; - var type = groups["type"].Value; + //var type = groups["type"].Value; var base64Data = groups["data"].Value; var binData = Convert.FromBase64String(base64Data); - + // 根据文件名取扩展名 + var ext = string.IsNullOrWhiteSpace(input.SysLogoFileName) ? ".png" : Path.GetExtension(input.SysLogoFileName); // 本地图标保存路径 var path = "Upload"; - var absoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path, $"logo.{type}"); + var absoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path, $"logo{ext}"); // 删除已存在文件 if (File.Exists(oldSysLogoAbsoluteFilePath)) @@ -295,16 +297,16 @@ public class SysConfigService : IDynamicApiController, ITransient await File.WriteAllBytesAsync(absoluteFilePath, binData); // 保存图标配置 - var relativeUrl = $"/{path}/logo.{type}"; - await UpdateConfigValue("sys_web_logo", relativeUrl); + var relativeUrl = $"/{path}/logo{ext}"; + await UpdateConfigValue(ConfigConst.SysWebLogo, relativeUrl); } - await UpdateConfigValue("sys_web_title", input.SysTitle); - await UpdateConfigValue("sys_web_viceTitle", input.SysViceTitle); - await UpdateConfigValue("sys_web_viceDesc", input.SysViceDesc); - await UpdateConfigValue("sys_web_watermark", input.SysWatermark); - await UpdateConfigValue("sys_web_copyright", input.SysCopyright); - await UpdateConfigValue("sys_web_icp", input.SysIcp); - await UpdateConfigValue("sys_web_icpUrl", input.SysIcpUrl); + await UpdateConfigValue(ConfigConst.SysWebTitle, input.SysTitle); + await UpdateConfigValue(ConfigConst.SysWebViceTitle, input.SysViceTitle); + await UpdateConfigValue(ConfigConst.SysWebViceDesc, input.SysViceDesc); + await UpdateConfigValue(ConfigConst.SysWebWatermark, input.SysWatermark); + await UpdateConfigValue(ConfigConst.SysWebCopyright, input.SysCopyright); + await UpdateConfigValue(ConfigConst.SysWebIcp, input.SysIcp); + await UpdateConfigValue(ConfigConst.SysWebIcpUrl, input.SysIcpUrl); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs index eccdff36..b9a1b183 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataInput.cs @@ -11,6 +11,7 @@ public class DictDataInput : BaseIdInput /// /// 状态 /// + [Dict("StatusEnum")] public StatusEnum Status { get; set; } } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs index 7884ada2..fc6327e7 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictTypeInput.cs @@ -11,6 +11,7 @@ public class DictTypeInput : BaseIdInput /// /// 状态 /// + [Dict("StatusEnum")] public StatusEnum Status { get; set; } } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs index dd99cf59..96015470 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs @@ -1,4 +1,4 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // @@ -13,8 +13,8 @@ namespace Admin.NET.Core.Service; [AllowAnonymous] public class SysDictDataService : IDynamicApiController, ITransient { - private readonly SysCacheService _sysCacheService; private readonly SqlSugarRepository _sysDictDataRep; + private readonly SysCacheService _sysCacheService; public SysDictDataService(SqlSugarRepository sysDictDataRep, SysCacheService sysCacheService) @@ -60,8 +60,10 @@ public class SysDictDataService : IDynamicApiController, ITransient public async Task AddDictData(AddDictDataInput input) { var isExist = await _sysDictDataRep.IsAnyAsync(u => u.Code == input.Code && u.DictTypeId == input.DictTypeId); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3003); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3003); + + var dictTypeCode = await _sysDictDataRep.AsQueryable().Where(u => u.DictTypeId == input.DictTypeId).Select(u => u.DictType.Code).FirstAsync(); + _sysCacheService.Remove($"{CacheConst.KeyDict}{dictTypeCode}"); await _sysDictDataRep.InsertAsync(input.Adapt()); } @@ -98,9 +100,7 @@ public class SysDictDataService : IDynamicApiController, ITransient [DisplayName("删除字典值")] public async Task DeleteDictData(DeleteDictDataInput input) { - var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); - if (dictData == null) - throw Oops.Oh(ErrorCodeEnum.D3004); + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3004); var dictTypeCode = await _sysDictDataRep.AsQueryable().Where(u => u.DictTypeId == dictData.Id).Select(u => u.DictType.Code).FirstAsync(); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictTypeCode}"); @@ -128,18 +128,13 @@ public class SysDictDataService : IDynamicApiController, ITransient [DisplayName("修改字典值状态")] public async Task SetStatus(DictDataInput input) { - var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id); - if (dictData == null) - throw Oops.Oh(ErrorCodeEnum.D3004); - - if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) - throw Oops.Oh(ErrorCodeEnum.D3005); + var dictData = await _sysDictDataRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3004); var dictTypeCode = await _sysDictDataRep.AsQueryable().Where(u => u.DictTypeId == dictData.Id).Select(u => u.DictType.Code).FirstAsync(); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictTypeCode}"); dictData.Status = input.Status; - await _sysDictDataRep.UpdateAsync(dictData); + await _sysDictDataRep.AsUpdateable(dictData).UpdateColumns(u => new { u.Status }, true).ExecuteCommandAsync(); } /// @@ -156,13 +151,9 @@ public class SysDictDataService : IDynamicApiController, ITransient if (dictDataList == null) { dictDataList = await _sysDictDataRep.AsQueryable() - .Where(u => u.DictTypeId == dictTypeId) - .OrderBy(u => new { u.OrderNo, u.Code }) - .ToListAsync(); - + .Where(u => u.DictTypeId == dictTypeId).OrderBy(u => new { u.OrderNo, u.Code }).ToListAsync(); _sysCacheService.Set($"{CacheConst.KeyDict}{dictType.Code}", dictDataList); } - return dictDataList; } diff --git a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs index b205336c..da75928a 100644 --- a/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs @@ -60,10 +60,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("获取字典类型-值列表")] public async Task> GetDataList([FromQuery] GetDataDictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); - + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code) ?? throw Oops.Oh(ErrorCodeEnum.D3000); return await _sysDictDataService.GetDictDataListByDictTypeId(dictType.Id); } @@ -77,8 +74,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient public async Task AddDictType(AddDictTypeInput input) { var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3001); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001); await _sysDictTypeRep.InsertAsync(input.Adapt()); } @@ -94,12 +90,10 @@ public class SysDictTypeService : IDynamicApiController, ITransient public async Task UpdateDictType(UpdateDictTypeInput input) { var isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Id == input.Id); - if (!isExist) - throw Oops.Oh(ErrorCodeEnum.D3000); + if (!isExist) throw Oops.Oh(ErrorCodeEnum.D3000); isExist = await _sysDictTypeRep.IsAnyAsync(u => u.Code == input.Code && u.Id != input.Id); - if (isExist) - throw Oops.Oh(ErrorCodeEnum.D3001); + if (isExist) throw Oops.Oh(ErrorCodeEnum.D3001); _sysCacheService.Remove($"{CacheConst.KeyDict}{input.Code}"); await _sysDictTypeRep.UpdateAsync(input.Adapt()); @@ -115,9 +109,7 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("删除字典类型")] public async Task DeleteDictType(DeleteDictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3000); // 删除字典值 await _sysDictTypeRep.DeleteAsync(dictType); @@ -144,17 +136,12 @@ public class SysDictTypeService : IDynamicApiController, ITransient [DisplayName("修改字典类型状态")] public async Task SetStatus(DictTypeInput input) { - var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id); - if (dictType == null) - throw Oops.Oh(ErrorCodeEnum.D3000); - - if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) - throw Oops.Oh(ErrorCodeEnum.D3005); + var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D3000); _sysCacheService.Remove($"{CacheConst.KeyDict}{dictType.Code}"); dictType.Status = input.Status; - await _sysDictTypeRep.UpdateAsync(dictType); + await _sysDictTypeRep.AsUpdateable(dictType).UpdateColumns(u => new { u.Status }, true).ExecuteCommandAsync(); } /// diff --git a/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs index e8e204bf..a15b1138 100644 --- a/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Enum/SysEnumService.cs @@ -49,7 +49,7 @@ public class SysEnumService : IDynamicApiController, ITransient { string description = type.Name; var attrs = type.GetCustomAttributes(typeof(DescriptionAttribute), false); - if (attrs.Any()) + if (attrs.Length != 0) { var att = ((DescriptionAttribute[])attrs)[0]; description = att.Description; diff --git a/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs index deadcd83..03679de3 100644 --- a/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs +++ b/Admin.NET/Admin.NET.Core/Service/Job/DbJobPersistence.cs @@ -42,14 +42,14 @@ public class DbJobPersistence : IJobPersistence var jobBuilder = schedulerBuilder.GetJobBuilder(); // 加载数据库数据 - var dbDetail = await db.Queryable().FirstAsync(u => u.JobId == jobBuilder.JobId); + var dbDetail = await db.Queryable().FirstAsync(u => u.JobId == jobBuilder.JobId, stoppingToken); if (dbDetail == null) continue; // 同步数据库数据 jobBuilder.LoadFrom(dbDetail); // 获取作业的所有数据库的触发器 - var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(); + var dbTriggers = await db.Queryable().Where(u => u.JobId == jobBuilder.JobId).ToListAsync(stoppingToken); // 遍历所有作业触发器 foreach (var (_, triggerBuilder) in schedulerBuilder.GetEnumerable()) { @@ -73,24 +73,16 @@ public class DbJobPersistence : IJobPersistence } // 获取数据库所有通过脚本创建的作业 - var allDbScriptJobs = await db.Queryable().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(); + var allDbScriptJobs = await db.Queryable().Where(u => u.CreateType != JobCreateTypeEnum.BuiltIn).ToListAsync(stoppingToken); foreach (var dbDetail in allDbScriptJobs) { // 动态创建作业 - Type jobType; - switch (dbDetail.CreateType) + Type jobType = dbDetail.CreateType switch { - case JobCreateTypeEnum.Script: - jobType = dynamicJobCompiler.BuildJob(dbDetail.ScriptCode); - break; - - case JobCreateTypeEnum.Http: - jobType = typeof(HttpJob); - break; - - default: - throw new NotSupportedException(); - } + JobCreateTypeEnum.Script => dynamicJobCompiler.BuildJob(dbDetail.ScriptCode), + JobCreateTypeEnum.Http => typeof(HttpJob), + _ => throw new NotSupportedException(), + }; // 动态构建的 jobType 的程序集名称为随机名称,需重新设置 dbDetail.AssemblyName = jobType.Assembly.FullName!.Split(',')[0]; @@ -131,25 +123,23 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnChangedAsync(PersistenceContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + + var jobDetail = context.JobDetail.Adapt(); + switch (context.Behavior) { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + case PersistenceBehavior.Appended: + await db.Insertable(jobDetail).ExecuteCommandAsync(); + break; - var jobDetail = context.JobDetail.Adapt(); - switch (context.Behavior) - { - case PersistenceBehavior.Appended: - await db.Insertable(jobDetail).ExecuteCommandAsync(); - break; + case PersistenceBehavior.Updated: + await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync(); + break; - case PersistenceBehavior.Updated: - await db.Updateable(jobDetail).WhereColumns(u => new { u.JobId }).IgnoreColumns(u => new { u.Id, u.CreateType, u.ScriptCode }).ExecuteCommandAsync(); - break; - - case PersistenceBehavior.Removed: - await db.Deleteable().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync(); - break; - } + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.JobId == jobDetail.JobId).ExecuteCommandAsync(); + break; } } @@ -160,25 +150,23 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnTriggerChangedAsync(PersistenceTriggerContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + + var jobTrigger = context.Trigger.Adapt(); + switch (context.Behavior) { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + case PersistenceBehavior.Appended: + await db.Insertable(jobTrigger).ExecuteCommandAsync(); + break; - var jobTrigger = context.Trigger.Adapt(); - switch (context.Behavior) - { - case PersistenceBehavior.Appended: - await db.Insertable(jobTrigger).ExecuteCommandAsync(); - break; + case PersistenceBehavior.Updated: + await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync(); + break; - case PersistenceBehavior.Updated: - await db.Updateable(jobTrigger).WhereColumns(u => new { u.TriggerId, u.JobId }).IgnoreColumns(u => new { u.Id }).ExecuteCommandAsync(); - break; - - case PersistenceBehavior.Removed: - await db.Deleteable().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync(); - break; - } + case PersistenceBehavior.Removed: + await db.Deleteable().Where(u => u.TriggerId == jobTrigger.TriggerId && u.JobId == jobTrigger.JobId).ExecuteCommandAsync(); + break; } } @@ -189,12 +177,10 @@ public class DbJobPersistence : IJobPersistence /// public async Task OnExecutionRecordAsync(PersistenceExecutionRecordContext context) { - using (var scope = _serviceScopeFactory.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService().CopyNew(); + using var scope = _serviceScopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService().CopyNew(); - var jobTriggerRecord = context.Timeline.Adapt(); - await db.Insertable(jobTriggerRecord).ExecuteCommandAsync(); - } + var jobTriggerRecord = context.Timeline.Adapt(); + await db.Insertable(jobTriggerRecord).ExecuteCommandAsync(); } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs index fca54e02..139b2ec6 100644 --- a/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs +++ b/Admin.NET/Admin.NET.Core/Service/Job/JobClusterServer.cs @@ -1,4 +1,4 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 // // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 // diff --git a/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs b/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs index eaffcb17..5dcb02ed 100644 --- a/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs +++ b/Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs @@ -33,7 +33,7 @@ public class JobMonitor : IJobMonitor if (await _sysConfigService.GetConfigValue(CommonConst.SysErrorMail) && context.Exception != null) { var errorInfo = $"【{context.Trigger.Description}】定时任务错误:{context.Exception}"; - await _eventPublisher.PublishAsync(CommonConst.SendErrorMail, errorInfo); + await _eventPublisher.PublishAsync(CommonConst.SendErrorMail, errorInfo, stoppingToken); } } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs index 4349ef44..dcf3849e 100644 --- a/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs @@ -59,7 +59,7 @@ public class SysMenuService : IDynamicApiController, ITransient /// /// 删除登录菜单树里面的按钮 /// - private void DeleteBtnFromMenuTree(List menuList) + private static void DeleteBtnFromMenuTree(List menuList) { if (menuList == null) return; for (var i = menuList.Count - 1; i >= 0; i--) diff --git a/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs b/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs index e136ab2e..8bcdd313 100644 --- a/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs @@ -16,10 +16,12 @@ namespace Admin.NET.Core.Service; public class SysEmailService : IDynamicApiController, ITransient { private readonly EmailOptions _emailOptions; + private readonly SysConfigService _sysConfigService; - public SysEmailService(IOptions emailOptions) + public SysEmailService(IOptions emailOptions, SysConfigService sysConfigService) { _emailOptions = emailOptions.Value; + _sysConfigService = sysConfigService; } /// @@ -29,8 +31,10 @@ public class SysEmailService : IDynamicApiController, ITransient /// /// [DisplayName("发送邮件")] - public async Task SendEmail([Required] string content, string title = "Admin.NET 系统邮件") + public async Task SendEmail([Required] string content, string title = "") { + var webTitle = await _sysConfigService.GetConfigValue(ConfigConst.SysWebTitle); + title = string.IsNullOrWhiteSpace(title) ? $"{webTitle} 系统邮件" : title; var message = new MimeMessage(); message.From.Add(new MailboxAddress(_emailOptions.DefaultFromEmail, _emailOptions.DefaultFromEmail)); message.To.Add(new MailboxAddress(_emailOptions.DefaultToEmail, _emailOptions.DefaultToEmail)); diff --git a/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs b/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs deleted file mode 100644 index b210421e..00000000 --- a/Admin.NET/Admin.NET.Core/Service/OAuth/HttpContextExtension.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 -// -// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 -// -// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! - -using Microsoft.AspNetCore.Authentication; - -namespace Admin.NET.Core.Service; - -public static class HttpContextExtension -{ - public static async Task GetExternalProvidersAsync(this HttpContext context) - { - ArgumentNullException.ThrowIfNull(context); - - var schemes = context.RequestServices.GetRequiredService(); - - return (from scheme in await schemes.GetAllSchemesAsync() - where !string.IsNullOrEmpty(scheme.DisplayName) - select scheme).ToArray(); - } - - public static async Task IsProviderSupportedAsync(this HttpContext context, string provider) - { - ArgumentNullException.ThrowIfNull(context); - - return (from scheme in await context.GetExternalProvidersAsync() - where string.Equals(scheme.Name, provider, StringComparison.OrdinalIgnoreCase) - select scheme).Any(); - } -} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs index a3d57932..ee899629 100644 --- a/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/Dto/OpenAccessInput.cs @@ -9,7 +9,7 @@ namespace Admin.NET.Core.Service; /// /// 开放接口身份输入参数 /// -public class OpenAccessInput : BasePageInput +public class PageOpenAccessInput : BasePageInput { /// /// 身份标识 diff --git a/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs index b8fdf2c9..d5d92b02 100644 --- a/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs +++ b/Admin.NET/Admin.NET.Core/Service/OpenAccess/SysOpenAccessService.cs @@ -54,7 +54,7 @@ public class SysOpenAccessService : IDynamicApiController, ITransient /// /// [DisplayName("获取开放接口身份分页列表")] - public async Task> Page(OpenAccessInput input) + public async Task> Page(PageOpenAccessInput input) { return await _sysOpenAccessRep.AsQueryable() .LeftJoin((u, a) => u.BindUserId == a.Id) diff --git a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs index bf0ca92d..332a82be 100644 --- a/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Region/SysRegionService.cs @@ -4,6 +4,9 @@ // // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! +using AngleSharp; +using AngleSharp.Html.Dom; + namespace Admin.NET.Core.Service; /// @@ -13,13 +16,15 @@ namespace Admin.NET.Core.Service; public class SysRegionService : IDynamicApiController, ITransient { private readonly SqlSugarRepository _sysRegionRep; + private readonly SysConfigService _sysConfigService; - //// Url地址-国家统计局行政区域2023年 - //private readonly string _url = "http://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/2023/index.html"; + // Url地址-国家统计局行政区域2023年 + private readonly string _url = "http://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/2023/index.html"; - public SysRegionService(SqlSugarRepository sysRegionRep) + public SysRegionService(SqlSugarRepository sysRegionRep, SysConfigService sysConfigService) { _sysRegionRep = sysRegionRep; + _sysConfigService = sysConfigService; } /// @@ -59,7 +64,7 @@ public class SysRegionService : IDynamicApiController, ITransient { input.Code = input.Code.Trim(); if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) - throw Oops.Oh("行政区代码只能为6、9或12位"); + throw Oops.Oh(ErrorCodeEnum.R2003); if (input.Pid != 0) { @@ -90,7 +95,7 @@ public class SysRegionService : IDynamicApiController, ITransient { input.Code = input.Code.Trim(); if (input.Code.Length != 12 && input.Code.Length != 9 && input.Code.Length != 6) - throw Oops.Oh("行政区代码只能为6、9或12位"); + throw Oops.Oh(ErrorCodeEnum.R2003); if (input.Pid != input.Pid && input.Pid != 0) { @@ -103,7 +108,7 @@ public class SysRegionService : IDynamicApiController, ITransient var regionTreeList = await _sysRegionRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true); var childIdList = regionTreeList.Select(u => u.Id).ToList(); if (childIdList.Contains(input.Pid)) - throw Oops.Oh("父节点不能为自己的子节点"); + throw Oops.Oh(ErrorCodeEnum.R2004); } if (input.Id == input.Pid) @@ -144,93 +149,117 @@ public class SysRegionService : IDynamicApiController, ITransient [DisplayName("同步行政区域")] public async Task Sync() { + var syncLevel = await _sysConfigService.GetConfigValue(CommonConst.SysRegionSyncLevel); + if (syncLevel < 1 || syncLevel > 5) + syncLevel = 3;//默认区县级 + var context = BrowsingContext.New(AngleSharp.Configuration.Default.WithDefaultLoader()); + var dom = await context.OpenAsync(_url); + + // 省级 + var itemList = dom.QuerySelectorAll("table.provincetable tr.provincetr td a"); + if (itemList.Length == 0) + throw Oops.Oh(ErrorCodeEnum.R2005); + await _sysRegionRep.DeleteAsync(u => u.Id > 0); - //var context = BrowsingContext.New(AngleSharp.Configuration.Default.WithDefaultLoader()); - //var dom = await context.OpenAsync(_url); + foreach (IHtmlAnchorElement item in itemList) + { + var list = new List(); - //// 省级 - //var itemList = dom.QuerySelectorAll("table.provincetable tr.provincetr td a"); - //foreach (IHtmlAnchorElement item in itemList) - //{ - // var region = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion - // { - // Pid = 0, - // Name = item.TextContent, - // Remark = item.Href, - // Level = 1, - // }); + var region = new SysRegion + { + Id = YitIdHelper.NextId(), + Pid = 0, + Name = item.TextContent, + Remark = item.Href, + Level = 1, + }; + list.Add(region); - // // 市级 - // if (string.IsNullOrEmpty(item.Href)) - // continue; - // var dom1 = await context.OpenAsync(item.Href); - // var itemList1 = dom1.QuerySelectorAll("table.citytable tr.citytr td a"); - // for (var i1 = 0; i1 < itemList1.Length; i1 += 2) - // { - // var item1 = (IHtmlAnchorElement)itemList1[i1 + 1]; - // var region1 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion - // { - // Pid = region.Id, - // Name = item1.TextContent, - // Code = itemList1[i1].TextContent, - // Remark = item1.Href, - // Level = 2, - // }); + // 市级 + if (!string.IsNullOrEmpty(item.Href) && syncLevel > 1) + { + var dom1 = await context.OpenAsync(item.Href); + var itemList1 = dom1.QuerySelectorAll("table.citytable tr.citytr td a"); + for (var i1 = 0; i1 < itemList1.Length; i1 += 2) + { + var item1 = (IHtmlAnchorElement)itemList1[i1 + 1]; + var region1 = new SysRegion + { + Id = YitIdHelper.NextId(), + Pid = region.Id, + Name = item1.TextContent, + Code = itemList1[i1].TextContent, + Remark = item1.Href, + Level = 2, + }; + list.Add(region1); - // // 区县级 - // if (string.IsNullOrEmpty(item1.Href)) - // continue; - // var dom2 = await context.OpenAsync(item1.Href); - // var itemList2 = dom2.QuerySelectorAll("table.countytable tr.countytr td a"); - // for (var i2 = 0; i2 < itemList2.Length; i2 += 2) - // { - // var item2 = (IHtmlAnchorElement)itemList2[i2 + 1]; - // var region2 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion - // { - // Pid = region1.Id, - // Name = item2.TextContent, - // Code = itemList2[i2].TextContent, - // Remark = item2.Href, - // Level = 3, - // }); + // 区县级 + if (!string.IsNullOrEmpty(item1.Href) && syncLevel > 2) + { + var dom2 = await context.OpenAsync(item1.Href); + var itemList2 = dom2.QuerySelectorAll("table.countytable tr.countytr td a"); + for (var i2 = 0; i2 < itemList2.Length; i2 += 2) + { + var item2 = (IHtmlAnchorElement)itemList2[i2 + 1]; + var region2 = new SysRegion + { + Id = YitIdHelper.NextId(), + Pid = region1.Id, + Name = item2.TextContent, + Code = itemList2[i2].TextContent, + Remark = item2.Href, + Level = 3, + }; + list.Add(region2); - // // 街道级 - // if (string.IsNullOrEmpty(item2.Href)) - // continue; - // var dom3 = await context.OpenAsync(item2.Href); - // var itemList3 = dom3.QuerySelectorAll("table.towntable tr.towntr td a"); - // for (var i3 = 0; i3 < itemList3.Length; i3 += 2) - // { - // var item3 = (IHtmlAnchorElement)itemList3[i3 + 1]; - // var region3 = await _sysRegionRep.InsertReturnEntityAsync(new SysRegion - // { - // Pid = region2.Id, - // Name = item3.TextContent, - // Code = itemList3[i3].TextContent, - // Remark = item3.Href, - // Level = 4, - // }); + // 街道级 + if (!string.IsNullOrEmpty(item2.Href) && syncLevel > 3) + { + var dom3 = await context.OpenAsync(item2.Href); + var itemList3 = dom3.QuerySelectorAll("table.towntable tr.towntr td a"); + for (var i3 = 0; i3 < itemList3.Length; i3 += 2) + { + var item3 = (IHtmlAnchorElement)itemList3[i3 + 1]; + var region3 = new SysRegion + { + Id = YitIdHelper.NextId(), + Pid = region2.Id, + Name = item3.TextContent, + Code = itemList3[i3].TextContent, + Remark = item3.Href, + Level = 4, + }; + list.Add(region3); - // // 村级 - // if (string.IsNullOrEmpty(item3.Href)) - // continue; - // var dom4 = await context.OpenAsync(item3.Href); - // var itemList4 = dom4.QuerySelectorAll("table.villagetable tr.villagetr td"); - // for (var i4 = 0; i4 < itemList4.Length; i4 += 3) - // { - // await _sysRegionRep.InsertAsync(new SysRegion - // { - // Pid = region3.Id, - // Name = itemList4[i4 + 2].TextContent, - // Code = itemList4[i4].TextContent, - // CityCode = itemList4[i4 + 1].TextContent, - // Level = 5, - // }); - // } - // } - // } - // } - //} + // 村级 + if (!string.IsNullOrEmpty(item3.Href) && syncLevel > 4) + { + var dom4 = await context.OpenAsync(item3.Href); + var itemList4 = dom4.QuerySelectorAll("table.villagetable tr.villagetr td"); + for (var i4 = 0; i4 < itemList4.Length; i4 += 3) + { + list.Add(new SysRegion + { + Id = YitIdHelper.NextId(), + Pid = region3.Id, + Name = itemList4[i4 + 2].TextContent, + Code = itemList4[i4].TextContent, + CityCode = itemList4[i4 + 1].TextContent, + Level = 5, + }); + } + } + } + } + } + } + } + } + + //按省份同步快速写入提升同步效率,全部一次性写入容易出现从统计局获取数据失败 + _sysRegionRep.Context.Fastest().BulkCopy(list); + } } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs index 23d52f70..9b85f273 100644 --- a/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Role/SysRoleApiService.cs @@ -27,7 +27,7 @@ public class SysRoleApiService : ITransient { await _sysRoleApiRep.DeleteAsync(u => u.RoleId == input.Id); - var roleApis = input.ApiList.Select(u => new SysRoleApi + var roleApis = input.ApiList.Where(u => !string.IsNullOrWhiteSpace(u)).Select(u => new SysRoleApi { RoleId = input.Id, Route = u diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs index 0ec4cede..ed73300e 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WxOpenInput.cs @@ -115,4 +115,30 @@ public class AddSubscribeMessageTemplateInput /// [Required(ErrorMessage = "服务场景描述不能为空")] public string SceneDescription { get; set; } +} + +/// +/// 验证签名 +/// +public class VerifySignatureInput +{ + /// + /// 签名 + /// + public string signature { get; set; } + + /// + /// 时间戳 + /// + public string timestamp { get; set; } + + /// + /// 随机数 + /// + public string nonce { get; set; } + + /// + /// 随机字符串 + /// + public string echostr { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs index 9dfd2e08..29d6dc53 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWxOpenService.cs @@ -114,6 +114,24 @@ public class SysWxOpenService : IDynamicApiController, ITransient }; } + /// + /// 验证签名 🔖 + /// + [AllowAnonymous] + [NonUnify] + [ApiDescriptionSettings(Name = "VerifySignature"), HttpGet] + [DisplayName("验证签名")] + public string VerifySignature([FromQuery] VerifySignatureInput input) + { + bool valid = _wechatApiClient.VerifyEventSignatureForEcho(input.timestamp, input.nonce, input.signature); + if (!valid) + { + return "fail"; + } + + return input.echostr; + } + /// /// 获取订阅消息模板列表 🔖 /// diff --git a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs index 05a9b022..db75b699 100644 --- a/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs +++ b/Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarFilter.cs @@ -68,8 +68,10 @@ public static class SqlSugarFilter if ((tAtt != null && db.CurrentConnectionConfig.ConfigId.ToString() != tAtt.configId.ToString())) continue; - var lambda = DynamicExpressionParser.ParseLambda(new[] { - Expression.Parameter(entityType, "u") }, typeof(bool), $"@0.Contains(u.{nameof(EntityBaseData.CreateOrgId)}??{default(long)})", orgIds); + //var lambda = DynamicExpressionParser.ParseLambda(new[] { + // Expression.Parameter(entityType, "u") }, typeof(bool), $"@0.Contains(u.{nameof(EntityBaseData.CreateOrgId)}??{default(long)})", orgIds); + var lambda = entityType.GetConditionExpression(orgIds); + db.QueryFilter.AddTableFilter(entityType, lambda); orgFilter.TryAdd(entityType, lambda); } @@ -114,8 +116,10 @@ public static class SqlSugarFilter if ((tAtt != null && db.CurrentConnectionConfig.ConfigId.ToString() != tAtt.configId.ToString())) continue; - var lambda = DynamicExpressionParser.ParseLambda(new[] { - Expression.Parameter(entityType, "u") }, typeof(bool), $"u.{nameof(EntityBaseData.CreateUserId)}=@0", userId); + //var lambda = DynamicExpressionParser.ParseLambda(new[] { + // Expression.Parameter(entityType, "u") }, typeof(bool), $"u.{nameof(EntityBaseData.CreateUserId)}=@0", userId); + var lambda = entityType.GetConditionExpression(new List { long.Parse(userId) }); + db.QueryFilter.AddTableFilter(entityType, lambda); dataScopeFilter.TryAdd(entityType, lambda); } diff --git a/Admin.NET/Admin.NET.Core/Util/BaseFilter.cs b/Admin.NET/Admin.NET.Core/Util/BaseFilter.cs new file mode 100644 index 00000000..5ab7d4ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Util/BaseFilter.cs @@ -0,0 +1,75 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 模糊查询条件 +/// +public class Search +{ + /// + /// 字段名称集合 + /// + public List Fields { get; set; } + + /// + /// 关键字 + /// + public string? Keyword { get; set; } +} + +/// +/// 筛选过滤条件 +/// +public class Filter +{ + /// + /// 过滤条件 + /// + public FilterLogicEnum? Logic { get; set; } + + /// + /// 筛选过滤条件子项 + /// + public IEnumerable? Filters { get; set; } + + /// + /// 字段名称 + /// + public string? Field { get; set; } + + /// + /// 逻辑运算符 + /// + public FilterOperatorEnum? Operator { get; set; } + + /// + /// 字段值 + /// + public object? Value { get; set; } +} + +/// +/// 过滤条件基类 +/// +public abstract class BaseFilter +{ + /// + /// 模糊查询条件 + /// + public Search? Search { get; set; } + + /// + /// 模糊查询关键字 + /// + public string? Keyword { get; set; } + + /// + /// 筛选过滤条件 + /// + public Filter? Filter { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs b/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs index 88203f6a..d8bea2f0 100644 --- a/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs +++ b/Admin.NET/Admin.NET.Core/Util/BasePageInput.cs @@ -9,7 +9,7 @@ namespace Admin.NET.Core; /// /// 全局分页查询输入参数 /// -public class BasePageInput +public class BasePageInput : BaseFilter { /// /// 当前页码 diff --git a/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs index 39587679..ab0910df 100644 --- a/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs +++ b/Admin.NET/Admin.NET.Core/Util/CommonUtil.cs @@ -49,12 +49,12 @@ public static class CommonUtil // 代理模式:获取真正的本机地址 // X-Original-Host=原始请求 // X-Forwarded-Server=从哪里转发过来 - if (App.HttpContext.Request.Headers.ContainsKey("Origin")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com - result = $"{App.HttpContext.Request.Headers["Origin"]}"; - else if (App.HttpContext.Request.Headers.ContainsKey("X-Original")) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com - result = $"{App.HttpContext.Request.Headers["X-Original"]}"; - else if (App.HttpContext.Request.Headers.ContainsKey("X-Original-Host")) - result = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Headers["X-Original-Host"]}"; + if (App.HttpContext.Request.Headers.TryGetValue("Origin", out Microsoft.Extensions.Primitives.StringValues value1)) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{value1}"; + else if (App.HttpContext.Request.Headers.TryGetValue("X-Original", out Microsoft.Extensions.Primitives.StringValues value2)) // 配置成完整的路径如(结尾不要带"/"),比如 https://www.abc.com + result = $"{value2}"; + else if (App.HttpContext.Request.Headers.TryGetValue("X-Original-Host", out Microsoft.Extensions.Primitives.StringValues value3)) + result = $"{App.HttpContext.Request.Scheme}://{value3}"; return result; } @@ -107,7 +107,7 @@ public static class CommonUtil /// public static async Task ExportExcelTemplate(string fileName = null) where T : class, new() { - IImporter importer = new ExcelImporter(); + var importer = new ExcelImporter(); var res = await importer.GenerateTemplateBytes(); return new FileContentResult(res, "application/octet-stream") { FileDownloadName = $"{(string.IsNullOrEmpty(fileName) ? typeof(T).Name : fileName)}.xlsx" }; @@ -152,9 +152,9 @@ public static class CommonUtil } var map = dict.Value.Item1; - if (map != null && map.ContainsKey(sourceVal)) + if (map != null && map.TryGetValue(sourceVal, out string value)) { - var newVal = map[sourceVal]; + var newVal = value; targeProp.SetValue(newData, newVal); } else @@ -190,7 +190,7 @@ public static class CommonUtil /// public static async Task> ImportExcelData([Required] IFormFile file) where T : class, new() { - IImporter importer = new ExcelImporter(); + var importer = new ExcelImporter(); var res = await importer.Import(file.OpenReadStream()); var message = string.Empty; if (res.HasError) @@ -203,7 +203,46 @@ public static class CommonUtil foreach (var item in drErrorInfo.FieldErrors) message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; } - message += "字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + throw Oops.Oh("导入异常:" + message); + } + return res.Data; + } + + /// + /// 导入Excel数据并错误标记 + /// + /// + /// + /// + /// + public static async Task> ImportExcelData([Required] IFormFile file, Func, ImportResult> importResultCallback = null) where T : class, new() + { + var importer = new ExcelImporter(); + var resultStream = new MemoryStream(); + var res = await importer.Import(file.OpenReadStream(), resultStream, importResultCallback); + resultStream.Seek(0, SeekOrigin.Begin); + var userId = App.User?.FindFirst(ClaimConst.UserId)?.Value; + + App.GetRequiredService().Remove(CacheConst.KeyExcelTemp + userId); + App.GetRequiredService().Set(CacheConst.KeyExcelTemp + userId, resultStream, TimeSpan.FromMinutes(5)); + + var message = string.Empty; + if (res.HasError) + { + if (res.Exception != null) + message += $"\r\n{res.Exception.Message}"; + foreach (DataRowErrorInfo drErrorInfo in res.RowErrors) + { + int rowNum = drErrorInfo.RowIndex; + foreach (var item in drErrorInfo.FieldErrors) + message += $"\r\n{item.Key}:{item.Value}(文件第{drErrorInfo.RowIndex}行)"; + } + if (res.TemplateErrors.Count > 0) + message += "\r\n字段缺失:" + string.Join(",", res.TemplateErrors.Select(m => m.RequireColumnName).ToList()); + + if (message.Length > 200) + message = string.Concat(message.AsSpan(0, 200), "...\r\n异常过多,建议下载错误标记文件查看详细错误信息并重新导入。"); throw Oops.Oh("导入异常:" + message); } return res.Data; @@ -300,7 +339,7 @@ public static class CommonUtil else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( - null, propertyInfo, tTargetProps.ContainsKey(propertyInfo.Name) ? tTargetProps[propertyInfo.Name] : null)); + null, propertyInfo, tTargetProps.TryGetValue(propertyInfo.Name, out PropertyInfo value) ? value : null)); } } @@ -342,34 +381,34 @@ public static class CommonUtil else { propMappings.Add(propertyInfo.Name, new Tuple, PropertyInfo, PropertyInfo>( - null, sourceProps.ContainsKey(propertyInfo.Name) ? sourceProps[propertyInfo.Name] : null, propertyInfo)); + null, sourceProps.TryGetValue(propertyInfo.Name, out PropertyInfo value) ? value : null, propertyInfo)); } } return propMappings; } - /// - /// 获取属性映射 - /// - /// - /// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 - private static Dictionary> GetExportDicttMap() where TTarget : new() - { - // 整理导入对象的属性名称,目标属性名,字典Code - var propMappings = new Dictionary>(); - var tTargetProps = typeof(TTarget).GetProperties(); - foreach (var propertyInfo in tTargetProps) - { - var attrs = propertyInfo.GetCustomAttribute(); - if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) - { - propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); - } - } + ///// + ///// 获取属性映射 + ///// + ///// + ///// 整理导入对象的 属性名称, 字典数据,原属性信息,目标属性信息 + //private static Dictionary> GetExportDicttMap() where TTarget : new() + //{ + // // 整理导入对象的属性名称,目标属性名,字典Code + // var propMappings = new Dictionary>(); + // var tTargetProps = typeof(TTarget).GetProperties(); + // foreach (var propertyInfo in tTargetProps) + // { + // var attrs = propertyInfo.GetCustomAttribute(); + // if (attrs != null && !string.IsNullOrWhiteSpace(attrs.TypeCode)) + // { + // propMappings.Add(propertyInfo.Name, new Tuple(attrs.TargetPropName, attrs.TypeCode)); + // } + // } - return propMappings; - } + // return propMappings; + //} /// /// 解析IP地址 diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm index 24c159e7..83b5d7df 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/editDialog.vue.vm @@ -169,16 +169,24 @@ import { ref,onMounted, reactive } from "vue"; import { ElMessage } from "element-plus"; import type { FormRules } from "element-plus"; - +@if (@Model.IsApiService) { // 接口函数 - import { getAPI } from '/@@/utils/axios-utils'; + @:import { getAPI } from '/@@/utils/axios-utils'; // 接口 - import { @(@Model.ClassName)Api } from '/@@/api-services/api'; + @:import { @(@Model.ClassName)Api } from '/@@/api-services/api'; // 模型 - import { Update@(@Model.ClassName)Input } from '/@@/api-services/models'; + @:import { Update@(@Model.ClassName)Input } from '/@@/api-services/models'; +} else { + @:import { add@(@Model.ClassName), update@(@Model.ClassName), detail@(@Model.ClassName) } from "/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)"; + if(@Model.TableField.Any(x=>x.EffectType == "Upload")){ + @:import { Plus } from "@@element-plus/icons-vue"; + @:import { UploadRequestOptions } from "element-plus"; + @:import {@string.Join(",",Model.TableField.Where(x=>x.EffectType == "Upload").Select(x=>"upload"+x.PropertyName).ToList())} from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; + } +} @if(@Model.TableField.Any(x=>x.EffectType == "ConstSelector")){ @:import { getConstType } from "/@@/utils/constHelper"; @@ -244,8 +252,11 @@ // 改用detail获取最新数据来编辑 let rowData = JSON.parse(JSON.stringify(row)); if (rowData.id) - //state.ruleForm = (await detail@(@Model.ClassName)(rowData.id)).data.result; - state.ruleForm = (await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)DetailGet(rowData.id)).data.result; + @if (@Model.IsApiService) { + @:state.ruleForm = (await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)DetailGet(rowData.id)).data.result; + } else { + @:state.ruleForm = (await detail@(@Model.ClassName)(rowData.id)).data.result; + } else state.ruleForm = rowData; state.isShowDialog = true; @@ -268,11 +279,17 @@ if (isValid) { let values = state.ruleForm; if (state.ruleForm.@(@pkFieldName) == undefined || state.ruleForm.@(@pkFieldName) == null || state.ruleForm.@(@pkFieldName) == "" || state.ruleForm.@(@pkFieldName) == 0) { - //await add@(@Model.ClassName)(values); - await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)AddPost(state.ruleForm); + @if (@Model.IsApiService) { + @:await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)AddPost(state.ruleForm); + } else { + @:await add@(@Model.ClassName)(values); + } } else { - //await update@(@Model.ClassName)(values); - await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)UpdatePost(state.ruleForm); + @if (@Model.IsApiService) { + @:await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)UpdatePost(state.ruleForm); + } else { + @:await update@(@Model.ClassName)(values); + } } closeDialog(); } else { @@ -324,7 +341,11 @@ if(column.WhetherAddUpdate=="N") continue; if(@column.EffectType == "Upload"){ @:const upload@(@column.PropertyName)Handle = async (options: UploadRequestOptions) => { + @if (@Model.IsApiService) { @:let list = await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)Upload@(@column.FkEntityName)PostForm(options); + } else { + @:let list = await upload@(@column.PropertyName)(options); + } @:state.ruleForm.@(column.LowerPropertyName) = res.data.result?.url; @:}; } diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm index 07501517..50484b16 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/index.vue.vm @@ -19,25 +19,30 @@ } diff --git a/Web/src/views/system/database/database.ts b/Web/src/views/system/database/database.ts index c99a0bde..4bbd5d69 100644 --- a/Web/src/views/system/database/database.ts +++ b/Web/src/views/system/database/database.ts @@ -104,4 +104,9 @@ export const dataTypeList = [ hasLength: false, hasDecimalDigits: false, }, + { + value: 'boolean', + hasLength: false, + hasDecimalDigits: false, + }, ]; diff --git a/Web/src/views/system/database/index.vue b/Web/src/views/system/database/index.vue index d0a90d27..dde043c4 100644 --- a/Web/src/views/system/database/index.vue +++ b/Web/src/views/system/database/index.vue @@ -32,7 +32,7 @@ 增加列 生成实体 - + @@ -81,7 +81,7 @@ import { onMounted, reactive, ref } from 'vue'; import { ElMessageBox, ElMessage } from 'element-plus'; import { useRouter } from 'vue-router'; import { VxeGridInstance } from 'vxe-table'; -import { useVxeTable } from '/@/hooks/vxeTableOptionsHook'; +import { useVxeTable } from '/@/hooks/useVxeTableOptionsHook'; import EditTable from '/@/views/system/database/component/editTable.vue'; import EditColumn from '/@/views/system/database/component/editColumn.vue'; @@ -118,54 +118,58 @@ const state = reactive({ }); // 表格参数配置 -const options = useVxeTable({ - id: 'sysDatabase', - name: '库表信息', - columns: [ - // { type: 'checkbox', width: 40, fixed: 'left' }, - { type: 'seq', title: '序号', width: 50, fixed: 'left' }, - { field: 'dbColumnName', title: '字段名', minWidth: 200, showOverflow: 'tooltip' }, - { field: 'dataType', title: '数据类型', minWidth: 120, showOverflow: 'tooltip' }, - { field: 'isPrimarykey', title: '主键', minWidth: 70, slots: { default: 'row_isPrimarykey' } }, - { field: 'isIdentity', title: '自增', minWidth: 70, slots: { default: 'row_isIdentity' } }, - { field: 'isNullable', title: '可空', minWidth: 70, slots: { default: 'row_isNullable' } }, - { field: 'length', title: '长度', minWidth: 80, showOverflow: 'tooltip' }, - { field: 'decimalDigits', title: '精度', minWidth: 80, showOverflow: 'tooltip' }, - { field: 'defaultValue', title: '默认值', minWidth: 80, showOverflow: 'tooltip' }, - { field: 'columnDescription', title: '描述', minWidth: 200, showOverflow: 'tooltip' }, - { title: '操作', fixed: 'right', width: 100, showOverflow: true, slots: { default: 'row_buttons' } }, - ], - enableExport: true, - searchCallback: () => handleQueryColumn(), - queryAllCallback: () => fetchData({ pageSize: 99999 }), -}); +const options = useVxeTable( + { + id: 'sysDatabase', + name: '库表信息', + columns: [ + // { type: 'checkbox', width: 40, fixed: 'left' }, + { type: 'seq', title: '序号', width: 50, fixed: 'left' }, + { field: 'dbColumnName', title: '字段名', minWidth: 200, showOverflow: 'tooltip' }, + { field: 'dataType', title: '数据类型', minWidth: 120, showOverflow: 'tooltip' }, + { field: 'isPrimarykey', title: '主键', minWidth: 70, slots: { default: 'row_isPrimarykey' } }, + { field: 'isIdentity', title: '自增', minWidth: 70, slots: { default: 'row_isIdentity' } }, + { field: 'isNullable', title: '可空', minWidth: 70, slots: { default: 'row_isNullable' } }, + { field: 'length', title: '长度', minWidth: 80, showOverflow: 'tooltip' }, + { field: 'decimalDigits', title: '精度', minWidth: 80, showOverflow: 'tooltip' }, + { field: 'defaultValue', title: '默认值', minWidth: 80, showOverflow: 'tooltip' }, + { field: 'columnDescription', title: '描述', minWidth: 200, showOverflow: 'tooltip' }, + { title: '操作', fixed: 'right', width: 100, showOverflow: true, slots: { default: 'row_buttons' } }, + ], + }, + // vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档 + { + // 代理配置 + proxyConfig: { autoLoad: true, ajax: { query: () => handleQueryColumnApi() } }, + // 分页配置 + pagerConfig: { enabled: false }, + // 工具栏配置 + toolbarConfig: { export: true }, + } +); // 页面初始化 onMounted(async () => { options.loading = true; - var res = await getAPI(SysDatabaseApi).apiSysDatabaseListGet(); + let res = await getAPI(SysDatabaseApi).apiSysDatabaseListGet(); state.dbData = res.data.result; - options.loading = false; + let appNamesRes = await getAPI(SysCodeGenApi).apiSysCodeGenApplicationNamespacesGet(); state.appNamespaces = appNamesRes.data.result as Array; + options.loading = false; }); -// 查询操作 -const handleQueryColumn = async () => { +// 查询列api +const handleQueryColumnApi = async () => { if (state.tableName == '' || typeof state.tableName == 'undefined') { - await xGrid.value?.loadData([]); - options.loading = false; return; } - options.loading = true; - var res = await fetchData(); - await xGrid.value?.loadData(res.data.result ?? []); - options.loading = false; + return getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(state.tableName, state.configId); }; -// 获取数据 -const fetchData = async (tableParams?: any) => { - return getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(state.tableName, state.configId); +// 查询列操作 +const handleQueryColumn = async () => { + await xGrid.value?.commitProxy('query'); }; // 增加表 diff --git a/Web/src/views/system/dict/component/editDictData.vue b/Web/src/views/system/dict/component/editDictData.vue index e9285172..8c29e081 100644 --- a/Web/src/views/system/dict/component/editDictData.vue +++ b/Web/src/views/system/dict/component/editDictData.vue @@ -1,6 +1,6 @@