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; /// /// 接口服务(包括上下游接口) /// [ApiExplorerSettings(GroupName = "接口服务")] public class InterfaceService : ApplicationService { private ParameterService ParameterService => LazyServiceProvider.GetRequiredService(); private FormParameterService FormParameterService => LazyServiceProvider.GetRequiredService(); private IInterfaceRepository InterfaceRepository => LazyServiceProvider.GetRequiredService(); private IBasicRepository ServiceProviderRepository => LazyServiceProvider.GetRequiredService>(); private IBasicRepository InterfaceMapRepository => LazyServiceProvider.GetRequiredService>(); private InterfaceCategoryService InterfaceCategoryService => LazyServiceProvider.GetRequiredService(); /// /// 接口树形列表 /// /// /// [HttpPost("Interface/GetInterfaceTreeListAsync")] public async Task>> GetInterfaceTreeListAsync(InterfaceTreeListInputObjectValue input) { var res = await InterfaceRepository.GetInterfaceListAsync(input); var tree = await InterfaceCategoryService.GetCategoryTree(false, res); return tree; } /// /// 接口分页列表 /// /// /// [HttpPost("Interface/InterfacePaginatedList")] public async Task> InterfacePaginatedListAsync(InterfacePaginatedInputObjectValue input) { return await InterfaceRepository.GetInterfacePaginatedListAsync(input); } /// /// 获取接口下拉列表 /// /// 是否上游 /// 服务商id,上游接口传0 /// [HttpGet("Interface/GetInterfaceDropdownList")] public async Task> 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(); } /// /// 获取接口明细 /// /// 接口id /// [HttpGet("Interface/GetInterfaceById")] public async Task GetInterfaceByIdAsync([Required] int id) { // 接口基本信息 var interfaceEntity = await InterfaceRepository.GetFirstAsync(x => x.Id == id); if (interfaceEntity == null) { throw new BusinessException("接口不存在"); } var res = ObjectMapper.Map(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; } /// /// 保存接口 /// /// /// 用OptionType来区分操作目的,1:新增,2:删除,3:修改 ///tips:新增时,参数id由调用者伪造,以表现层级关系,不能小于1,你必须真实的表达id和pid的关系 /// /// /// /// [HttpPost("Interface/SaveInterface")] public async Task 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; } /// /// 批量删除接口 /// /// 是否上游 /// 接口id集合 /// [HttpPost("Interface/BatchDeleteInterfaceByIds")] public async Task BatchDeleteInterfaceByIdsAsync([FromQuery] bool isUpstream, [FromBody] List 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 CreateInterfaceAsync(SaveInterfaceInput input) { // 校验参数合法性 ParameterService.ValidateParameter( JsonConvert.DeserializeObject>( JsonConvert.SerializeObject(GetInputParameterList(input)))!); ParameterService.ValidateParameter( JsonConvert.DeserializeObject>( 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(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 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(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 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 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); } }