itfx/src/InterfaceForward.Application/Services/Interface/InterfaceService.cs
xiaolipro d8ae8a76ba
All checks were successful
Build and Deploy / deploy (push) Successful in 48s
fix
2026-10-12 10:47:29 +08:00

451 lines
18 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 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);
}
}