From ec69cc94e8eccd9cd4615ee0222e898b7c0b35fc Mon Sep 17 00:00:00 2001 From: PhilPan Date: Wed, 2 Aug 2023 17:52:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=A7=86=E5=9B=BE=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=BC=95=E6=93=8E=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tnb.Server.sln | 7 + .../Extensions/ConfigureSqlSugarExtensions.cs | 56 +- apihost/Tnb.API.Entry/Tnb.API.Entry.csproj | 1 + system/Tnb.OAuth/Dto/LoginInput.cs | 4 +- system/Tnb.Systems/Common/TestService.cs | 10 +- .../Entity/TimeTaskEntity.cs | 2 +- visualdev/Tnb.Vmodel/BaseAppService.cs | 16 + visualdev/Tnb.Vmodel/Constants/DbConsts.cs | 70 +++ visualdev/Tnb.Vmodel/Constants/ModuleConst.cs | 7 + visualdev/Tnb.Vmodel/Constants/VmodelEnum.cs | 109 ++++ visualdev/Tnb.Vmodel/DataAccess/DataAccess.cs | 422 +++++++++++++ .../Tnb.Vmodel/DataAccess/IDataAccess.cs | 71 +++ .../Tnb.Vmodel/DataAccess/SugarHelper.cs | 34 ++ visualdev/Tnb.Vmodel/Dtos/VmDto.cs | 198 ++++++ visualdev/Tnb.Vmodel/Dtos/VmodelDto.cs | 34 ++ visualdev/Tnb.Vmodel/Entities/Entity.cs | 30 + visualdev/Tnb.Vmodel/Entities/Vmodel.cs | 444 ++++++++++++++ visualdev/Tnb.Vmodel/Entities/VmodelLink.cs | 50 ++ visualdev/Tnb.Vmodel/Entities/VmodelPage.cs | 107 ++++ visualdev/Tnb.Vmodel/Entities/VmodelProp.cs | 231 +++++++ .../Tnb.Vmodel/Extension/StringExtensions.cs | 573 ++++++++++++++++++ visualdev/Tnb.Vmodel/IVmodelAppService.cs | 16 + visualdev/Tnb.Vmodel/IVmodelPageAppService.cs | 16 + visualdev/Tnb.Vmodel/Mapper/TypeAdapter.cs | 25 + visualdev/Tnb.Vmodel/Mapper/VmodelMapper.cs | 46 ++ visualdev/Tnb.Vmodel/Tnb.VmodelEngine.csproj | 13 + visualdev/Tnb.Vmodel/Util/ThrowIf.cs | 168 +++++ visualdev/Tnb.Vmodel/VmAppService.cs | 200 ++++++ visualdev/Tnb.Vmodel/VmAppServiceT.cs | 119 ++++ visualdev/Tnb.Vmodel/VmodelAppService.cs | 150 +++++ visualdev/Tnb.Vmodel/VmodelPageAppService.cs | 166 +++++ 31 files changed, 3362 insertions(+), 33 deletions(-) create mode 100644 visualdev/Tnb.Vmodel/BaseAppService.cs create mode 100644 visualdev/Tnb.Vmodel/Constants/DbConsts.cs create mode 100644 visualdev/Tnb.Vmodel/Constants/ModuleConst.cs create mode 100644 visualdev/Tnb.Vmodel/Constants/VmodelEnum.cs create mode 100644 visualdev/Tnb.Vmodel/DataAccess/DataAccess.cs create mode 100644 visualdev/Tnb.Vmodel/DataAccess/IDataAccess.cs create mode 100644 visualdev/Tnb.Vmodel/DataAccess/SugarHelper.cs create mode 100644 visualdev/Tnb.Vmodel/Dtos/VmDto.cs create mode 100644 visualdev/Tnb.Vmodel/Dtos/VmodelDto.cs create mode 100644 visualdev/Tnb.Vmodel/Entities/Entity.cs create mode 100644 visualdev/Tnb.Vmodel/Entities/Vmodel.cs create mode 100644 visualdev/Tnb.Vmodel/Entities/VmodelLink.cs create mode 100644 visualdev/Tnb.Vmodel/Entities/VmodelPage.cs create mode 100644 visualdev/Tnb.Vmodel/Entities/VmodelProp.cs create mode 100644 visualdev/Tnb.Vmodel/Extension/StringExtensions.cs create mode 100644 visualdev/Tnb.Vmodel/IVmodelAppService.cs create mode 100644 visualdev/Tnb.Vmodel/IVmodelPageAppService.cs create mode 100644 visualdev/Tnb.Vmodel/Mapper/TypeAdapter.cs create mode 100644 visualdev/Tnb.Vmodel/Mapper/VmodelMapper.cs create mode 100644 visualdev/Tnb.Vmodel/Tnb.VmodelEngine.csproj create mode 100644 visualdev/Tnb.Vmodel/Util/ThrowIf.cs create mode 100644 visualdev/Tnb.Vmodel/VmAppService.cs create mode 100644 visualdev/Tnb.Vmodel/VmAppServiceT.cs create mode 100644 visualdev/Tnb.Vmodel/VmodelAppService.cs create mode 100644 visualdev/Tnb.Vmodel/VmodelPageAppService.cs diff --git a/Tnb.Server.sln b/Tnb.Server.sln index f7d31d69..8b41da8c 100644 --- a/Tnb.Server.sln +++ b/Tnb.Server.sln @@ -140,6 +140,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tnb.PerMgr.Entities", "PerM EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tnb.PerMgr.Interfaces", "PerMgr\Tnb.PerMgr.Interfaces\Tnb.PerMgr.Interfaces.csproj", "{F3656494-27D3-4BD7-B831-8D909DFBD7B9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tnb.VmodelEngine", "visualdev\Tnb.Vmodel\Tnb.VmodelEngine.csproj", "{437AE0E4-66AE-4627-9ACD-29F5BB9E6642}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -338,6 +340,10 @@ Global {F3656494-27D3-4BD7-B831-8D909DFBD7B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3656494-27D3-4BD7-B831-8D909DFBD7B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3656494-27D3-4BD7-B831-8D909DFBD7B9}.Release|Any CPU.Build.0 = Release|Any CPU + {437AE0E4-66AE-4627-9ACD-29F5BB9E6642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {437AE0E4-66AE-4627-9ACD-29F5BB9E6642}.Debug|Any CPU.Build.0 = Debug|Any CPU + {437AE0E4-66AE-4627-9ACD-29F5BB9E6642}.Release|Any CPU.ActiveCfg = Release|Any CPU + {437AE0E4-66AE-4627-9ACD-29F5BB9E6642}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -390,6 +396,7 @@ Global {D41946CF-09C6-4CA4-A1F4-42E7E1538BF7} = {74AB6486-1090-4CC9-9D1A-F1245E3ECFC3} {42AD083D-D199-4B09-ADD8-89251011C959} = {74AB6486-1090-4CC9-9D1A-F1245E3ECFC3} {F3656494-27D3-4BD7-B831-8D909DFBD7B9} = {74AB6486-1090-4CC9-9D1A-F1245E3ECFC3} + {437AE0E4-66AE-4627-9ACD-29F5BB9E6642} = {161853F8-ADB9-4281-B706-E2E23D40D0F1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {646DDD1C-F143-42C2-894F-F5C7B3A0CE74} diff --git a/apihost/Tnb.API.Entry/Extensions/ConfigureSqlSugarExtensions.cs b/apihost/Tnb.API.Entry/Extensions/ConfigureSqlSugarExtensions.cs index 7f7378b4..6dbf0ca7 100644 --- a/apihost/Tnb.API.Entry/Extensions/ConfigureSqlSugarExtensions.cs +++ b/apihost/Tnb.API.Entry/Extensions/ConfigureSqlSugarExtensions.cs @@ -30,37 +30,43 @@ public static class ConfigureSqlSugarExtensions InitKeyType = InitKeyType.Attribute, MoreSettings = new ConnMoreSettings() { - IsAutoRemoveDataCache = true // 自动清理缓存 + IsAutoRemoveDataCache = true, // 自动清理缓存 + IsAutoToUpper = false, + //PgSqlIsAutoToLower = false, + DisableNvarchar = true }, }); services.AddSqlSugar(connectConfigList, client => { - connectConfigList.ForEach(config => - { - var db = ((SqlSugarScope)client).GetConnectionScope((string)config.ConfigId); + //connectConfigList.ForEach(config => + //{ + // var db = ((SqlSugarScope)client).GetConnectionScope((string)config.ConfigId); - // 设置超时时间 - db.Ado.CommandTimeOut = 30; - //db.Aop.OnLogExecuted = (sql, pars) => - //{ - // var oldColor = Console.ForegroundColor; - // Console.ForegroundColor = ConsoleColor.Green; - // var finalSql = UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, sql, pars); - // Console.WriteLine($"【{DateTime.Now.ToString("HH:mm:ss.fff")}——SQL执行完成】{db.Ado.SqlExecutionTime.TotalMilliseconds} ms"); - // Console.WriteLine(finalSql); - // Console.ForegroundColor = oldColor; - // if (db.Ado.SqlExecutionTime.TotalMilliseconds > 3000) - // { - // Log.Warning($"慢查询: {db.Ado.SqlExecutionTime.TotalMilliseconds}ms, SQL: " + finalSql); - // } - // Console.WriteLine(); - //}; - //db.Aop.OnError = (ex) => - //{ - // Log.Error(UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, ex.Sql, (SugarParameter[])ex.Parametres)); - //}; - }); + // // 设置超时时间 + // db.Ado.CommandTimeOut = 30; + // db.Aop.OnLogExecuted = (sql, pars) => + // { + // var oldColor = Console.ForegroundColor; + // Console.ForegroundColor = ConsoleColor.Green; + // var finalSql = UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, sql, pars); + // Console.WriteLine($"【{DateTime.Now.ToString("HH:mm:ss.fff")}——SQL执行完成】{db.Ado.SqlExecutionTime.TotalMilliseconds} ms"); + // if (db.Ado.SqlExecutionTime.TotalMilliseconds > 3000) + // { + // Log.Warning($"慢查询: {db.Ado.SqlExecutionTime.TotalMilliseconds}ms, SQL: " + finalSql); + // } + // else + // { + // Console.WriteLine(finalSql); + // Console.ForegroundColor = oldColor; + // Console.WriteLine(); + // } + // }; + // db.Aop.OnError = (ex) => + // { + // Log.Error(UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, ex.Sql, (SugarParameter[])ex.Parametres)); + // }; + //}); }); services.AddUnitOfWork(); services.AddConfigurableOptions(); diff --git a/apihost/Tnb.API.Entry/Tnb.API.Entry.csproj b/apihost/Tnb.API.Entry/Tnb.API.Entry.csproj index e3a54fc6..7e3429bd 100644 --- a/apihost/Tnb.API.Entry/Tnb.API.Entry.csproj +++ b/apihost/Tnb.API.Entry/Tnb.API.Entry.csproj @@ -44,6 +44,7 @@ + diff --git a/system/Tnb.OAuth/Dto/LoginInput.cs b/system/Tnb.OAuth/Dto/LoginInput.cs index 2ba45843..091fba62 100644 --- a/system/Tnb.OAuth/Dto/LoginInput.cs +++ b/system/Tnb.OAuth/Dto/LoginInput.cs @@ -12,14 +12,14 @@ public class LoginInput /// /// 用户名. /// - /// 13459475357 + /// admin [Required(ErrorMessage = "用户名不能为空")] public string? account { get; set; } /// /// 密码. /// - /// e10adc3949ba59abbe56e057f20f883e + /// f5252ff163e76623601a9a84d275c842 [Required(ErrorMessage = "密码不能为空")] public string? password { get; set; } diff --git a/system/Tnb.Systems/Common/TestService.cs b/system/Tnb.Systems/Common/TestService.cs index 23d62315..ac3b083f 100644 --- a/system/Tnb.Systems/Common/TestService.cs +++ b/system/Tnb.Systems/Common/TestService.cs @@ -24,14 +24,14 @@ namespace JNPF.Systems.Common; [Route("api")] public class TestService : IDynamicApiController, ITransient { - private readonly ISqlSugarRepository _repository; + //private readonly ISqlSugarRepository _repository; private readonly SqlSugarScope _sugar; private readonly IDataBaseManager _databaseService; - public TestService(ISqlSugarRepository repository, IDataBaseManager databaseService) + public TestService(ISqlSugarClient db, IDataBaseManager databaseService) { - _repository = repository; - _sugar = (SqlSugarScope)repository.AsSugarClient(); + //_repository = repository; + _sugar = (SqlSugarScope)db; _databaseService = databaseService; } @@ -60,7 +60,7 @@ public class TestService : IDynamicApiController, ITransient //var xx = App.HttpContext.Request.Host.ToString(); //var sql = "SELECT TOP 1 [F_PARENTID],[F_PROCESSID],[F_ENCODE],[F_FULLNAME],[F_FLOWURGENT],[F_FLOWID],[F_FLOWCODE],[F_FLOWNAME],[F_FLOWTYPE],[F_FLOWCATEGORY],[F_FLOWFORM],[F_FLOWFORMCONTENTJSON],[F_FLOWTEMPLATEJSON],[F_FLOWVERSION],[F_STARTTIME],[F_ENDTIME],[F_THISSTEP],[F_THISSTEPID],[F_GRADE],[F_STATUS],[F_COMPLETION],[F_DESCRIPTION],[F_SORTCODE],[F_ISASYNC],[F_ISBATCH],[F_TASKNODEID],[F_TEMPLATEID],[F_REJECTDATAID],[F_DELEGATEUSER],[F_CREATORTIME],[F_CREATORUSERID],[F_ENABLEDMARK],[F_LastModifyTime],[F_LastModifyUserId],[F_DeleteMark],[F_DeleteTime],[F_DeleteUserId],[F_Id] FROM [FLOW_TASK] WHERE (( [F_DeleteMark] IS NULL ) AND ( [F_Id] = N'367536153122855173' ))"; //var darta = _sqlSugarRepository.AsSugarClient().Ado.SqlQuery(sql); - var data = await _repository.GetFirstAsync(a => true); + var data = await _sugar.Queryable().FirstAsync(a => true); var json = App.GetService(); return data; } diff --git a/taskschedule/Tnb.TaskScheduler.Entitys/Entity/TimeTaskEntity.cs b/taskschedule/Tnb.TaskScheduler.Entitys/Entity/TimeTaskEntity.cs index acccd160..ea046fd7 100644 --- a/taskschedule/Tnb.TaskScheduler.Entitys/Entity/TimeTaskEntity.cs +++ b/taskschedule/Tnb.TaskScheduler.Entitys/Entity/TimeTaskEntity.cs @@ -10,7 +10,7 @@ namespace JNPF.TaskScheduler.Entitys; /// 版 权:拓通智联科技有限公司(http://www.tuotong-tech.com) /// 日 期:2021-06-01 . /// -[SugarTable("BASE_TIMETASK")] +[SugarTable("base_timetask")] public class TimeTaskEntity : CLDEntityBase { /// diff --git a/visualdev/Tnb.Vmodel/BaseAppService.cs b/visualdev/Tnb.Vmodel/BaseAppService.cs new file mode 100644 index 00000000..e6fa3d23 --- /dev/null +++ b/visualdev/Tnb.Vmodel/BaseAppService.cs @@ -0,0 +1,16 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.DependencyInjection; +using JNPF.DynamicApiController; + +namespace Tnb.VmodelEngine; + +/// +/// 增删改查基类 +/// +public class BaseAppService : IDynamicApiController, ITransient +{ +} diff --git a/visualdev/Tnb.Vmodel/Constants/DbConsts.cs b/visualdev/Tnb.Vmodel/Constants/DbConsts.cs new file mode 100644 index 00000000..162f3f70 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Constants/DbConsts.cs @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +namespace Tnb.VmodelEngine; + +public static class DbConsts +{ + public const string DefaultDbCode = "totedp"; + public const string ConnectNameDapper = "Dapper"; + public const string ConnectNameMySql = "MySql"; + public const string ConnectNameSqlServer = "SqlServer"; + public const string ConnectNameSqlite = "Sqlite"; + public const string ConnectNamePostgreSql = "PostgreSql"; + public const string ConnectNameOracle = "Oracle"; + public const string ConnectNameMongoDb = "MongoDb"; + + public const string DbCode = "totemp"; + public const string DbTablePrefix = "app"; + public const string DbSchema = null; + public const string DbSystemPrefix = "sys"; + public const string DbMESPrefix = "mes"; + public const string DbWMSPrefix = "wms"; + public const string DbBasePrefix = "bas"; + public const string DbConfigPrefix = "cfg"; + public const string DbEqpPrefix = "eqp"; + public const string DbQCPrefix = "qc"; + + public const string OneToOne = "OneToOne"; + public const string OneToMany = "OneToMany"; + public const string ManyToMany = "ManyToMany"; + + public static string[] HiddenFields = new string[] { "Id", "CreationTime", "CreatorId", "LastModificationTime", "LastModifierId" }; + + public const string CsString = "string"; + public const string CsInt = "int"; + public const string CsShort = "short"; + public const string CsBool = "bool"; + public const string CsFloat = "float"; + public const string CsDouble = "double"; + public const string CsGuid = "Guid"; + public const string CsDateTime = "DateTime"; + public const string CsClass = "class"; + public const string CsJson = "json"; + public const string CsEnum = "enum"; + public const string CsCustomer = "customer"; + + public const int LengthXXS = 10; + public const int LengthXS = 25; + public const int LengthS = 50; + public const int LengthM = 100; + public const int LengthL = 250; + public const int LengthXL = 1000; + public const int LengthXXL = 2000; + public const int LengthXXXL = 3000; + public const int LengthXXXXL = 4000; + public const int LengthPassword = 32; + public const int LengthNodePath = 742; //20组GUID + public const int LengthText = -1; + + public const string SuperAdminName = "admin"; + public static Guid SuperAdminId = Guid.Parse("39fe9e87-4659-05a7-99ce-9a23d84875df"); + public static Guid SuperRoleId = Guid.Parse("39fea2c5-088a-32c1-2c2c-b814700aee7e"); + public static Guid UserAdminId = Guid.Parse("39fe9e87-4695-8eb7-273c-e49b0524aea8"); + public static Guid RoleAdminId = Guid.Parse("39fea2c8-01a4-75cd-026f-2f0349a94098"); + public static Guid EventDefaultCategoryId = Guid.Parse("39ffae5d-13d2-1e43-3f0e-98f774f71c8a"); + public static Guid DefaultViewId = Guid.Parse("39fea2c1-b2dd-cfae-1c3d-89de77f94709"); + +} diff --git a/visualdev/Tnb.Vmodel/Constants/ModuleConst.cs b/visualdev/Tnb.Vmodel/Constants/ModuleConst.cs new file mode 100644 index 00000000..1e99e591 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Constants/ModuleConst.cs @@ -0,0 +1,7 @@ +namespace Tnb.VmodelEngine; + +public class ModuleConst +{ + public const string Tag = "Tnb"; + public const string Area = "tnb"; +} diff --git a/visualdev/Tnb.Vmodel/Constants/VmodelEnum.cs b/visualdev/Tnb.Vmodel/Constants/VmodelEnum.cs new file mode 100644 index 00000000..f4e4fe1b --- /dev/null +++ b/visualdev/Tnb.Vmodel/Constants/VmodelEnum.cs @@ -0,0 +1,109 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using System.ComponentModel; + +namespace Tnb.VmodelEngine; + +public enum eCsType +{ + [Description("string")] + String = 10, + [Description("bool")] + Bool = 12, + [Description("uuid")] + Guid = 14, + [Description("int")] + Int = 20, + [Description("short")] + Short = 22, + [Description("long")] + Long = 24, + [Description("float")] + Float = 30, + [Description("double")] + Double = 32, + [Description("decimal")] + Decimal = 34, + [Description("date")] + Date = 40, + [Description("time")] + Time = 42, + [Description("datetime")] + DateTime = 44, + [Description("timestamp")] + Timestamp = 46, + [Description("enum")] + Enum = 50, + [Description("dictionary")] + Dictionary = 52, + [Description("json")] + Json = 60, + [Description("entity")] + Entity = 70, + [Description("customer")] + Customer = 80, +} +public enum eDbType +{ + MySql, + SqlServer, + PostgreSQL, + Oracle, + Sqlite, + MsAccess, + Dameng, + ClickHouse, + Redis, + InfluxDb +} +public enum eResourceType +{ + [Description("菜单")] + Menu = 10, + [Description("页面")] + Page = 20, + [Description("按钮")] + Button = 30, + [Description("接口")] + Interface = 40, + [Description("视图")] + View = 50, +} +public enum eSearchType +{ + [Description("无")] + None = 0, + [Description("精准查询")] + Exact = 1, + [Description("模糊查询")] + Fuzzy = 2, + [Description("范围查询")] + Range = 3, +} +public enum eNavigateType +{ + [Description("无")] + None = 0, + [Description("一对一")] + OneToOne = 1, + [Description("一对多")] + OneToMany = 2, + [Description("多对多")] + ManyToMany = 3, +} + +/// +/// 模型属性类型 +/// +public enum ePropType +{ + [Description("表字段")] + DbTable = 0, + [Description("计算属性")] + Calculate = 1, + [Description("导航属性")] + Navigate = 2, +} diff --git a/visualdev/Tnb.Vmodel/DataAccess/DataAccess.cs b/visualdev/Tnb.Vmodel/DataAccess/DataAccess.cs new file mode 100644 index 00000000..367878d9 --- /dev/null +++ b/visualdev/Tnb.Vmodel/DataAccess/DataAccess.cs @@ -0,0 +1,422 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using System.Collections.Concurrent; +using JNPF; +using JNPF.DependencyInjection; +using Mapster; +using SqlSugar; +using Tnb.VmodelEngine; + +namespace Tnb.DataAccess; + +/// +/// +/// +public class DataAccess : IDataAccess, ITransient, IDisposable +{ + const int MAX_PAGE_SIZE = 1000; + private ISqlSugarClient? sugar; + protected ISqlSugarClient Db + { + get + { + if (sugar == null) + { + ConnectionStringsOptions conn = App.GetConfig("ConnectionStrings", true); + //var DBType = (DbType)Enum.Parse(typeof(DbType), conn.DBType); + sugar = new SqlSugarScope(new ConnectionConfig + { + ConnectionString = conn.ConnectString, + DbType = conn.DBType.Adapt(), + IsAutoCloseConnection = true, + ConfigId = conn.ConfigId, + InitKeyType = InitKeyType.Attribute, + MoreSettings = new ConnMoreSettings() + { + IsAutoRemoveDataCache = true, // 自动清理缓存 + IsAutoToUpper = false, + PgSqlIsAutoToLower = false, + DisableNvarchar = true + }, + }, SugarHelper.ConfigSugar); + } + return sugar; + } + } + + /// + /// 全局缓存 + /// + static ConcurrentDictionary DbCache = new ConcurrentDictionary(); + + /// + /// 构造 + /// + public DataAccess() + { + } + + /// + /// 释放 + /// + public void Dispose() + { + foreach (var item in DbCache) + { + item.Value.Dispose(); + } + DbCache.Clear(); + } + + /// + /// 获取 ISqlSugarClient + /// + public ISqlSugarClient GetSqlSugar(string? dbCode = null) + { + if (string.IsNullOrEmpty(dbCode) || dbCode == DbConsts.DefaultDbCode) + { + return Db; + } + if (DbCache.ContainsKey(dbCode)) + { + return DbCache[dbCode]; + } + + var dblink = GetVmLink(dbCode); + if (dblink == null) + { + throw new Exception($"没有此数据库{dbCode}连接信息"); + } + var dbType = Enum.Parse(dblink.dbType.ToString(), true); + var sugar = new SqlSugarScope(new ConnectionConfig + { + ConnectionString = dblink.dbConnection, + DbType = dbType, + IsAutoCloseConnection = true, + ConfigId = dblink.dbCode, + InitKeyType = InitKeyType.Attribute, + MoreSettings = new ConnMoreSettings() + { + IsAutoRemoveDataCache = true, // 自动清理缓存 + IsAutoToUpper = false, + PgSqlIsAutoToLower = false, + DisableNvarchar = true + }, + }, SugarHelper.ConfigSugar); + if (sugar.Ado.IsValidConnection()) + { + DbCache[dbCode] = sugar; + } + else + { + sugar.Dispose(); + throw new Exception($"无法连接到数据库{dbCode}"); + } + return DbCache[dbCode]; + } + + /// + /// 获取 DbLink + /// + public VmodelLink GetVmLink(string dbCode) + { + var model = Db.Queryable().First(a => a.dbCode == dbCode); + return model; + } + + /// + /// 获取 Vmodel, 为空时不抛异常 + /// + public async Task TryGetVmodelAsync(string id, bool loadNavigate = false) + { + Vmodel vm = await Db.Queryable().FirstAsync(a => a.id == id && a.deleted == 0); + if (vm != null && loadNavigate) + { + await LoadVmodelNavigateAsync(vm); + } + return vm; + } + + /// + /// 获取 Vmodel, 为空时抛异常 + /// + public async Task GetVmodelAsync(string id, bool loadNavigate = false) + { + Vmodel vm = await Db.Queryable().FirstAsync(a => a.id == id && a.deleted == 0); + ArgumentNullException.ThrowIfNull(vm, $"找不到vmid={id}的模型"); + if (loadNavigate) + { + await LoadVmodelNavigateAsync(vm); + } + return vm; + } + + /// + /// 获取 Vmodel, 为空时不抛异常 + /// + public async Task TryGetVmodelAsync(string area, string vmCode, bool loadNavigate = false) + { + Vmodel vm = await Db.Queryable().FirstAsync(a => a.area == area && a.vmCode == vmCode && a.deleted == 0); + if (vm != null && loadNavigate) + { + await LoadVmodelNavigateAsync(vm); + } + + return vm; + } + + /// + /// 获取 Vmodel, 为空时抛异常 + /// + public async Task GetVmodelAsync(string area, string vmCode, bool loadNavigate = false) + { + Vmodel vm = await Db.Queryable().FirstAsync(a => a.area == area && a.vmCode == vmCode && a.deleted == 0); + ArgumentNullException.ThrowIfNull(vm, $"找不到area={area}, vmCode={vmCode}的模型"); + if (loadNavigate) + { + await LoadVmodelNavigateAsync(vm); + } + + return vm; + } + + ///// + ///// 获取 Vmodel + ///// + //public async Task GetVmodelAsync(string tableName, string? dbCode) + //{ + // Vmodel vm = await _db.Queryable().FirstAsync(a => a.tableName == tableName && a.dbCode == dbCode && a.deleted == 0); + // return vm; + //} + + /// + /// 加载模型的导航属性 + /// + /// + /// + private async Task LoadVmodelNavigateAsync(Vmodel vm) + { + Dictionary dictVm = new(); + foreach (var navProp in vm.navProps) + { + if (!dictVm.ContainsKey(navProp.vmid)) + { + var navModel = await GetVmodelAsync(navProp.vmid); + dictVm.Add(navProp.vmid, navModel); + } + navProp.naviModel = dictVm[navProp.vmid]; + } + } + + /// + /// 查询数据 默认方法 + /// + public async Task QueryDataAsync(Vmodel vm, VmQueryInput input) + { + ISqlSugarClient db = GetSqlSugar(vm.dbCode); + var query = db.Queryable().AS(vm.tableName, VmSelectProp.MAIN_ALIES); + var selProps = vm.GetVmSelectProps(input.o); + //处理导航属性联表 + List joins = vm.GetJoinInfos(selProps); + query.AddJoinInfo(joins); + List wheres = vm.GetConditionalModels(input.q); + if (!string.IsNullOrEmpty(input.k)) + { + var lsCondition = new List>(); + var wType = WhereType.And; + foreach (var prop in vm.dbProps.Where(a => a.fuzzy)) + { + lsCondition.Add(new(wType, new ConditionalModel() { FieldName = prop.field, ConditionalType = ConditionalType.Like, FieldValue = input.k })); + wType = WhereType.Or; + } + wheres.Add(new ConditionalCollections() { ConditionalList = lsCondition }); + } + //处理查询参数 + query.Where(wheres); + if (!string.IsNullOrEmpty(input.sort)) + { + query.OrderBy(input.sort); + } + //处理输出字段 + List selects = vm.GetSelectModels(selProps); + query.Select(selects); + //查询数据 + VmPagedOutput result = new(); + List> ls = new(); + int skip = input.pnum > 0 ? (input.pnum - 1) * input.psize : 0; + int take = input.psize == 0 ? MAX_PAGE_SIZE : input.psize; + if (input.pnum > 0) { result.total = await query.CountAsync(); } + ls = await query.Skip(skip).Take(take).ToDictionaryListAsync(); + //组装输出对象 + foreach (var data in ls) + { + DObject ret = await NestedOutputAsync(vm, data, selProps); + result.items.Add(ret); + } + + return result; + } + + /// + /// 组装子模型对象 + /// + /// + /// + /// + /// + private async Task NestedOutputAsync(Vmodel vm, Dictionary src, List selProps) + { + DObject ret = new(); + foreach (var prop in selProps) + { + if (prop.navType == eNavigateType.None || prop.navCode == VmSelectProp.MAIN_ALIES) + { + if (src.ContainsKey(prop.code)) + { + ret.Add(prop.code, src[prop.code]); + } + } + else + { + if (prop.navType == eNavigateType.OneToOne) + { + var key = prop.navCode + "_" + prop.code; + ret.Add(key, src[key]); + //if (!ret.ContainsKey(prop.navCode)) + //{ + // ret.Add(prop.navCode, new DObject()); + //} + //var key = prop.navCode + "_" + prop.code; + //if (src.ContainsKey(key)) + //{ + // ((DObject)ret[prop.navCode]).Add(prop.code, src[key]); + //} + } + else if (prop.navType == eNavigateType.OneToMany) + { + if (!ret.ContainsKey(prop.navCode)) + { + ret.Add(prop.navCode, new List()); + } + var navProp = vm.navProps.First(a => a.code == prop.navCode); + if (navProp != null && navProp.naviModel != null && src.ContainsKey(navProp.refField)) + { + VmQueryInput input = new VmQueryInput(); + input.q = new DObject(navProp.refField, src[navProp.refField]); + input.o = string.Join(',', selProps.Where(a => a.navCode == prop.navCode).Select(a => a.code)); + ret[prop.navCode] = (await QueryDataAsync(navProp.naviModel, input)).items; + } + } + else if (prop.navType == eNavigateType.ManyToMany) + { + if (!ret.ContainsKey(prop.navCode)) + { + ret.Add(prop.navCode, new List()); + } + + } + + } + } + return ret; + } + + /// + /// 新增数据 默认方法 + /// + public async Task CreateDataAsync(Vmodel vm, VmCreateInput input) + { + ISqlSugarClient db = GetSqlSugar(vm.dbCode); + int num = 0; + if (input.data != null) + { + var model = vm.PropToField(input.data); + num = await db.Insertable(model).AS(vm.tableName).ExecuteCommandAsync(); + return input.data; + } + else if (input.items != null) + { + List lst = new List(); + foreach (var item in input.items) + { + lst.Add(vm.PropToField(item)); + } + num = await db.Insertable(lst).AS(vm.tableName).ExecuteCommandAsync(); + return input.items; + } + else + { + input.data = vm.GetDefaultDObject(); + var model = vm.PropToField(input.data); + num = await db.Insertable(model).AS(vm.tableName).ExecuteCommandAsync(); + return input.data; + } + + } + + /// + /// 更新数据 默认方法 + /// + public async Task UpdateDataAsync(Vmodel vm, VmUpdateInput input) + { + ISqlSugarClient db = GetSqlSugar(vm.dbCode); + var pk = vm.GetPrimary(); + int num = 0; + if (input.data != null) + { + var model = vm.PropToField(input.data); + if (!model.ContainsKey(pk.field)) + { + throw new Exception($"更新数据时主键({pk.code})不可为空"); + } + //if (!model.ContainsKey(pk.field) && input.id != null) + //{ + // model.Add(pk.field, input.id); + //} + num = await db.Updateable(model).AS(vm.tableName).WhereColumns(pk.field).ExecuteCommandAsync(); + } + else if (input.items != null) + { + List lst = new(); + foreach (var item in input.items) + { + var model = vm.PropToField(item); + if (model.ContainsKey(pk.field)) + { + lst.Add(model); + } + } + num = await db.Updateable(lst).AS(vm.tableName).WhereColumns(pk.field).ExecuteCommandAsync(); + } + return num; + } + + /// + /// 删除数据 默认方法 + /// + public async Task DeleteDataAsync(Vmodel vm, VmDeleteInput input) + { + ISqlSugarClient db = GetSqlSugar(vm.dbCode); + var pk = vm.GetPrimary(); + int num = 0; + List> ids = new(); + if (input.id != null) + { + ids.Add(new DObject(pk.field, input.id)); + } + else if (input.ids != null) + { + ids.AddRange(input.ids.Select(a => new DObject(pk.field, a))); + } + if (ids.Count > 0) + { + num = await db.Deleteable().AS(vm.tableName).WhereColumns(ids).ExecuteCommandAsync(); + } + + return num; + } + +} diff --git a/visualdev/Tnb.Vmodel/DataAccess/IDataAccess.cs b/visualdev/Tnb.Vmodel/DataAccess/IDataAccess.cs new file mode 100644 index 00000000..549e8018 --- /dev/null +++ b/visualdev/Tnb.Vmodel/DataAccess/IDataAccess.cs @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.DependencyInjection; +using SqlSugar; +using Tnb.VmodelEngine; + +namespace Tnb.DataAccess; + +/// +/// +/// +public interface IDataAccess : ITransient +{ + /// + /// 获取 SqlSugar + /// + /// + /// + ISqlSugarClient GetSqlSugar(string? dbCode = null); + + /// + /// 获取DbLink + /// + /// + /// + VmodelLink GetVmLink(string dbCode); + + /// + /// 获取 Vmodel, 为空时不抛异常 + /// + Task TryGetVmodelAsync(string id, bool loadNavigate = false); + /// + /// 获取 Vmodel, 为空时抛异常 + /// + Task GetVmodelAsync(string id, bool loadNavigate = false); + /// + /// 获取 Vmodel, 为空时不抛异常 + /// + Task TryGetVmodelAsync(string area, string vmCode, bool loadNavigate = false); + /// + /// 获取 Vmodel, 为空时抛异常 + /// + Task GetVmodelAsync(string area, string vmCode, bool loadNavigate = false); + //Task QueryDataAsync(VmBaseInput input); + + /// + /// 查询数据 默认方法 + /// + Task QueryDataAsync(Vmodel vm, VmQueryInput input); + + //Task CreateDataAsync(VmCreateInput input); + /// + /// 新增数据 默认方法 + /// + Task CreateDataAsync(Vmodel vm, VmCreateInput input); + + //Task UpdateDataAsync(VmUpdateInput input); + /// + /// 更新数据 默认方法 + /// + Task UpdateDataAsync(Vmodel vm, VmUpdateInput input); + + //Task DeleteDataAsync(VmDeleteInput input); + /// + /// 删除数据 默认方法 + /// + Task DeleteDataAsync(Vmodel vm, VmDeleteInput input); +} diff --git a/visualdev/Tnb.Vmodel/DataAccess/SugarHelper.cs b/visualdev/Tnb.Vmodel/DataAccess/SugarHelper.cs new file mode 100644 index 00000000..68364c26 --- /dev/null +++ b/visualdev/Tnb.Vmodel/DataAccess/SugarHelper.cs @@ -0,0 +1,34 @@ +using JNPF.Logging; +using SqlSugar; + +namespace Tnb.VmodelEngine; + +public class SugarHelper +{ + public static void ConfigSugar(ISqlSugarClient db) + { + // 设置超时时间 + db.Ado.CommandTimeOut = 30; + db.Aop.OnLogExecuted = (sql, pars) => + { + var finalSql = UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, sql, pars); + if (db.Ado.SqlExecutionTime.TotalMilliseconds > 3000) + { + Log.Warning($"慢查询: {db.Ado.SqlExecutionTime.TotalMilliseconds}ms, SQL: " + finalSql); + } + else + { + var oldColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"【{DateTime.Now.ToString("HH:mm:ss.fff")}——SQL执行完成】{db.Ado.SqlExecutionTime.TotalMilliseconds} ms"); + Console.WriteLine(finalSql); + Console.ForegroundColor = oldColor; + Console.WriteLine(); + } + }; + db.Aop.OnError = (ex) => + { + Log.Error(UtilMethods.GetSqlString(db.CurrentConnectionConfig.DbType, ex.Sql, (SugarParameter[])ex.Parametres)); + }; + } +} diff --git a/visualdev/Tnb.Vmodel/Dtos/VmDto.cs b/visualdev/Tnb.Vmodel/Dtos/VmDto.cs new file mode 100644 index 00000000..97a6c736 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Dtos/VmDto.cs @@ -0,0 +1,198 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +namespace Tnb.VmodelEngine; + +/// +/// 字典对象 +/// +public class DObject : Dictionary +{ + public DObject() { } + public DObject(string key, object value) + { + Add(key, value); + } + public DObject(Dictionary dictionary) : base(dictionary) + { + } + public void AddCascade(string code, object value) + { + var keys = code.Split('.'); + if(keys.Length == 1 ) + { + Add(code, value); + return; + } + for (int i = 0; i < keys.Length; i++) + { + DObject temp = this; + if(i < keys.Length - 1) + { + if (!ContainsKey(keys[i])) + { + temp = new DObject(); + Add(keys[i], temp); + } + else + { + temp = (DObject)temp[keys[i]]; + } + } + else + { + temp.Add(keys[i], value); + } + } + } +} + +public class VmBaseInput +{ + ///// + ///// 视图模型id + ///// + //public string vmid { get; set; } = string.Empty; +} +public class VmGetInput : VmBaseInput +{ + /// + /// 要获取数据的id + /// + public string? id { get; set; } + + /// + /// 过滤条件 + /// + public string? q { get; set; } + + /// + /// 输出字段 + /// + public string o { get; set; } = "*"; +} + +public class VmGetListInput : VmBaseInput +{ + /// + /// 当前页数 + /// + public int pnum { get; set; } + + /// + /// 每页记录数 + /// + public int psize { get; set; } + + /// + /// 排序 + /// + public string? sort { get; set; } = null; + + /// + /// 模糊查询 + /// + public string? k { get; set; } + + /// + /// 过滤条件 + /// + public string? q { get; set; } + + /// + /// 输出字段 + /// + public string o { get; set; } = "*"; +} + +/// +/// 获取多条数据输入参数 +/// +public class VmQueryInput : VmGetListInput +{ + /// + /// 查询条件 + /// + public new DObject? q { get; set; } + + /// + /// 高级查询 + /// + public DObject? adv { get; set; } +} + +/// +/// 新增数据输入参数 +/// +public class VmCreateInput : VmBaseInput +{ + /// + /// 数据 + /// + public DObject? data { get; set; } + + /// + /// 批量添加 + /// + public List? items { get; set; } +} + +/// +/// 修改数据输入参数 +/// +public class VmUpdateInput : VmCreateInput +{ + ///// + ///// 要更新的数据id + ///// + //public string? id { get; set; } +} + +/// +/// 删除数据输入参数 +/// +public class VmDeleteInput : VmBaseInput +{ + /// + /// 要删除的数据id + /// + public string? id { get; set; } + + /// + /// 要删除的id列表 + /// + public List? ids { get; set; } +} + +/// +/// 分页列表输出对象 +/// +/// +public class PagedOutput +{ + public int total { get; set; } + public List items { get; set; } = new List(); +} + +/// +/// 动态分页列表输出对象 +/// +public class VmPagedOutput : PagedOutput +{ + +} + +/// +/// 查询属性信息 +/// +public class VmSelectProp +{ + public const string MAIN_ALIES = "m"; + public string code { get; set; } = string.Empty; + public string field { get; set; } = string.Empty; + public string navCode { get; set; } = MAIN_ALIES; + public ePropType propType { get; set; } + public eNavigateType navType { get; set; } +} diff --git a/visualdev/Tnb.Vmodel/Dtos/VmodelDto.cs b/visualdev/Tnb.Vmodel/Dtos/VmodelDto.cs new file mode 100644 index 00000000..78d43e09 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Dtos/VmodelDto.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +namespace Tnb.VmodelEngine; + +public class VmodelCreateFromTableInput +{ + public string? dbCode { get; set; } + + public string tableName { get; set; } = string.Empty; + + public string? removePrefix { get; set; } + public string area { get; set; } = "edp"; +} + +public class CreatePageFromVmodelInput +{ + public Guid? viewId { get; set; } + public string? vmid { get; set; } +} + +public class VmodelGetInput +{ + public long? id { get; set; } + public string? moduleCode { get; set; } + public string? vmCode { get; set; } + public string? dbCode { get; set; } + public string? tableName { get; set; } + public bool drill { get; set; } + +} + diff --git a/visualdev/Tnb.Vmodel/Entities/Entity.cs b/visualdev/Tnb.Vmodel/Entities/Entity.cs new file mode 100644 index 00000000..c938c9f8 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Entities/Entity.cs @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.Common.Contracts; + +namespace Tnb.VmodelEngine; + +[Serializable] +public abstract class Entity : IEntity +{ + protected Entity() + { + //EntityHelper.TrySetTenantId(this); + } + + /// + public override string ToString() + { + return $"[ENTITY: {GetType().Name}] Keys = {string.Join(", ", GetKeys())}"; + } + + public abstract object[] GetKeys(); + + //public bool EntityEquals(IEntity other) + //{ + // return EntityHelper.EntityEquals(this, other); + //} +} diff --git a/visualdev/Tnb.Vmodel/Entities/Vmodel.cs b/visualdev/Tnb.Vmodel/Entities/Vmodel.cs new file mode 100644 index 00000000..c4da2a12 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Entities/Vmodel.cs @@ -0,0 +1,444 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Reflection; +using Mapster; +using Newtonsoft.Json.Linq; +using SqlSugar; +using Tnb.DataAccess; +using Yitter.IdGenerator; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型 +/// +[SugarTable("sys_vmodel")] +public partial class Vmodel : Entity +{ + #region Properties + /// + /// 主键标识 + /// + [SugarColumn(IsPrimaryKey = true)] + public string id { get; set; } = YitIdHelper.NextId().ToString(); + + /// + /// 模块代码 + /// + [SugarColumn(ColumnName = "area", Length = DbConsts.LengthS)] + public string area { get; set; } = "edp"; + + /// + /// 视图代码 + /// + [SugarColumn(ColumnName = "vm_code", IsNullable = false, Length = DbConsts.LengthM)] + public string vmCode { get; set; } = string.Empty; + + /// + /// 视图名称 + /// + [SugarColumn(ColumnName = "vm_name", IsNullable = false, Length = DbConsts.LengthM)] + public string vmName { get; set; } = string.Empty; + + /// + /// 数据库连接 + /// + [SugarColumn(ColumnName = "db_code", Length = DbConsts.LengthS)] + public string? dbCode { get; set; } + + /// + /// 主表名称 + /// + [SugarColumn(ColumnName = "table_name", IsNullable = false, Length = DbConsts.LengthS)] + public string tableName { get; set; } = string.Empty; + + /// + /// 表字段属性 + /// + [SugarColumn(ColumnName = "db_props", IsNullable = false, IsJson = true)] + public List dbProps { get; set; } = new List(); + + /// + /// 导航属性 + /// + [SugarColumn(ColumnName = "nav_props", IsNullable = true, IsJson = true)] + public List navProps { get; set; } = new List(); + + /// + /// 计算属性 + /// + [SugarColumn(ColumnName = "cal_props", IsNullable = true, IsJson = true)] + public List calProps { get; set; } = new List(); + + /// + /// 排序 + /// + [SugarColumn(ColumnName = "ordinal", IsNullable = false)] + public int ordinal { get; set; } + + /// + /// 软删除 + /// + [SugarColumn(ColumnName = "soft_delete", IsNullable = false)] + public short softDelete { get; set; } + + /// + /// 是否激活 + /// + [SugarColumn(ColumnName = "enabled", IsNullable = false)] + public short enabled { get; set; } = 1; + + /// + /// 是否删除 + /// + [SugarColumn(ColumnName = "deleted", IsNullable = false)] + public short deleted { get; set; } + + /// + /// 描述 + /// + [SugarColumn(ColumnName = "descrip", Length = DbConsts.LengthL)] + public string? descrip { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time", IsNullable = false)] + public DateTime createTime { get; set; } = DateTime.Now; + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_id", Length = DbConsts.LengthS)] + public string? createId { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "modify_time", Length = DbConsts.LengthS)] + public DateTime? modifyTime { get; set; } + + /// + /// 修改人 + /// + [SugarColumn(ColumnName = "modify_id", Length = DbConsts.LengthS)] + public string? modifyId { get; set; } + + /// + /// 主键 + /// + public override object[] GetKeys() + { + return new object[] { id }; + } + #endregion + + /// + /// 通过实体创建模型 + /// + /// + /// + /// + public static Vmodel CreateByEntity(Type tpEntity, string? dbCode = null) + { + Vmodel model = new() { dbCode = dbCode, vmCode = tpEntity.Name }; + var sugarTableAttr = tpEntity.GetCustomAttribute(); + if (sugarTableAttr != null) + { + model.tableName = sugarTableAttr.TableName; + model.vmName = sugarTableAttr.TableDescription; + } + if (string.IsNullOrEmpty(model.tableName)) + { + model.tableName = tpEntity.GetCustomAttribute()?.Name ?? tpEntity.Name; + } + if (string.IsNullOrEmpty(model.vmName)) + { + model.vmName = tpEntity.GetCustomAttribute()?.Name ?? tpEntity.GetCustomAttribute()?.Description ?? model.vmCode; + } + var props = tpEntity.GetProperties(BindingFlags.Public); + int n = 1; + foreach (var p in props) + { + VmDbProp prop = new(); + var sugarColumn = p.GetCustomAttribute(); + if (sugarColumn != null) + { + prop = sugarColumn.Adapt(); + } + prop.code = p.Name; + prop.ordinal = n++; + model.dbProps.Add(prop); + } + return model; + } + + /// + /// 获取模型的主键字段属性 + /// + /// + public VmDbProp GetPrimary() + { + return dbProps.First(a => a.pkey); + } + + /// + /// 根据属性名获取字段名 + /// + /// + /// + public string? PropCodeToFieldCode(string propCode) + { + return dbProps.Where(a => a.code == propCode).Select(a => a.field).FirstOrDefault(); + } + + /// + /// 根据字段名获取属性名 + /// + /// + /// + public string? FieldCodeToPropCode(string fieldCode) + { + return dbProps.Where(a => a.field == fieldCode).Select(a => a.code).FirstOrDefault(); + } + + /// + /// 属性代码转换为字段代码 + /// + /// + /// + /// + public DObject PropToField(DObject input, bool ignoreNotMapped = true) + { + DObject ret = new(); + foreach (var item in input) + { + var fcode = PropCodeToFieldCode(item.Key); + if (!string.IsNullOrEmpty(fcode)) + { + ret.Add(fcode, item.Value); + } + else if (!ignoreNotMapped) + { + ret.Add(item.Key, item.Value); + } + } + return ret; + } + + /// + /// 字段代码转换为属性代码 + /// + /// + /// + /// + public DObject FieldToProp(DObject input, bool ignoreNotMapped = true) + { + DObject ret = new(); + foreach (var item in input) + { + var pcode = FieldCodeToPropCode(item.Key); + if (!string.IsNullOrEmpty(pcode)) + { + ret.Add(pcode, item.Value); + } + else if (!ignoreNotMapped) + { + ret.Add(item.Key, item.Value); + } + } + return ret; + } + + /// + /// 获取查询字段的属性信息 + /// + /// + /// + public List GetVmSelectProps(string? outputProps) + { + if (string.IsNullOrEmpty(outputProps) || outputProps == "*") + { + return dbProps.Select(a => new VmSelectProp { code = a.code, field = a.field }).ToList(); + } + List selProps = new(); + var outputs = outputProps.Split(',').Distinct().ToList(); + foreach (var propCode in outputs) + { + if (!propCode.Contains(".")) + { + var fieldCode = PropCodeToFieldCode(propCode); + if (!string.IsNullOrEmpty(fieldCode)) + { + selProps.Add(new VmSelectProp { code = propCode, field = fieldCode }); + } + continue; + } + var codes = propCode.Split('.'); + if (codes.Length != 2) continue; + if (codes[0] == VmSelectProp.MAIN_ALIES) + { + var fieldCode = PropCodeToFieldCode(propCode); + if (!string.IsNullOrEmpty(fieldCode)) + { + selProps.Add(new VmSelectProp { code = propCode, field = fieldCode }); + } + continue; + } + var navProp = navProps.FirstOrDefault(a => a.code == codes[0]); + if (navProp?.naviModel != null) + { + var fieldCode = navProp.naviModel.PropCodeToFieldCode(codes[1]); + if (!string.IsNullOrEmpty(fieldCode)) + { + selProps.Add(new VmSelectProp { code = codes[1], field = fieldCode, navCode = codes[0], propType = ePropType.Navigate, navType = navProp.navType }); + } + } + } + return selProps; + } + + /// + /// 获取联表配置信息 + /// + /// + /// + public List GetJoinInfos(List selProps) + { + var navigates = selProps.Where(a => a.propType == ePropType.Navigate).Select(a => a.navCode).Distinct().ToList(); + List joins = new(); + foreach (var navCode in navigates) + { + if (navCode == VmSelectProp.MAIN_ALIES) continue; + var navProp = navProps.First(a => a.code == navCode); + if (navProp.naviModel == null || navProp.navType != eNavigateType.OneToOne) continue; + JoinInfoParameter join = new JoinInfoParameter { TableName = navProp.naviModel.tableName, ShortName = navCode, Type = JoinType.Inner }; + var fkField = navProp.naviModel.PropCodeToFieldCode(navProp.fkField); + var refField = navProp.refField; + if (navProp.refCode != VmSelectProp.MAIN_ALIES) + { + var refProp = navProps.First(a => a.code == navProp.refCode); + refField = refProp.naviModel!.PropCodeToFieldCode(navProp.refField); + } + join.Models = ObjectFuncModel.Create("Equals", $"{navCode}.{fkField}", $"{navProp.refCode}.{refField}"); + joins.Add(join); + } + return joins; + } + + /// + /// 转换为查询过滤条件 + /// + /// + /// + public List GetConditionalModels(DObject? filter) + { + List wheres = new List(); + if (filter == null) return wheres; + foreach (var item in filter) + { + // TODO 按子表条件查询 + if (item.Key.Contains(".")) + { + + } + var prop = dbProps.FirstOrDefault(a => a.code == item.Key); + if (prop == null) continue; + if (item.Value is JArray val) + { + var op = val[0].ToString(); + switch (op) + { + case "><": + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[1].ToString(), ConditionalType = ConditionalType.GreaterThan }); + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[2].ToString(), ConditionalType = ConditionalType.LessThan }); + break; + case ">=<": + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[1].ToString(), ConditionalType = ConditionalType.GreaterThanOrEqual }); + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[2].ToString(), ConditionalType = ConditionalType.LessThan }); + break; + case "><=": + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[1].ToString(), ConditionalType = ConditionalType.GreaterThan }); + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[2].ToString(), ConditionalType = ConditionalType.LessThanOrEqual }); + break; + case ">=<=": + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[1].ToString(), ConditionalType = ConditionalType.GreaterThanOrEqual }); + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val[2].ToString(), ConditionalType = ConditionalType.LessThanOrEqual }); + break; + case "in": + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = val.Skip(1).ToString(), ConditionalType = ConditionalType.In }); + break; + default: op = string.Empty; break; + } + } + else + { + //if (item.Value == null) continue; + var conditionalType = ConditionalType.Equal; + string? value = item.Value?.ToString(); + if (string.IsNullOrEmpty(value)) continue; + if (value.Length >= 2) + { + var op = value.Substring(0, 2); + switch (op) + { + case "%%": conditionalType = ConditionalType.Like; break; + case ">>": conditionalType = ConditionalType.GreaterThan; break; + case "<<": conditionalType = ConditionalType.LessThan; break; + case ">=": conditionalType = ConditionalType.GreaterThanOrEqual; break; + case "<=": conditionalType = ConditionalType.LessThanOrEqual; break; + case "==": conditionalType = ConditionalType.Equal; break; + default: op = string.Empty; break; + } + if (!string.IsNullOrEmpty(op)) + { + value = value.RemovePreFix(op); + if (value.ToLower() == "null") + { + value = null; + } + } + } + wheres.Add(new ConditionalModel { FieldName = prop.field, FieldValue = value, ConditionalType = conditionalType }); + } + } + return wheres; + } + + /// + /// 转换为查询字段列表 + /// + /// + /// + public List GetSelectModels(List selProps) + { + return selProps.Where(a => a.navType != eNavigateType.OneToMany && a.navType != eNavigateType.ManyToMany).Select(a => new SelectModel + { + FiledName = (a.navCode == VmSelectProp.MAIN_ALIES ? "" : a.navCode + ".") + a.field, + AsName = (a.navCode == VmSelectProp.MAIN_ALIES ? "" : a.navCode + "_") + a.code + }).ToList(); + } + + /// + /// 获取默认对象 + /// + /// + public DObject GetDefaultDObject() + { + DObject obj = new(); + foreach (var p in dbProps) + { + obj.Add(p.code, p.GetDefaultValue()!); + } + return obj; + } + + +} + diff --git a/visualdev/Tnb.Vmodel/Entities/VmodelLink.cs b/visualdev/Tnb.Vmodel/Entities/VmodelLink.cs new file mode 100644 index 00000000..5391c498 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Entities/VmodelLink.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.Common.Contracts; +using SqlSugar; +using Yitter.IdGenerator; + +namespace Tnb.VmodelEngine; + +/// +/// 数据库连接 +/// +[SugarTable("sys_vmodel_link")] +public partial class VmodelLink : Entity +{ + /// + /// 主键标识 + /// + [SugarColumn(IsPrimaryKey = true)] + public string id { get; set; } = YitIdHelper.NextId().ToString(); + /// + /// 数据库连接 + /// + [SugarColumn(ColumnName = "db_code", Length = DbConsts.LengthS)] + public string? dbCode { get; set; } + + /// + /// 数据库类型 + /// + [SugarColumn(ColumnName = "db_type", IsNullable = false)] + public eDbType dbType { get; set; } + + /// + /// 连接串 + /// + [SugarColumn(ColumnName = "db_connection", IsNullable = false, Length = DbConsts.LengthXL)] + public string dbConnection { get; set; } = ""; + + /// + /// 主键 + /// + public override object[] GetKeys() + { + return new object[] { id }; + } + +} + diff --git a/visualdev/Tnb.Vmodel/Entities/VmodelPage.cs b/visualdev/Tnb.Vmodel/Entities/VmodelPage.cs new file mode 100644 index 00000000..0c5576d3 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Entities/VmodelPage.cs @@ -0,0 +1,107 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using Newtonsoft.Json.Linq; +using SqlSugar; +using Yitter.IdGenerator; + +namespace Tnb.VmodelEngine; + +/// +/// 功能页面 +/// +[SugarTable("sys_vmodel_page")] +public partial class VmodelPage : Entity +{ + #region Properties + /// + /// 主键标识 + /// + [SugarColumn(IsPrimaryKey = true)] + public string id { get; set; } = YitIdHelper.NextId().ToString(); + + /// + /// 模型id + /// + [SugarColumn(ColumnName = "vmid", Length = DbConsts.LengthS)] + public string? vmid { get; set; } + + /// + /// 页面代码 + /// + [SugarColumn(ColumnName = "code", Length = DbConsts.LengthS)] + public string code { get; set; } = string.Empty; + + /// + /// 页面名称 + /// + [SugarColumn(ColumnName = "name", Length = DbConsts.LengthM)] + public string name { get; set; } = string.Empty; + + /// + /// 页面类型 + /// + [SugarColumn(ColumnName = "page_type", Length = DbConsts.LengthS)] + public string pageType { get; set; } = string.Empty; + + /// + /// 页面配置 + /// + [SugarColumn(ColumnName = "page_schema", Length = DbConsts.LengthS, IsJson = true)] + public JObject pageSchema { get; set; } = new JObject(); + + /// + /// 页面配置 + /// + [SugarColumn(ColumnName = "option", Length = DbConsts.LengthS)] + public string? option { get; set; } = string.Empty; + + /// + /// 是否启用 + /// + [SugarColumn(ColumnName = "enabled")] + public short enabled { get; set; } = 1; + + /// + /// 是否删除 + /// + [SugarColumn(ColumnName = "deleted")] + public short deleted { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime createTime { get; set; } = DateTime.Now; + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_id", Length = DbConsts.LengthS)] + public string? createId { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "modify_time", Length = DbConsts.LengthS)] + public DateTime? modifyTime { get; set; } + + /// + /// 修改人 + /// + [SugarColumn(ColumnName = "modify_id", Length = DbConsts.LengthS)] + public string? modifyId { get; set; } + + /// + /// 主键 + /// + public override object[] GetKeys() + { + return new object[] { id }; + } + #endregion + +} + diff --git a/visualdev/Tnb.Vmodel/Entities/VmodelProp.cs b/visualdev/Tnb.Vmodel/Entities/VmodelProp.cs new file mode 100644 index 00000000..ef001b24 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Entities/VmodelProp.cs @@ -0,0 +1,231 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Tnb.DataAccess; +using Yitter.IdGenerator; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型属性 +/// +public class VmProp +{ + /// + /// 属性代码 + /// + public string code { get; set; } = string.Empty; + + /// + /// 显示名称 + /// + public string name { get; set; } = string.Empty; +} + +/// +/// 字段属性 +/// +public class VmDbProp : VmProp +{ + #region Properties + /// + /// 字段名称 + /// + public string field { get; set; } = string.Empty; + + /// + /// 数据类型 + /// + public string dataType { get; set; } = "varchar"; + + /// + /// 数据类型 + /// + public string? csType { get; set; } + + /// + /// 长度 + /// + public int length { get; set; } + + /// + /// 精度 + /// + public int digit { get; set; } + + /// + /// 排序 + /// + public int ordinal { get; set; } + + /// + /// 非空 + /// + public bool required { get; set; } + + /// + /// 是否主键 + /// + public bool pkey { get; set; } + + /// + /// 是否模糊搜索 + /// + public bool fuzzy { get; set; } + + /// + /// 默认值 + /// + public string? defValue { get; set; } + + /// + /// 描述 + /// + public string? descrip { get; set; } + #endregion + + /// + /// 获取默认值 + /// + /// + public object? GetDefaultValue() + { + object? val = null; + if (string.IsNullOrEmpty(defValue)) + { + val = defValue switch + { + "@@snowid" => YitIdHelper.NextId().ToString(), + "@@now" => DateTime.Now, + "@@userid" => YitIdHelper.NextId().ToString(), + "@@orgid" => YitIdHelper.NextId().ToString(), + _ => null + }; + } + else + { + val = csType switch + { + "string" => string.Empty, + "short" or "int" or "long" => 0, + "float" or "double" or "decimal" => 0f, + "DateTime" => DateTime.Now, + _ => null + }; + } + return val; + } + + /// + /// 获取默认宽度 + /// + /// + public string GetDefaultWidth() + { + return csType switch + { + "string" => "\"width\": \"auto\"", + "int" or "short" or "long" => "\"width\": 80", + "DateTime" => "\"width\": 150", + _ => "" + }; + } + + /// + /// 获取默认组件 + /// + /// + public CompOption GetDefaultComp() + { + CompOption comp = new CompOption(); + if (pkey) + { + comp.attr.Add("disabled", true); + return comp; + } + switch (csType) + { + case "string": + comp.attr.Add("clearable", true); + comp.attr.Add("maxlength", length); + comp.attr.Add("showWordLimit", true); + break; + case "int": + case "short": + case "long": + comp.type = "el-input-number"; + break; + case "DateTime": + comp.type = "el-date-picker"; + break; + }; + return comp; + } +} + +/// +/// 导航属性 +/// +public class VmNavProp : VmProp +{ + /// + /// 导航属性模型id + /// + public string vmid { get; set; } = string.Empty; + + /// + /// 导航关联类型 + /// + public eNavigateType navType { get; set; } + + /// + /// 源表字段 + /// + public string refCode { get; set; } = VmSelectProp.MAIN_ALIES; + + /// + /// 被引用字段 + /// + public string refField { get; set; } = string.Empty; + + /// + /// 源表字段 + /// + public string fkField { get; set; } = string.Empty; + + ///// + ///// 关联表表名 + ///// + //[JsonIgnore] + //public string refTable { get; set; } = string.Empty; + + ///// + ///// 被引用表(中间表) + ///// + //[JsonIgnore] + //public string? midTable { get; set; } + + [JsonIgnore] + public Vmodel? naviModel { get; set; } +} + +public class VmCalProp : VmProp +{ + public string calculate { get; set; } = string.Empty; +} + +public class DictOption +{ + public string dictTypeId { get; set; } = string.Empty; + public string refField { get; set; } = "id"; +} + +public class CompOption +{ + public string type { get; set; } = "el-input"; + public JObject attr { get; set; } = new JObject(); +} \ No newline at end of file diff --git a/visualdev/Tnb.Vmodel/Extension/StringExtensions.cs b/visualdev/Tnb.Vmodel/Extension/StringExtensions.cs new file mode 100644 index 00000000..51a5990d --- /dev/null +++ b/visualdev/Tnb.Vmodel/Extension/StringExtensions.cs @@ -0,0 +1,573 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using JNPF.Common.Extension; +using JNPF.DependencyInjection; +using Tnb.VmodelEngine; + +namespace System; + +/// +/// 字符串扩展类,来自Abp +/// +[SuppressSniffer] +public static class StringExtensions +{ + /// + /// Adds a char to end of given string if it does not ends with the char. + /// + public static string EnsureEndsWith(this string str, char c, StringComparison comparisonType = StringComparison.Ordinal) + { + ThrowIf.IsNull(str, nameof(str)); + + if (str.EndsWith(c.ToString(), comparisonType)) + { + return str; + } + + return str + c; + } + + /// + /// Adds a char to beginning of given string if it does not starts with the char. + /// + public static string EnsureStartsWith(this string str, char c, StringComparison comparisonType = StringComparison.Ordinal) + { + ThrowIf.IsNull(str, nameof(str)); + + if (str.StartsWith(c.ToString(), comparisonType)) + { + return str; + } + + return c + str; + } + + /// + /// Indicates whether this string is null or an System.String.Empty string. + /// + public static bool IsNullOrEmpty(this string str) + { + return string.IsNullOrEmpty(str); + } + + /// + /// indicates whether this string is null, empty, or consists only of white-space characters. + /// + public static bool IsNullOrWhiteSpace(this string str) + { + return string.IsNullOrWhiteSpace(str); + } + + /// + /// Gets a substring of a string from beginning of the string. + /// + /// Thrown if is null + /// Thrown if is bigger that string's length + public static string Left(this string str, int len) + { + ThrowIf.IsNull(str, nameof(str)); + + if (str.Length < len) + { + throw new ArgumentException("len argument can not be bigger than given string's length!"); + } + + return str.Substring(0, len); + } + + /// + /// Converts line endings in the string to . + /// + public static string NormalizeLineEndings(this string str) + { + return str.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Environment.NewLine); + } + + /// + /// Gets index of nth occurrence of a char in a string. + /// + /// source string to be searched + /// Char to search in + /// Count of the occurrence + public static int NthIndexOf(this string str, char c, int n) + { + ThrowIf.IsNull(str, nameof(str)); + + var count = 0; + for (var i = 0; i < str.Length; i++) + { + if (str[i] != c) + { + continue; + } + + if ((++count) == n) + { + return i; + } + } + + return -1; + } + + /// + /// Removes first occurrence of the given postfixes from end of the given string. + /// + /// The string. + /// one or more postfix. + /// Modified string or the same string if it has not any of given postfixes + public static string RemovePostFix(this string str, params string[] postFixes) + { + return str.RemovePostFix(StringComparison.Ordinal, postFixes); + } + + /// + /// Removes first occurrence of the given postfixes from end of the given string. + /// + /// The string. + /// String comparison type + /// one or more postfix. + /// Modified string or the same string if it has not any of given postfixes + public static string RemovePostFix(this string str, StringComparison comparisonType, params string[] postFixes) + { + if (str.IsNullOrEmpty()) + { + return str; + } + + if (postFixes.IsNullOrEmpty()) + { + return str; + } + + foreach (var postFix in postFixes) + { + if (str.EndsWith(postFix, comparisonType)) + { + return str.Left(str.Length - postFix.Length); + } + } + + return str; + } + + /// + /// Removes first occurrence of the given prefixes from beginning of the given string. + /// + /// The string. + /// one or more prefix. + /// Modified string or the same string if it has not any of given prefixes + public static string RemovePreFix(this string str, params string[] preFixes) + { + return str.RemovePreFix(StringComparison.Ordinal, preFixes); + } + + /// + /// Removes first occurrence of the given prefixes from beginning of the given string. + /// + /// The string. + /// String comparison type + /// one or more prefix. + /// Modified string or the same string if it has not any of given prefixes + public static string RemovePreFix(this string str, StringComparison comparisonType, params string[] preFixes) + { + if (str.IsNullOrEmpty()) + { + return str; + } + + if (preFixes.IsNullOrEmpty()) + { + return str; + } + + foreach (var preFix in preFixes) + { + if (str.StartsWith(preFix, comparisonType)) + { + return str.Right(str.Length - preFix.Length); + } + } + + return str; + } + + public static string ReplaceFirst(this string str, string search, string replace, StringComparison comparisonType = StringComparison.Ordinal) + { + + var pos = str.IndexOf(search, comparisonType); + if (pos < 0) + { + return str; + } + + return str.Substring(0, pos) + replace + str.Substring(pos + search.Length); + } + + /// + /// Gets a substring of a string from end of the string. + /// + /// Thrown if is null + /// Thrown if is bigger that string's length + public static string Right(this string str, int len) + { + + if (str.Length < len) + { + throw new ArgumentException("len argument can not be bigger than given string's length!"); + } + + return str.Substring(str.Length - len, len); + } + + /// + /// Uses string.Split method to split given string by given separator. + /// + public static string[] Split(this string str, string separator) + { + return str.Split(new[] { separator }, StringSplitOptions.None); + } + + /// + /// Uses string.Split method to split given string by given separator. + /// + public static string[] Split(this string str, string separator, StringSplitOptions options) + { + return str.Split(new[] { separator }, options); + } + + /// + /// Uses string.Split method to split given string by . + /// + public static string[] SplitToLines(this string str) + { + return str.Split(Environment.NewLine); + } + + /// + /// Uses string.Split method to split given string by . + /// + public static string[] SplitToLines(this string str, StringSplitOptions options) + { + return str.Split(Environment.NewLine, options); + } + + /// + /// Converts PascalCase string to camelCase string. + /// + /// String to convert + /// set true to use current culture. Otherwise, invariant culture will be used. + /// set true to if you want to convert 'XYZ' to 'xyz'. + /// camelCase of the string + public static string ToCamelCase(this string str, bool useCurrentCulture = false, bool handleAbbreviations = false) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + if (str.Length == 1) + { + return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant(); + } + + if (handleAbbreviations && IsAllUpperCase(str)) + { + return useCurrentCulture ? str.ToLower() : str.ToLowerInvariant(); + } + + return (useCurrentCulture ? char.ToLower(str[0]) : char.ToLowerInvariant(str[0])) + str.Substring(1); + } + + /// + /// Converts given PascalCase/camelCase string to sentence (by splitting words by space). + /// Example: "ThisIsSampleSentence" is converted to "This is a sample sentence". + /// + /// String to convert. + /// set true to use current culture. Otherwise, invariant culture will be used. + public static string ToSentenceCase(this string str, bool useCurrentCulture = false) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + return useCurrentCulture + ? Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1])) + : Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLowerInvariant(m.Value[1])); + } + + /// + /// Converts given PascalCase/camelCase string to kebab-case. + /// + /// String to convert. + /// set true to use current culture. Otherwise, invariant culture will be used. + public static string ToKebabCase(this string str, bool useCurrentCulture = false) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + str = str.ToCamelCase(); + + return useCurrentCulture + ? Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + "-" + char.ToLower(m.Value[1])) + : Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + "-" + char.ToLowerInvariant(m.Value[1])); + } + + /// + /// Converts given PascalCase/camelCase string to snake case. + /// Example: "ThisIsSampleSentence" is converted to "this_is_a_sample_sentence". + /// https://github.com/npgsql/npgsql/blob/dev/src/Npgsql/NameTranslation/NpgsqlSnakeCaseNameTranslator.cs#L51 + /// + /// String to convert. + /// + public static string ToSnakeCase(this string str) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + var builder = new StringBuilder(str.Length + Math.Min(2, str.Length / 5)); + var previousCategory = default(UnicodeCategory?); + + for (var currentIndex = 0; currentIndex < str.Length; currentIndex++) + { + var currentChar = str[currentIndex]; + if (currentChar == '_') + { + builder.Append('_'); + previousCategory = null; + continue; + } + + var currentCategory = char.GetUnicodeCategory(currentChar); + switch (currentCategory) + { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.TitlecaseLetter: + if (previousCategory == UnicodeCategory.SpaceSeparator || + previousCategory == UnicodeCategory.LowercaseLetter || + previousCategory != UnicodeCategory.DecimalDigitNumber && + previousCategory != null && + currentIndex > 0 && + currentIndex + 1 < str.Length && + char.IsLower(str[currentIndex + 1])) + { + builder.Append('_'); + } + + currentChar = char.ToLower(currentChar); + break; + + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.DecimalDigitNumber: + if (previousCategory == UnicodeCategory.SpaceSeparator) + { + builder.Append('_'); + } + break; + + default: + if (previousCategory != null) + { + previousCategory = UnicodeCategory.SpaceSeparator; + } + continue; + } + + builder.Append(currentChar); + previousCategory = currentCategory; + } + + return builder.ToString(); + } + + /// + /// Converts string to enum value. + /// + /// Type of enum + /// String value to convert + /// Returns enum object + public static T ToEnum(this string value) + where T : struct + { + ThrowIf.IsNull(value, nameof(value)); + return (T)Enum.Parse(typeof(T), value); + } + + /// + /// Converts string to enum value. + /// + /// Type of enum + /// String value to convert + /// Ignore case + /// Returns enum object + public static T ToEnum(this string value, bool ignoreCase) + where T : struct + { + ThrowIf.IsNull(value, nameof(value)); + return (T)Enum.Parse(typeof(T), value, ignoreCase); + } + + public static string ToMd5(this string str) + { + using (var md5 = MD5.Create()) + { + var inputBytes = Encoding.UTF8.GetBytes(str); + var hashBytes = md5.ComputeHash(inputBytes); + + var sb = new StringBuilder(); + foreach (var hashByte in hashBytes) + { + sb.Append(hashByte.ToString("X2")); + } + + return sb.ToString(); + } + } + + /// + /// Converts camelCase string to PascalCase string. + /// + /// String to convert + /// set true to use current culture. Otherwise, invariant culture will be used. + /// PascalCase of the string + public static string ToPascalCase(this string str, bool useCurrentCulture = false) + { + if (string.IsNullOrWhiteSpace(str)) + { + return str; + } + + if (str.Length == 1) + { + return useCurrentCulture ? str.ToUpper() : str.ToUpperInvariant(); + } + + return (useCurrentCulture ? char.ToUpper(str[0]) : char.ToUpperInvariant(str[0])) + str.Substring(1); + } + + /// + /// Gets a substring of a string from beginning of the string if it exceeds maximum length. + /// + public static string Truncate(this string str, int maxLength) + { + if (str.Length <= maxLength) + { + return str; + } + + return str.Left(maxLength); + } + + /// + /// Gets a substring of a string from Ending of the string if it exceeds maximum length. + /// + public static string TruncateFromBeginning(this string str, int maxLength) + { + if (str.Length <= maxLength) + { + return str; + } + + return str.Right(maxLength); + } + + /// + /// Gets a substring of a string from beginning of the string if it exceeds maximum length. + /// It adds a "..." postfix to end of the string if it's truncated. + /// Returning string can not be longer than maxLength. + /// + /// Thrown if is null + public static string TruncateWithPostfix(this string str, int maxLength) + { + return TruncateWithPostfix(str, maxLength, "..."); + } + + /// + /// Gets a substring of a string from beginning of the string if it exceeds maximum length. + /// It adds given to end of the string if it's truncated. + /// Returning string can not be longer than maxLength. + /// + /// Thrown if is null + public static string TruncateWithPostfix(this string str, int maxLength, string postfix) + { + if (str == string.Empty || maxLength == 0) + { + return string.Empty; + } + + if (str.Length <= maxLength) + { + return str; + } + + if (maxLength <= postfix.Length) + { + return postfix.Left(maxLength); + } + + return str.Left(maxLength - postfix.Length) + postfix; + } + + /// + /// Converts given string to a byte array using encoding. + /// + public static byte[] GetBytes(this string str) + { + return str.GetBytes(Encoding.UTF8); + } + + /// + /// Converts given string to a byte array using the given + /// + public static byte[] GetBytes([NotNull] this string str, [NotNull] Encoding encoding) + { + ThrowIf.IsNull(str, nameof(str)); + ThrowIf.IsNull(encoding, nameof(encoding)); + + return encoding.GetBytes(str); + } + + private static bool IsAllUpperCase(string input) + { + for (int i = 0; i < input.Length; i++) + { + if (Char.IsLetter(input[i]) && !Char.IsUpper(input[i])) + { + return false; + } + } + + return true; + } + + /// + /// Converts snake_case string to PascalCase string. + /// + /// String to convert + /// set true to use current culture. Otherwise, invariant culture will be used. + /// PascalCase of the string + public static string SnakeToPascalCase(this string str, bool useCurrentCulture = false) + { + var sArr = str.Split(new char[] { '-', '_' }).Select(a => a.ToPascalCase(useCurrentCulture)); + return string.Join("", sArr); + } + /// + /// Converts snake_case string to PascalCase string. + /// + /// String to convert + /// set true to use current culture. Otherwise, invariant culture will be used. + /// PascalCase of the string + public static string SnakeToCamelCase(this string str, bool useCurrentCulture = false) + { + return SnakeToPascalCase(str, useCurrentCulture).ToCamelCase(); + } + +} \ No newline at end of file diff --git a/visualdev/Tnb.Vmodel/IVmodelAppService.cs b/visualdev/Tnb.Vmodel/IVmodelAppService.cs new file mode 100644 index 00000000..746bd7f2 --- /dev/null +++ b/visualdev/Tnb.Vmodel/IVmodelAppService.cs @@ -0,0 +1,16 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.DependencyInjection; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型服务接口 +/// +public interface IVmodelAppService : ITransient +{ + +} diff --git a/visualdev/Tnb.Vmodel/IVmodelPageAppService.cs b/visualdev/Tnb.Vmodel/IVmodelPageAppService.cs new file mode 100644 index 00000000..4b19ef28 --- /dev/null +++ b/visualdev/Tnb.Vmodel/IVmodelPageAppService.cs @@ -0,0 +1,16 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.DependencyInjection; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型服务接口 +/// +public interface IVmodelPageAppService : ITransient +{ + +} diff --git a/visualdev/Tnb.Vmodel/Mapper/TypeAdapter.cs b/visualdev/Tnb.Vmodel/Mapper/TypeAdapter.cs new file mode 100644 index 00000000..ea56e06d --- /dev/null +++ b/visualdev/Tnb.Vmodel/Mapper/TypeAdapter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Mapster; +using Newtonsoft.Json.Linq; + +namespace Tnb.VmodelEngine; + +public class TypeAdapter +{ + public static TypeAdapterConfig IgnoreNull { get; } + static TypeAdapter() + { + TypeAdapterConfig.GlobalSettings.Default.PreserveReference(true); + TypeAdapterConfig.GlobalSettings.NewConfig().MapWith(json => json); + TypeAdapterConfig.GlobalSettings.NewConfig().MapWith(json => json); + TypeAdapterConfig.GlobalSettings.NewConfig().MapWith(json => json); + IgnoreNull = TypeAdapterConfig.GlobalSettings.Clone(); + IgnoreNull.Default.IgnoreNullValues(true); + } + +} diff --git a/visualdev/Tnb.Vmodel/Mapper/VmodelMapper.cs b/visualdev/Tnb.Vmodel/Mapper/VmodelMapper.cs new file mode 100644 index 00000000..0cfbc7d8 --- /dev/null +++ b/visualdev/Tnb.Vmodel/Mapper/VmodelMapper.cs @@ -0,0 +1,46 @@ +using JNPF.Common.Security; +using Mapster; +using SqlSugar; + +namespace Tnb.VmodelEngine; + +public class VmodelMapper : IRegister +{ + public void Register(TypeAdapterConfig config) + { + config.ForType() + .Map(dest => dest.psize, src => 1) + .Map(dest => dest.pnum, src => 0) + .Map(dest => dest.q, src => string.IsNullOrEmpty(src.q) ? null : src.q.ToObject()); + config.ForType() + .Map(dest => dest.q, src => string.IsNullOrEmpty(src.q) ? null : src.q.ToObject()); + config.ForType() + .Map(dest => dest.code, src => src.DbColumnName.SnakeToCamelCase(false)) + .Map(dest => dest.name, src => src.ColumnDescription) + .Map(dest => dest.field, src => src.DbColumnName) + .Map(dest => dest.dataType, src => src.DataType) + //.Map(dest => dest.csType, src => src.DbColumnName) + //.Map(dest => dest.propType, src => ePropType.DbTable) + .Map(dest => dest.length, src => src.Length) + .Map(dest => dest.digit, src => src.DecimalDigits) + //.Map(dest => dest.ordinal, src => src.i) + .Map(dest => dest.required, src => !src.IsNullable) + .Map(dest => dest.pkey, src => src.IsPrimarykey) + //.Map(dest => dest.descrip, src => src.DbColumnName) + .Map(dest => dest.defValue, src => src.DefaultValue); + config.ForType() + //.Map(dest => dest.code, src => src.DbColumnName.SnakeToCamelCase(false)) + .Map(dest => dest.name, src => src.ColumnDescription) + .Map(dest => dest.field, src => src.ColumnName) + .Map(dest => dest.dataType, src => src.ColumnDataType) + //.Map(dest => dest.csType, src => src.DbColumnName) + //.Map(dest => dest.propType, src => ePropType.DbTable) + .Map(dest => dest.length, src => src.Length) + .Map(dest => dest.digit, src => src.DecimalDigits) + //.Map(dest => dest.ordinal, src => src.i) + .Map(dest => dest.required, src => !src.IsNullable) + .Map(dest => dest.pkey, src => src.IsPrimaryKey); + //.Map(dest => dest.descrip, src => src.DbColumnName) + //.Map(dest => dest.defValue, src => src.DefaultValue); + } +} diff --git a/visualdev/Tnb.Vmodel/Tnb.VmodelEngine.csproj b/visualdev/Tnb.Vmodel/Tnb.VmodelEngine.csproj new file mode 100644 index 00000000..4e4e987f --- /dev/null +++ b/visualdev/Tnb.Vmodel/Tnb.VmodelEngine.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/visualdev/Tnb.Vmodel/Util/ThrowIf.cs b/visualdev/Tnb.Vmodel/Util/ThrowIf.cs new file mode 100644 index 00000000..a86da9ba --- /dev/null +++ b/visualdev/Tnb.Vmodel/Util/ThrowIf.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Tnb.VmodelEngine; + +public static class ThrowIf +{ + public static void When(bool isMatch, string message) + { + if (isMatch) + { + throw new ArgumentException(message); + } + } + + public static T IsNull([NotNull] T? value, string parameterName, string? message = null) + { + if (value == null) + { + throw new ArgumentNullException(parameterName, message); + } + + return value; + } + + public static T IsNullOrDefault([NotNull] T? value, string parameterName) where T : struct + { + if (!value.HasValue) + { + throw new ArgumentException(parameterName + " is null!", parameterName); + } + + if (value.Value.Equals(default(T))) + { + throw new ArgumentException(parameterName + " has a default value!", parameterName); + } + + return value.Value; + } + + + public static string IsNullOrWhiteSpace(string value, string parameterName, int maxLength = int.MaxValue, int minLength = 0) + { + if (value.IsNullOrWhiteSpace()) + { + throw new ArgumentException(parameterName + " can not be null, empty or white space!", parameterName); + } + + if (value.Length > maxLength) + { + throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName); + } + + if (minLength > 0 && value.Length < minLength) + { + throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName); + } + + return value; + } + + public static string IsNullOrEmpty(string value, string parameterName, int maxLength = int.MaxValue, int minLength = 0) + { + if (value.IsNullOrEmpty()) + { + throw new ArgumentException(parameterName + " can not be null or empty!", parameterName); + } + + if (value.Length > maxLength) + { + throw new ArgumentException($"{parameterName} length must be equal to or lower than {maxLength}!", parameterName); + } + + if (minLength > 0 && value.Length < minLength) + { + throw new ArgumentException($"{parameterName} length must be equal to or bigger than {minLength}!", parameterName); + } + + return value; + } + + //public static ICollection NotNullOrEmpty(ICollection value, string parameterName) + //{ + // if (value.IsNullOrEmpty()) + // { + // throw new ArgumentException(parameterName + " can not be null or empty!", parameterName); + // } + + // return value; + //} + + //public static Type AssignableTo(Type type, string parameterName) + //{ + // NotNull(type, parameterName); + // if (!type.IsAssignableTo()) + // { + // throw new ArgumentException(parameterName + " (type of " + type.AssemblyQualifiedName + ") should be assignable to the " + typeof(TBaseType).GetFullNameWithAssemblyName() + "!"); + // } + + // return type; + //} + + public static short OutOfRange(short value, string parameterName, short minimumValue, short maximumValue = short.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + + public static int OutOfRange(int value, string parameterName, int minimumValue, int maximumValue = int.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + + public static long OutOfRange(long value, string parameterName, long minimumValue, long maximumValue = long.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + + public static float OutOfRange(float value, string parameterName, float minimumValue, float maximumValue = float.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + + public static double OutOfRange(double value, string parameterName, double minimumValue, double maximumValue = double.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + + public static decimal OutOfRange(decimal value, string parameterName, decimal minimumValue, decimal maximumValue = decimal.MaxValue) + { + if (value < minimumValue || value > maximumValue) + { + throw new ArgumentException($"{parameterName} is out of range min: {minimumValue} - max: {maximumValue}"); + } + + return value; + } + +} diff --git a/visualdev/Tnb.Vmodel/VmAppService.cs b/visualdev/Tnb.Vmodel/VmAppService.cs new file mode 100644 index 00000000..3dd7294a --- /dev/null +++ b/visualdev/Tnb.Vmodel/VmAppService.cs @@ -0,0 +1,200 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.Common.Security; +using JNPF.DependencyInjection; +using JNPF.DynamicApiController; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using Tnb.DataAccess; + +namespace Tnb.VmodelEngine; + +/// +/// 增删改查基类 +/// +[ApiDescriptionSettings(Tag = ModuleConst.Tag, Area = ModuleConst.Area, Order = 10, KeepVerb = true)] +[Route("api")] +public class VmAppService : BaseAppService +{ + private readonly IDataAccess _dataAccess; + private readonly ISqlSugarClient _db; + + /// + /// 构造函数 + /// + public VmAppService(IDataAccess da) + { + _dataAccess = da; + _db = _dataAccess.GetSqlSugar(); + } + + #region 根据vmodel id进行增删改查接口 + /// + /// 获取一条 数据信息 + /// + [HttpGet("[area]/[controller]/{vmid}/[action]")] + public async Task GetAsync(string vmid, [FromQuery] VmGetInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid, true); + VmQueryInput arg = input.Adapt(); + if (input.id != null) + { + if (arg.q == null) arg.q = new DObject(); + arg.q.Add(vm.GetPrimary().code, input.id); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls.items.FirstOrDefault(); + } + + /// + /// 获取多条 数据列表 + /// + [HttpGet("[area]/[controller]/{vmid}/[action]")] + public async Task GetListAsync(string vmid, [FromQuery] VmGetListInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid, true); + VmQueryInput arg = input.Adapt(); + if (!string.IsNullOrEmpty(input.q)) + { + arg.q = input.q.ToObject(); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls; + } + + /// + /// 获取多条 数据列表 + /// + [HttpPost("[area]/[controller]/{vmid}/[action]")] + public async Task QueryAsync(string vmid, [FromBody] VmQueryInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid, true); + var ls = await _dataAccess.QueryDataAsync(vm, input); + return ls; + } + + /// + /// 新增 数据 + /// + [HttpPost("[area]/[controller]/{vmid}/[action]")] + public async Task CreateAsync(string vmid, VmCreateInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid); + var ret = await _dataAccess.CreateDataAsync(vm, input); + return ret; + } + + /// + /// 更新 数据 + /// + [HttpPut("[area]/[controller]/{vmid}/[action]")] + public async Task UpdateAsync(string vmid, VmUpdateInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid); + var ret = await _dataAccess.UpdateDataAsync(vm, input); + return ret; + } + + /// + /// 删除 数据 + /// + [HttpDelete("[area]/[controller]/{vmid}/[action]")] + public async Task DeleteAsync(string vmid, [FromQuery] VmDeleteInput input) + { + var vm = await _dataAccess.GetVmodelAsync(vmid); + var ret = await _dataAccess.DeleteDataAsync(vm, input); + return ret; + } + + private async Task GetVmodelAsync(string area, string vmCode) + { + var vm = await _dataAccess.GetVmodelAsync(area.SnakeToPascalCase(), vmCode.SnakeToPascalCase(), true); + return vm; + } + #endregion + + #region 根据vmode的area和code进行增删改查接口 + /// + /// 获取一条 数据信息 + /// + [HttpGet("{areaCode}/{vmCode}/[action]")] + public async Task GetAsync(string areaCode, string vmCode, [FromQuery] VmGetInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + VmQueryInput arg = input.Adapt(); + if (input.id != null) + { + if (arg.q == null) arg.q = new DObject(); + arg.q.Add(vm.GetPrimary().code, input.id); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls.items.FirstOrDefault(); + } + + /// + /// 获取多条 数据列表 + /// + [HttpGet("{areaCode}/{vmCode}/[action]")] + public async Task GetListAsync(string areaCode, string vmCode, [FromQuery] VmGetListInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + VmQueryInput arg = input.Adapt(); + if (!string.IsNullOrEmpty(input.q)) + { + arg.q = input.q.ToObject(); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls; + } + + /// + /// 获取多条 数据列表 + /// + [HttpPost("{areaCode}/{vmCode}/[action]")] + public async Task QueryAsync(string areaCode, string vmCode, [FromBody] VmQueryInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + var ls = await _dataAccess.QueryDataAsync(vm, input); + return ls; + } + + /// + /// 新增 数据 + /// + [HttpPost("{areaCode}/{vmCode}/[action]")] + public async Task CreateAsync(string areaCode, string vmCode, VmCreateInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + var ret = await _dataAccess.CreateDataAsync(vm, input); + return ret; + } + + /// + /// 更新 数据 + /// + [HttpPut("{areaCode}/{vmCode}/[action]")] + public async Task UpdateAsync(string areaCode, string vmCode, VmUpdateInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + var ret = await _dataAccess.UpdateDataAsync(vm, input); + return ret; + } + + /// + /// 删除 数据 + /// + [HttpDelete("{areaCode}/{vmCode}/[action]")] + public async Task DeleteAsync(string areaCode, string vmCode, [FromQuery] VmDeleteInput input) + { + var vm = await GetVmodelAsync(areaCode, vmCode); + var ret = await _dataAccess.DeleteDataAsync(vm, input); + return ret; + } + #endregion + +} diff --git a/visualdev/Tnb.Vmodel/VmAppServiceT.cs b/visualdev/Tnb.Vmodel/VmAppServiceT.cs new file mode 100644 index 00000000..177eac81 --- /dev/null +++ b/visualdev/Tnb.Vmodel/VmAppServiceT.cs @@ -0,0 +1,119 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using JNPF.Common.Contracts; +using JNPF.Common.Security; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using Tnb.DataAccess; + +namespace Tnb.VmodelEngine; + +/// +/// 增删改查基类 +/// +[ApiDescriptionSettings(Area = ModuleConst.Area, Order = 10, KeepVerb = true)] +[Route("api/[area]/[controller]/[action]")] +public class VmAppService : BaseAppService where TEntity : IEntity +{ + protected readonly IDataAccess _dataAccess; + protected readonly ISqlSugarClient _db; + + /// + /// 构造函数 + /// + public VmAppService(IDataAccess dataAccess) + { + _dataAccess = dataAccess; + _db = _dataAccess.GetSqlSugar(); + } + + protected async Task GetVmodelAsync() + { + var tp = typeof(TEntity); + if (string.IsNullOrEmpty(tp?.Namespace)) + { + throw new ArgumentNullException($"类型 {nameof(tp)} 的命名空间不可为空"); + } + var area = tp.Namespace.Split('.').Last().ToKebabCase(); + var vm = await _dataAccess.GetVmodelAsync(area, tp.Name, true); + + return vm; + } + + /// + /// 获取一条 数据信息 + /// + public virtual async Task GetAsync([FromQuery] VmGetInput input) + { + var vm = await GetVmodelAsync(); + VmQueryInput arg = input.Adapt(); + if (input.id != null) + { + if (arg.q == null) arg.q = new DObject(); + arg.q.Add(vm.GetPrimary().code, input.id); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls.items.FirstOrDefault()!; + } + + /// + /// 获取多条 数据列表 + /// + public virtual async Task GetListAsync([FromQuery] VmGetListInput input) + { + var vm = await GetVmodelAsync(); + VmQueryInput arg = input.Adapt(); + if (!string.IsNullOrEmpty(input.q)) + { + arg.q = input.q.ToObject(); + } + var ls = await _dataAccess.QueryDataAsync(vm, arg); + return ls; + } + + /// + /// 获取多条 数据列表 + /// + [HttpPost] + public virtual async Task QueryAsync([FromBody] VmQueryInput input) + { + var vm = await GetVmodelAsync(); + var ls = await _dataAccess.QueryDataAsync(vm, input); + return ls; + } + + /// + /// 新增 数据 + /// + public virtual async Task CreateAsync(VmCreateInput input) + { + var vm = await GetVmodelAsync(); + var ret = await _dataAccess.CreateDataAsync(vm, input); + return ret; + } + + /// + /// 更新 数据 + /// + public virtual async Task UpdateAsync(VmUpdateInput input) + { + var vm = await GetVmodelAsync(); + var ret = await _dataAccess.UpdateDataAsync(vm, input); + return ret; + } + + /// + /// 删除 数据 + /// + public virtual async Task DeleteAsync([FromQuery] VmDeleteInput input) + { + var vm = await GetVmodelAsync(); + var ret = await _dataAccess.DeleteDataAsync(vm, input); + return ret; + } + +} diff --git a/visualdev/Tnb.Vmodel/VmodelAppService.cs b/visualdev/Tnb.Vmodel/VmodelAppService.cs new file mode 100644 index 00000000..e3ac361b --- /dev/null +++ b/visualdev/Tnb.Vmodel/VmodelAppService.cs @@ -0,0 +1,150 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using Mapster; +using Microsoft.AspNetCore.Mvc; +using SqlSugar; +using Tnb.DataAccess; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型服务类 +/// +public class VmodelAppService : VmAppService, IVmodelAppService +{ + + /// + /// 构造函数 + /// + public VmodelAppService(IDataAccess da) : base(da) + { + } + + /// + /// 获取一条 数据信息 + /// + public override async Task GetAsync(VmGetInput input) + { + //return await _dataAccess.GetVmodelAsync(input.id); + var query = _db.Queryable().Where(a => a.deleted == 0); + Vmodel vm; + if (long.TryParse(input.id, out long id)) + { + vm = await query.FirstAsync(a => a.id == input.id); + } + else + { + vm = await query.FirstAsync(a => a.vmCode == input.id); + } + return vm; + } + + /// + /// 获取多条 数据列表 + /// + public override async Task GetListAsync(VmGetListInput input) + { + VmPagedOutput ret = new(); + var q = _db.Queryable().WhereIF(!string.IsNullOrEmpty(input.k), a => a.vmCode.Contains(input.k!) || a.vmName.Contains(input.k!)); + RefAsync total = 0; + var data = await q.OrderBy(input.sort).ToPageListAsync((input.pnum - 1) * input.psize, input.psize, total); + ret.total = total; + ret.items = data.ConvertAll(a => a); + return ret; + } + + [NonAction] + public override Task QueryAsync(VmQueryInput input) + { + return base.QueryAsync(input); + } + + /// + /// 新增 模型 + /// + public override async Task CreateAsync(VmCreateInput input) + { + //ThrowIf.IsNull(input.data, nameof(input)); + ArgumentNullException.ThrowIfNull(input.data); + Vmodel vm = input.data.Adapt(); + await _db.Insertable(vm).ExecuteCommandAsync(); + return input; + } + + /// + /// 更新 数据 + /// + public override async Task UpdateAsync(VmUpdateInput input) + { + ArgumentNullException.ThrowIfNull(input.data); + Vmodel vm = input.data.Adapt(); + await _db.Updateable(vm).WhereColumns(a => a.id).ExecuteCommandAsync(); + return input; + } + + /// + /// 删除 数据 + /// + public override async Task DeleteAsync(VmDeleteInput input) + { + var ret = await _db.Deleteable(input.id).ExecuteCommandAsync(); + return ret; + } + + /// + /// 从数据表创建模型 + /// + public async Task> CreateFromTable(VmodelCreateFromTableInput input) + { + ThrowIf.IsNull(input.tableName, nameof(input.tableName)); + var sugar = _dataAccess.GetSqlSugar(input.dbCode); + var lsTable = sugar.DbMaintenance.GetTableInfoList().WhereIF(input.tableName != "ALL", a => a.Name == input.tableName); + + List lsToAdd = new List(); + List lsToUpdate = new List(); + foreach (var tb in lsTable) + { + if (!string.IsNullOrEmpty(input.removePrefix) && !tb.Name.StartsWith(input.removePrefix)) continue; + var colInfo = sugar.DbMaintenance.GetColumnInfosByTableName(tb.Name); + Vmodel model = new() { dbCode = input.dbCode, vmName = tb.Description, tableName = tb.Name }; + model.area = input.area; + model.vmCode = (string.IsNullOrEmpty(input.removePrefix) ? tb.Name : tb.Name.RemovePreFix(input.removePrefix)).SnakeToPascalCase(); + //model.createId = CurrentUser.Id; + int n = 1; + foreach (var p in colInfo) + { + var prop = p.Adapt(); + prop.ordinal = n++; + prop.csType = sugar.Ado.DbBind.GetPropertyTypeName(p.DataType); + model.dbProps.Add(prop); + } + var exist = await _db.Queryable().FirstAsync(a => a.dbCode == input.dbCode && a.tableName == tb.Name); + if (exist == null) + { + lsToAdd.Add(model); + } + else + { + exist.area = model.area; + model.dbProps.Adapt(exist.dbProps); + //exist.dbProps.Clear(); + //exist.dbProps.AddRange(model.dbProps.OrderBy(a => a.ordinal)); + lsToUpdate.Add(exist); + } + } + if (lsToAdd.Count > 0) + { + await _db.Insertable(lsToAdd).ExecuteCommandAsync(); + } + if (lsToUpdate.Count > 0) + { + await _db.Updateable(lsToUpdate).ExecuteCommandAsync(); + } + return lsToAdd.Union(lsToUpdate).ToList(); + } + + +} diff --git a/visualdev/Tnb.Vmodel/VmodelPageAppService.cs b/visualdev/Tnb.Vmodel/VmodelPageAppService.cs new file mode 100644 index 00000000..1b957326 --- /dev/null +++ b/visualdev/Tnb.Vmodel/VmodelPageAppService.cs @@ -0,0 +1,166 @@ +///////////////////////////////////////////////////////////////////////////////// +// 宁波拓通e智造平台 ToTong Next Builder // +// https://git.tuotong-tech.com/tnb/tnb.server // +///////////////////////////////////////////////////////////////////////////////// + +using System.Text; +using JNPF.Common.Security; +using Mapster; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json.Linq; +using SqlSugar; +using Tnb.DataAccess; + +namespace Tnb.VmodelEngine; + +/// +/// 视图模型服务类 +/// +public class VmodelPageAppService : VmAppService, IVmodelPageAppService +{ + /// + /// 构造函数 + /// + public VmodelPageAppService(IDataAccess da) : base(da) + { + } + + /// + /// 获取一条 数据信息 + /// + public override async Task GetAsync(VmGetInput input) + { + var query = _db.Queryable().Where(a => a.deleted == 0); + VmodelPage vm = await query.FirstAsync(a => a.id == input.id); + return vm; + } + + /// + /// 获取多条 数据列表 + /// + public override async Task GetListAsync(VmGetListInput input) + { + VmPagedOutput ret = new(); + var q = _db.Queryable().WhereIF(!string.IsNullOrEmpty(input.k), a => a.code.Contains(input.k!) || a.name.Contains(input.k!)); + RefAsync total = 0; + var data = await q.OrderBy(input.sort).ToPageListAsync((input.pnum - 1) * input.psize, input.psize, total); + ret.total = total; + ret.items = data.ConvertAll(a => a); + return ret; + } + + /// + /// 新增 模型 + /// + public override async Task CreateAsync(VmCreateInput input) + { + ArgumentNullException.ThrowIfNull(input.data); + VmodelPage vpage = input.data.Adapt(); + await _db.Insertable(vpage).ExecuteCommandAsync(); + return vpage; + } + + /// + /// 更新 数据 + /// + public override async Task UpdateAsync(VmUpdateInput input) + { + ArgumentNullException.ThrowIfNull(input.data); + if (!input.data.ContainsKey(nameof(VmodelPage.id))) + { + throw new Exception($"更新数据时主键({nameof(VmodelPage.id)})不可为空"); + } + var id = input.data[nameof(VmodelPage.id)].ToString(); + var model = await _db.Queryable().FirstAsync(a => a.id == id); + ArgumentNullException.ThrowIfNull(model); + input.data.Adapt(model, TypeAdapter.IgnoreNull); + await _db.Updateable(model).WhereColumns(a => a.id).ExecuteCommandAsync(); + return model; + } + + /// + /// 删除 数据 + /// + public override async Task DeleteAsync(VmDeleteInput input) + { + var ret = await _db.Deleteable(input.id).ExecuteCommandAsync(); + return ret; + } + + /// + /// 从数据表创建模型 + /// + public async Task CreateByVmodel(CreatePageFromVmodelInput input) + { + ArgumentNullException.ThrowIfNull(input.vmid); + var vm = await _dataAccess.GetVmodelAsync(input.vmid); + ArgumentNullException.ThrowIfNull(vm); + + var page = await _db.Queryable().FirstAsync(a => a.vmid == vm.id); + if (page == null) + { + page = new VmodelPage { vmid = vm.id, code = vm.vmCode, name = vm.vmName }; + page.pageSchema = CreatePageSchema(vm, page.id); + await _db.Insertable(page).ExecuteCommandAsync(); + } + else + { + page.pageSchema = CreatePageSchema(vm, page.id); + await _db.Updateable(page).ExecuteCommandAsync(); + } + return page; + } + + private JObject CreatePageSchema(Vmodel vm, string pageid) + { + StringBuilder str = new StringBuilder(); + str.AppendLine("{"); + str.AppendLine($"\"page\": {{ \"loadList\": true, \"watchClient\": false }},"); + str.AppendLine($"\"queryData\": {{ }},"); + str.AppendLine($"\"queryForm\": {{"); + str.AppendLine($"\"show\": false,"); + str.AppendLine($"\"attr\": {{ \"labelWidth\": \"106px\", \"hasKeyword\":false }},"); + str.AppendLine($"\"cols\": {{"); + var pQuery = vm.dbProps.Skip(1).Take(1).FirstOrDefault(); + if (pQuery != null) + { + str.AppendLine($"\"{pQuery.code}\": {{ \"label\": \"{pQuery.name}\", \"span\": 8, \"qtype\": 2, \"isQuick\": true, \"comp\": {{ \"type\": \"el-input\", \"attr\": {{ \"placeholder\": \"{pQuery.name}\", \"clearable\": true, \"maxlength\": 20 }} }} }}"); + } + str.AppendLine($"}}"); + str.AppendLine($"}},"); + str.AppendLine($"\"list\": {{"); + str.AppendLine($"\"opt\": {{ \"isPage\": true, \"isCheck\": true, \"sortBy\": \"\", \"pkey\": \"{vm.GetPrimary().code}\" }},"); + str.AppendLine($"\"attr\": {{ \"border\": false }},"); + str.AppendLine($"\"cols\": {{"); + foreach (var p in vm.dbProps) + { + str.AppendLine($"\"{p.code}\":{{ \"label\": \"{p.name}\", \"show\": true, \"attr\": {{ {p.GetDefaultWidth()} }}, \"comp\": {{}} }},"); + } + str.AppendLine($"}}"); + str.AppendLine($"}},"); + str.AppendLine($"\"editData\": {vm.GetDefaultDObject().ToJsonString()},"); + str.AppendLine($"\"editDlg\": {{ \"isAdd\": true, \"tabHeight\": 300, \"name\": \"{vm.vmName}\" }},"); + str.AppendLine($"\"editForm\": {{"); + str.AppendLine($"\"attr\": {{ \"labelWidth\": \"106px\" }},"); + str.AppendLine($"\"rules\": {{"); + foreach (var p in vm.dbProps.Where(a => a.required && !a.pkey)) + { + str.AppendLine($"\"{p.code}\": [{{ \"required\": true, \"message\": \"必填项不能为空\", \"trigger\": \"blur\" }}],"); + } + str.AppendLine($"}},"); + str.AppendLine($"\"cols\": {{"); + foreach (var p in vm.dbProps) + { + str.AppendLine($"\"{p.code}\": {{ \"label\": \"{p.name}\", \"show\": true, \"comp\": {p.GetDefaultComp().ToJsonString()} }},"); + } + str.AppendLine($"}}"); + str.AppendLine($"}},"); + str.AppendLine($"\"tree\": {{ \"key\": \"id\", \"height\": 300, \"props\": {{ \"label\": \"enumName\" }}, \"data\": [] }}"); + str.AppendLine($"}}"); + var s = str.ToString(); + Console.WriteLine(s); + return JObject.Parse(s); + } + +}