diff --git a/apihost/Tnb.API.Entry/Configurations/App.json b/apihost/Tnb.API.Entry/Configurations/App.json index 2610456e..136f83e5 100644 --- a/apihost/Tnb.API.Entry/Configurations/App.json +++ b/apihost/Tnb.API.Entry/Configurations/App.json @@ -71,7 +71,8 @@ "txt", "rar", "zip", - "csv" + "csv", + "json" ], //过滤上传文件名称特殊字符 "SpecialString": [ @@ -188,5 +189,5 @@ "DoMainApp": "http://localhost:8081", // 前端App外网能访问的地址(域名), 回调的时候拼接接口地址用 "AppPushUrl": "https://8e84eea8-6922-4033-8e86-67ad7442e692.bspapp.com/unipush" }, - "IsStartTimeJob": true //是否开启定时任务 + "IsStartTimeJob": false //是否开启定时任务 } \ No newline at end of file diff --git a/common/Tnb.Common/Extension/TnbStringExtensions.cs b/common/Tnb.Common/Extension/TnbStringExtensions.cs index e8436ddb..8be6669e 100644 --- a/common/Tnb.Common/Extension/TnbStringExtensions.cs +++ b/common/Tnb.Common/Extension/TnbStringExtensions.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using JNPF.Common.Extension; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace System; @@ -183,4 +184,17 @@ public static class StringExtensions public static string ToCamel(this string str) => str.SplitWord().Select((a, i) => i == 0 ? a : a.UpperFirst()).JoinAsString(""); #endregion + + public static (string, string) LastSplit(this string str, char seperate) + { + int n = str.LastIndexOf(seperate); + if(n > 0) + { + return (str.Substring(0, n), str.Substring(n + 1)); + } + else + { + return (str, null); + } + } } \ No newline at end of file diff --git a/visualdev/Tnb.Vengine/DataAccess/DataAccess.cs b/visualdev/Tnb.Vengine/DataAccess/DataAccess.cs index 79b070ca..22299cdb 100644 --- a/visualdev/Tnb.Vengine/DataAccess/DataAccess.cs +++ b/visualdev/Tnb.Vengine/DataAccess/DataAccess.cs @@ -1,6 +1,8 @@ using System.Collections.Concurrent; +using System.Security.Cryptography.Xml; using JNPF; using JNPF.Common.Core.Manager; +using JNPF.Common.Extension; using JNPF.DependencyInjection; using Microsoft.Extensions.Configuration; using SqlSugar; @@ -15,7 +17,7 @@ namespace Tnb.Vengine.DataAccess; public class DataAccess : IDataAccess, ITransient, IDisposable { private const int MAX_PAGE_SIZE = 1000; - private ISqlSugarClient? _db; + private static ISqlSugarClient? _db; protected ISqlSugarClient Db { @@ -170,14 +172,18 @@ public class DataAccess : IDataAccess, ITransient, IDisposable private async Task LoadVmodelNavigateAsync(Vmodel vm) { Dictionary dictVm = new(); + var vmids = vm.navProps.Select(a => a.vmid).Distinct().ToList(); + var ls = await Db.Queryable().Where(a => vmids.Contains(a.id)).ToListAsync(); + var navs = ls.ToDictionary(a => a.id); 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]; + navProp.naviModel = (Vmodel)navs.GetOrDefault(navProp.vmid); + //if (!dictVm.ContainsKey(navProp.vmid)) + //{ + // var navModel = await GetVmodelAsync(navProp.vmid); + // dictVm.Add(navProp.vmid, navModel); + //} + //navProp.naviModel = dictVm[navProp.vmid]; } } @@ -191,10 +197,8 @@ public class DataAccess : IDataAccess, ITransient, IDisposable var selProps = vm.GetVmSelectProps(input.o); //处理导航属性联表 List joins = vm.GetJoinInfos(selProps); - query.AddJoinInfo(joins); //if (joins.Count > 0) - //{ - //} + query.AddJoinInfo(joins); List wheres = vm.GetConditionalModels(input.q); if (!string.IsNullOrEmpty(input.k)) { @@ -208,10 +212,8 @@ public class DataAccess : IDataAccess, ITransient, IDisposable wheres.Add(new ConditionalCollections() { ConditionalList = lsCondition }); } //处理查询参数 - query.Where(wheres); //if (wheres.Count > 0) - //{ - //} + query.Where(wheres); if (!string.IsNullOrEmpty(input.sort)) { query.OrderBy(input.sort); @@ -229,7 +231,7 @@ public class DataAccess : IDataAccess, ITransient, IDisposable //组装输出对象 foreach (var data in ls) { - DObject ret = await NestedOutputAsync(vm, new DObject(data), selProps); + DObject ret = await CombineOutputAsync(vm, new DObject(data), selProps); result.items.Add(ret); } @@ -243,11 +245,12 @@ public class DataAccess : IDataAccess, ITransient, IDisposable /// /// /// - private async Task NestedOutputAsync(Vmodel vm, DObject src, List selProps) + private async Task CombineOutputAsync(Vmodel vm, DObject 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)) @@ -257,40 +260,14 @@ public class DataAccess : IDataAccess, ITransient, IDisposable } else { + // 加载关联表字段 if (prop.navType == eNavigateType.OneToOne) { - //以 nav_prop的形式返回 - var key = prop.navCode + "_" + prop.code; - ret.Add(key, src[key]); - //以 nav.prop的形式返回 - //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]); - //} + NestedOutput(vm, src, ret, prop); } 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)) - { - VmListInput input = new VmListInput(); - var fkProp = navProp.naviModel.FieldCodeToPropCode(navProp.fkField); - if (!string.IsNullOrEmpty(fkProp)) - { - input.q = new DObject(fkProp, 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; - } - } + await NestedOneToManyAsync(vm, src, ret, prop, selProps); } else if (prop.navType == eNavigateType.ManyToMany) { @@ -304,6 +281,50 @@ public class DataAccess : IDataAccess, ITransient, IDisposable return ret; } + /// + /// 将一对一的关联表字段嵌入到返回值中 + /// + private void NestedOutput(Vmodel vm, DObject src, DObject ret, VmSelectProp prop) + { + // 以 nav_prop的形式返回 + var key = prop.navCode + "_" + prop.code; + ret.Add(key, src[key]); + // 以 nav.prop的形式返回 + //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]); + //} + } + + private async Task NestedOneToManyAsync(Vmodel vm, DObject src, DObject ret, VmSelectProp prop, List selProps) + { + // 在返回值中增加导航属性 + if (ret.ContainsKey(prop.navCode)) + { + return; + } + ret.Add(prop.navCode, new List()); + + // 找到导航属性的配置 + var navCfg = vm.navProps.First(a => a.code == prop.navCode); + if (navCfg != null && navCfg.naviModel != null && src.ContainsKey(navCfg.refField) && navCfg.refCode == VmSelectProp.MAIN_ALIES) + { + VmListInput input = new VmListInput(); + var fkProp = navCfg.naviModel.FieldCodeToPropCode(navCfg.fkField); + if (!string.IsNullOrEmpty(fkProp)) + { + input.q = new DObject(fkProp, src[navCfg.refField]); + input.o = string.Join(',', selProps.Where(a => a.navCode == prop.navCode).Select(a => a.code)); + ret[prop.navCode] = (await QueryDataAsync(navCfg.naviModel, input)).items; + } + } + } + /// /// 新增数据 默认方法 /// diff --git a/visualdev/Tnb.Vengine/Domain/OutputParser.cs b/visualdev/Tnb.Vengine/Domain/OutputParser.cs new file mode 100644 index 00000000..2a90c0b9 --- /dev/null +++ b/visualdev/Tnb.Vengine/Domain/OutputParser.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JNPF.Common.Extension; +using Tnb.Vengine.DataAccess; + +namespace Tnb.Vengine.Domain +{ + public class OutputParser + { + private readonly IDataAccess _dataAccess; + private readonly Vmodel _root; + private List _outputs; + private Dictionary _selectProps = new Dictionary(); + private Dictionary _navModels = new Dictionary(); + public OutputParser(IDataAccess dataAccess, Vmodel rootModel, string output) + { + _dataAccess = dataAccess; + _root = rootModel; + _outputs = output.Split(',').Distinct().ToList(); + ParseOutputStr(); + } + /// + /// 按模型组织要输出的属性 + /// + private void ParseOutputStr() + { + foreach (var outStr in _outputs) + { + if (string.IsNullOrWhiteSpace(outStr)) + { + continue; + } + string vmPath; + int n = outStr.LastIndexOf('.'); + if (n < 1) + { + vmPath = Vmodel.MAIN_ALIES; + } + else + { + vmPath = outStr.Substring(0, n); + } + if (!_selectProps.ContainsKey(vmPath)) + { + _selectProps.Add(vmPath, new OutputSelect { navPaths = vmPath.Split(',').ToList(), vmPath = vmPath }); + } + var outSelect = _selectProps[vmPath]; + outSelect.propCodes.Add(outStr.Substring(n + 1)); + } + } + + private async Task LoadNavModel() + { + var navProps = _root.navProps.Where(a => _selectProps.Values.Any(b => b.vmPath.StartsWith(a.code + "."))).ToList(); + await LoadVmodelNavigateAsync(navProps); + } + private async Task LoadVmodelNavigateAsync(List navProps) + { + var db = _dataAccess.GetSqlSugar(); + + var vmids = navProps.Select(a => a.vmid).Distinct().ToList(); + var navs = await db.Queryable().Where(a => vmids.Contains(a.id)).ToDictionaryAsync(a => a.id, a => a); + foreach (var navProp in navProps) + { + navProp.naviModel = (Vmodel)navs.GetOrDefault(navProp.vmid); + navProp.naviModel.navProps.Where(a => _selectProps.Values.Any(b => b.vmPath.StartsWith(a.code + "."))).ToList(); + } + } + + } + + internal class OutputSelect + { + public string vmId { get; set; } = string.Empty; + public string vmCode { get; set; } = string.Empty; + public string vmPath { get; set; } = string.Empty; + public eNavigateType navType { get; set; } = eNavigateType.None; + public List navPaths { get; set; } = new List(); + + public List propCodes { get; set; } = new List(); + public List fieldCodes { get; set; } = new List(); + + public OutputSelect() + { + + } + public OutputSelect(Vmodel model) + { + vmId = model.id; + vmCode = model.vmCode; + vmPath = Vmodel.MAIN_ALIES; + } + } +} diff --git a/visualdev/Tnb.Vengine/Domain/VengineDto.cs b/visualdev/Tnb.Vengine/Domain/VengineDto.cs index d1d441bf..f83b61a9 100644 --- a/visualdev/Tnb.Vengine/Domain/VengineDto.cs +++ b/visualdev/Tnb.Vengine/Domain/VengineDto.cs @@ -164,6 +164,7 @@ public class VmSelectProp public const string MAIN_ALIES = "m"; public string code { get; set; } = string.Empty; public string field { get; set; } = string.Empty; + public List navPath { get; set; } = new List(); public string navCode { get; set; } = MAIN_ALIES; public ePropType propType { get; set; } public eNavigateType navType { get; set; } diff --git a/visualdev/Tnb.Vengine/Domain/VmDbProp.cs b/visualdev/Tnb.Vengine/Domain/VmDbProp.cs index c654a1f3..b985f776 100644 --- a/visualdev/Tnb.Vengine/Domain/VmDbProp.cs +++ b/visualdev/Tnb.Vengine/Domain/VmDbProp.cs @@ -111,7 +111,7 @@ public class VmDbProp : VmBaseProp /// 获取默认值文本 /// /// - public object? GetDefaultValueString() + public string? GetDefaultValueString() { string val = ""; if (!required) return val; diff --git a/visualdev/Tnb.Vengine/Domain/Vmodel.cs b/visualdev/Tnb.Vengine/Domain/Vmodel.cs index 9c5fb6cb..89b0d03f 100644 --- a/visualdev/Tnb.Vengine/Domain/Vmodel.cs +++ b/visualdev/Tnb.Vengine/Domain/Vmodel.cs @@ -23,6 +23,8 @@ namespace Tnb.Vengine.Domain; [SugarTable("sys_vmodel")] public partial class Vmodel : Entity { + public const string MAIN_ALIES = "m"; + #region Properties /// @@ -146,7 +148,8 @@ public partial class Vmodel : Entity } #endregion Properties - + //private Dictionary? _mapField2Prop = null; + //private Dictionary? _mapProp2Field = null; /// /// 通过实体创建模型 /// @@ -351,11 +354,30 @@ public partial class Vmodel : Entity if (filter == null) return wheres; foreach (var item in filter) { + VmDbProp? prop = null; // TODO 按子表条件查询 if (item.Key.Contains(".")) { + var codes = item.Key.Split('.'); + if (codes.Length >= 2) + { + var navProp = navProps.FirstOrDefault(a => a.code == codes[0]); + if (navProp != null && navProp.naviModel != null) + { + var dbProp = navProp.naviModel.dbProps.FirstOrDefault(a => a.code == codes[1]); + if (dbProp != null) + { + prop = new VmDbProp(); + prop.field = codes[0] + "." + dbProp.field; + prop.csType = dbProp.csType; + } + } + } + } + else + { + prop = dbProps.FirstOrDefault(a => a.code == item.Key); } - var prop = dbProps.FirstOrDefault(a => a.code == item.Key); if (prop == null) continue; if (item.Value is JArray val) {