diff --git a/Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/CodeGenStrategyAttribute.cs
similarity index 70%
rename from Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs
rename to Admin.NET/Admin.NET.Core/Attribute/CodeGenStrategyAttribute.cs
index e09f8e72..62ca4e77 100644
--- a/Admin.NET/Admin.NET.Core/Attribute/CustomUnifyResultAttribute.cs
+++ b/Admin.NET/Admin.NET.Core/Attribute/CodeGenStrategyAttribute.cs
@@ -7,15 +7,15 @@
namespace Admin.NET.Core;
///
-/// 自定义规范化结果特性
+/// 代码生成策略特性
///
-[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
-public class CustomUnifyResultAttribute : Attribute
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class CodeGenStrategyAttribute : Attribute
{
- public string Name { get; set; }
+ public CodeGenSceneEnum Scene { get; }
- public CustomUnifyResultAttribute(string name)
+ public CodeGenStrategyAttribute(CodeGenSceneEnum scene)
{
- Name = name;
+ Scene = scene;
}
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Attribute/CommonValidationAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/CommonValidationAttribute.cs
deleted file mode 100644
index 5e1b861f..00000000
--- a/Admin.NET/Admin.NET.Core/Attribute/CommonValidationAttribute.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-///
-/// 通用接口参数验证特性类
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class CommonValidationAttribute : ValidationAttribute
-{
- private readonly Dictionary _conditions;
- private static readonly Dictionary CompiledConditions = [];
-
- ///
- ///
- /// 条件对参数,长度必须为偶数
- /// 奇数字符串参数:动态条件
- /// 偶数字符串参数:提示消息
- ///
- ///
- ///
- /// public class ModelInput {
- ///
- ///
- /// public string A { get; set; }
- ///
- ///
- /// [CommonValidation(
- /// "A == 1 && B == null", "当 A == 1时,B不能为空",
- /// "C == 2 && B == null", "当 C == 2时,B不能为空"
- /// )]
- /// public string B { get; set; }
- /// }
- ///
- ///
- public CommonValidationAttribute(params string[] conditionPairs)
- {
- if (conditionPairs.Length % 2 != 0) throw new ArgumentException("条件对必须以偶数个字符串的形式提供。");
-
- var conditions = new Dictionary();
- for (int i = 0; i < conditionPairs.Length; i += 2)
- conditions.Add(conditionPairs[i], conditionPairs[i + 1]);
-
- _conditions = conditions;
- }
-
- protected override ValidationResult IsValid(object value, ValidationContext validationContext)
- {
- foreach (var (expr, errorMessage) in _conditions)
- {
- var conditionKey = $"{validationContext.ObjectType.FullName}.{expr}";
- if (!CompiledConditions.TryGetValue(conditionKey, out var condition))
- {
- condition = CreateCondition(validationContext.ObjectType, expr);
- CompiledConditions[conditionKey] = condition;
- }
-
- if ((bool)condition.DynamicInvoke(validationContext.ObjectInstance)!)
- {
- return new ValidationResult(errorMessage ?? $"[{validationContext.MemberName}]校验失败");
- }
- }
-
- return ValidationResult.Success;
- }
-
- private Delegate CreateCondition(Type modelType, string expression)
- {
- try
- {
- // 创建参数表达式
- var parameter = Expression.Parameter(typeof(object), "x");
-
- // 构建 Lambda 表达式
- var lambda = DynamicExpressionParser.ParseLambda([Expression.Parameter(modelType, "x")], typeof(bool), expression);
-
- // 创建新的 Lambda 表达式,接受 object 参数并调用编译后的表达式
- var invokeExpression = Expression.Invoke(lambda, Expression.Convert(parameter, modelType));
- var finalLambda = Expression.Lambda>(invokeExpression, parameter);
-
- return finalLambda.Compile();
- }
- catch (Exception ex)
- {
- throw new ArgumentException($"无法解析表达式 '{expression}': {ex.Message}", ex);
- }
- }
-}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs
index 42658f21..9902fc9c 100644
--- a/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs
+++ b/Admin.NET/Admin.NET.Core/Attribute/DictAttribute.cs
@@ -125,7 +125,7 @@ public class DictAttribute : ValidationAttribute, ITransient
///
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
- return IsValidAsync(value, validationContext, CancellationToken.None).GetAwaiter().GetResult();
+ return IsValidAsync(value, validationContext, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
diff --git a/Admin.NET/Admin.NET.Core/Attribute/ThemeAttribute.cs b/Admin.NET/Admin.NET.Core/Attribute/ThemeAttribute.cs
index 85e1cc6d..e38301e7 100644
--- a/Admin.NET/Admin.NET.Core/Attribute/ThemeAttribute.cs
+++ b/Admin.NET/Admin.NET.Core/Attribute/ThemeAttribute.cs
@@ -1,21 +1,21 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-///
-/// 枚举拓展主题样式
-///
-[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field)]
-public class ThemeAttribute : Attribute
-{
- public string Theme { get; private set; }
-
- public ThemeAttribute(string theme)
- {
- this.Theme = theme;
- }
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+///
+/// 枚举拓展主题样式
+///
+[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field)]
+public class ThemeAttribute : Attribute
+{
+ public string Theme { get; private set; }
+
+ public ThemeAttribute(string theme)
+ {
+ this.Theme = theme;
+ }
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs b/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs
new file mode 100644
index 00000000..65ed8fb7
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/CodeGenStrategyFactory.cs
@@ -0,0 +1,26 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 代码生成策略工厂
+///
+public class CodeGenStrategyFactory : IScoped
+{
+ private readonly Dictionary<(Type, CodeGenSceneEnum), ICodeGenStrategy> _strategyMap;
+
+ public CodeGenStrategyFactory(IEnumerable strategies)
+ {
+ _strategyMap = strategies.ToDictionary(s => (s.GetType().BaseType!.GenericTypeArguments.First(), s.GetType().GetCustomAttribute()!.Scene), s => s);
+ }
+
+ public CodeGenStrategy GetStrategy(CodeGenSceneEnum scene)
+ {
+ if (_strategyMap.TryGetValue((typeof(T), scene), out var strategy)) return (CodeGenStrategy)strategy;
+ throw Oops.Oh(ErrorCodeEnum.D1400, scene.GetDescription());
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs
new file mode 100644
index 00000000..48db5539
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/CustomTemplateEngine.cs
@@ -0,0 +1,240 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 自定义模板引擎
+///
+public class CustomTemplateEngine : ViewEngineModel
+{
+ ///
+ /// 场景
+ ///
+ public int Scene { get; set; }
+
+ ///
+ /// 作者
+ ///
+ public string AuthorName { get; set; }
+
+ ///
+ /// 邮箱
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// 模块名称
+ ///
+ public string ModuleName { get; set; }
+
+ ///
+ /// 命名空间
+ ///
+ public string NameSpace { get; set; }
+
+ ///
+ /// 项目最后名称
+ ///
+ public string ProjectLastName { get; set; }
+
+ ///
+ /// 前端页面目录名
+ ///
+ public string PagePath { get; set; } = "main";
+
+ ///
+ /// 打印模板类型
+ ///
+ public string PrintType { get; set; }
+
+ ///
+ /// 自定义打印模板名
+ ///
+ public string PrintName { get; set; }
+
+ ///
+ /// 是否使用Swagger接口
+ ///
+ public bool IsApiService { get; set; }
+
+ ///
+ /// 库定位器
+ ///
+ public string ConfigId { get; set; }
+
+ ///
+ /// 主表类名
+ ///
+ public string ClassName { get; set; }
+
+ ///
+ /// 首字母小写主表类名
+ ///
+ public string LowerClassName { get; set; }
+
+ ///
+ /// 业务名
+ ///
+ public string BusName { get; set; }
+
+ ///
+ /// 是否有状态字段
+ ///
+ public bool HasStatus { get; set; }
+
+ ///
+ /// 是否有上传字段
+ ///
+ public bool HasUpload { get; set; }
+
+ ///
+ /// 是否有关联表
+ ///
+ public bool HasJoinTable { get; set; }
+
+ ///
+ /// 是否只有Id单主键
+ ///
+ public bool IsOnlyIdPrimary { get; set; }
+
+ ///
+ /// 上级联表字段
+ ///
+ public string? LastLinkPropertyName { get; set; }
+
+ ///
+ /// 下级联表字段
+ ///
+ public string? NextLinkPropertyName { get; set; }
+
+ ///
+ /// 树组件配置
+ ///
+ public TreeWithTableConfigInput TreeConfig { get; set; }
+
+ ///
+ /// 是否是横向布局
+ ///
+ public bool IsHorizontal { get; set; }
+
+ ///
+ /// 对照目标表配置
+ ///
+ public TableRelationshipConfigInput RelationshipTable { get; set; }
+
+ ///
+ /// 从表配置
+ ///
+ public CustomTemplateEngine SlaveTable { get; set; }
+
+ ///
+ /// 所有字段集
+ ///
+ public List AllFields { get; set; }
+
+ ///
+ /// 查询字段集
+ ///
+ public List QueryFields { get; set; }
+
+ ///
+ /// 表格字段集
+ ///
+ public List TableFields { get; set; }
+
+ ///
+ /// 导入导出字段集
+ ///
+ public List ImportFields { get; set; }
+
+ ///
+ /// 主键字段集
+ ///
+ public List PrimaryFields { get; set; }
+
+ ///
+ /// 增改字段集
+ ///
+ public List AddUpdateFields { get; set; }
+
+ ///
+ /// 上传字段集
+ ///
+ public List UploadFields { get; set; }
+
+ ///
+ /// 下拉框字段集
+ ///
+ public List DropdownFields { get; set; }
+
+ ///
+ /// 接口字段集
+ ///
+ public List ApiFields { get; set; }
+
+ ///
+ /// 数据库字段集
+ ///
+ public List ColumnList { get; set; }
+
+ ///
+ /// 获取首字母小写文本
+ ///
+ ///
+ ///
+ public string GetFirstLower(string text) => text?.ToFirstLetterLowerCase();
+
+ ///
+ /// 根据.NET类型获取TypeScript类型
+ ///
+ ///
+ ///
+ public string GetTypeScriptType(string type) => CodeGenHelper.GetTypeScriptType(type);
+
+ ///
+ /// 格式化主键查询条件
+ /// 例: PrimaryKeysFormat(" || ", "u.{0} == input.{0}")
+ /// 单主键返回 u.Id == input.Id
+ /// 组合主键返回 u.Id == input.Id || u.FkId == input.FkId
+ ///
+ /// 分隔符
+ /// 模板字符串
+ /// 字段首字母小写
+ ///
+ public string PrimaryKeysFormat(string separator, string format, bool lowerFirstLetter = false) => string.Join(separator, PrimaryFields.Select(u => string.Format(format, lowerFirstLetter ? u.LowerPropertyName : u.PropertyName)));
+
+ ///
+ /// 根据配置获取前端HttpApi方法名
+ ///
+ ///
+ ///
+ ///
+ ///
+ public string GetHttpApiMethodName(CodeGenColumnConfig column, string type, CustomTemplateEngine engine = null)
+ {
+ engine ??= this;
+ var entityName = SlaveTable != null && SlaveTable.ApiFields.Any(u => u == column) ? SlaveTable.ClassName : engine.ClassName;
+ var suffixName = type switch
+ {
+ "page" => IsApiService ? "PagePost" : type, // 分页查询
+ "update" => IsApiService ? "UpdatePost" : type, // 更新记录
+ "add" => IsApiService ? "AddPost" : type, // 新增记录
+ "detail" => IsApiService ? "DetailGet" : type, // 详情
+ "delete" => IsApiService ? "DeletePost" : type, // 删除
+ "batchDelete" => IsApiService ? "BatchDeletePost" : type, // 批量删除
+ "setStatus" => IsApiService ? "SetStatusPost" : type, // 设置状态
+ "exportData" => IsApiService ? "ExportPost" : type, // 数据导出
+ "importData" => IsApiService ? "ImportPostForm" : type, // 数据导入
+ "downloadTemplate" => IsApiService ? "ImportGet" : type, // 下载模板
+ "fkTable" => IsApiService ? $"{column.JoinConfig.EntityName}PagePost" : "page", // 外键
+ "fkTree" => IsApiService ? $"{column.JoinConfig.EntityName}TreePost" : $"get{column.JoinConfig.EntityName}Tree", // 树
+ "upload" => IsApiService ? $"Upload{column?.PropertyName}PostForm" : $"upload{column?.PropertyName}", // 上传
+ _ => throw new Exception("未知接口类型")
+ };
+ return IsApiService ? $"getAPI({entityName}Api).api{entityName}{suffixName}" : $"{GetFirstLower(entityName)}Api.{suffixName}";
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs
new file mode 100644
index 00000000..b5f2e083
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableEntityEngine.cs
@@ -0,0 +1,55 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+public class TableEntityEngine : ViewEngineModel
+{
+ ///
+ /// 作者
+ ///
+ public string AuthorName { get; set; } = "喵你个汪呀";
+
+ ///
+ /// 邮箱
+ ///
+ public string Email { get; set; } = "jason-dom@qq.com";
+
+ ///
+ /// 命名空间
+ ///
+ public string NameSpace { get; set; }
+
+ ///
+ /// 库配置Id
+ ///
+ public string ConfigId { get; set; }
+
+ ///
+ /// 表名
+ ///
+ public string TableName { get; set; }
+
+ ///
+ /// 实体名
+ ///
+ public string EntityName { get; set; }
+
+ ///
+ /// 表描述
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// 基类名
+ ///
+ public string BaseClassName { get; set; }
+
+ ///
+ /// 字段集合
+ ///
+ public List TableFields { get; set; }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/VerifyRuleItem.cs b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs
similarity index 59%
rename from Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/VerifyRuleItem.cs
rename to Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs
index a40c8a0f..662edc2b 100644
--- a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/VerifyRuleItem.cs
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Engines/TableSeedDataEngine.cs
@@ -4,45 +4,37 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-namespace Admin.NET.Core.Service;
+namespace Admin.NET.Core.CodeGen;
-///
-/// 验证规则选项
-///
-public class VerifyRuleItem
+public class TableSeedDataEngine : ViewEngineModel
{
///
- /// 编码
+ /// 作者
///
- public long Key { get; set; }
+ public string AuthorName { get; set; } = "喵你个汪呀";
///
- /// 验证类型
+ /// 邮箱
///
- public string Type { get; set; }
+ public string Email { get; set; } = "jason-dom@qq.com";
///
- /// 验证错误消息
+ /// 命名空间
///
- public string Message { get; set; }
+ public string NameSpace { get; set; }
///
- /// 最小值
+ /// 实体名
///
- public string Min { get; set; }
+ public string EntityName { get; set; }
///
- /// 最大值
+ /// 描述
///
- public string Max { get; set; }
+ public string Description { get; set; }
///
- /// 正则表达式
+ /// 记录列表
///
- public string Pattern { get; set; }
-
- ///
- /// 数据类型(搭配正则)
- ///
- public string DataType { get; set; }
+ public List RecordList { get; set; }
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs b/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs
new file mode 100644
index 00000000..23e23016
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Models/CodeGenColumnConfig.cs
@@ -0,0 +1,263 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 代码生成详细配置参数
+///
+public class CodeGenColumnConfig : SysCodeGenColumn
+{
+ ///
+ /// 首字母小写的属性名
+ ///
+ public string LowerPropertyName => PropertyName[..1].ToLower() + PropertyName[1..];
+
+ ///
+ /// 是否字典字段
+ ///
+ public bool IsDict => EffectType is CodeGenEffectTypeEnum.DictSelector;
+
+ ///
+ /// 是否枚举字段
+ ///
+ public bool IsEnum => EffectType is CodeGenEffectTypeEnum.EnumSelector;
+
+ ///
+ /// 是否常量字段
+ ///
+ public bool IsConst => EffectType is CodeGenEffectTypeEnum.ConstSelector;
+
+ ///
+ /// 是否上传字段
+ ///
+ public bool IsUpload => EffectType is CodeGenEffectTypeEnum.Upload;
+
+ ///
+ /// 是否开关字段
+ ///
+ public bool IsSwitch => EffectType is CodeGenEffectTypeEnum.Switch;
+
+ ///
+ /// 是否输入框字段
+ ///
+ public bool IsInput => EffectType is CodeGenEffectTypeEnum.Input;
+
+ ///
+ /// 是否数字输入框字段
+ ///
+ public bool IsInputNumber => EffectType is CodeGenEffectTypeEnum.InputNumber;
+
+ ///
+ /// 是否文本域字段
+ ///
+ public bool IsInputTextArea => EffectType is CodeGenEffectTypeEnum.InputTextArea;
+
+ ///
+ /// 是否时间选择器字段
+ ///
+ public bool IsDatePicker => EffectType is CodeGenEffectTypeEnum.DatePicker;
+
+ ///
+ /// 是否外键字段
+ ///
+ public bool IsForeignKey => EffectType is CodeGenEffectTypeEnum.ForeignKey;
+
+ ///
+ /// 是否树字段
+ ///
+ public bool IsTree => EffectType is CodeGenEffectTypeEnum.ApiTreeSelector;
+
+ ///
+ /// 是否多选字段
+ ///
+ public bool Multiple => JoinConfig?.Multiple ?? false;
+
+ ///
+ /// 是否唯一字段
+ ///
+ public bool IsUnique => FromValid == CodeGenFromRuleValidEnum.Unique;
+
+ ///
+ /// 状态字段
+ ///
+ public bool IsStatus => PropertyName == nameof(BaseStatusInput.Status) && DictConfig.Code.Trim('?') == nameof(StatusEnum);
+
+ ///
+ /// 是否要联表
+ ///
+ public bool HasJoinTable => EffectType is CodeGenEffectTypeEnum.Upload or CodeGenEffectTypeEnum.ForeignKey or CodeGenEffectTypeEnum.ApiTreeSelector;
+
+ ///
+ /// 是否要插槽
+ ///
+ public bool HasSlots => !(IsConst || IsInput || IsInputNumber || IsInputTextArea) || IsCopy;
+
+ ///
+ /// 连表别名
+ ///
+ public string LeftJoinAliasName => Regex.Replace(LowerPropertyName, "Id$", "");
+
+ ///
+ /// 字典配置
+ ///
+ public EffectFileConfigInput FileConfig => !string.IsNullOrWhiteSpace(Config) ? JSON.Deserialize(Config) : null;
+
+ ///
+ /// 字典配置
+ ///
+ public EffectDictConfigInput DictConfig => !string.IsNullOrWhiteSpace(Config) ? JSON.Deserialize(Config) : null;
+
+ ///
+ /// 联表配置
+ ///
+ public EffectTreeConfigInput JoinConfig => !string.IsNullOrWhiteSpace(Config) ? JSON.Deserialize(Config) : null;
+
+ ///
+ /// 时间控件配置
+ ///
+ public EffectDatePickerConfigInput DateConfig => !string.IsNullOrWhiteSpace(Config) ? JSON.Deserialize(Config) : null;
+
+ ///
+ /// 获取可空类型
+ ///
+ public string NullableNetType
+ {
+ get
+ {
+ if (IsEnum) return DictConfig.Multiple || NetType.StartsWith("string") ? "string" : $"{DictConfig.Code}?";
+ if (NetType.StartsWith("string")) return "string";
+ if (NetType.StartsWith("Guid")) return "Guid";
+ return NetType.EndsWith("?") ? NetType : NetType + "?";
+ }
+ }
+
+ ///
+ /// 获取字段验证特性
+ ///
+ ///
+ public string GetFromValidAttribute()
+ {
+ bool isCustomValid = FromValid is CodeGenFromRuleValidEnum.IDCard or CodeGenFromRuleValidEnum.Range or CodeGenFromRuleValidEnum.MaxLength or CodeGenFromRuleValidEnum.Unique;
+ if (isCustomValid)
+ {
+ switch (FromValid)
+ {
+ case CodeGenFromRuleValidEnum.Unique: return null;
+ case CodeGenFromRuleValidEnum.IDCard:
+ if (!NetType.StartsWith("string")) return null;
+ return "[IdCardNo]";
+
+ case CodeGenFromRuleValidEnum.MaxLength:
+ if (!NetType.StartsWith("string") || ColumnLength <= 0) return null;
+ return $"[MaxLength({ColumnLength}, ErrorMessage = \"{ColumnComment}不能超过{ColumnLength}个字符\")]";
+
+ case CodeGenFromRuleValidEnum.Range:
+ return $"[Range(0, 100, ErrorMessage = \"{ColumnComment}范围不正确\")]";
+
+ default:
+ return null;
+ }
+ }
+ return $"[DataValidation(ValidationTypes.{FromValid.ToString()}, ErrorMessage = \"{ColumnComment}不正确\")]";
+ }
+
+ ///
+ /// 显示字段
+ ///
+ public string DisplayPropertyNames
+ {
+ get
+ {
+ var expList = JoinConfig?.DisplayPropertyNames?.Split(",").Select(u => $"{LeftJoinAliasName}.{u}");
+ if (expList?.Count() == 1) return expList.FirstOrDefault();
+ if (expList == null) return null;
+ return $"$\"{{{expList.Join("}-{")}}}\"";
+ }
+ }
+
+ ///
+ /// 连表属性名称
+ ///
+ public string LeftJoinPropertyName
+ {
+ get
+ {
+ if (!HasJoinTable) throw Oops.Bah("不是连表字段");
+ return EffectType switch
+ {
+ CodeGenEffectTypeEnum.ForeignKey or CodeGenEffectTypeEnum.ApiTreeSelector => Regex.Replace(PropertyName, "Id([s]?)$", "$1") + JoinConfig?.DisplayPropertyNames?.Split(",").FirstOrDefault(),
+ CodeGenEffectTypeEnum.Upload => Regex.Replace(PropertyName, "Id([s]?)$", "$1") + "Attachment",
+ _ => null
+ };
+ }
+ }
+
+ ///
+ /// 获取列格式化配置方法
+ ///
+ public string GetVxeColumnExtraConfig
+ {
+ get
+ {
+ if (!HasSlots) return "";
+ if (IsCopy) return ", slots: { default: '$row_copy' }"; // 复制字段
+ if (IsStatistical) return ", slots: { default: '$row_statistical' }"; // 统计字段
+ switch (EffectType)
+ {
+ case CodeGenEffectTypeEnum.Switch: // 开关
+ return ", slots: { default: '$row_switch' }";
+
+ case CodeGenEffectTypeEnum.ApiTreeSelector: // 树组件
+ case CodeGenEffectTypeEnum.ForeignKey: // 外键
+ return $", formatter: ({{ row }}) => row.{LeftJoinPropertyName.ToFirstLetterLowerCase()}";
+
+ case CodeGenEffectTypeEnum.DatePicker: // 时间选择器
+ return $", formatter: ({{ cellValue, row }}) => commonFun.dateFormat{GetDateReaderName(DateConfig.Format)}([], 0, cellValue)";
+
+ case CodeGenEffectTypeEnum.Upload: // 上传
+ return $", params: {{ attachmentName: '{LeftJoinAliasName.ToFirstLetterLowerCase()}', isImage: {FileConfig.IsImage.ToString().ToLower()}, useDownload: {FileConfig.UseDownload.ToString().ToLower()}, downloadText: '{FileConfig.DownloadText}' }}" +
+ ", slots: { default: '$row_upload' }";
+
+ case CodeGenEffectTypeEnum.ConstSelector: // 常量选择器
+ case CodeGenEffectTypeEnum.DictSelector: // 字典选择器
+ case CodeGenEffectTypeEnum.EnumSelector: // 枚举选择器
+ if (IsStatus) return ", slots: { default: '$row_status' }"; // 状态字段
+ return $", params: {{ code: '{DictConfig.Code}', multiple: {DictConfig.Multiple.ToString().ToLower()}, isConst: {IsConst.ToString().ToLower()} }}" +
+ ", slots: { default: '$row_sysDict' }";
+ }
+ return "";
+ string GetDateReaderName(string format) => format switch
+ {
+ "date" => "YMD",
+ "time" => "HMS",
+ _ => "YMDHMS"
+ };
+ }
+ }
+
+ ///
+ /// 获取表单验证规则
+ ///
+ public string GetFormRules
+ {
+ get
+ {
+ var result = "";
+ var trigger = IsDatePicker || IsDict || IsEnum || IsTree ? "change" : "blur";
+ if (IsRequired) result += $" :rules=\"[{{required: true, message: '请选择{ColumnComment}', trigger: '{trigger}'}}]\"";
+ return result;
+ }
+ }
+
+ ///
+ /// 获取外键显示值语句
+ ///
+ /// 表别名
+ /// 多字段时的连接符
+ ///
+ public string GetDisplayColumn(string tableAlias, string separator = "-") => "$\"" + string.Join(separator, DisplayPropertyNames?.Split(",").Select(name => $"{{{tableAlias}.{name}}}") ?? new List()) + "\"";
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableUniqueConfigItem.cs b/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs
similarity index 58%
rename from Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableUniqueConfigItem.cs
rename to Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs
index 0cac927b..2f5dd52a 100644
--- a/Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableUniqueConfigItem.cs
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Models/TemplateContextOutput.cs
@@ -4,28 +4,30 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-namespace Admin.NET.Core.Service;
+namespace Admin.NET.Core.CodeGen;
///
-/// 表唯一配置项
+/// 模板输出上下文
///
-public class TableUniqueConfigItem
+public class TemplateContextOutput
{
///
- /// 字段列表
+ /// 文件名
///
- public List Columns { get; set; }
+ public string Name { get; set; }
///
- /// 描述信息
+ /// 模板类型
///
- public string Message { get; set; }
+ public int Type { get; set; }
///
- /// 格式化查询条件
+ /// 输出路径
///
- /// 分隔符
- /// 模板字符串
- ///
- public string Format(string separator, string format) => string.Join(separator, Columns.Select(name => string.Format(format, name)));
+ public string OutPath { get; set; }
+
+ ///
+ /// 内容
+ ///
+ public string Context { get; set; }
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/Enum/SerialTypeEnum.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs
similarity index 53%
rename from Admin.NET/Admin.NET.Core/Enum/SerialTypeEnum.cs
rename to Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs
index a307c199..6859fd81 100644
--- a/Admin.NET/Admin.NET.Core/Enum/SerialTypeEnum.cs
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenEntityStrategyBase.cs
@@ -1,20 +1,15 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-namespace Admin.NET.Core;
+namespace Admin.NET.Core.CodeGen;
///
-/// 系统流水号类型枚举
+/// 种子数据策略基类(处理TableSeedDataEngine类型)
///
-[Description("系统流水号类型枚举")]
-public enum SerialTypeEnum
+///
+public abstract class CodeGenEntityStrategyBase : CodeGenStrategy
{
- ///
- /// 其他
- ///
- [Description("其他")]
- Other = 999,
}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs
new file mode 100644
index 00000000..fc159d04
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenStrategy.cs
@@ -0,0 +1,59 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 基础策略接口
+///
+public interface ICodeGenStrategy
+{
+}
+
+///
+/// 泛型策略接口
+///
+///
+public abstract class CodeGenStrategy : ICodeGenStrategy
+{
+ protected DbConnectionOptions _dbConnectionOption => App.GetOptions();
+ protected CodeGenOptions _codeGenOption => App.GetOptions();
+ protected ISqlSugarClient _db => App.GetRequiredService();
+ protected IViewEngine _viewEngine => App.GetRequiredService();
+
+ public abstract Task> GenerateCode(T input);
+
+ ///
+ /// 渲染模板
+ ///
+ /// 模板上下文
+ /// 渲染动作
+ ///
+ protected async Task RenderAsync(TemplateContextOutput template, Func, string> action)
+ {
+ try
+ {
+ var nameSpace = this.GetType().Namespace;
+ string content = await File.ReadAllTextAsync(Path.Combine(Path.Combine(App.WebHostEnvironment.WebRootPath, "template"), template.Name));
+ template.Context = action.Invoke(content, builder =>
+ {
+ builder.AddAssemblyReferenceByName("System.Text.RegularExpressions");
+ builder.AddAssemblyReferenceByName("System.Collections");
+ builder.AddAssemblyReferenceByName("System.Linq");
+ builder.AddUsing("System.Text.RegularExpressions");
+ builder.AddUsing("System.Collections.Generic");
+ builder.AddUsing("System.Linq");
+ builder.AddUsing(nameSpace);
+ });
+ template.Name = template.Name.Replace(".vm", "");
+ return template;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception($"模板渲染失败:{template.Name}", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs
new file mode 100644
index 00000000..699e2a67
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Base/CodeGenTableStrategyBase.cs
@@ -0,0 +1,153 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 表策略基类(处理SysCodeGen类型)
+///
+///
+public abstract class CodeGenTableStrategyBase : CodeGenStrategy where T : SysCodeGen
+{
+ protected IViewEngine ViewEngine => App.GetRequiredService();
+ protected CodeGenOptions CodeGenOption => App.GetOptions();
+
+ ///
+ /// 模板上下文列表
+ ///
+ protected static List TemplateList => new()
+ {
+ new() { Type = 900, Name = "web_api.ts.vm", OutPath = "api/{PagePath}/{ModuleNameLower}.ts" },
+ new() { Type = 1000, Name = "web_views_index.vue.vm", OutPath = "views/{PagePath}/{ModuleNameLower}/index.vue" },
+ new() { Type = 1010, Name = "web_views_editDialog.vue.vm", OutPath = "views/{PagePath}/{ModuleNameLower}/component/edit{ModuleName}Dialog.vue" },
+ new() { Type = 1020, Name = "web_views_Tree.vue.vm", OutPath = "views/{PagePath}/{ModuleNameLower}/component/{LowerTreeEntityName}Tree.vue" },
+
+ new() { Type = 2000, Name = "service_Service.cs.vm", OutPath = "Service/{ModuleName}/{ModuleName}Service.cs" },
+ new() { Type = 2010, Name = "service_OutputDto.cs.vm", OutPath = "Service/{ModuleName}/Dto/{ModuleName}Output.cs" },
+ new() { Type = 2020, Name = "service_InputDto.cs.vm", OutPath = "Service/{ModuleName}/Dto/{ModuleName}Input.cs" },
+ };
+
+ ///
+ /// 根据生成方式获取模板路径列表
+ ///
+ ///
+ protected List GetTemplateList(SysCodeGen codeGen)
+ {
+ var templateList = codeGen.GenerateMethod switch
+ {
+ CodeGenMethodEnum.DownloadZipFrontend or CodeGenMethodEnum.GenerateToProjectFrontend => TemplateList.Where(u => u.Type < 2000).ToList(),
+ CodeGenMethodEnum.DownloadZipBackend or CodeGenMethodEnum.GenerateToProjectBackend => TemplateList.Where(u => u.Type >= 2000).ToList(),
+ _ => TemplateList
+ };
+ // 传统接口模式去掉 web_api 模板
+ if (codeGen.IsApiService) templateList = templateList.Where(e => e.Type != 900).ToList();
+ // 非树组件场景去掉树组件模板
+ if (codeGen.Scene.ToInt() % 100 != 10) templateList = templateList.Where(e => e.Type != 1020).ToList();
+ return templateList;
+ }
+
+ ///
+ /// 处理模板输出路径
+ ///
+ ///
+ ///
+ protected void ReplaceTemplateOutput(TemplateContextOutput template, SysCodeGen codeGen)
+ {
+ // 处理模板输出路径
+ string outputPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "codeGen", codeGen.ModuleName);
+ template.OutPath = template.OutPath.Replace("{PagePath}", codeGen.PagePath)
+ .Replace("{ModuleName}", codeGen.ModuleName)
+ .Replace("{ModuleNameLower}", codeGen.ModuleName.ToFirstLetterLowerCase())
+ .Replace("{LowerTreeEntityName}", codeGen.TreeConfig?.EntityName.ToFirstLetterLowerCase());
+
+ // 替换从表模板输出路径占位符
+ if (codeGen.TableList.Count > 1) template.OutPath = template.OutPath.Replace("{SlaveModuleName}", codeGen.TableList.Skip(1).First().ModuleName);
+
+ string tempPath;
+ if ((int)codeGen.GenerateMethod < 200)
+ {
+ // 下载模式下的路径处理
+ tempPath = !template.OutPath.EndsWith(".cs")
+ ? Path.Combine(outputPath, CodeGenOption.FrontRootPath, "src")
+ : Path.Combine(outputPath, codeGen.NameSpace);
+ }
+ else
+ {
+ // 生成到本项目下的路径处理
+ tempPath = !template.OutPath.EndsWith(".cs")
+ ? Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.Parent!.FullName, CodeGenOption.FrontRootPath, "src")
+ : Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.FullName, codeGen.NameSpace!);
+ }
+ template.OutPath = Path.Combine(tempPath, template.OutPath);
+ }
+
+ ///
+ /// 批量渲染模板
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected async Task> RenderList(SysCodeGen codeGen, List templateList, CustomTemplateEngine engine)
+ {
+ void SetEnginePropertyValue(CustomTemplateEngine input)
+ {
+ input.ModuleName ??= codeGen.ModuleName; // 模块名称
+ input.BusName ??= codeGen.BusName; // 业务名
+ input.AuthorName ??= codeGen.AuthorName; // 作者
+ input.Email ??= codeGen.Email; // 作者邮箱
+ input.NameSpace = codeGen.NameSpace; // 命名空间
+ input.PagePath = codeGen.PagePath; // 页面目录
+ input.Scene = codeGen.Scene.ToInt(); // 场景
+ input.ProjectLastName = codeGen.NameSpace!.Split('.').Last(); // 项目最后个名称,生成的时候赋值
+ input.IsApiService = codeGen.IsApiService; // 是否使用 Api Service
+ input.PrintType = codeGen.PrintType.ToString(); // 支持打印类型
+ input.PrintName = codeGen.PrintName; // 打印模板名称
+ input.LastLinkPropertyName ??= input.LastLinkPropertyName; // 上级关联字段
+ input.NextLinkPropertyName ??= input.NextLinkPropertyName; // 下级关联字段
+ input.IsHorizontal = codeGen.IsHorizontal;
+
+ input.QueryFields = input.AllFields.Where(e => e.IsQuery).ToList(); // 查询字段列表
+ input.TableFields = input.AllFields.Where(e => e.IsTable).ToList(); // 表格字段列表
+ input.ImportFields = input.AllFields.Where(e => e.IsImport).ToList(); // 导入导出字段列表
+ input.PrimaryFields = input.AllFields.Where(e => e.IsPrimarykey).ToList(); // 主键字段列表
+ input.AddUpdateFields = input.AllFields.Where(e => e.IsAddUpdate).ToList(); // 增改字段列表
+ input.UploadFields = input.AllFields.Where(e => e.EffectType == CodeGenEffectTypeEnum.Upload).ToList(); // 上传文件字段列表
+ input.DropdownFields = input.AllFields.Where(e => e.EffectType is CodeGenEffectTypeEnum.ApiTreeSelector or CodeGenEffectTypeEnum.ForeignKey or CodeGenEffectTypeEnum.DictSelector or CodeGenEffectTypeEnum.EnumSelector).ToList(); // 选择类字段列表
+ var map = new Dictionary();
+ input.ApiFields = input.AllFields.Where(e => e.IsTree || e.IsForeignKey).Where(e => map.TryAdd(e.JoinConfig.EntityName + e.IsTree, 1)).ToList();
+
+ // input.ColumnList = await CodeGenColumnService.GetColumnList(new BaseIdInput{ Id = codeGen.TableList.First().Id }); // 数据库字段列表,
+ input.IsOnlyIdPrimary = input.AllFields.Where(u => u.IsPrimarykey).All(u => u.PropertyName == "Id"); // 是否主键只有Id
+ input.HasStatus = input.AllFields.Any(u => u.PropertyName == nameof(BaseStatusInput.Status) && u.DictConfig.Code.Trim('?') == nameof(StatusEnum)); // 是否有启用禁用字段
+ input.HasJoinTable = input.AllFields.Any(e => e.EffectType is CodeGenEffectTypeEnum.ForeignKey or CodeGenEffectTypeEnum.ApiTreeSelector); // 是否有联表
+ input.HasUpload = input.UploadFields.Count != 0; // 是否有上传
+
+ // 校验模板参数
+ input.Validate();
+
+ // 必须有主键
+ if (!input.PrimaryFields.Any()) throw Oops.Oh(ErrorCodeEnum.D1405);
+ }
+
+ // 设置主表参数
+ SetEnginePropertyValue(engine);
+
+ // 设置从表参数
+ if (engine.SlaveTable != null) SetEnginePropertyValue(engine.SlaveTable);
+
+ // 异步渲染模板
+ var tasks = templateList.Select(template => Task.Run(() => RenderAsync(template, (context, builder) => _viewEngine.RunCompile(context, engine, builder)))).ToList();
+ await Task.WhenAll(tasks);
+
+ // 获取结果集
+ var result = new List();
+ foreach (var task in tasks) result.Add(await task);
+
+ // 处理输出路径
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs
new file mode 100644
index 00000000..84053397
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableEntityCodeGenStrategy.cs
@@ -0,0 +1,60 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 表实体代码生成策略类
+///
+[CodeGenStrategy(CodeGenSceneEnum.TableEntity)]
+public class TableEntityCodeGenStrategy : CodeGenEntityStrategyBase, ISingleton
+{
+ private readonly TemplateContextOutput _template = new() { Name = "Entity.cs.vm", OutPath = "Entity/{ModuleName}.cs" };
+
+ public override async Task> GenerateCode(CreateEntityInput input)
+ {
+ var config = _dbConnectionOption.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId) ?? throw Oops.Oh(ErrorCodeEnum.db1004);
+ input.EntityName = string.IsNullOrWhiteSpace(input.EntityName) ? (config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(input.TableName, null) : input.TableName) : input.EntityName;
+ input.Position = string.IsNullOrWhiteSpace(input.Position) ? "X.Application" : input.Position;
+
+ // Entity.cs.vm中是允许创建没有基类的实体的,所以这里也要做出相同的判断
+ string[] dbColumnNames = [];
+ if (!string.IsNullOrWhiteSpace(input.BaseClassName))
+ {
+ _codeGenOption.EntityBaseColumn.TryGetValue(input.BaseClassName, out dbColumnNames);
+ if (dbColumnNames is null or { Length: 0 }) throw Oops.Oh("基类配置文件不存在此类型");
+ }
+
+ var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
+ var dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false);
+ dbColumnInfos.ForEach(u =>
+ {
+ u.PropertyName = config.DbSettings.EnableUnderLine ? CodeGenHelper.CamelColumnName(u.DbColumnName, dbColumnNames) : u.DbColumnName; // 转下划线后的列名需要再转回来
+ u.DataType = CodeGenHelper.ConvertDataType(u, config.DbType);
+ });
+ if (_codeGenOption.BaseEntityNames.Contains(input.BaseClassName, StringComparer.OrdinalIgnoreCase))
+ dbColumnInfos = dbColumnInfos.Where(u => !dbColumnNames.Contains(u.PropertyName, StringComparer.OrdinalIgnoreCase)).ToList();
+
+ var dbTableInfo = db.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == input.TableName || u.Name == input.TableName.ToLower()) ?? throw Oops.Oh(ErrorCodeEnum.db1001);
+
+ var engine = new TableEntityEngine
+ {
+ NameSpace = $"{input.Position}.Entity",
+ TableName = input.TableName,
+ EntityName = input.EntityName,
+ BaseClassName = string.IsNullOrWhiteSpace(input.BaseClassName) ? "" : $": {input.BaseClassName}",
+ ConfigId = input.ConfigId,
+ Description = string.IsNullOrWhiteSpace(dbTableInfo.Description)
+ ? input.EntityName + "业务表"
+ : dbTableInfo.Description,
+ TableFields = dbColumnInfos
+ };
+
+ var result = await RenderAsync(_template, (context, builder) => _viewEngine.RunCompile(context, engine, builder));
+ result.OutPath = Path.Combine(Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent!.FullName, input.Position), result.OutPath.Replace("{ModuleName}", input.EntityName));
+ return [result];
+ }
+}
\ No newline at end of file
diff --git a/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs
new file mode 100644
index 00000000..776f91f7
--- /dev/null
+++ b/Admin.NET/Admin.NET.Core/CodeGen/Strategies/Entity/TableSeedDataCodeGenStrategy.cs
@@ -0,0 +1,176 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.CodeGen;
+
+///
+/// 表种子数据代码生成策略类
+///
+[CodeGenStrategy(CodeGenSceneEnum.TableSeedData)]
+public class TableSeedDataCodeGenStrategy : CodeGenEntityStrategyBase, ISingleton
+{
+ private readonly TemplateContextOutput _template = new() { Name = "SeedData.cs.vm", OutPath = "SeedData/{ModuleName}SeedData.cs" };
+
+ public override async Task> GenerateCode(CreateSeedDataInput input)
+ {
+ var config = _dbConnectionOption.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId) ?? throw Oops.Oh(ErrorCodeEnum.db1004);
+ input.Position = string.IsNullOrWhiteSpace(input.Position) ? "X.Core" : input.Position;
+
+ var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
+ var tableInfo = db.DbMaintenance.GetTableInfoList(false).First(u => u.Name == input.TableName); // 表名
+ List dbColumnInfos = db.DbMaintenance.GetColumnInfosByTableName(input.TableName, false); // 所有字段
+ IEnumerable entityInfos = await GetEntityInfos();
+ Type entityType = null;
+ foreach (var item in entityInfos)
+ {
+ if (tableInfo.Name.ToLower() != (config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(item.DbTableName) : item.DbTableName).ToLower()) continue;
+ entityType = item.Type;
+ break;
+ }
+ if (entityType == null) throw Oops.Oh(ErrorCodeEnum.db1003);
+
+ input.EntityName = entityType.Name;
+ input.SeedDataName = entityType.Name + "SeedData";
+ if (!string.IsNullOrWhiteSpace(input.Suffix)) input.SeedDataName += input.Suffix;
+
+ // 查询所有数据
+ var query = db.QueryableByObject(entityType);
+
+ // 优先用创建时间排序
+ DbColumnInfo orderField = dbColumnInfos.FirstOrDefault(u => u.DbColumnName.ToLower() is "create_time" or "createtime");
+ if (orderField != null) query = query.OrderBy(orderField.DbColumnName);
+
+ // 优先用创建时间排序,再使用第一个主键排序
+ if (dbColumnInfos.Any(u => u.IsPrimarykey))
+ query = query.OrderBy(dbColumnInfos.First(u => u.IsPrimarykey).DbColumnName);
+ var records = ((IEnumerable)await query.ToListAsync()).ToDynamicList();
+
+ // 过滤已存在的数据
+ if (input.FilterExistingData && records.Any())
+ {
+ // 获取实体类型-所有种数据数据类型
+ var entityTypes = App.EffectiveTypes.Where(u => u.FullName != null && !u.IsInterface && !u.IsAbstract && u.IsClass && u.IsDefined(typeof(SugarTable), false) && u.FullName.EndsWith("." + input.EntityName))
+ .Where(u => !u.GetCustomAttributes().Any())
+ .ToList();
+ if (entityTypes.Count == 1) // 只有一个实体匹配才能过滤
+ {
+ // 获取实体的主键对应的属性名称
+ var pkInfo = entityTypes[0].GetProperties().FirstOrDefault(u => u.GetCustomAttribute()?.IsPrimaryKey == true);
+ if (pkInfo != null)
+ {
+ var seedDataTypes = App.EffectiveTypes.Where(u => !u.IsInterface && !u.IsAbstract && u.IsClass && u.GetInterfaces()
+ .Any(i => i.HasImplementedRawGeneric(typeof(ISqlSugarEntitySeedData<>)) && i.GenericTypeArguments[0] == entityTypes[0])).ToList();
+ // 可能会重名的种子数据不作为过滤项
+ string doNotFilterFullName1 = $"{input.Position}.SeedData.{input.SeedDataName}";
+ string doNotFilterFullName2 = $"{input.Position}.{input.SeedDataName}"; // Core中的命名空间没有SeedData
+
+ PropertyInfo idPropertySeedData = records[0].GetType().GetProperty("Id");
+ for (int i = seedDataTypes.Count - 1; i >= 0; i--)
+ {
+ string fullName = seedDataTypes[i].FullName;
+ if ((fullName == doNotFilterFullName1) || (fullName == doNotFilterFullName2)) continue;
+
+ // 删除重复数据
+ var instance = Activator.CreateInstance(seedDataTypes[i]);
+ var hasDataMethod = seedDataTypes[i].GetMethod("HasData");
+ var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast