451 lines
18 KiB
C#
451 lines
18 KiB
C#
using System.ComponentModel.DataAnnotations;
|
||
using System.Diagnostics;
|
||
using Newtonsoft.Json;
|
||
using InterfaceForward.Application.Contracts.Dtos.Interface;
|
||
using InterfaceForward.Application.Helpers;
|
||
using InterfaceForward.Application.Services.Parameter;
|
||
using InterfaceForward.Domain.Shared;
|
||
using InterfaceForward.Domain.Shared.Enum;
|
||
using InterfaceForward.Repositories;
|
||
using InterfaceForward.Repositories.Interface.Entitys;
|
||
using InterfaceForward.Repositories.Interface.Services;
|
||
using InterfaceForward.Repositories.Interface.ValueObjects;
|
||
using InterfaceForward.Repositories.ServiceProvider.Entitys;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
|
||
namespace InterfaceForward.Application.Services.Interface;
|
||
|
||
/// <summary>
|
||
/// 接口服务(包括上下游接口)
|
||
/// </summary>
|
||
[ApiExplorerSettings(GroupName = "接口服务")]
|
||
public class InterfaceService : ApplicationService
|
||
{
|
||
private ParameterService ParameterService => LazyServiceProvider.GetRequiredService<ParameterService>();
|
||
|
||
private FormParameterService FormParameterService =>
|
||
LazyServiceProvider.GetRequiredService<FormParameterService>();
|
||
|
||
private IInterfaceRepository InterfaceRepository =>
|
||
LazyServiceProvider.GetRequiredService<IInterfaceRepository>();
|
||
|
||
private IBasicRepository<ServiceProviderEntity> ServiceProviderRepository =>
|
||
LazyServiceProvider.GetRequiredService<IBasicRepository<ServiceProviderEntity>>();
|
||
|
||
private IBasicRepository<InterfaceMapEntity> InterfaceMapRepository =>
|
||
LazyServiceProvider.GetRequiredService<IBasicRepository<InterfaceMapEntity>>();
|
||
|
||
private InterfaceCategoryService InterfaceCategoryService =>
|
||
LazyServiceProvider.GetRequiredService<InterfaceCategoryService>();
|
||
|
||
|
||
/// <summary>
|
||
/// 接口树形列表
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("Interface/GetInterfaceTreeListAsync")]
|
||
public async Task<List<CategoryTreeNode<InterfaceTreeListOutputValueObject>>>
|
||
GetInterfaceTreeListAsync(InterfaceTreeListInputObjectValue input)
|
||
{
|
||
var res = await InterfaceRepository.GetInterfaceListAsync(input);
|
||
|
||
var tree = await InterfaceCategoryService.GetCategoryTree(false, res);
|
||
|
||
return tree;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 接口分页列表
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("Interface/InterfacePaginatedList")]
|
||
public async Task<PagedResult<InterfacePaginatedOutputValueObject>>
|
||
InterfacePaginatedListAsync(InterfacePaginatedInputObjectValue input)
|
||
{
|
||
return await InterfaceRepository.GetInterfacePaginatedListAsync(input);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取接口下拉列表
|
||
/// </summary>
|
||
/// <param name="isUpstream">是否上游</param>
|
||
/// <param name="serviceProviderId">服务商id,上游接口传0</param>
|
||
/// <returns></returns>
|
||
[HttpGet("Interface/GetInterfaceDropdownList")]
|
||
public async Task<List<InterfaceDropdownListItem>> GetInterfaceDropdownListAsync(
|
||
[Required] bool isUpstream, [Required] int serviceProviderId)
|
||
{
|
||
var res = await InterfaceRepository.GetListAsync(x => x.IsUpStream == isUpstream &&
|
||
x.ServiceProviderId == serviceProviderId,
|
||
x => new { x.Id, x.Code, x.Name });
|
||
|
||
return res.Select(x => new InterfaceDropdownListItem
|
||
{
|
||
Id = x.Id,
|
||
Name = x.Name,
|
||
Code = x.Code
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取接口明细
|
||
/// </summary>
|
||
/// <param name="id">接口id</param>
|
||
/// <returns></returns>
|
||
[HttpGet("Interface/GetInterfaceById")]
|
||
public async Task<InterfaceOutput> GetInterfaceByIdAsync([Required] int id)
|
||
{
|
||
// 接口基本信息
|
||
var interfaceEntity = await InterfaceRepository.GetFirstAsync(x => x.Id == id);
|
||
if (interfaceEntity == null)
|
||
{
|
||
throw new BusinessException("接口不存在");
|
||
}
|
||
|
||
var res = ObjectMapper.Map<InterfaceEntity, InterfaceOutput>(interfaceEntity);
|
||
|
||
// 构造参数
|
||
var paras = await InterfaceRepository.GetParameterListByIdAsync(id);
|
||
|
||
res.RequestBodyParameterList = !interfaceEntity.IsUpStream && interfaceEntity.ContentType is ContentType.Json or ContentType.Xml
|
||
? paras.Where(x => x.IsInPara && x.ParaKind == ParameterParaKind.Normal).ToList()
|
||
: [];
|
||
res.MappingBodyParameterList = !interfaceEntity.IsUpStream && interfaceEntity.ContentType is ContentType.None or ContentType.FormUrlEncoded
|
||
? paras.Where(x => x.IsInPara && x.ParaKind == ParameterParaKind.MappingBody).ToList()
|
||
: [];
|
||
// 出参树形列表
|
||
res.OutParameterList = paras.Where(x => !x.IsInPara).ToList();
|
||
|
||
// 表单列表
|
||
res.QueryFormParameterList = await FormParameterService.GetParameterListAsync(id, FieldPosition.Query);
|
||
res.HeaderFormParameterList = await FormParameterService.GetParameterListAsync(id, FieldPosition.Header);
|
||
res.BodyFormParameterList = await FormParameterService.GetParameterListAsync(id, FieldPosition.Body);
|
||
|
||
return res;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 保存接口
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 用OptionType来区分操作目的,1:新增,2:删除,3:修改
|
||
///<para>tips:新增时,参数id由调用者伪造,以表现层级关系,不能小于1,你必须真实的表达id和pid的关系</para>
|
||
/// </remarks>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="BusinessException"></exception>
|
||
[HttpPost("Interface/SaveInterface")]
|
||
public async Task<int> SaveInterfaceAsync(SaveInterfaceInput input)
|
||
{
|
||
NormalizeRootArrayName(GetInputParameterList(input));
|
||
NormalizeRootArrayName(input.OutParameterList);
|
||
ValidateBodyLists(input);
|
||
|
||
switch (input.OptionType)
|
||
{
|
||
case OptionType.新增:
|
||
return await CreateInterfaceAsync(input);
|
||
case OptionType.修改:
|
||
await UpdateInterfaceAsync(input);
|
||
break;
|
||
case OptionType.删除:
|
||
await BatchDeleteInterfaceByIdsAsync(input.IsUpStream, [input.Id ?? 0]);
|
||
break;
|
||
case OptionType.无:
|
||
break;
|
||
default:
|
||
throw new ArgumentOutOfRangeException();
|
||
}
|
||
|
||
return input.Id ?? 0;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 批量删除接口
|
||
/// </summary>
|
||
/// <param name="isUpstream">是否上游</param>
|
||
/// <param name="ids">接口id集合</param>
|
||
/// <returns></returns>
|
||
[HttpPost("Interface/BatchDeleteInterfaceByIds")]
|
||
public async Task<bool> BatchDeleteInterfaceByIdsAsync([FromQuery] bool isUpstream,
|
||
[FromBody] List<int> ids)
|
||
{
|
||
using var tran = TransacationHelper.GetReadCommitted();
|
||
|
||
if (isUpstream)
|
||
{
|
||
// 删除接口
|
||
await InterfaceRepository.SoftDeleteAsync(x => x.IsUpStream && ids.Contains(x.Id));
|
||
// 删除接口映射
|
||
var mapIds = await InterfaceMapRepository.GetListAsync(x => ids.Contains(x.UpStreamId), x => x.Id);
|
||
if (mapIds.Count > 0)
|
||
{
|
||
await InterfaceMapRepository.SoftDeleteAsync(x => mapIds.Contains(x.Id));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 删除接口
|
||
await InterfaceRepository.SoftDeleteAsync(x => !x.IsUpStream && ids.Contains(x.Id));
|
||
// 删除接口映射
|
||
var mapIds = await InterfaceMapRepository.GetListAsync(x => ids.Contains(x.DownStreamId), x => x.Id);
|
||
if (mapIds.Count > 0)
|
||
{
|
||
await InterfaceMapRepository.SoftDeleteAsync(x => mapIds.Contains(x.Id));
|
||
}
|
||
}
|
||
|
||
tran.Complete();
|
||
|
||
return true;
|
||
}
|
||
|
||
internal async Task<int> CreateInterfaceAsync(SaveInterfaceInput input)
|
||
{
|
||
// 校验参数合法性
|
||
ParameterService.ValidateParameter(
|
||
JsonConvert.DeserializeObject<List<ParameterInput>>(
|
||
JsonConvert.SerializeObject(GetInputParameterList(input)))!);
|
||
ParameterService.ValidateParameter(
|
||
JsonConvert.DeserializeObject<List<ParameterInput>>(
|
||
JsonConvert.SerializeObject(input.OutParameterList))!);
|
||
|
||
/*
|
||
* 1.校验业务逻辑
|
||
* 接口名唯一性,接口简码唯一性,请求地址唯一性
|
||
* 2.清洗数据结构
|
||
* 参数合并,参数合法性,参数扁平化bfs
|
||
* 3.数据持久化
|
||
* 由于业务的观测粒度是保存行为(即取消不做任何数据保存),因此保持行为可以直接覆盖上次行为,注意事务
|
||
*/
|
||
|
||
#region 校验业务逻辑
|
||
|
||
if (input.Code.IsNullOrWhiteSpace())
|
||
{
|
||
input.Code = await GenerateInterfaceCode(input.ServiceProviderId);
|
||
}
|
||
|
||
if (await InterfaceRepository.IsAnyAsync(x =>
|
||
x.IsUpStream == input.IsUpStream && (x.Name.Equals(input.Name) || x.Code.Equals(input.Code)) &&
|
||
x.ServiceProviderId == input.ServiceProviderId))
|
||
{
|
||
throw new BusinessException($"接口名或code:{input.Name} 已存在");
|
||
}
|
||
|
||
#endregion
|
||
|
||
var interfaceEntity = ObjectMapper.Map<SaveInterfaceInput, InterfaceEntity>(input);
|
||
|
||
// 开启事务
|
||
using var ts = TransacationHelper.GetReadCommitted();
|
||
|
||
// 持久化接口
|
||
var interfaceId = await InterfaceRepository.InsertReturnIdentityAsync(interfaceEntity);
|
||
|
||
// 持久化参数
|
||
await CreateInputParametersAsync(input, interfaceId);
|
||
await ParameterService.CreateParametersAsync(input.OutParameterList, interfaceId, false);
|
||
|
||
// 持久化表单参数
|
||
await CreateFormParametersAsync(input, interfaceId);
|
||
|
||
// 接口代码生成时并发兜底方案
|
||
if (await InterfaceRepository.CountAsync(x => x.Code.Equals(interfaceEntity.Code)) > 1)
|
||
{
|
||
throw new BusinessException("接口代码生成异常,请稍后重试");
|
||
}
|
||
|
||
// 提交事务
|
||
ts.Complete();
|
||
|
||
return interfaceId;
|
||
}
|
||
|
||
private async Task<string> GenerateInterfaceCode(int serviceProviderId)
|
||
{
|
||
// 系统生成,由服务商代码+3位数字组成(0-999按顺序生成)
|
||
// tips:唯一性
|
||
var entity = await ServiceProviderRepository.GetFirstAsync(x => x.Id == serviceProviderId);
|
||
if (entity == null) throw new BusinessException("服务商不存在,系统接口自己定义code谢谢配合");
|
||
|
||
string res = entity.Code;
|
||
|
||
// tips:要考虑并发
|
||
var code = await InterfaceRepository.GetCurrentCodeBySpId(serviceProviderId);
|
||
int count = 0;
|
||
if (!string.IsNullOrEmpty(code)) count = int.Parse(code.Substring(code.Length - 3, 3));
|
||
res += (count + 1).ToString().PadLeft(3, '0');
|
||
|
||
return res;
|
||
}
|
||
|
||
private async Task UpdateInterfaceAsync(SaveInterfaceInput input)
|
||
{
|
||
#region 校验业务逻辑
|
||
|
||
if (!await InterfaceRepository.IsAnyAsync(x => x.Id == input.Id))
|
||
{
|
||
throw new BusinessException("接口不存在");
|
||
}
|
||
|
||
if (await InterfaceRepository.IsAnyAsync(x =>
|
||
x.Name.Equals(input.Name) && x.Id != input.Id && x.IsUpStream == input.IsUpStream &&
|
||
x.ServiceProviderId == input.ServiceProviderId))
|
||
{
|
||
throw new BusinessException($"接口名:{input.Name} 已存在");
|
||
}
|
||
|
||
#endregion
|
||
|
||
Debug.Assert(input.Id != null, $"{input.Id} 不能为空");
|
||
|
||
var interfaceEntity = ObjectMapper.Map<SaveInterfaceInput, InterfaceEntity>(input);
|
||
|
||
// 开启事务
|
||
using var ts = TransacationHelper.GetReadCommitted();
|
||
|
||
// step1、更新接口
|
||
await InterfaceRepository.UpdateAsync(interfaceEntity, x => new
|
||
{
|
||
x.Code,
|
||
x.IsUpStream,
|
||
x.ServiceProviderId,
|
||
x.CreateTime,
|
||
x.CreateUserId,
|
||
x.IsDeleted
|
||
});
|
||
|
||
// step2、维护参数(增删改)
|
||
await MaintainInputParametersAsync(input, (int)input.Id);
|
||
|
||
await ParameterService.MaintainParametersAsync(input.OutParameterList, (int)input.Id, false);
|
||
|
||
// step3、更新表单参数
|
||
await MaintainFormParametersAsync(input, interfaceEntity.Id);
|
||
|
||
// 提交事务
|
||
ts.Complete();
|
||
}
|
||
|
||
private static List<ParameterInput> GetInputParameterList(SaveInterfaceInput input)
|
||
{
|
||
if (input.IsUpStream) return input.InParameterList;
|
||
return input.ContentType switch
|
||
{
|
||
ContentType.Json or ContentType.Xml => input.RequestBodyParameterList,
|
||
ContentType.None or ContentType.FormUrlEncoded => input.MappingBodyParameterList,
|
||
_ => []
|
||
};
|
||
}
|
||
|
||
private static void NormalizeRootArrayName(List<ParameterInput> list)
|
||
{
|
||
list.ForEach(x =>
|
||
{
|
||
if (x.PId == 0 && x.Type == ParameterType.Array && x.Name == GlobalConst.RootParameterName)
|
||
x.Name = string.Empty;
|
||
});
|
||
}
|
||
|
||
private static void ValidateBodyLists(SaveInterfaceInput input)
|
||
{
|
||
if (input.IsUpStream)
|
||
{
|
||
if (input.RequestBodyParameterList.Count > 0 || input.MappingBodyParameterList.Count > 0 || input.BodyFormParameterList.Count > 0)
|
||
throw new BusinessException("上游接口只能维护 inParameterList");
|
||
return;
|
||
}
|
||
|
||
switch (input.ContentType)
|
||
{
|
||
case ContentType.None:
|
||
if (input.InParameterList.Count > 0 || input.RequestBodyParameterList.Count > 0 || input.BodyFormParameterList.Count > 0)
|
||
throw new BusinessException("None 下游接口只能维护 Query/Header/MappingBody/Response");
|
||
break;
|
||
case ContentType.Json:
|
||
case ContentType.Xml:
|
||
if (input.InParameterList.Count > 0 || input.MappingBodyParameterList.Count > 0 || input.BodyFormParameterList.Count > 0)
|
||
throw new BusinessException("Json/Xml 下游接口只能维护 requestBodyParameterList");
|
||
break;
|
||
case ContentType.FormUrlEncoded:
|
||
if (input.InParameterList.Count > 0 || input.RequestBodyParameterList.Count > 0)
|
||
throw new BusinessException("FormUrlEncoded 下游接口只能维护 bodyFormParameterList 和 mappingBodyParameterList");
|
||
break;
|
||
case ContentType.FormData:
|
||
throw new BusinessException("暂时不支持该ContentType");
|
||
default:
|
||
throw new ArgumentOutOfRangeException();
|
||
}
|
||
}
|
||
|
||
private async Task CreateInputParametersAsync(SaveInterfaceInput input, int interfaceId)
|
||
{
|
||
if (input.IsUpStream)
|
||
{
|
||
await ParameterService.CreateParametersAsync(input.InParameterList, interfaceId, true);
|
||
return;
|
||
}
|
||
|
||
switch (input.ContentType)
|
||
{
|
||
case ContentType.None:
|
||
await ParameterService.CreateParametersAsync(input.MappingBodyParameterList, interfaceId, true,
|
||
ParameterParaKind.MappingBody);
|
||
break;
|
||
case ContentType.Json:
|
||
case ContentType.Xml:
|
||
await ParameterService.CreateParametersAsync(input.RequestBodyParameterList, interfaceId, true);
|
||
break;
|
||
case ContentType.FormUrlEncoded:
|
||
await ParameterService.CreateParametersAsync(input.MappingBodyParameterList, interfaceId, true,
|
||
ParameterParaKind.MappingBody);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private async Task MaintainInputParametersAsync(SaveInterfaceInput input, int interfaceId)
|
||
{
|
||
if (input.IsUpStream)
|
||
{
|
||
await ParameterService.MaintainParametersAsync(input.InParameterList, interfaceId, true);
|
||
return;
|
||
}
|
||
|
||
switch (input.ContentType)
|
||
{
|
||
case ContentType.None:
|
||
await ParameterService.MaintainParametersAsync(input.MappingBodyParameterList, interfaceId, true,
|
||
ParameterParaKind.MappingBody);
|
||
break;
|
||
case ContentType.Json:
|
||
case ContentType.Xml:
|
||
await ParameterService.MaintainParametersAsync(input.RequestBodyParameterList, interfaceId, true);
|
||
break;
|
||
case ContentType.FormUrlEncoded:
|
||
await ParameterService.MaintainParametersAsync(input.MappingBodyParameterList, interfaceId, true,
|
||
ParameterParaKind.MappingBody);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private async Task CreateFormParametersAsync(SaveInterfaceInput input, int interfaceId)
|
||
{
|
||
await FormParameterService.CreateParametersAsync(input.QueryFormParameterList, interfaceId, FieldPosition.Query);
|
||
await FormParameterService.CreateParametersAsync(input.HeaderFormParameterList, interfaceId, FieldPosition.Header);
|
||
if (!input.IsUpStream && input.ContentType == ContentType.FormUrlEncoded)
|
||
await FormParameterService.CreateParametersAsync(input.BodyFormParameterList, interfaceId, FieldPosition.Body);
|
||
}
|
||
|
||
private async Task MaintainFormParametersAsync(SaveInterfaceInput input, int interfaceId)
|
||
{
|
||
await FormParameterService.MaintainParametersAsync(input.QueryFormParameterList, interfaceId, FieldPosition.Query);
|
||
await FormParameterService.MaintainParametersAsync(input.HeaderFormParameterList, interfaceId, FieldPosition.Header);
|
||
if (!input.IsUpStream && input.ContentType == ContentType.FormUrlEncoded)
|
||
await FormParameterService.MaintainParametersAsync(input.BodyFormParameterList, interfaceId, FieldPosition.Body);
|
||
}
|
||
} |