586 lines
20 KiB
C#
586 lines
20 KiB
C#
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;
|
||
}
|
||
} |