feat: initialize Evergreen Health backend

This commit is contained in:
fuenmao
2026-09-21 14:08:22 +08:00
commit 8553df5dd2
493 changed files with 75649 additions and 0 deletions
@@ -0,0 +1,184 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 自定义模板引擎
/// </summary>
public class CustomViewEngine : ViewEngineModel
{
/// <summary>
/// 库定位器
/// </summary>
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 LowerClassName { get; set; }
public string ProjectLastName { get; set; }
public string PagePath { get; set; } = "main";
public string PrintType { get; set; }
public string PrintName { get; set; }
public bool HasLikeQuery { get; set; }
public bool HasJoinTable { get; set; }
public bool HasEnumField { get; set; }
public bool HasDictField { get; set; }
public bool HasConstField { get; set; }
public bool HasSetStatus => TableField.Any(IsStatus);
public List<CodeGenConfig> TableField { get; set; }
public List<CodeGenConfig> ImportFieldList { get; set; }
public List<CodeGenConfig> UploadFieldList { get; set; }
public List<CodeGenConfig> QueryWhetherList { get; set; }
public List<CodeGenConfig> ApiTreeFieldList { get; set; }
public List<CodeGenConfig> DropdownFieldList { get; set; }
public List<CodeGenConfig> AddUpdateFieldList { get; set; }
public List<CodeGenConfig> PrimaryKeyFieldList { get; set; }
public List<TableUniqueConfigItem> TableUniqueConfigList { get; set; }
public List<CodeGenConfig> IgnoreUpdateFieldList => TableField.Where(u => u.WhetherAddUpdate == "N" && u.ColumnKey != "True" && u.WhetherCommon != "Y").ToList();
/// <summary>
/// 格式化主键查询条件
/// 例: PrimaryKeysFormat(" || ", "u.{0} == input.{0}")
/// 单主键返回 u.Id == input.Id
/// 组合主键返回 u.Id == input.Id || u.FkId == input.FkId
/// </summary>
/// <param name="separator">分隔符</param>
/// <param name="format">模板字符串</param>
/// <param name="lowerFirstLetter">字段首字母小写</param>
/// <returns></returns>
public string PrimaryKeysFormat(string separator, string format, bool lowerFirstLetter = false) => string.Join(separator, PrimaryKeyFieldList.Select(u => string.Format(format, lowerFirstLetter ? u.LowerPropertyName : u.PropertyName)));
/// <summary>
/// 注入的服务
/// </summary>
/// <returns></returns>
public Dictionary<string, string> InjectServiceMap
{
get
{
var injectMap = new Dictionary<string, string>();
if (UploadFieldList.Count > 0) injectMap.Add(nameof(SysFileService), ToLowerFirstLetter(nameof(SysFileService)));
if (DropdownFieldList.Count > 0 || ImportFieldList.Count > 0) injectMap.Add(nameof(ISqlSugarClient), ToLowerFirstLetter(nameof(ISqlSugarClient).TrimStart('I')));
if (ImportFieldList.Any(c => c.EffectType == "DictSelector")) injectMap.Add(nameof(SysDictTypeService), ToLowerFirstLetter(nameof(SysDictTypeService)));
return injectMap;
}
}
/// <summary>
/// 服务构造参数
/// </summary>
public string InjectServiceArgs => InjectServiceMap.Count > 0 ? ", " + string.Join(", ", InjectServiceMap.Select(kv => $"{kv.Key} {kv.Value}")) : "";
/// <summary>
/// 默认值列表
/// </summary>
public List<CodeGenConfig> DefaultValueList { get; set; }
/// <summary>
/// 判断字段是否为状态字段
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
public bool IsStatus(CodeGenConfig column) => column.NetType == nameof(StatusEnum);
/// <summary>
/// 获取首字母小写字符串
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public string ToLowerFirstLetter(string text) => string.IsNullOrWhiteSpace(text) ? text : text[..1].ToLower() + text[1..];
/// <summary>
/// 将基本字段类型转为可空类型
/// </summary>
/// <param name="netType"></param>
/// <returns></returns>
public string GetNullableNetType(string netType) => Regex.IsMatch(netType, "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? netType.TrimEnd('?') + "?" : netType;
/// <summary>
/// 获取前端表格列定义的属性
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
public string GetElTableColumnCustomProperty(CodeGenConfig column)
{
var content = $"prop='{column.LowerPropertyName}' label='{column.ColumnComment}'";
if (IsStatus(column)) content += $" v-auth=\"'{LowerClassName}:setStatus'\"";
if (column.WhetherSortable == "Y") content += " sortable='custom'";
return content;
}
/// <summary>
/// 设置默认值
/// </summary>
/// <returns></returns>
public string GetAddDefaultValue()
{
var content = "";
if (DefaultValueList.Count == 0)
{
var status = TableField.FirstOrDefault(IsStatus);
var orderNo = TableField.FirstOrDefault(c => c.NetType.TrimEnd('?') == "int" && c.PropertyName == nameof(SysUser.OrderNo));
if (status != null) content += $"{status.LowerPropertyName}: {(int)StatusEnum.Enable},";
if (orderNo != null) content += $"{orderNo.LowerPropertyName}: 100,";
}
else
{
foreach (var item in DefaultValueList)
{
if (!string.IsNullOrWhiteSpace(item.DefaultValue))
{
switch (item.EffectType)
{
case "InputNumber":
case "EnumSelector"://枚举和数字框,通过正则提取 数字:如 ('0')
content += $"{item.LowerPropertyName}: {Regex.Match(item.DefaultValue, @"\d+").Value},";
break;
case "Switch":
content += $"{item.LowerPropertyName}: {(item.DefaultValue == "1" ? true.ToString().ToLower() : false.ToString().ToLower())},";
break;
case "DatePicker"://忽略适配日期格式
break;
default:
content += $"{item.LowerPropertyName}: \"{item.DefaultValue}\",";// 如果是字符串 DefaultValue=('男')
break;
}
}
}
}
return content;
}
}
@@ -0,0 +1,234 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 代码生成详细配置参数
/// </summary>
public class CodeGenConfig
{
/// <summary>
/// 主键Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 代码生成主表ID
/// </summary>
public long CodeGenId { get; set; }
/// <summary>
/// 数据库字段名
/// </summary>
public string ColumnName { get; set; }
/// <summary>
/// 主外键
/// </summary>
public string ColumnKey { get; set; }
/// <summary>
/// 实体属性名
/// </summary>
public string PropertyName { get; set; }
/// <summary>
/// 字段数据长度
/// </summary>
public int ColumnLength { get; set; }
/// <summary>
/// 数据库字段名(首字母小写)
/// </summary>
public string LowerPropertyName => string.IsNullOrWhiteSpace(PropertyName) ? null : PropertyName[..1].ToLower() + PropertyName[1..];
/// <summary>
/// 字段描述
/// </summary>
public string ColumnComment { get; set; }
/// <summary>
/// .NET类型
/// </summary>
public string NetType { get; set; }
/// <summary>
/// 数据库中类型(物理类型)
/// </summary>
public string DataType { get; set; }
/// <summary>
/// 字段数据默认值
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// 可空.NET类型
/// </summary>
public string NullableNetType => Regex.IsMatch(NetType ?? "", "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? NetType.TrimEnd('?') + "?" : NetType;
/// <summary>
/// 作用类型(字典)
/// </summary>
public string EffectType { get; set; }
/// <summary>
/// 外键库标识
/// </summary>
public string FkConfigId { get; set; }
/// <summary>
/// 外键实体名称
/// </summary>
public string FkEntityName { get; set; }
/// <summary>
/// 外键表名称
/// </summary>
public string FkTableName { get; set; }
/// <summary>
/// 外键实体名称(首字母小写)
/// </summary>
public string LowerFkEntityName => string.IsNullOrWhiteSpace(FkEntityName) ? null : FkEntityName[..1].ToLower() + FkEntityName[1..];
/// <summary>
/// 外键链接字段
/// </summary>
public string FkLinkColumnName { get; set; }
/// <summary>
/// 外键显示字段
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public string FkDisplayColumns { get; set; }
/// <summary>
/// 外键显示字段
/// </summary>
public List<string> FkDisplayColumnList { get; set; }
/// <summary>
/// 外键显示字段(首字母小写)
/// </summary>
public List<string> LowerFkDisplayColumnsList => FkDisplayColumnList?.Select(name => name[..1].ToLower() + name[1..]).ToList();
/// <summary>
/// 外键显示字段.NET类型
/// </summary>
public string FkColumnNetType { get; set; }
/// <summary>
/// 父级字段
/// </summary>
public string PidColumn { get; set; }
/// <summary>
/// 字典code
/// </summary>
public string DictTypeCode { get; set; }
/// <summary>
/// 查询方式
/// </summary>
public string QueryType { get; set; }
/// <summary>
/// 是否是查询条件
/// </summary>
public string WhetherQuery { get; set; }
/// <summary>
/// 列表是否缩进(字典)
/// </summary>
public string WhetherRetract { get; set; }
/// <summary>
/// 是否必填(字典)
/// </summary>
public string WhetherRequired { get; set; }
/// <summary>
/// 是否可排序(字典)
/// </summary>
public string WhetherSortable { get; set; }
/// <summary>
/// 列表显示
/// </summary>
public string WhetherTable { get; set; }
/// <summary>
/// 增改
/// </summary>
public string WhetherAddUpdate { get; set; }
/// <summary>
/// 导入
/// </summary>
public string WhetherImport { get; set; }
/// <summary>
/// 是否是通用字段
/// </summary>
public string WhetherCommon { get; set; }
/// <summary>
/// 排序
/// </summary>
public int OrderNo { get; set; }
/// <summary>
/// 是否是选择器控件
/// </summary>
public bool IsSelectorEffectType => Regex.IsMatch(EffectType ?? "", "Selector$|ForeignKey", RegexOptions.IgnoreCase);
/// <summary>
/// 去掉尾部Id的属性名
/// </summary>
public string PropertyNameTrimEndId => PropertyName.TrimEnd("Id");
/// <summary>
/// 去掉尾部Id的属性名
/// </summary>
public string LowerPropertyNameTrimEndId => LowerPropertyName.TrimEnd("Id");
/// <summary>
/// 扩展属性名称
/// </summary>
public string ExtendedPropertyName => EffectType switch
{
"ForeignKey" => $"{PropertyName.TrimEnd("Id")}FkDisplayName",
"ApiTreeSelector" => $"{PropertyName.TrimEnd("Id")}DisplayName",
"DictSelector" => $"{PropertyName.TrimEnd("Id")}DictLabel",
"Upload" => $"{PropertyName.TrimEnd("Id")}Attachment",
"Upload_SingleFile" => $"{PropertyName.TrimEnd("Id")}Attachment",
_ => PropertyName
};
/// <summary>
/// 首字母小写的扩展属性名称
/// </summary>
public string LowerExtendedPropertyName
{
get
{
var displayPropertyName = ExtendedPropertyName;
if (string.IsNullOrWhiteSpace(displayPropertyName)) return null;
return displayPropertyName[..1].ToLower() + displayPropertyName[1..];
}
}
/// <summary>
/// 获取外键显示值语句
/// </summary>
/// <param name="tableAlias">表别名</param>
/// <param name="separator">多字段时的连接符</param>
/// <returns></returns>
public string GetDisplayColumn(string tableAlias, string separator = "-") => "$\"" + string.Join(separator, FkDisplayColumnList?.Select(name => $"{{{tableAlias}.{name}}}") ?? new List<string>()) + "\"";
}
@@ -0,0 +1,199 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 代码生成参数类
/// </summary>
public class CodeGenInput : BasePageInput
{
/// <summary>
/// 作者姓名
/// </summary>
public virtual string AuthorName { get; set; }
/// <summary>
/// 类名
/// </summary>
public virtual string ClassName { get; set; }
/// <summary>
/// 是否移除表前缀
/// </summary>
public virtual string TablePrefix { get; set; }
/// <summary>
/// 库定位器名
/// </summary>
public virtual string ConfigId { get; set; }
/// <summary>
/// 数据库名(保留字段)
/// </summary>
public virtual string DbName { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public virtual string DbType { get; set; }
/// <summary>
/// 数据库链接
/// </summary>
public virtual string ConnectionString { get; set; }
/// <summary>
/// 生成方式
/// </summary>
public virtual string GenerateType { get; set; }
/// <summary>
/// 数据库表名
/// </summary>
public virtual string TableName { get; set; }
/// <summary>
/// 命名空间
/// </summary>
public virtual string NameSpace { get; set; }
/// <summary>
/// 业务名(业务代码包名称)
/// </summary>
public virtual string BusName { get; set; }
/// <summary>
/// 功能名(数据库表名称)
/// </summary>
public virtual string TableComment { get; set; }
/// <summary>
/// 表唯一字段
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public virtual string TableUniqueConfig { get; set; }
/// <summary>
/// 表唯一字段列表
/// </summary>
public virtual List<TableUniqueConfigItem> TableUniqueList { get; set; }
/// <summary>
/// 菜单应用分类(应用编码)
/// </summary>
public virtual string MenuApplication { get; set; }
/// <summary>
/// 是否生成菜单
/// </summary>
public virtual bool GenerateMenu { get; set; }
/// <summary>
/// 菜单父级
/// </summary>
public virtual long? MenuPid { get; set; }
/// <summary>
/// 菜单图标
/// </summary>
public virtual string MenuIcon { get; set; }
/// <summary>
/// 页面目录
/// </summary>
public virtual string PagePath { get; set; }
/// <summary>
/// 支持打印类型
/// </summary>
public virtual string PrintType { get; set; }
/// <summary>
/// 打印模版名称
/// </summary>
public virtual string PrintName { get; set; }
}
public class AddCodeGenInput : CodeGenInput
{
/// <summary>
/// 数据库表名
/// </summary>
[Required(ErrorMessage = "数据库表名不能为空")]
public override string TableName { get; set; }
/// <summary>
/// 业务名(业务代码包名称)
/// </summary>
[Required(ErrorMessage = "业务名不能为空")]
public override string BusName { get; set; }
/// <summary>
/// 命名空间
/// </summary>
[Required(ErrorMessage = "命名空间不能为空")]
public override string NameSpace { get; set; }
/// <summary>
/// 作者姓名
/// </summary>
[Required(ErrorMessage = "作者姓名不能为空")]
public override string AuthorName { get; set; }
///// <summary>
///// 类名
///// </summary>
//[Required(ErrorMessage = "类名不能为空")]
//public override string ClassName { get; set; }
///// <summary>
///// 是否移除表前缀
///// </summary>
//[Required(ErrorMessage = "是否移除表前缀不能为空")]
//public override string TablePrefix { get; set; }
/// <summary>
/// 生成方式
/// </summary>
[Required(ErrorMessage = "生成方式不能为空")]
public override string GenerateType { get; set; }
///// <summary>
///// 功能名(数据库表名称)
///// </summary>
//[Required(ErrorMessage = "数据库表名不能为空")]
//public override string TableComment { get; set; }
/// <summary>
/// 是否生成菜单
/// </summary>
[Required(ErrorMessage = "是否生成菜单不能为空")]
public override bool GenerateMenu { get; set; }
}
public class DeleteCodeGenInput
{
/// <summary>
/// 代码生成器Id
/// </summary>
[Required(ErrorMessage = "代码生成器Id不能为空")]
public long Id { get; set; }
}
public class UpdateCodeGenInput : AddCodeGenInput
{
/// <summary>
/// 代码生成器Id
/// </summary>
[Required(ErrorMessage = "代码生成器Id不能为空")]
public long Id { get; set; }
}
public class QueryCodeGenInput : DeleteCodeGenInput
{
}
@@ -0,0 +1,83 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 代码生成参数类
/// </summary>
public class CodeGenOutput
{
/// <summary>
/// 代码生成器Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 作者姓名
/// </summary>
public string AuthorName { get; set; }
/// <summary>
/// 类名
/// </summary>
public string ClassName { get; set; }
/// <summary>
/// 是否移除表前缀
/// </summary>
public string TablePrefix { get; set; }
/// <summary>
/// 生成方式
/// </summary>
public string GenerateType { get; set; }
/// <summary>
/// 数据库表名
/// </summary>
public string TableName { get; set; }
/// <summary>
/// 包名
/// </summary>
public string PackageName { get; set; }
/// <summary>
/// 业务名(业务代码包名称)
/// </summary>
public string BusName { get; set; }
/// <summary>
/// 功能名(数据库表名称)
/// </summary>
public string TableComment { get; set; }
/// <summary>
/// 菜单应用分类(应用编码)
/// </summary>
public string MenuApplication { get; set; }
/// <summary>
/// 是否生成菜单
/// </summary>
public bool GenerateMenu { get; set; }
/// <summary>
/// 菜单父级
/// </summary>
public long? MenuPid { get; set; }
/// <summary>
/// 支持打印类型
/// </summary>
public string PrintType { get; set; }
/// <summary>
/// 打印模版名称
/// </summary>
public string PrintName { get; set; }
}
@@ -0,0 +1,68 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 数据库表列
/// </summary>
public class ColumnOuput
{
/// <summary>
/// 字段名
/// </summary>
public string ColumnName { get; set; }
/// <summary>
/// 实体的Property名
/// </summary>
public string PropertyName { get; set; }
/// <summary>
/// 字段数据长度
/// </summary>
public int ColumnLength { get; set; }
/// <summary>
/// 数据库中类型
/// </summary>
public string DataType { get; set; }
/// <summary>
/// 字段数据默认值
/// </summary>
public string DefaultValue { get; set; }
/// <summary>
/// 是否为主键
/// </summary>
public bool IsPrimarykey { get; set; }
/// <summary>
/// 是否允许为空
/// </summary>
public bool IsNullable { get; set; }
/// <summary>
/// .NET字段类型
/// </summary>
public string NetType { get; set; }
/// <summary>
/// 字典编码
/// </summary>
public string DictTypeCode { get; set; }
/// <summary>
/// 字段描述
/// </summary>
public string ColumnComment { get; set; }
/// <summary>
/// 主外键
/// </summary>
public string ColumnKey { get; set; }
}
@@ -0,0 +1,33 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 数据库
/// </summary>
public class DatabaseOutput
{
/// <summary>
/// 库定位器名
/// </summary>
public string ConfigId { get; set; }
/// <summary>
/// 库名
/// </summary>
public string DbNickName { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public SqlSugar.DbType DbType { get; set; }
/// <summary>
/// 数据库连接字符串
/// </summary>
public string ConnectionString { get; set; }
}
@@ -0,0 +1,43 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 数据库表
/// </summary>
public class TableOutput
{
/// <summary>
/// 库定位器名
/// </summary>
public string ConfigId { get; set; }
/// <summary>
/// 表名(字母形式的)
/// </summary>
public string TableName { get; set; }
/// <summary>
/// 实体名称
/// </summary>
public string EntityName { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public string CreateTime { get; set; }
/// <summary>
/// 更新时间
/// </summary>
public string UpdateTime { get; set; }
/// <summary>
/// 表名称描述(功能名)
/// </summary>
public string TableComment { get; set; }
}
@@ -0,0 +1,31 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 表唯一配置项
/// </summary>
public class TableUniqueConfigItem
{
/// <summary>
/// 字段列表
/// </summary>
public List<string> Columns { get; set; }
/// <summary>
/// 描述信息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 格式化查询条件
/// </summary>
/// <param name="separator">分隔符</param>
/// <param name="format">模板字符串</param>
/// <returns></returns>
public string Format(string separator, string format) => string.Join(separator, Columns.Select(name => string.Format(format, name)));
}
@@ -0,0 +1,256 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统代码生成配置服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 260)]
public class SysCodeGenConfigService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
public SysCodeGenConfigService(ISqlSugarClient db)
{
_db = db;
}
/// <summary>
/// 获取代码生成配置列表 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取代码生成配置列表")]
public async Task<List<CodeGenConfig>> GetList([FromQuery] CodeGenConfig input)
{
return await _db.Queryable<SysCodeGenConfig>()
.Where(u => u.CodeGenId == input.CodeGenId)
.Select<CodeGenConfig>()
.Mapper(u =>
{
u.NetType = (u.EffectType == "EnumSelector" ? u.DictTypeCode : u.NetType);
u.FkDisplayColumnList = u.FkDisplayColumns?.Split(",").ToList();
})
.OrderBy(u => new { u.OrderNo, u.Id })
.ToListAsync();
}
/// <summary>
/// 更新代码生成配置 🔖
/// </summary>
/// <param name="inputList"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Update"), HttpPost]
[DisplayName("更新代码生成配置")]
public async Task UpdateCodeGenConfig(List<CodeGenConfig> inputList)
{
if (inputList == null || inputList.Count < 1) return;
inputList.ForEach(e =>
{
e.FkDisplayColumns = e.FkDisplayColumnList?.Count > 0 ? string.Join(",", e.FkDisplayColumnList) : null;
});
await _db.Updateable(inputList.Adapt<List<SysCodeGenConfig>>())
.IgnoreColumns(u => new { u.ColumnLength, u.ColumnName, u.PropertyName })
.ExecuteCommandAsync();
}
/// <summary>
/// 删除代码生成配置
/// </summary>
/// <param name="codeGenId"></param>
/// <returns></returns>
[NonAction]
public async Task DeleteCodeGenConfig(long codeGenId)
{
await _db.Deleteable<SysCodeGenConfig>().Where(u => u.CodeGenId == codeGenId).ExecuteCommandAsync();
}
/// <summary>
/// 获取代码生成配置详情 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取代码生成配置详情")]
public async Task<SysCodeGenConfig> GetDetail([FromQuery] CodeGenConfig input)
{
return await _db.Queryable<SysCodeGenConfig>().FirstAsync(u => u.Id == input.Id);
}
/// <summary>
/// 批量增加代码生成配置
/// </summary>
/// <param name="tableColumnOutputList"></param>
/// <param name="codeGenerate"></param>
[NonAction]
public void AddList(List<ColumnOuput> tableColumnOutputList, SysCodeGen codeGenerate)
{
if (tableColumnOutputList == null) return;
var codeGenConfigs = new List<SysCodeGenConfig>();
var orderNo = 100;
foreach (var tableColumn in tableColumnOutputList)
{
var codeGenConfig = new SysCodeGenConfig();
var yesOrNo = YesNoEnum.Y.ToString();
if (Convert.ToBoolean(tableColumn.ColumnKey)) yesOrNo = YesNoEnum.N.ToString();
if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName))
{
codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString();
yesOrNo = YesNoEnum.N.ToString();
}
else
{
codeGenConfig.WhetherCommon = YesNoEnum.N.ToString();
}
codeGenConfig.CodeGenId = codeGenerate.Id;
codeGenConfig.ColumnName = tableColumn.ColumnName; // 字段名
codeGenConfig.PropertyName = tableColumn.PropertyName;// 实体属性名
codeGenConfig.ColumnLength = tableColumn.ColumnLength;// 长度
codeGenConfig.ColumnComment = tableColumn.ColumnComment;
codeGenConfig.NetType = tableColumn.NetType;
codeGenConfig.DefaultValue = tableColumn.DefaultValue;
codeGenConfig.WhetherRetract = YesNoEnum.N.ToString();
// 生成代码时,主键并不是必要输入项,故一定要排除主键字段
codeGenConfig.WhetherRequired = (tableColumn.IsNullable || tableColumn.IsPrimarykey) ? YesNoEnum.N.ToString() : YesNoEnum.Y.ToString();
codeGenConfig.WhetherQuery = yesOrNo;
codeGenConfig.WhetherImport = yesOrNo;
codeGenConfig.WhetherAddUpdate = yesOrNo;
codeGenConfig.WhetherTable = yesOrNo;
codeGenConfig.ColumnKey = tableColumn.ColumnKey;
codeGenConfig.DataType = tableColumn.DataType;
codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType);
codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString();
codeGenConfig.OrderNo = orderNo;
codeGenConfigs.Add(codeGenConfig);
if (!string.IsNullOrWhiteSpace(tableColumn.DictTypeCode))
{
codeGenConfig.QueryType = "==";
codeGenConfig.DictTypeCode = tableColumn.DictTypeCode;
codeGenConfig.EffectType = tableColumn.DictTypeCode.EndsWith("Enum") ? "EnumSelector" : "DictSelector";
}
orderNo += 10; // 每个配置排序间隔10
}
// 多库代码生成---这里要切回主库
var provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId);
provider.Insertable(codeGenConfigs).ExecuteCommand();
}
/// <summary>
/// 批量更新代码字段:先删除再新增,会保留历史字段操作类型
/// </summary>
/// <param name="tableColumnOutputList"></param>
/// <param name="codeGenId"></param>
[NonAction]
public async Task UpdateList(List<ColumnOuput> tableColumnOutputList, long codeGenId)
{
if (tableColumnOutputList == null) return;
//获取历史数据
var oldList = await GetList(new CodeGenConfig() { CodeGenId = codeGenId, });
//删除历史数据
await DeleteCodeGenConfig(codeGenId);
var codeGenConfigs = new List<SysCodeGenConfig>();
var orderNo = 100;
foreach (var tableColumn in tableColumnOutputList)
{
var oldItem = oldList.FirstOrDefault(u => u.ColumnName == tableColumn.ColumnName);
var codeGenConfig = new SysCodeGenConfig();
var yesOrNo = YesNoEnum.Y.ToString();
if (Convert.ToBoolean(tableColumn.ColumnKey)) yesOrNo = YesNoEnum.N.ToString();
if (CodeGenUtil.IsCommonColumn(tableColumn.PropertyName))
{
codeGenConfig.WhetherCommon = YesNoEnum.Y.ToString();
yesOrNo = YesNoEnum.N.ToString();
}
else
{
codeGenConfig.WhetherCommon = YesNoEnum.N.ToString();
}
codeGenConfig.CodeGenId = codeGenId;
codeGenConfig.ColumnName = tableColumn.ColumnName; // 字段名
codeGenConfig.PropertyName = tableColumn.PropertyName;// 实体属性名
codeGenConfig.ColumnLength = tableColumn.ColumnLength;// 长度
codeGenConfig.ColumnComment = tableColumn.ColumnComment;
codeGenConfig.NetType = tableColumn.NetType;
codeGenConfig.DefaultValue = tableColumn.DefaultValue;
codeGenConfig.WhetherRetract = YesNoEnum.N.ToString();
// 生成代码时,主键并不是必要输入项,故一定要排除主键字段
codeGenConfig.WhetherRequired = (tableColumn.IsNullable || tableColumn.IsPrimarykey) ? YesNoEnum.N.ToString() : YesNoEnum.Y.ToString();
codeGenConfig.WhetherQuery = yesOrNo;
codeGenConfig.WhetherImport = yesOrNo;
codeGenConfig.WhetherAddUpdate = yesOrNo;
codeGenConfig.WhetherTable = yesOrNo;
codeGenConfig.ColumnKey = tableColumn.ColumnKey;
codeGenConfig.DataType = tableColumn.DataType;
codeGenConfig.EffectType = CodeGenUtil.DataTypeToEff(codeGenConfig.NetType);
codeGenConfig.QueryType = GetDefaultQueryType(codeGenConfig); // QueryTypeEnum.eq.ToString();
codeGenConfig.OrderNo = orderNo;
if (oldItem != null)
{
//如果历史存在,则继承
codeGenConfig.WhetherQuery = oldItem.WhetherQuery;
codeGenConfig.WhetherImport = oldItem.WhetherImport;
codeGenConfig.WhetherAddUpdate = oldItem.WhetherAddUpdate;
codeGenConfig.WhetherTable = oldItem.WhetherTable;
codeGenConfig.EffectType = oldItem.EffectType;
codeGenConfig.FkConfigId = oldItem.FkConfigId;
codeGenConfig.FkEntityName = oldItem.FkEntityName;
codeGenConfig.FkTableName = oldItem.FkTableName;
codeGenConfig.FkDisplayColumns = oldItem.FkDisplayColumns;
codeGenConfig.FkLinkColumnName = oldItem.FkLinkColumnName;
codeGenConfig.FkColumnNetType = oldItem.FkColumnNetType;
}
codeGenConfigs.Add(codeGenConfig);
if (!string.IsNullOrWhiteSpace(tableColumn.DictTypeCode))
{
codeGenConfig.QueryType = "==";
codeGenConfig.DictTypeCode = tableColumn.DictTypeCode;
codeGenConfig.EffectType = tableColumn.DictTypeCode.EndsWith("Enum") ? "EnumSelector" : "DictSelector";
}
orderNo += 10; // 每个配置排序间隔10
}
// 多库代码生成---这里要切回主库
var provider = _db.AsTenant().GetConnectionScope(SqlSugarConst.MainConfigId);
provider.Insertable(codeGenConfigs).ExecuteCommand();
}
/// <summary>
/// 默认查询类型
/// </summary>
/// <param name="codeGenConfig"></param>
/// <returns></returns>
private static string GetDefaultQueryType(SysCodeGenConfig codeGenConfig)
{
return (codeGenConfig.NetType?.TrimEnd('?')) switch
{
"string" => "like",
"DateTime" => "~",
_ => "==",
};
}
}
@@ -0,0 +1,825 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.IO.Compression;
namespace Admin.NET.Core.Service;
/// <summary>
/// 系统代码生成器服务 🧩
/// </summary>
[ApiDescriptionSettings(Order = 270)]
public class SysCodeGenService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
private readonly SysCodeGenConfigService _codeGenConfigService;
private readonly DbConnectionOptions _dbConnectionOptions;
private readonly CodeGenOptions _codeGenOptions;
private readonly SysMenuService _sysMenuService;
private readonly IViewEngine _viewEngine;
private readonly UserManager _userManager;
public SysCodeGenService(ISqlSugarClient db,
IOptions<DbConnectionOptions> dbConnectionOptions,
SysCodeGenConfigService codeGenConfigService,
IOptions<CodeGenOptions> codeGenOptions,
SysMenuService sysMenuService,
UserManager userManager,
IViewEngine viewEngine)
{
_db = db;
_viewEngine = viewEngine;
_userManager = userManager;
_sysMenuService = sysMenuService;
_codeGenOptions = codeGenOptions.Value;
_codeGenConfigService = codeGenConfigService;
_dbConnectionOptions = dbConnectionOptions.Value;
}
/// <summary>
/// 获取代码生成分页列表 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取代码生成分页列表")]
public async Task<SqlSugarPagedList<SysCodeGen>> Page(CodeGenInput input)
{
return await _db.Queryable<SysCodeGen>()
.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);
}
/// <summary>
/// 增加代码生成 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Add"), HttpPost]
[DisplayName("增加代码生成")]
public async Task AddCodeGen(AddCodeGenInput input)
{
var isExist = await _db.Queryable<SysCodeGen>().Where(u => u.TableName == input.TableName).AnyAsync();
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
if (input.TableUniqueList?.Count > 0) input.TableUniqueConfig = JSON.Serialize(input.TableUniqueList);
var codeGen = input.Adapt<SysCodeGen>();
var newCodeGen = await _db.Insertable(codeGen).ExecuteReturnEntityAsync();
// 增加配置表
_codeGenConfigService.AddList(GetColumnList(input), newCodeGen);
}
/// <summary>
/// 更新代码生成 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Update"), HttpPost]
[DisplayName("更新代码生成")]
public async Task UpdateCodeGen(UpdateCodeGenInput input)
{
var isExist = await _db.Queryable<SysCodeGen>().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
var oldRecord = await _db.Queryable<SysCodeGen>().FirstAsync(u => u.Id == input.Id);
try
{
// 开启事务
_db.AsTenant().BeginTran();
if (input.GenerateMenu)
{
var oldTitle = $"{oldRecord.BusName}管理";
var newTitle = $"{input.BusName}管理";
var updateObj = await _db.Queryable<SysMenu>().FirstAsync(u => u.Title == oldTitle);
if (updateObj != null)
{
updateObj.Title = newTitle;
var result = _db.Updateable(updateObj).UpdateColumns(it => new { it.Title }).ExecuteCommand();
_sysMenuService.DeleteMenuCache();
}
}
if (input.TableUniqueList?.Count > 0) input.TableUniqueConfig = JSON.Serialize(input.TableUniqueList);
var codeGen = input.Adapt<SysCodeGen>();
await _db.Updateable(codeGen).ExecuteCommandAsync();
// 仅当数据表名称发生了变化,才更新配置表
//if (oldRecord.TableName != input.TableName)
//{
await _codeGenConfigService.DeleteCodeGenConfig(codeGen.Id);
_codeGenConfigService.AddList(GetColumnList(input.Adapt<AddCodeGenInput>()), codeGen);
//}
_db.AsTenant().CommitTran();
}
catch (Exception ex)
{
_db.AsTenant().RollbackTran();
throw Oops.Oh(ex);
}
}
/// <summary>
/// 同步代码字段(保留历史作用类型) 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "SyncField"), HttpPost]
[DisplayName("同步代码字段")]
public async Task SyncCodeFieldGen(UpdateCodeGenInput input)
{
var isExist = await _db.Queryable<SysCodeGen>().AnyAsync(u => u.TableName == input.TableName && u.Id != input.Id);
if (isExist) throw Oops.Oh(ErrorCodeEnum.D1400);
try
{
// 开启事务
_db.AsTenant().BeginTran();
await _codeGenConfigService.UpdateList(GetColumnList(input.Adapt<AddCodeGenInput>()), input.Id);
_db.AsTenant().CommitTran();
}
catch (Exception ex)
{
_db.AsTenant().RollbackTran();
throw Oops.Oh(ex);
}
}
/// <summary>
/// 删除代码生成 🔖
/// </summary>
/// <param name="inputs"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
[DisplayName("删除代码生成")]
public async Task DeleteCodeGen(List<DeleteCodeGenInput> inputs)
{
if (inputs == null || inputs.Count < 1) return;
var codeGenConfigTaskList = new List<Task>();
inputs.ForEach(u =>
{
_db.Deleteable<SysCodeGen>().In(u.Id).ExecuteCommand();
// 删除配置表
codeGenConfigTaskList.Add(_codeGenConfigService.DeleteCodeGenConfig(u.Id));
});
await Task.WhenAll(codeGenConfigTaskList);
}
/// <summary>
/// 获取代码生成详情 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取代码生成详情")]
public async Task<SysCodeGen> GetDetail([FromQuery] QueryCodeGenInput input)
{
return await _db.Queryable<SysCodeGen>().SingleAsync(u => u.Id == input.Id);
}
/// <summary>
/// 获取数据库库集合 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取数据库库集合")]
public async Task<List<DatabaseOutput>> GetDatabaseList()
{
var dbConfigs = _dbConnectionOptions.ConnectionConfigs;
return await Task.FromResult(dbConfigs.Adapt<List<DatabaseOutput>>());
}
/// <summary>
/// 获取数据库表(实体)集合 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取数据库表(实体)集合")]
public async Task<List<TableOutput>> GetTableList(string configId = SqlSugarConst.MainConfigId)
{
var provider = _db.AsTenant().GetConnectionScope(configId);
var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false); // 不能走缓存,否则切库不起作用
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId));
// var dbTableNames = dbTableInfos.Select(u => u.Name.ToLower()).ToList();
IEnumerable<EntityInfo> entityInfos = await GetEntityInfos(configId);
var tableOutputList = new List<TableOutput>();
foreach (var item in entityInfos)
{
var tbConfigId = item.Type.GetCustomAttribute<TenantAttribute>()?.configId as string ?? SqlSugarConst.MainConfigId;
if (item.Type.IsDefined(typeof(LogTableAttribute))) tbConfigId = SqlSugarConst.LogConfigId;
if (tbConfigId != configId) continue;
var table = dbTableInfos.FirstOrDefault(u => string.Equals(u.Name, (config!.DbSettings.EnableUnderLine ? item.DbTableName.ToUnderLine() : item.DbTableName), StringComparison.CurrentCultureIgnoreCase));
if (table == null) continue;
tableOutputList.Add(new TableOutput
{
ConfigId = configId,
EntityName = item.EntityName,
TableName = table.Name,
TableComment = item.TableDescription
});
}
return tableOutputList;
}
/// <summary>
/// 根据表名获取列集合 🔖
/// </summary>
/// <returns></returns>
[DisplayName("根据表名获取列集合")]
public List<ColumnOuput> GetColumnListByTableName([Required] string tableName, string configId = SqlSugarConst.MainConfigId)
{
// 切库---多库代码生成用
var provider = _db.AsTenant().GetConnectionScope(configId);
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId) ?? throw Oops.Oh(ErrorCodeEnum.D1401);
if (config.DbSettings.EnableUnderLine) tableName = tableName.ToUnderLine();
// 获取实体类型属性
var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName);
if (entityType == null) return null;
var properties = GetEntityInfos(configId).Result.First(e => e.DbTableName.EndsWithIgnoreCase(tableName)).Type.GetProperties()
.Where(e => e.GetCustomAttribute<SugarColumn>()?.IsIgnore == false).Select(e => new
{
PropertyName = e.Name,
ColumnComment = e.GetCustomAttribute<SugarColumn>()?.ColumnDescription,
ColumnName = e.GetCustomAttribute<SugarColumn>()?.ColumnName ?? e.Name
}).ToList();
// 按原始类型的顺序获取所有实体类型属性(不包含导航属性,会返回null)
var columnList = provider.DbMaintenance.GetColumnInfosByTableName(tableName).Select(u => new ColumnOuput
{
ColumnName = config!.DbSettings.EnableUnderLine ? u.DbColumnName.ToUnderLine() : u.DbColumnName,
ColumnKey = u.IsPrimarykey.ToString(),
DataType = u.DataType.ToString(),
NetType = CodeGenUtil.ConvertDataType(u, provider.CurrentConnectionConfig.DbType),
ColumnComment = u.ColumnDescription
}).ToList();
foreach (var column in columnList)
{
// ToLowerInvariant 将字段名转成小写再比较,避免因大小写不一致导致无法匹配(pgsql创建表会默认全小写,而我们的实体中又是大写,就会匹配不上)
var property = properties.FirstOrDefault(e => (config!.DbSettings.EnableUnderLine ? e.ColumnName.ToUnderLine() : e.ColumnName).ToLowerInvariant() == column.ColumnName.ToLowerInvariant());
column.ColumnComment ??= property?.ColumnComment;
column.PropertyName = property?.PropertyName;
}
return columnList;
}
/// <summary>
/// 获取数据表列(实体属性)集合
/// </summary>
/// <returns></returns>
private List<ColumnOuput> GetColumnList([FromQuery] AddCodeGenInput input)
{
var entityType = GetEntityInfos(input.ConfigId).GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName);
if (entityType == null) return null;
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId);
var dbTableName = config!.DbSettings.EnableUnderLine ? entityType.DbTableName.ToUnderLine() : entityType.DbTableName;
// 切库---多库代码生成用
var provider = _db.AsTenant().GetConnectionScope(!string.IsNullOrEmpty(input.ConfigId) ? input.ConfigId : SqlSugarConst.MainConfigId);
var entityBasePropertyNames = CodeGenUtil.GetPropertyInfoArray(typeof(EntityBaseTenant))?.Select(p => p.Name).ToArray();
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,
DefaultValue = u.DefaultValue,
}).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 => string.Equals((u.GetCustomAttribute<SugarColumn>()?.ColumnName ?? ""), columnOutput.ColumnName, StringComparison.CurrentCultureIgnoreCase)) ??
entityProperties.FirstOrDefault(u => u.GetCustomAttribute<SugarColumn>() != 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<SugarColumn>()!.ColumnDescription;
var propertyType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
if (propertyInfo.PropertyType.IsEnum || (propertyType?.IsEnum ?? false))
{
columnOutput.DictTypeCode = (propertyType ?? propertyInfo.PropertyType).Name;
}
else
{
var dict = propertyInfo.GetCustomAttribute<DictAttribute>();
if (dict != null) columnOutput.DictTypeCode = dict.DictTypeCode;
}
}
else
{
result.RemoveAt(i); // 移除没有定义此属性的字段
}
}
return result;
}
/// <summary>
/// 获取库表信息
/// </summary>
/// <returns></returns>
private async Task<IEnumerable<EntityInfo>> GetEntityInfos(string configId)
{
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId) ?? throw Oops.Oh(ErrorCodeEnum.D1401);
var entityInfos = new List<EntityInfo>();
var type = typeof(SugarTable);
var types = new List<Type>();
if (_codeGenOptions.EntityAssemblyNames != null)
{
types = App.EffectiveTypes.Where(c => c.IsClass)
.Where(c => _codeGenOptions.EntityAssemblyNames.Contains(c.Assembly.GetName().Name) || _codeGenOptions.EntityAssemblyNames.Any(name => c.Assembly.GetName().Name!.Contains(name)))
.ToList();
}
Type[] cosType = types.Where(o => IsMyAttribute(Attribute.GetCustomAttributes(o, true))).ToArray();
foreach (var ct in cosType)
{
var sugarAttribute = ct.GetCustomAttributes(type, true).FirstOrDefault();
var description = "";
var des = ct.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (des.Length > 0) description = ((DescriptionAttribute)des[0]).Description;
var dbTableName = sugarAttribute == null || string.IsNullOrWhiteSpace(((SugarTable)sugarAttribute).TableName) ? ct.Name : ((SugarTable)sugarAttribute).TableName;
if (config.DbSettings.EnableUnderLine) dbTableName = dbTableName.ToUnderLine();
entityInfos.Add(new EntityInfo
{
EntityName = ct.Name,
DbTableName = dbTableName,
TableDescription = sugarAttribute == null ? description : ((SugarTable)sugarAttribute).TableDescription,
Type = ct
});
}
return await Task.FromResult(entityInfos);
bool IsMyAttribute(Attribute[] o) => o.Any(a => a.GetType() == type);
}
/// <summary>
/// 获取程序保存位置 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取程序保存位置")]
public List<string> GetApplicationNamespaces()
{
return _codeGenOptions.BackendApplicationNamespaces;
}
/// <summary>
/// 代码生成到本地 🔖
/// </summary>
/// <returns></returns>
[UnitOfWork]
[DisplayName("代码生成到本地")]
public async Task<dynamic> RunLocal(SysCodeGen input)
{
if (string.IsNullOrEmpty(input.GenerateType))
input.GenerateType = "200";
// 先删除该表已生成的菜单列表
List<string> 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, result) = await RenderTemplateAsync(input);
var templatePathList = GetTemplatePathList(input);
for (var i = 0; i < templatePathList.Count; i++)
{
var content = result.GetValueOrDefault(templatePathList[i]?.TrimEnd(".vm"));
if (string.IsNullOrWhiteSpace(content)) continue;
var dirPath = new DirectoryInfo(targetPathList[i]).Parent!.FullName;
if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);
_ = File.WriteAllTextAsync(targetPathList[i], content, Encoding.UTF8);
}
if (input.GenerateMenu) await AddOrUpdateMenu(input.TableName, input.BusName, input.MenuPid ?? 0, input.MenuIcon, input.PagePath, tableFieldList);
// 非ZIP压缩返回空
if (!input.GenerateType.StartsWith('1')) return null;
// 判断是否存在同名称文件
string downloadPath = zipPath + ".zip";
if (File.Exists(downloadPath)) File.Delete(downloadPath);
// 创建zip文件并返回下载地址
ZipFile.CreateFromDirectory(zipPath, downloadPath);
return new { url = $"{App.HttpContext.Request.Scheme}://{App.HttpContext.Request.Host.Value}/codeGen/{input.TableName}.zip" };
}
/// <summary>
/// 获取代码生成预览 🔖
/// </summary>
/// <returns></returns>
[DisplayName("获取代码生成预览")]
public async Task<Dictionary<string, string>> Preview(SysCodeGen input)
{
var (_, result) = await RenderTemplateAsync(input);
return result;
}
/// <summary>
/// 渲染模板
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private async Task<(List<CodeGenConfig> tableFieldList, Dictionary<string, string> result)> RenderTemplateAsync(SysCodeGen input)
{
var tableFieldList = await _codeGenConfigService.GetList(new CodeGenConfig { CodeGenId = input.Id }); // 字段集合
var joinTableList = tableFieldList.Where(u => u.EffectType is "Upload" or "ForeignKey" or "ApiTreeSelector").ToList(); // 需要连表查询的字段
var data = new CustomViewEngine
{
ConfigId = input.ConfigId,
BusName = input.BusName,
PagePath = input.PagePath,
NameSpace = input.NameSpace,
ClassName = input.TableName,
PrintType = input.PrintType,
PrintName = input.PrintName,
AuthorName = input.AuthorName,
ProjectLastName = input.NameSpace!.Split('.').Last(),
LowerClassName = input.TableName!.ToFirstLetterLowerCase(),
TableUniqueConfigList = input.TableUniqueList ?? new(),
TableField = tableFieldList,
QueryWhetherList = tableFieldList.Where(u => u.WhetherQuery == "Y").ToList(),
ImportFieldList = tableFieldList.Where(u => u.WhetherImport == "Y").ToList(),
UploadFieldList = tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "Upload_SingleFile").ToList(),
PrimaryKeyFieldList = tableFieldList.Where(c => c.ColumnKey == "True").ToList(),
AddUpdateFieldList = tableFieldList.Where(u => u.WhetherAddUpdate == "Y").ToList(),
ApiTreeFieldList = tableFieldList.Where(u => u.EffectType == "ApiTreeSelector").ToList(),
DropdownFieldList = tableFieldList.Where(u => u.EffectType is "ForeignKey" or "ApiTreeSelector").ToList(),
DefaultValueList = tableFieldList.Where(u => u.DefaultValue != null && u.DefaultValue.Length > 0).ToList(),
HasJoinTable = joinTableList.Count > 0,
HasDictField = tableFieldList.Any(u => u.EffectType == "DictSelector"),
HasEnumField = tableFieldList.Any(u => u.EffectType == "EnumSelector"),
HasConstField = tableFieldList.Any(u => u.EffectType == "ConstSelector"),
HasLikeQuery = tableFieldList.Any(c => c.WhetherQuery == "Y" && c.QueryType == "like")
};
// 获取模板文件并替换
var templatePathList = GetTemplatePathList();
var templatePath = Path.Combine(App.WebHostEnvironment.WebRootPath, "template");
var result = new Dictionary<string, string>();
foreach (var path in templatePathList)
{
var templateFilePath = Path.Combine(templatePath, path);
if (!File.Exists(templateFilePath)) continue;
var tContent = await File.ReadAllTextAsync(templateFilePath);
var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, data, builderAction: 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");
});
result.Add(path?.TrimEnd(".vm"), tResult);
}
return (tableFieldList, result);
}
/// <summary>
/// 添加或更新菜单
/// </summary>
/// <param name="className"></param>
/// <param name="busName"></param>
/// <param name="pid"></param>
/// <param name="menuIcon"></param>
/// <param name="pagePath"></param>
/// <param name="tableFieldList"></param>
/// <returns></returns>
private async Task AddOrUpdateMenu(string className, string busName, long pid, string menuIcon, string pagePath, List<CodeGenConfig> tableFieldList)
{
var title = $"{busName}管理";
var lowerClassName = className.ToFirstLetterLowerCase();
var menuType = pid == 0 ? MenuTypeEnum.Dir : MenuTypeEnum.Menu;
// 查询是否已有主菜单
var existingMenu = await _db.Queryable<SysMenu>()
.Where(m => m.Title == title && m.Type == menuType && m.Pid == pid)
.FirstAsync();
string parentPath = "";
if (pid != 0)
{
var parent = await _db.Queryable<SysMenu>().FirstAsync(m => m.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505);
parentPath = parent.Path;
}
long menuId;
if (existingMenu == null)
{
// 不存在则新增
var newMenu = new SysMenu
{
Pid = pid,
Title = title,
Type = menuType,
Icon = menuIcon ?? "menu",
Path = (pid == 0 ? "/" : parentPath + "/") + className.ToLower(),
Component = pid == 0 ? "Layout" : $"/{pagePath}/{lowerClassName}/index"
};
menuId = await _sysMenuService.AddMenu(newMenu.Adapt<AddMenuInput>());
}
else
{
// 存在则更新
existingMenu.Icon = menuIcon;
existingMenu.Path = (pid == 0 ? "/" : parentPath + "/") + className.ToLower();
existingMenu.Component = pid == 0 ? "Layout" : $"/{pagePath}/{lowerClassName}/index";
await _sysMenuService.UpdateMenu(existingMenu.Adapt<UpdateMenuInput>());
menuId = existingMenu.Id;
}
// 定义应有的按钮
var orderNo = 100;
var newButtons = new List<SysMenu>
{
new() { Title = "查询", Permission = $"{lowerClassName}:page", OrderNo = orderNo += 10 },
new() { Title = "详情", Permission = $"{lowerClassName}:detail", OrderNo = orderNo += 10 },
new() { Title = "增加", Permission = $"{lowerClassName}:add", OrderNo = orderNo += 10 },
new() { Title = "编辑", Permission = $"{lowerClassName}:update", OrderNo = orderNo += 10 },
new() { Title = "删除", Permission = $"{lowerClassName}:delete", OrderNo = orderNo += 10 },
new() { Title = "批量删除", Permission = $"{lowerClassName}:batchDelete", OrderNo = orderNo += 10 },
new() { Title = "设置状态", Permission = $"{lowerClassName}:setStatus", OrderNo = orderNo += 10 },
new() { Title = "打印", Permission = $"{lowerClassName}:print", OrderNo = orderNo += 10 },
new() { Title = "导入", Permission = $"{lowerClassName}:import", OrderNo = orderNo += 10 },
new() { Title = "导出", Permission = $"{lowerClassName}:export", OrderNo = orderNo += 10 }
};
if (tableFieldList.Any(u => u.EffectType is "ForeignKey" or "ApiTreeSelector" && (u.WhetherAddUpdate == "Y" || u.WhetherQuery == "Y")))
{
newButtons.Add(new SysMenu
{
Title = "下拉列表数据",
Permission = $"{lowerClassName}:dropdownData",
OrderNo = orderNo += 10
});
}
foreach (var column in tableFieldList.Where(u => u.EffectType == "Upload" || u.EffectType == "Upload_SingleFile"))
{
newButtons.Add(new SysMenu
{
Title = $"上传{column.ColumnComment}",
Permission = $"{lowerClassName}:upload{column.PropertyName}",
OrderNo = orderNo += 10
});
}
// 获取当前菜单下的按钮
var existingButtons = await _db.Queryable<SysMenu>()
.Where(m => m.Pid == menuId && m.Type == MenuTypeEnum.Btn)
.ToListAsync();
var newPermissions = newButtons.Select(b => b.Permission).ToHashSet();
// 添加或更新按钮
foreach (var btn in newButtons)
{
var match = existingButtons.FirstOrDefault(b => b.Permission == btn.Permission);
if (match == null)
{
btn.Type = MenuTypeEnum.Btn;
btn.Pid = menuId;
btn.Icon = "";
await _sysMenuService.AddMenu(btn.Adapt<AddMenuInput>());
}
else
{
match.Title = btn.Title;
match.OrderNo = btn.OrderNo;
await _sysMenuService.UpdateMenu(match.Adapt<UpdateMenuInput>());
}
}
// 删除多余的旧按钮
var toDelete = existingButtons.Where(b => !newPermissions.Contains(b.Permission)).ToList();
foreach (var del in toDelete)
await _sysMenuService.DeleteMenu(new DeleteMenuInput { Id = del.Id });
}
/// <summary>
/// 增加菜单
/// </summary>
/// <param name="className"></param>
/// <param name="busName"></param>
/// <param name="pid"></param>
/// <param name="menuIcon"></param>
/// <param name="pagePath"></param>
/// <param name="tableFieldList"></param>
/// <returns></returns>
private async Task AddMenu(string className, string busName, long pid, string menuIcon, string pagePath, List<CodeGenConfig> tableFieldList)
{
// 删除已存在的菜单
var title = $"{busName}管理";
await DeleteMenuTree(title, pid == 0 ? MenuTypeEnum.Dir : MenuTypeEnum.Menu);
var parentMenuPath = "";
var lowerClassName = className!.ToFirstLetterLowerCase();
if (pid == 0)
{
// 新增目录,并记录Id
var dirMenu = new SysMenu { Pid = 0, Title = title, Type = MenuTypeEnum.Dir, Icon = "robot", Path = "/" + className.ToLower(), Component = "Layout" };
pid = await _sysMenuService.AddMenu(dirMenu.Adapt<AddMenuInput>());
}
else
{
var parentMenu = await _db.Queryable<SysMenu>().FirstAsync(u => u.Id == pid) ?? throw Oops.Oh(ErrorCodeEnum.D1505);
parentMenuPath = parentMenu.Path;
}
// 新增菜单,并记录Id
var rootMenu = new SysMenu { Pid = pid, Title = title, Type = MenuTypeEnum.Menu, Icon = menuIcon, Path = $"{parentMenuPath}/{className.ToLower()}", Component = $"/{pagePath}/{lowerClassName}/index" };
pid = await _sysMenuService.AddMenu(rootMenu.Adapt<AddMenuInput>());
var orderNo = 100;
var menuList = new List<SysMenu>
{
new() { Title="查询", Permission=$"{lowerClassName}:page", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="详情", Permission=$"{lowerClassName}:detail", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="增加", Permission=$"{lowerClassName}:add", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="编辑", Permission=$"{lowerClassName}:update", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="删除", Permission=$"{lowerClassName}:delete", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="批量删除", Permission=$"{lowerClassName}:batchDelete", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="设置状态", Permission=$"{lowerClassName}:setStatus", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="打印", Permission=$"{lowerClassName}:print", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="导入", Permission=$"{lowerClassName}:import", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10},
new() { Title="导出", Permission=$"{lowerClassName}:export", Pid=pid, Type=MenuTypeEnum.Btn, OrderNo=orderNo+=10}
};
if (tableFieldList.Any(u => u.EffectType is "ForeignKey" or "ApiTreeSelector" && (u.WhetherAddUpdate == "Y" || u.WhetherQuery == "Y")))
menuList.Add(new SysMenu { Title = "下拉列表数据", Permission = $"{lowerClassName}:dropdownData", Pid = pid, Type = MenuTypeEnum.Btn, OrderNo = orderNo += 10 });
foreach (var column in tableFieldList.Where(u => u.EffectType == "Upload"))
menuList.Add(new SysMenu { Title = $"上传{column.ColumnComment}", Permission = $"{lowerClassName}:upload{column.PropertyName}", Pid = pid, Type = MenuTypeEnum.Btn, OrderNo = orderNo += 10 });
foreach (var menu in menuList) await _sysMenuService.AddMenu(menu.Adapt<AddMenuInput>());
}
/// <summary>
/// 根据菜单名称和类型删除关联的菜单树
/// </summary>
/// <param name="title"></param>
/// <param name="type"></param>
private async Task DeleteMenuTree(string title, MenuTypeEnum type)
{
var menuList = await _db.Queryable<SysMenu>().Where(u => u.Title == title && u.Type == type).ToListAsync() ?? new();
foreach (var menu in menuList) await _sysMenuService.DeleteMenu(new DeleteMenuInput { Id = menu.Id });
}
/// <summary>
/// 获取模板文件路径集合
/// </summary>
/// <returns></returns>
private static List<string> GetTemplatePathList(SysCodeGen input)
{
if (input.GenerateType!.Substring(1, 1).Contains('1')) return new() { "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
if (input.GenerateType.Substring(1, 1).Contains('2')) return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm" };
return new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
}
/// <summary>
/// 获取模板文件路径集合
/// </summary>
/// <returns></returns>
private static List<string> GetTemplatePathList() => new() { "Service.cs.vm", "Input.cs.vm", "Output.cs.vm", "Dto.cs.vm", "index.vue.vm", "editDialog.vue.vm", "api.ts.vm" };
/// <summary>
/// 设置生成文件路径
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private List<string> 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<string>
{
indexPath,
formModalPath,
apiJsPath
};
}
if (input.GenerateType.Substring(1, 1).Contains('2'))
{
// 生成到本项目(后端)
return new List<string>
{
servicePath,
inputPath,
outputPath,
viewPath,
};
}
// 前后端同时生成到本项目
return new List<string>
{
servicePath,
inputPath,
outputPath,
viewPath,
indexPath,
formModalPath,
apiJsPath
};
}
/// <summary>
/// 设置生成文件路径
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private List<string> GetZipPathList(SysCodeGen input)
{
var zipPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "CodeGen", input.TableName!);
var firstLowerTableName = input.TableName!.ToFirstLetterLowerCase();
//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, firstLowerTableName, "index.vue");
var formModalPath = Path.Combine(frontendPath, firstLowerTableName, "component", "editDialog.vue");
var apiJsPath = Path.Combine(zipPath, _codeGenOptions.FrontRootPath, "src", "api", input.PagePath, firstLowerTableName + ".ts");
if (input.GenerateType!.StartsWith("11"))
{
return new List<string>
{
indexPath,
formModalPath,
apiJsPath
};
}
if (input.GenerateType.StartsWith("12"))
{
return new List<string>
{
servicePath,
inputPath,
outputPath,
viewPath
};
}
return new List<string>
{
servicePath,
inputPath,
outputPath,
viewPath,
indexPath,
formModalPath,
apiJsPath
};
}
}