using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Xml; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using InterfaceForward.Application.Contracts.Dtos.Interface; using InterfaceForward.Application.Helpers; using InterfaceForward.Domain.Shared; using InterfaceForward.Domain.Shared.Enum; using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Interface.Services; using InterfaceForward.Repositories.Parameter.Services; using InterfaceForward.Repositories.Parameter.VO; using Microsoft.AspNetCore.Mvc; namespace InterfaceForward.Application.Services.Parameter; /// /// 参数服务 /// [ApiExplorerSettings(GroupName = "参数服务")] public class ParameterService : ApplicationService { private readonly IInterfaceRepository _interfaceRepository; private readonly IParameterRepository _parameterRepository; /// /// 参数根id /// public static readonly int ParameterRootId = 0; /// public ParameterService(IInterfaceRepository interfaceRepository , IParameterRepository parameterRepository) { _interfaceRepository = interfaceRepository; _parameterRepository = parameterRepository; } /// /// 获取参数列表 /// /// 是否入参(true:入参,false:出参) /// 接口id(不分上下游) /// 只要叶子节点,默认false,返回所有节点 /// [HttpGet("Parameter/GetParameterList")] public async Task> GetParameterListAsync([Required] bool isInPara, [Required] int id, bool onlyLeafs = false) { var paras = await _interfaceRepository.GetParameterListByIdAsync(id, onlyLeafs); return paras.Where(x => x.IsInPara == isInPara); } /// /// 保存参数 /// /// /// [HttpPost("Parameter/Save")] public async Task SaveAsync(SaveParameterInput input) { if (!await _interfaceRepository.IsAnyAsync(x => x.Id == input.InterfaceId)) { throw new BusinessException(message: "接口不存在"); } return await MaintainParametersAsync(input.ParameterList, input.InterfaceId, input.IsInPara); } /// /// 保存参数序列 /// /// /// /// [HttpPost("Parameter/SaveSort")] public async Task SaveSortAsync(SaveParameterSortInput input) { if (!await _interfaceRepository.IsAnyAsync(x => x.Id == input.InterfaceId)) { throw new BusinessException(message: "接口不存在"); } var entities = input.ParameterSortList.Select(x => { if (x.Sort.IsNullOrWhiteSpace()) { return new ParameterEntity() { Id = x.Id, InterfaceId = input.InterfaceId }; } var segments = x.Sort.Split('.'); var newSegments = new string[segments.Length]; for (int i = 0; i < segments.Length; i++) { newSegments[i] = segments[i].PadLeft(3, '0'); } return new ParameterEntity() { Id = x.Id, Sort = newSegments.JoinAsString("."), InterfaceId = input.InterfaceId }; }).ToList(); await _parameterRepository.SaveSortAsync(entities); return true; } /// /// 参数解析 /// /// /// [HttpPost("Parameter/Parse")] public List Parse( ParameterParseInput input) { switch (input.ParameterParseType) { case ParameterParseTypeEnum.Json: return ParseJson(input.ParameterContent); case ParameterParseTypeEnum.Xml: return ParseXml(input.ParameterContent); default: throw new BusinessException(message: "暂不支持该格式解析"); } } /// /// 解析xml /// /// xml字符串 /// /// xml无效 [ApiExplorerSettings(IgnoreApi = true)] public List ParseXml(string str) { var doc = new XmlDocument(); string json; try { doc.LoadXml(str); json = JsonConvert.SerializeXmlNode(doc); } catch (Exception) { throw new BusinessException(message: "无法正常解析,请传入合法xml"); } return ParseJson(json); } /// /// 解析json /// /// 参数json字符串 /// /// json无效 [ApiExplorerSettings(IgnoreApi = true)] public List ParseJson(string str) { JToken jtoken; try { jtoken = JToken.Parse(str, new JsonLoadSettings { CommentHandling = CommentHandling.Load, LineInfoHandling = LineInfoHandling.Ignore, DuplicatePropertyNameHandling = DuplicatePropertyNameHandling.Replace }); } catch (Exception) { throw new BusinessException(message: "无法正常解析,请传入合法json"); } Debug.Assert(jtoken != null, nameof(jtoken) + " != null"); int idx = 1, pid = 0; return JToken2List(jtoken, ref idx, pid); } private List JToken2List(JToken jtoken, ref int idx, int pid) { if (jtoken is JObject obj) return Jobject2List(obj, ref idx, pid); var annotation = GetAnnotation(jtoken); if (jtoken is JArray arr) { idx++; var res = JArray2List(arr, ref idx, 1); var name = pid == 0 ? GlobalConst.RootParameterName : "arr"; res.Insert(0, new(1, 0, name, ParameterType.Array, annotation)); return res; } // value var type = ParameterType.String; if (jtoken.Type is JTokenType.Boolean) type = ParameterType.Boolean; else if (jtoken.Type is JTokenType.Float) type = ParameterType.Float; else if (jtoken.Type is JTokenType.Integer) type = ParameterType.Integer; return new List { new(idx, pid, "val", type, annotation) }; } private List Jobject2List(JObject jobject, ref int idx, int pid) { List res = new(); foreach (var cur in jobject.Properties()) { // 不插入的 if (cur.Name.Equals("?xml") || cur.Name.StartsWith("@xmlns:")) continue; var annotation = GetAnnotation(cur); if (cur.Value is JObject obj) { res.Add(new ParameterParseOutput(idx++, pid, cur.Name, ParameterType.Object, annotation)); res.AddRange(Jobject2List(obj, ref idx, idx - 1)); } else if (cur.Value is JArray arr) { res.Add(new ParameterParseOutput(idx++, pid, cur.Name, ParameterType.Array, annotation)); var v = JArray2List(arr, ref idx, idx - 1); res.AddRange(v); } else { var type = ParameterType.String; if (cur.Value.Type is JTokenType.Boolean) type = ParameterType.Boolean; else if (cur.Value.Type is JTokenType.Float) type = ParameterType.Float; else if (cur.Value.Type is JTokenType.Integer) type = ParameterType.Integer; res.Add(new ParameterParseOutput(idx++, pid, cur.Name, type, annotation)); } } return res; } List JArray2List(JArray jarray, ref int idx, int pid) { var annotation = GetAnnotation(jarray); List res = new(); // 空数组 if (!jarray.HasValues) { res.Add(new ParameterParseOutput(idx++, pid, "val", ParameterType.String, annotation)); return res; } // 数组的第一个结构就是所有元素的结构 var jToken = jarray[0]; annotation = GetAnnotation(jToken); if (jToken is JArray arr) { res = res.Concat(JArray2List(arr, ref idx, idx - 1)).ToList(); } else if (jToken is JObject obj) { res.Add(new ParameterParseOutput(idx++, pid, "obj", ParameterType.Object, annotation)); res = res.Concat(Jobject2List(obj, ref idx, idx - 1)).ToList(); } else if (jToken is JValue val) { var type = ParameterType.String; if (val.Type is JTokenType.Boolean) type = ParameterType.Boolean; else if (val.Type is JTokenType.Float) type = ParameterType.Float; else if (val.Type is JTokenType.Integer) type = ParameterType.Integer; res.Add(new ParameterParseOutput(idx++, pid, "val", type, annotation)); } return res; } static string GetAnnotation(JToken token) { return string.Empty; } internal async Task MaintainParametersAsync(List list, int interfaceId, bool isInPara) { #region 防止请求端没传入已存在的节点 var parameters = await _parameterRepository.GetParameterListByNameAsync(interfaceId, isInPara); foreach (var item in parameters) { if (list.All(x => x.Id != item.Id)) list.Add(ObjectMapper.Map(item)); } #endregion ValidateParameter( JsonConvert.DeserializeObject>( JsonConvert.SerializeObject(list))); // 开启事务 using var ts = TransacationHelper.GetReadCommitted(); // 创建 await CreateParametersAsync(list.Where(x => x.OptionType == OptionType.新增).ToList(), interfaceId, isInPara); // 更新 await UpdateParametersAsync(list.Where(x => x.OptionType == OptionType.修改).ToList(), interfaceId, isInPara); // 删除 await DeleteParametersAsync(list.Where(x => x.OptionType == OptionType.删除).Select(x => x.Id).ToList()); // 提交事务 ts.Complete(); return true; } /// /// bfs持久化参数 /// /// /// /// /// internal async Task CreateParametersAsync(List list, int interfaceId, bool isInPara) { var parameters = await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara); var tops = new List(); foreach (var item in list) { if (item.PId == ParameterRootId) { item.Alias = item.Name; tops.Add(item); continue; } if (list.All(x => x.Id != item.PId)) // 非顶级根 { var entity = parameters.FirstOrDefault(x => x.Id == item.PId); // parent if (entity == null) { continue; // 走空 } // 参数层级关系用”.“进行区分,点击“新增”按钮后,取上一级的参数+“.”填充到新的参数名空格内(例:data.shipper.shipper_name) item.Alias = entity.Alias + "." + item.Name; tops.Add(item); } } var queue = new Queue(tops); while (queue.Count > 0) { var cur = queue.Dequeue(); var entity = ObjectMapper.Map(cur); entity.Alias = cur.Alias; entity.InterfaceId = interfaceId; entity.IsInPara = isInPara; if (!cur.Sort.IsNullOrWhiteSpace()) { var segments = cur.Sort.Split('.'); var newSegments = new string[segments.Length]; for (int i = 0; i < segments.Length; i++) { newSegments[i] = segments[i].PadLeft(3, '0'); } entity.Sort = newSegments.JoinAsString("."); } int id = await _parameterRepository.InsertReturnIdentityAsync(entity); foreach (var item in list.Where(x => x.PId == cur.Id)) { item.PId = id; if (cur.Type == ParameterType.Array) { item.Alias = entity.Alias + "[*]"; } else { // 参数层级关系用”.“进行区分,点击“新增”按钮后,取上一级的参数+“.”填充到新的参数名空格内(例:data.shipper.shipper_name) item.Alias = entity.Alias + "." + item.Name; } queue.Enqueue(item); } } } private async Task DeleteParametersAsync(List ids) { if (ids.Count == 0) return; await _parameterRepository.SoftDeleteAsync(x => ids.Contains(x.Id)); } /// /// 给一棵树的任意几个节点 快速将以其为根的子树染色 /// /// /// /// private async Task UpdateParametersAsync(List list, int interfaceId, bool isInPara) { if (list.Count == 0) return; Debug.Assert(!list.Any(x => x.Id < 1), "参数id均需大于0"); var parameters = await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara); var treeList = BuildTreeList(parameters, ParameterRootId); var realUpdateObjs = GeneratorUpdateParameterEntities(treeList, ObjectMapper.Map, List>(list)); await _parameterRepository.UpdateRangeAsync(realUpdateObjs, x => new { x.PId, x.Alias, x.Name, x.CnName, x.Type, x.IsRequired, x.Sort, x.Description }); } private class ParameterNode { public ParameterEntity Val { get; set; } public List Childrens { get; set; } } private List BuildTreeList(List list, int pid) { var res = new List(); foreach (var item in list.Where(x => x.PId == pid)) { var node = new ParameterNode(); node.Val = item; node.Childrens = BuildTreeList(list, item.Id); res.Add(node); } return res; } List GeneratorUpdateParameterEntities(List treeList, List nodes) { var res = new List(); var queue = new Queue(treeList); while (queue.Count > 0) { if (nodes.Count == 0) break; var node = queue.Dequeue(); foreach (var subNode in node.Childrens) { queue.Enqueue(subNode); } var entity = nodes.FirstOrDefault(x => x.Id == node.Val.Id); if (entity == null) continue; nodes.Remove(entity); entity.Alias = ReplaceLast(node.Val.Alias, node.Val.Name, entity.Name); if (!node.Val.Sort.IsNullOrWhiteSpace()) { var segments = node.Val.Sort.Split('.'); var newSegments = new string[segments.Length]; for (int i = 0; i < segments.Length; i++) { newSegments[i] = segments[i].PadLeft(3, '0'); } entity.Sort = newSegments.JoinAsString("."); } res.Add(entity); foreach (var subNode in node.Childrens) // 只刷一层 { if (entity.Type == ParameterType.Array) { subNode.Val.Alias = entity.Alias + "[*]"; } else { subNode.Val.Alias = entity.Alias + "." + subNode.Val.Name; } if (nodes.All(x => x.Id != subNode.Val.Id)) { nodes.Add(subNode.Val); } } } return res; } private static string ReplaceLast(string str, string old, string cur, StringComparison comparisonType = StringComparison.Ordinal) { ThrowHelper.ThrowIfNull(str, nameof(str)); if (old == cur) return str; var pos = str.LastIndexOf(old, comparisonType); if (pos < 0) { return str; } if (pos - 1 >= 0 && str[pos - 1] != '.') return str; if (pos + old.Length < str.Length) { if (str[pos + old.Length] != '[') return str; } return string.Concat(str.AsSpan(0, pos), cur, str.AsSpan(pos + old.Length)); } /// /// 参数校验 /// /// /// [HttpPost("Parameter/ValidateParameter")] public bool ValidateParameter(List list) { if (list.Count < 1) return true; list = list.Where(x => x.OptionType != OptionType.删除).ToList(); var tops = list.Where(x => x.PId == ParameterRootId).ToList(); tops.ForEach(x => x.Alias = x.Name); var queue = new Queue(tops); var set = new HashSet(list.Count); while (queue.Count > 0) { var cur = queue.Dequeue(); // 校验唯一性 if (!set.Add(cur.Alias)) throw new BusinessException(message: $"不能存在相同路径的参数({cur.Name})"); if (cur.Type == ParameterType.Object) { if (list.All(x => x.PId != cur.Id)) throw new BusinessException(message: $"非法结构,空对象({cur.Name})"); } else if (cur.Type == ParameterType.Array) { if (list.Count(x => x.PId == cur.Id) != 1) throw new BusinessException(message: $"非法结构,数组元素(数组的子级)只能是一个对象或基本类型({cur.Name})"); } else { if (list.Any(x => x.PId == cur.Id)) throw new BusinessException(message: $"非法结构,基本类型不能拥有下级({cur.Name})"); continue; } foreach (var item in list.Where(x => x.PId == cur.Id)) { // 参数层级关系用”.“进行区分,点击“新增”按钮后,取上一级的参数+“.”填充到新的参数名空格内(例:data.shipper.shipper_name) item.Alias = cur.Alias + "." + item.Name; queue.Enqueue(item); } } return true; } }