itfx/src/InterfaceForward.Application/Services/Parameter/ParameterService.cs

586 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 参数服务
/// </summary>
[ApiExplorerSettings(GroupName = "参数服务")]
public class ParameterService : ApplicationService
{
private readonly IInterfaceRepository _interfaceRepository;
private readonly IParameterRepository _parameterRepository;
/// <summary>
/// 参数根id
/// </summary>
public static readonly int ParameterRootId = 0;
/// <inheritdoc />
public ParameterService(IInterfaceRepository interfaceRepository
, IParameterRepository parameterRepository)
{
_interfaceRepository = interfaceRepository;
_parameterRepository = parameterRepository;
}
/// <summary>
/// 获取参数列表
/// </summary>
/// <param name="isInPara">是否入参(true:入参,false:出参)</param>
/// <param name="id">接口id不分上下游</param>
/// <param name="onlyLeafs">只要叶子节点默认false返回所有节点</param>
/// <returns></returns>
[HttpGet("Parameter/GetParameterList")]
public async Task<IEnumerable<InterfaceParameterOutputValueObject>> GetParameterListAsync([Required] bool isInPara,
[Required] int id, bool onlyLeafs = false)
{
var paras = await _interfaceRepository.GetParameterListByIdAsync(id, onlyLeafs);
return paras.Where(x => x.IsInPara == isInPara);
}
/// <summary>
/// 保存参数
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Parameter/Save")]
public async Task<bool> 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);
}
/// <summary>
/// 保存参数序列
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
[HttpPost("Parameter/SaveSort")]
public async Task<bool> 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;
}
/// <summary>
/// 参数解析
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost("Parameter/Parse")]
public List<ParameterParseOutput> 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: "暂不支持该格式解析");
}
}
/// <summary>
/// 解析xml
/// </summary>
/// <param name="str">xml字符串</param>
/// <returns></returns>
/// <exception cref="BusinessException">xml无效</exception>
[ApiExplorerSettings(IgnoreApi = true)]
public List<ParameterParseOutput> 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);
}
/// <summary>
/// 解析json
/// </summary>
/// <param name="str">参数json字符串</param>
/// <returns></returns>
/// <exception cref="BusinessException">json无效</exception>
[ApiExplorerSettings(IgnoreApi = true)]
public List<ParameterParseOutput> 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<ParameterParseOutput> 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<ParameterParseOutput> { new(idx, pid, "val", type, annotation) };
}
private List<ParameterParseOutput> Jobject2List(JObject jobject, ref int idx, int pid)
{
List<ParameterParseOutput> 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<ParameterParseOutput> JArray2List(JArray jarray, ref int idx, int pid)
{
var annotation = GetAnnotation(jarray);
List<ParameterParseOutput> 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<bool> MaintainParametersAsync(List<ParameterInput> 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<InterfaceParameterOutputValueObject, ParameterInput>(item));
}
#endregion
ValidateParameter(
JsonConvert.DeserializeObject<List<ParameterInput>>(
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;
}
/// <summary>
/// bfs持久化参数
/// </summary>
/// <param name="list"></param>
/// <param name="interfaceId"></param>
/// <param name="isInPara"></param>
/// <exception cref="BusinessException"></exception>
internal async Task CreateParametersAsync(List<ParameterInput> list, int interfaceId, bool isInPara)
{
var parameters =
await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara);
var tops = new List<ParameterInput>();
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<ParameterInput>(tops);
while (queue.Count > 0)
{
var cur = queue.Dequeue();
var entity = ObjectMapper.Map<ParameterInput, ParameterEntity>(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<int> ids)
{
if (ids.Count == 0) return;
await _parameterRepository.SoftDeleteAsync(x => ids.Contains(x.Id));
}
/// <summary>
/// 给一棵树的任意几个节点 快速将以其为根的子树染色
/// </summary>
/// <param name="list"></param>
/// <param name="interfaceId"></param>
/// <param name="isInPara"></param>
private async Task UpdateParametersAsync(List<ParameterInput> 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<ParameterInput>, List<ParameterEntity>>(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<ParameterNode> Childrens { get; set; }
}
private List<ParameterNode> BuildTreeList(List<ParameterEntity> list, int pid)
{
var res = new List<ParameterNode>();
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<ParameterEntity> GeneratorUpdateParameterEntities(List<ParameterNode> treeList, List<ParameterEntity> nodes)
{
var res = new List<ParameterEntity>();
var queue = new Queue<ParameterNode>(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));
}
/// <summary>
/// 参数校验
/// </summary>
/// <param name="list"></param>
/// <exception cref="BusinessException"></exception>
[HttpPost("Parameter/ValidateParameter")]
public bool ValidateParameter(List<ParameterInput> 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<ParameterInput>(tops);
var set = new HashSet<string>(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;
}
}