From d70c9545a6d25423ec7f19d31ce967d8e65b0bee Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Wed, 3 Jun 2026 14:49:58 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/Interface/ParameterInput.cs | 5 + .../Dtos/Interface/SaveInterfaceInput.cs | 26 +- .../ForwardCore/ForwardCoreContext.cs | 10 + .../ForwardCore/DefaultForwardFlow.cs | 56 ++- .../Services/Interface/InterfaceService.cs | 142 ++++++- .../Services/InterfaceForwardCommon.cs | 6 +- .../Parameter/FormParameterService.cs | 15 +- .../Services/Parameter/ParameterService.cs | 26 +- .../Dtos/FormParameterItem.cs | 5 + .../Dtos/ForwardCoreContextCache.cs | 10 + .../Enum/ParameterParaKind.cs | 7 + .../GlobalConst.cs | 1 + .../Interface/ValueObjects/InterfaceOutput.cs | 26 +- .../InterfaceForwardQuery.cs | 23 +- .../Parameter/Entities/FormParameterEntity.cs | 10 +- .../Parameter/Entities/ParameterEntity.cs | 6 + .../Services/IParameterRepository.cs | 4 +- .../Parameter/Services/ParameterRepository.cs | 4 +- .../VO/InterfaceParameterOutputValueObject.cs | 8 +- .../UnitTest1.cs | 58 +++ 方案B-重构清单-前后对比.md | 372 ++++++++++++++++++ 21 files changed, 756 insertions(+), 64 deletions(-) create mode 100644 src/InterfaceForward.Domain.Shared/Enum/ParameterParaKind.cs create mode 100644 方案B-重构清单-前后对比.md diff --git a/src/InterfaceForward.Application.Contracts/Dtos/Interface/ParameterInput.cs b/src/InterfaceForward.Application.Contracts/Dtos/Interface/ParameterInput.cs index ff0d757..2e07aa4 100644 --- a/src/InterfaceForward.Application.Contracts/Dtos/Interface/ParameterInput.cs +++ b/src/InterfaceForward.Application.Contracts/Dtos/Interface/ParameterInput.cs @@ -42,6 +42,11 @@ namespace InterfaceForward.Application.Contracts.Dtos.Interface /// public ParameterType Type { get; set; } + /// + /// 参数用途 + /// + public ParameterParaKind ParaKind { get; set; } + /// /// 是否必填 /// diff --git a/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveInterfaceInput.cs b/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveInterfaceInput.cs index eb7d5b3..1c64720 100644 --- a/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveInterfaceInput.cs +++ b/src/InterfaceForward.Application.Contracts/Dtos/Interface/SaveInterfaceInput.cs @@ -106,17 +106,37 @@ public class SaveInterfaceInput public ParameterType? MappedParamType { get; set; } /// - /// 入参列表 + /// 上游入参列表 /// public List InParameterList { get; set; } = new(); + /// + /// Json/Xml 请求体参数树 + /// + public List RequestBodyParameterList { get; set; } = new(); + + /// + /// Form 类映射体参数树 + /// + public List MappingBodyParameterList { get; set; } = new(); + /// /// 出参列表 /// public List OutParameterList { get; set; } = new(); /// - /// 表单列表 + /// Query KV 列表 /// - public List FormParameterList { get; set; } = new(); + public List QueryFormParameterList { get; set; } = new(); + + /// + /// Header KV 列表 + /// + public List HeaderFormParameterList { get; set; } = new(); + + /// + /// Body Form KV 列表 + /// + public List BodyFormParameterList { get; set; } = new(); } \ No newline at end of file diff --git a/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs b/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs index 6b65a94..3070152 100644 --- a/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs +++ b/src/InterfaceForward.Application.Contracts/ForwardCore/ForwardCoreContext.cs @@ -31,6 +31,16 @@ public class ForwardCoreContext /// public List FormFieldList { get; set; } = []; + /// + /// Query 字段列表 + /// + public List QueryFieldList { get; set; } = []; + + /// + /// Header 字段列表 + /// + public List HeaderFieldList { get; set; } = []; + /// /// 入参映射列表(树形结构) /// diff --git a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs index 0d2f375..b457827 100644 --- a/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs +++ b/src/InterfaceForward.Application/ForwardCore/DefaultForwardFlow.cs @@ -279,6 +279,12 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency // 将参数绑定到query var builder = new UriBuilder(targetInterface.RequestAddress); var query = HttpUtility.ParseQueryString(builder.Query); + foreach (var item in context.QueryFieldList) + { + query.Remove(item.Name); + query.Add(item.Name, item.Value); + } + foreach (var item in context.FixedFieldList.Where(x => x.FieldPosition == FieldPosition.Query)) { query.Remove(item.FieldName); @@ -293,6 +299,16 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency SetContentType(context, msg); // 将参数绑定到header + foreach (var item in context.HeaderFieldList) + { + if (msg.Headers.Contains(item.Name)) + msg.Headers.Remove(item.Name); + if (!msg.Headers.TryAddWithoutValidation(item.Name, item.Value)) + { + ThrowBusinessException(context, $"请求头无法插入 {item.Name}:{item.Value}"); + } + } + foreach (var item in context.FixedFieldList.Where(x => x.FieldPosition == FieldPosition.Header)) { //msg.Content.Headers.TryAddWithoutValidation(item.FieldName, item.FieldValue); @@ -399,7 +415,7 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency Dictionary nameValueCollection; if (context.FormFieldList.Count != 0) { - var field = context.FormFieldList.FirstOrDefault(x => x.Value == GlobalConst.FromBody); + var field = context.FormFieldList.FirstOrDefault(x => x.Value == GlobalConst.FromMappingBody); if (field != null) field.Value = body; nameValueCollection = context.FormFieldList.ToDictionary(x => x.Name, x => x.Value); } @@ -504,19 +520,20 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency } } - // 表单 + // Query / Header / 表单 + foreach (var item in context.QueryFieldList) + { + item.Value = ReplaceFormPlaceholder(item.Name, item.Value, item.Description); + } + + foreach (var item in context.HeaderFieldList) + { + item.Value = ReplaceFormPlaceholder(item.Name, item.Value, item.Description); + } + foreach (var item in context.FormFieldList) { - item.Value = item.Value switch - { - GlobalConst.TimeStamp => item.Description == "ms" - ? timeStamp.ToString() - : (timeStamp / 1000).ToString(), // s - GlobalConst.DateTime => TurnDate(now, item.Description), - GlobalConst.Url => context.TargetInterface.RequestAddress, - GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(item.Name) ?? string.Empty, - _ => item.Value // 不处理 - }; + item.Value = ReplaceFormPlaceholder(item.Name, item.Value, item.Description); } // 绑定body部分的字段 @@ -551,6 +568,21 @@ public class DefaultForwardFlow : IForwardFlow, ITransientDependency return dateTime.ToString(GlobalConst.DefaultDateStyle); } } + + string ReplaceFormPlaceholder(string name, string value, string? description) + { + return value switch + { + GlobalConst.TimeStamp => description == "ms" + ? timeStamp.ToString() + : (timeStamp / 1000).ToString(), + GlobalConst.DateTime => TurnDate(now, description), + GlobalConst.Url => context.TargetInterface.RequestAddress, + GlobalConst.FromAccount => context.GetAccountFieldValueOrNull(name) ?? string.Empty, + GlobalConst.FromMappingBody => GlobalConst.FromMappingBody, + _ => value + }; + } } void ReplaceCore(JToken obj, string path, JToken value) diff --git a/src/InterfaceForward.Application/Services/Interface/InterfaceService.cs b/src/InterfaceForward.Application/Services/Interface/InterfaceService.cs index d448bd8..96e6f54 100644 --- a/src/InterfaceForward.Application/Services/Interface/InterfaceService.cs +++ b/src/InterfaceForward.Application/Services/Interface/InterfaceService.cs @@ -109,12 +109,22 @@ public class InterfaceService : ApplicationService // 构造参数 var paras = await InterfaceRepository.GetParameterListByIdAsync(id); // 入参树形列表 - res.InParameterList = paras.Where(x => x.IsInPara).ToList(); + res.InParameterList = interfaceEntity.IsUpStream + ? paras.Where(x => x.IsInPara && x.ParaKind == ParameterParaKind.Normal).ToList() + : []; + 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 == ContentType.FormUrlEncoded + ? paras.Where(x => x.IsInPara && x.ParaKind == ParameterParaKind.MappingBody).ToList() + : []; // 出参树形列表 res.OutParameterList = paras.Where(x => !x.IsInPara).ToList(); // 表单列表 - res.FormParameterList = await FormParameterService.GetParameterListAsync(id); + 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; } @@ -133,16 +143,9 @@ public class InterfaceService : ApplicationService [HttpPost("Interface/SaveInterface")] public async Task SaveInterfaceAsync(SaveInterfaceInput input) { - input.InParameterList.ForEach(x => - { - if (x.PId == 0 && x.Type == ParameterType.Array && x.Name == GlobalConst.RootParameterName) - x.Name = string.Empty; - }); - input.OutParameterList.ForEach(x => - { - if (x.PId == 0 && x.Type == ParameterType.Array && x.Name == GlobalConst.RootParameterName) - x.Name = string.Empty; - }); + NormalizeRootArrayName(GetInputParameterList(input)); + NormalizeRootArrayName(input.OutParameterList); + ValidateBodyLists(input); switch (input.OptionType) { @@ -209,7 +212,7 @@ public class InterfaceService : ApplicationService // 校验参数合法性 ParameterService.ValidateParameter( JsonConvert.DeserializeObject>( - JsonConvert.SerializeObject(input.InParameterList))!); + JsonConvert.SerializeObject(GetInputParameterList(input)))!); ParameterService.ValidateParameter( JsonConvert.DeserializeObject>( JsonConvert.SerializeObject(input.OutParameterList))!); @@ -248,11 +251,11 @@ public class InterfaceService : ApplicationService var interfaceId = await InterfaceRepository.InsertReturnIdentityAsync(interfaceEntity); // 持久化参数 - await ParameterService.CreateParametersAsync(input.InParameterList, interfaceId, true); + await CreateInputParametersAsync(input, interfaceId); await ParameterService.CreateParametersAsync(input.OutParameterList, interfaceId, false); // 持久化表单参数 - await FormParameterService.CreateParametersAsync(input.FormParameterList, interfaceId); + await CreateFormParametersAsync(input, interfaceId); // 接口代码生成时并发兜底方案 if (await InterfaceRepository.CountAsync(x => x.Code.Equals(interfaceEntity.Code)) > 1) @@ -321,14 +324,119 @@ public class InterfaceService : ApplicationService }); // step2、维护参数(增删改) - await ParameterService.MaintainParametersAsync(input.InParameterList, (int)input.Id, true); + await MaintainInputParametersAsync(input, (int)input.Id); await ParameterService.MaintainParametersAsync(input.OutParameterList, (int)input.Id, false); // step3、更新表单参数 - await FormParameterService.MaintainParametersAsync(input.FormParameterList, interfaceEntity.Id); + 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.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(message: "上游接口只能维护 inParameterList"); + return; + } + + switch (input.ContentType) + { + case ContentType.Json: + case ContentType.Xml: + if (input.InParameterList.Count > 0 || input.MappingBodyParameterList.Count > 0 || input.BodyFormParameterList.Count > 0) + throw new BusinessException(message: "Json/Xml 下游接口只能维护 requestBodyParameterList"); + break; + case ContentType.FormUrlEncoded: + if (input.InParameterList.Count > 0 || input.RequestBodyParameterList.Count > 0) + throw new BusinessException(message: "FormUrlEncoded 下游接口只能维护 bodyFormParameterList 和 mappingBodyParameterList"); + break; + case ContentType.FormData: + throw new BusinessException(message: "暂时不支持该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.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.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); + } } \ No newline at end of file diff --git a/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs b/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs index 04e95c6..3728f70 100644 --- a/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs +++ b/src/InterfaceForward.Application/Services/InterfaceForwardCommon.cs @@ -105,6 +105,8 @@ public class InterfaceForwardCommon( AccountFieldList = accountFieldCache, ReturnConfigList = item.ReturnConfigList, FormFieldList = item.FormFieldList, + QueryFieldList = item.QueryFieldList, + HeaderFieldList = item.HeaderFieldList, InParamTreeList = item.InParamTreeList, PrefixScript = item.PrefixScript, PostfixScript = item.PostfixScript, @@ -192,7 +194,9 @@ public class InterfaceForwardCommon( PrefixScript = interfaceDto.PrefixScript, PostfixScript = interfaceDto.PostfixScript, OriginalInterfaceInput = body, - FormFieldList = interfaceDto.FormFieldList // normal case is null + FormFieldList = interfaceDto.FormFieldList, + QueryFieldList = interfaceDto.QueryFieldList, + HeaderFieldList = interfaceDto.HeaderFieldList }; authContext.ServiceProviderRequestLog = new RequestLogDto(logRepository.AppRequestLog.RequestId) { diff --git a/src/InterfaceForward.Application/Services/Parameter/FormParameterService.cs b/src/InterfaceForward.Application/Services/Parameter/FormParameterService.cs index ce11324..a29425e 100644 --- a/src/InterfaceForward.Application/Services/Parameter/FormParameterService.cs +++ b/src/InterfaceForward.Application/Services/Parameter/FormParameterService.cs @@ -27,9 +27,10 @@ public class FormParameterService : ApplicationService /// 接口id /// [HttpGet("FormParameter/GetParameterList")] - public async Task> GetParameterListAsync([Required] int interfaceId) + public async Task> GetParameterListAsync([Required] int interfaceId, FieldPosition? fieldPosition = null) { - var formList = await _formParameterRepository.GetListAsync(x => x.InterfaceId == interfaceId); + var formList = await _formParameterRepository.GetListAsync(x => + x.InterfaceId == interfaceId && (fieldPosition == null || x.FieldPosition == fieldPosition)); return ObjectMapper.Map, List>(formList); } @@ -38,9 +39,10 @@ public class FormParameterService : ApplicationService /// /// /// 接口id - internal async Task CreateParametersAsync(List list, int interfaceId) + internal async Task CreateParametersAsync(List list, int interfaceId, FieldPosition fieldPosition) { if (list.Count <= 0) return; + list.ForEach(x => x.FieldPosition = fieldPosition); var formEntities = ObjectMapper.Map, List>(list); formEntities.ForEach(x => x.InterfaceId = interfaceId); await _formParameterRepository.InsertRangeAsync(formEntities); @@ -51,9 +53,10 @@ public class FormParameterService : ApplicationService /// /// /// 接口id - internal async Task MaintainParametersAsync(List list, int interfaceId) + internal async Task MaintainParametersAsync(List list, int interfaceId, FieldPosition fieldPosition) { if (list.Count == 0) return; + list.ForEach(x => x.FieldPosition = fieldPosition); using var ts = TransacationHelper.GetReadCommitted(); @@ -68,12 +71,12 @@ public class FormParameterService : ApplicationService .Where(x => x.OptionType == OptionType.修改).ToList()); await _formParameterRepository.UpdateRangeAsync(updEntities, x => new { - x.Name, x.Value, x.Description + x.Name, x.Value, x.Description, x.FieldPosition }); // 创建 await CreateParametersAsync(list.Where(x => x.OptionType == OptionType.新增).ToList() - , interfaceId); + , interfaceId, fieldPosition); ts.Complete(); } diff --git a/src/InterfaceForward.Application/Services/Parameter/ParameterService.cs b/src/InterfaceForward.Application/Services/Parameter/ParameterService.cs index 73179a0..d0da268 100644 --- a/src/InterfaceForward.Application/Services/Parameter/ParameterService.cs +++ b/src/InterfaceForward.Application/Services/Parameter/ParameterService.cs @@ -48,10 +48,10 @@ public class ParameterService : ApplicationService [HttpGet("Parameter/GetParameterList")] [Authorize] public async Task> GetParameterListAsync([Required] bool isInPara, - [Required] int id, bool onlyLeafs = false) + [Required] int id, bool onlyLeafs = false, ParameterParaKind? paraKind = null) { var paras = await _interfaceRepository.GetParameterListByIdAsync(id, onlyLeafs); - return paras.Where(x => x.IsInPara == isInPara); + return paras.Where(x => x.IsInPara == isInPara && (paraKind == null || x.ParaKind == paraKind)); } /// @@ -287,11 +287,13 @@ public class ParameterService : ApplicationService } - internal async Task MaintainParametersAsync(List list, int interfaceId, bool isInPara) + internal async Task MaintainParametersAsync(List list, int interfaceId, bool isInPara, + ParameterParaKind paraKind = ParameterParaKind.Normal) { #region 防止请求端没传入已存在的节点 - var parameters = await _parameterRepository.GetParameterListByNameAsync(interfaceId, isInPara); + list.ForEach(x => x.ParaKind = paraKind); + var parameters = await _parameterRepository.GetParameterListByNameAsync(interfaceId, isInPara, paraKind); foreach (var item in parameters) { if (list.All(x => x.Id != item.Id)) @@ -307,9 +309,9 @@ public class ParameterService : ApplicationService using var ts = TransacationHelper.GetReadCommitted(); // 创建 - await CreateParametersAsync(list.Where(x => x.OptionType == OptionType.新增).ToList(), interfaceId, isInPara); + await CreateParametersAsync(list.Where(x => x.OptionType == OptionType.新增).ToList(), interfaceId, isInPara, paraKind); // 更新 - await UpdateParametersAsync(list.Where(x => x.OptionType == OptionType.修改).ToList(), interfaceId, isInPara); + await UpdateParametersAsync(list.Where(x => x.OptionType == OptionType.修改).ToList(), interfaceId, isInPara, paraKind); // 删除 await DeleteParametersAsync(list.Where(x => x.OptionType == OptionType.删除).Select(x => x.Id).ToList()); @@ -328,11 +330,13 @@ public class ParameterService : ApplicationService /// /// /// - internal async Task CreateParametersAsync(List list, int interfaceId, bool isInPara) + internal async Task CreateParametersAsync(List list, int interfaceId, bool isInPara, + ParameterParaKind paraKind = ParameterParaKind.Normal) { if (list.Count <= 0) return; + list.ForEach(x => x.ParaKind = paraKind); var parameters = - await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara); + await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara && x.ParaKind == paraKind); var tops = new List(); foreach (var item in list) @@ -368,6 +372,7 @@ public class ParameterService : ApplicationService entity.Alias = cur.Alias; entity.InterfaceId = interfaceId; entity.IsInPara = isInPara; + entity.ParaKind = paraKind; if (!cur.Sort.IsNullOrWhiteSpace()) { @@ -414,13 +419,14 @@ public class ParameterService : ApplicationService /// /// /// - private async Task UpdateParametersAsync(List list, int interfaceId, bool isInPara) + private async Task UpdateParametersAsync(List list, int interfaceId, bool isInPara, + ParameterParaKind paraKind) { 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); + await _parameterRepository.GetListAsync(x => x.InterfaceId == interfaceId && x.IsInPara == isInPara && x.ParaKind == paraKind); var treeList = BuildTreeList(parameters, ParameterRootId); diff --git a/src/InterfaceForward.Domain.Shared/Dtos/FormParameterItem.cs b/src/InterfaceForward.Domain.Shared/Dtos/FormParameterItem.cs index f273364..0509ebd 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/FormParameterItem.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/FormParameterItem.cs @@ -19,6 +19,11 @@ public class FormParameterItem /// public string Value { get; set; } + /// + /// 字段位置 + /// + public FieldPosition FieldPosition { get; set; } + /// /// 参数描述 /// diff --git a/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs b/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs index 362b234..b78ef84 100644 --- a/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs +++ b/src/InterfaceForward.Domain.Shared/Dtos/ForwardCoreContextCache.cs @@ -96,4 +96,14 @@ public class TargetInterfaceSummary /// 表单字段列表 /// public List FormFieldList { get; set; } = []; + + /// + /// Query 字段列表 + /// + public List QueryFieldList { get; set; } = []; + + /// + /// Header 字段列表 + /// + public List HeaderFieldList { get; set; } = []; } \ No newline at end of file diff --git a/src/InterfaceForward.Domain.Shared/Enum/ParameterParaKind.cs b/src/InterfaceForward.Domain.Shared/Enum/ParameterParaKind.cs new file mode 100644 index 0000000..2079fac --- /dev/null +++ b/src/InterfaceForward.Domain.Shared/Enum/ParameterParaKind.cs @@ -0,0 +1,7 @@ +namespace InterfaceForward.Domain.Shared.Enum; + +public enum ParameterParaKind +{ + Normal = 0, + MappingBody = 1 +} diff --git a/src/InterfaceForward.Domain.Shared/GlobalConst.cs b/src/InterfaceForward.Domain.Shared/GlobalConst.cs index b42c910..e18091e 100644 --- a/src/InterfaceForward.Domain.Shared/GlobalConst.cs +++ b/src/InterfaceForward.Domain.Shared/GlobalConst.cs @@ -4,6 +4,7 @@ public static class GlobalConst { public const string FromAccount = "$FromAccount"; public const string FromBody = "$FromBody"; + public const string FromMappingBody = "$FromMappingBody"; public const string TimeStamp = "$TimeStamp"; public const string DateTime = "$DateTime"; public const string Url = "$Url"; diff --git a/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceOutput.cs b/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceOutput.cs index 9ffc5fc..32dcdcf 100644 --- a/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceOutput.cs +++ b/src/InterfaceForward.Repositories/Interface/ValueObjects/InterfaceOutput.cs @@ -90,17 +90,37 @@ public class InterfaceOutput public ParameterType? MappedParamType { get; set; } /// - /// 入参列表 + /// 上游入参列表 /// public List InParameterList { get; set; } + /// + /// Json/Xml 请求体参数树 + /// + public List RequestBodyParameterList { get; set; } + + /// + /// Form 类映射体参数树 + /// + public List MappingBodyParameterList { get; set; } + /// /// 出参列表 /// public List OutParameterList { get; set; } /// - /// 表单列表 + /// Query KV 列表 /// - public List FormParameterList { get; set; } + public List QueryFormParameterList { get; set; } + + /// + /// Header KV 列表 + /// + public List HeaderFormParameterList { get; set; } + + /// + /// Body Form KV 列表 + /// + public List BodyFormParameterList { get; set; } } \ No newline at end of file diff --git a/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs b/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs index 2c457b9..36bf31c 100644 --- a/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs +++ b/src/InterfaceForward.Repositories/InterfaceForwardQuery.cs @@ -109,7 +109,7 @@ public class InterfaceForwardQuery : BasicRepository, ISco /// 是入参映射 /// public async Task> GetParameterMapListAsync(List mapIds, - bool isInParaMap) + bool isInParaMap, ParameterParaKind? paraKind = null) { // tips:这里要拿所有节点,而非仅叶子,因为要构建参数树 @@ -130,7 +130,7 @@ ORDER BY `b`.`Sort` ASC select `b`.`Id` AS `Id` , `b`.`PId` AS `PId` , b.InterfaceId, `b`.`Name` AS `Name` ,b.Sort, `b`.`Type` AS `Type` , `b`.`Alias` AS `Alias` , {(isMultiMap ? "CONCAT(d.Code, '.', c.Alias)" : "c.Alias")} AS `MapAlias` , `b`.`IsRequired` AS `IsRequired` , t.FreeMap from t_interface_map t -LEFT JOIN `t_parameter` `b` ON {(isInParaMap ? "t.DownStreamId" : "t.UpStreamId")} = `b`.`InterfaceId` and b.IsInPara = {isInParaMap} AND ( `b`.`IsDeleted` = 0 ) +LEFT JOIN `t_parameter` `b` ON {(isInParaMap ? "t.DownStreamId" : "t.UpStreamId")} = `b`.`InterfaceId` and b.IsInPara = {isInParaMap} {(paraKind == null ? string.Empty : $"and b.ParaKind = {(int)paraKind}")} AND ( `b`.`IsDeleted` = 0 ) Left JOIN `t_interface_map_detail` `a` ON ( `t`.`Id` = `a`.`InterfaceMapId` ) and b.id = a.ParaId and a.IsDeleted = 0 Left JOIN `t_parameter` `c` ON ( `a`.`MappedParaId` = `c`.`Id` ) AND ( `c`.`IsDeleted` = 0 ) Left JOIN t_interface d on c.interfaceid = d.id @@ -207,15 +207,20 @@ ORDER BY `b`.`Sort` ASC").ToListAsync(); InParamTreeList = null, FixedFieldList = await GetAllFixedFieldListAsync(serviceProvider.Id, authInterface.Id), ReturnConfigList = await GetInterfaceReturnConfigListAsync(authInterface.Id), - FormFieldList = await GetFormFieldListAsync(authInterface.Id), + FormFieldList = await GetFormFieldListAsync(authInterface.Id, FieldPosition.Body), + QueryFieldList = await GetFormFieldListAsync(authInterface.Id, FieldPosition.Query), + HeaderFieldList = await GetFormFieldListAsync(authInterface.Id, FieldPosition.Header), }; } } - var inParaMaps = await GetParameterMapListAsync(mapIds, true); foreach (var map in maps) { var targetInterface = targetInterfaceList.First(x => map.DownStreamCode == x.Code); + var paraKind = targetInterface.ContentType == ContentType.FormUrlEncoded + ? ParameterParaKind.MappingBody + : ParameterParaKind.Normal; + var inParaMaps = await GetParameterMapListAsync([map.Id], true, paraKind); var items = inParaMaps.Where(x => x.InterfaceId == targetInterface.Id).ToList(); var fixedFieldList = await GetAllFixedFieldListAsync(serviceProvider.Id, targetInterface.Id); @@ -235,17 +240,19 @@ ORDER BY `b`.`Sort` ASC").ToListAsync(); ReturnConfigList = await GetInterfaceReturnConfigListAsync(targetInterface.Id), PrefixScript = map.PrefixScript, PostfixScript = map.PostfixScript, - FormFieldList = await GetFormFieldListAsync(targetInterface.Id), + FormFieldList = await GetFormFieldListAsync(targetInterface.Id, FieldPosition.Body), + QueryFieldList = await GetFormFieldListAsync(targetInterface.Id, FieldPosition.Query), + HeaderFieldList = await GetFormFieldListAsync(targetInterface.Id, FieldPosition.Header), }); } return context; } - private async Task> GetFormFieldListAsync(int interfaceId) + private async Task> GetFormFieldListAsync(int interfaceId, FieldPosition fieldPosition) { return await Context.Queryable() - .Where(x => x.InterfaceId == interfaceId) + .Where(x => x.InterfaceId == interfaceId && x.FieldPosition == fieldPosition) .Select() .ToListAsync(); } @@ -260,7 +267,7 @@ ORDER BY `b`.`Sort` ASC").ToListAsync(); int interfaceId) { var fixedFieldList = await Context.Queryable() - .Where(a => a.ServiceProviderId == serviceProviderId || a.InterfaceId == interfaceId) + .Where(a => a.ServiceProviderId == serviceProviderId) .Select(x => new { x.ServiceProviderId, diff --git a/src/InterfaceForward.Repositories/Parameter/Entities/FormParameterEntity.cs b/src/InterfaceForward.Repositories/Parameter/Entities/FormParameterEntity.cs index b23c013..0906744 100644 --- a/src/InterfaceForward.Repositories/Parameter/Entities/FormParameterEntity.cs +++ b/src/InterfaceForward.Repositories/Parameter/Entities/FormParameterEntity.cs @@ -1,4 +1,6 @@ -namespace InterfaceForward.Repositories.Parameter.Entities; +using InterfaceForward.Domain.Shared.Enum; + +namespace InterfaceForward.Repositories.Parameter.Entities; /// /// 表单参数 @@ -30,6 +32,12 @@ public class FormParameterEntity : FullAuditedAggregateRoot [SugarColumn(ColumnName = "Value")] public string Value { get; set; } + /// + /// 字段位置 + /// + [SugarColumn(ColumnName = "FieldPosition")] + public FieldPosition FieldPosition { get; set; } + /// /// 参数描述 /// diff --git a/src/InterfaceForward.Repositories/Parameter/Entities/ParameterEntity.cs b/src/InterfaceForward.Repositories/Parameter/Entities/ParameterEntity.cs index 1727b5a..f26af58 100644 --- a/src/InterfaceForward.Repositories/Parameter/Entities/ParameterEntity.cs +++ b/src/InterfaceForward.Repositories/Parameter/Entities/ParameterEntity.cs @@ -63,6 +63,12 @@ namespace InterfaceForward.Repositories.Interface.Entitys [SugarColumn(ColumnName = "IsInPara")] public bool IsInPara { get; set; } + /// + /// 参数用途:0普通;1映射体 + /// + [SugarColumn(ColumnName = "ParaKind")] + public ParameterParaKind ParaKind { get; set; } + /// /// 参数描述 /// diff --git a/src/InterfaceForward.Repositories/Parameter/Services/IParameterRepository.cs b/src/InterfaceForward.Repositories/Parameter/Services/IParameterRepository.cs index 13f8b2f..b4ea21a 100644 --- a/src/InterfaceForward.Repositories/Parameter/Services/IParameterRepository.cs +++ b/src/InterfaceForward.Repositories/Parameter/Services/IParameterRepository.cs @@ -1,5 +1,6 @@ using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Parameter.VO; +using InterfaceForward.Domain.Shared.Enum; namespace InterfaceForward.Repositories.Parameter.Services; @@ -14,7 +15,8 @@ public interface IParameterRepository:IBasicRepository /// /// /// - Task> GetParameterListByNameAsync(int interfaceId, bool isInPara); + Task> GetParameterListByNameAsync(int interfaceId, bool isInPara, + ParameterParaKind? paraKind = null); /// /// 保存参数序列 diff --git a/src/InterfaceForward.Repositories/Parameter/Services/ParameterRepository.cs b/src/InterfaceForward.Repositories/Parameter/Services/ParameterRepository.cs index b0934ab..3411a8d 100644 --- a/src/InterfaceForward.Repositories/Parameter/Services/ParameterRepository.cs +++ b/src/InterfaceForward.Repositories/Parameter/Services/ParameterRepository.cs @@ -1,5 +1,6 @@ using InterfaceForward.Repositories.Interface.Entitys; using InterfaceForward.Repositories.Parameter.VO; +using InterfaceForward.Domain.Shared.Enum; namespace InterfaceForward.Repositories.Parameter.Services; @@ -10,11 +11,12 @@ public class ParameterRepository : BasicRepository, IScopedDepe { /// public async Task> GetParameterListByNameAsync(int interfaceId, - bool isInPara) + bool isInPara, ParameterParaKind? paraKind = null) { var query = Context.Queryable() .Where(x => x.InterfaceId == interfaceId) .Where(x => x.IsInPara == isInPara) + .WhereIF(paraKind != null, x => x.ParaKind == paraKind) .OrderBy(x => x.Sort); return await query.Select().ToListAsync(); diff --git a/src/InterfaceForward.Repositories/Parameter/VO/InterfaceParameterOutputValueObject.cs b/src/InterfaceForward.Repositories/Parameter/VO/InterfaceParameterOutputValueObject.cs index c281de1..a92c6c1 100644 --- a/src/InterfaceForward.Repositories/Parameter/VO/InterfaceParameterOutputValueObject.cs +++ b/src/InterfaceForward.Repositories/Parameter/VO/InterfaceParameterOutputValueObject.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using InterfaceForward.Domain.Shared.Enum; +using Newtonsoft.Json; namespace InterfaceForward.Repositories.Parameter.VO { @@ -34,6 +35,11 @@ namespace InterfaceForward.Repositories.Parameter.VO /// public int Type { get; set; } + /// + /// 参数用途 + /// + public ParameterParaKind ParaKind { get; set; } + /// /// 是否必填 /// diff --git a/tests/InterfaceForward.Application.Tests/UnitTest1.cs b/tests/InterfaceForward.Application.Tests/UnitTest1.cs index 0aee79f..6df47f4 100644 --- a/tests/InterfaceForward.Application.Tests/UnitTest1.cs +++ b/tests/InterfaceForward.Application.Tests/UnitTest1.cs @@ -1,5 +1,10 @@ using InterfaceForward.Application.Contracts.Dtos.ScriptAssistant; +using InterfaceForward.Application.Contracts.ForwardCore; +using InterfaceForward.Application.ForwardCore; using InterfaceForward.Application.Services.ScriptAssistant; +using InterfaceForward.Domain.Shared; +using InterfaceForward.Domain.Shared.Dtos; +using InterfaceForward.Domain.Shared.Enum; using Newtonsoft.Json.Linq; namespace InterfaceForward.Application.Tests; @@ -71,4 +76,57 @@ public class ScriptAssistantServiceTests Assert.Contains(output.Warnings, x => x.Contains("System.IO")); } +} + +public class DefaultForwardFlowTests +{ + [Fact] + public void SerializationBody_ShouldUseMappedInputForJson() + { + var flow = new TestForwardFlow(); + var context = new ForwardCoreContext + { + TargetInterface = new InterfaceDto { ContentType = ContentType.Json }, + OriginalInterfaceMappedInput = new JObject { ["orderId"] = "A001" } + }; + + var body = flow.SerializationBody(context); + + Assert.Equal("{\"orderId\":\"A001\"}", body); + } + + [Fact] + public async Task SetContentType_ShouldReplaceFromMappingBodyForFormUrlEncoded() + { + var flow = new TestForwardFlow(); + var context = new ForwardCoreContext + { + TargetInterface = new InterfaceDto { ContentType = ContentType.FormUrlEncoded }, + TargetInterfaceInput = "{\"orderId\":\"A001\"}", + OriginalInterfaceMappedInput = new JObject { ["orderId"] = "A001" }, + FormFieldList = + [ + new InterfaceFormFieldDto + { + Name = "notifyBizEventDTO", + Value = GlobalConst.FromMappingBody + } + ] + }; + + using var message = new HttpRequestMessage(); + flow.SetContentTypeForTest(context, message); + + var content = await message.Content!.ReadAsStringAsync(); + + Assert.Equal("notifyBizEventDTO=%7B%22orderId%22%3A%22A001%22%7D", content); + } + + class TestForwardFlow : DefaultForwardFlow + { + public void SetContentTypeForTest(ForwardCoreContext context, HttpRequestMessage message) + { + SetContentType(context, message); + } + } } \ No newline at end of file diff --git a/方案B-重构清单-前后对比.md b/方案B-重构清单-前后对比.md new file mode 100644 index 0000000..a543458 --- /dev/null +++ b/方案B-重构清单-前后对比.md @@ -0,0 +1,372 @@ +# 方案 B 重构清单(前后对比 · 修订版) + +> **前提**:全量重构,不做旧字段兼容。 +> **修订要点**:先定义「一次 HTTP 请求」应保存哪些结构,再说明 **Mapping Body 仅在某些 Content-Type 下才存在**;`application/json` 的请求体参数树 **本身就是映射体**,不单独拆 Mapping Body。 + +--- + +## 1. 设计顺序(先看这个) + +```text +第一步 请求接口应保存什么(与 Content-Type 无关 + 有关部分分开) + Query / Header / Body 传输层 + 响应 + 基本信息 + +第二步 映射体放哪(由 Content-Type 决定) + Json/Xml → Body 参数树 = 映射体 + Form 类 → 传输用表单 KV + 独立 Mapping Body 树(表单字段可 $FromMappingBody) +``` + +**Mapping Body 不是每个接口都有**,只有「Body(或 Query)走表单 KV,且业务 JSON 需要单独做上游映射」时才配置。 + +--- + +## 2. 改造后:一次请求应保存什么(目标模型) + +### 2.1 与 Content-Type **无关**(任何下游接口都有) + +| 存储 | 表 | UI(对齐 ApiTest) | 说明 | +|------|-----|-------------------|------| +| **基本信息** | `t_interface` | 地址、Method、Protocol、ContentType、QPS… | 不变 | +| **Query** | `t_parameter_form`(FieldPosition=**Query**) | Query Tab,KV | 真实 URL Query,不仅 FormUrlEncoded 才有 | +| **Header** | `t_parameter_form`(FieldPosition=**Header**) | Header Tab,KV | 请求头;占位符 $TimeStamp、$token 等 | +| **Response** | `t_parameter`(IsInPara=false) | Response 参数树 | 不变 | +| **返回配置** | `t_interface_return_config` | 返回配置 Tab | 不变 | +| **cURL 示例** | `t_interface.InParams` | cURL Tab | 不变 | +| **服务商级 Fixed** | `t_parameter_fixed`(ServiceProviderId) | 不在接口明细维护 | Url 占位、服务商通用 Header、脚本写入 | + +### 2.2 与 Content-Type **有关**(Body 区不同) + +| ContentType | Body 传输层保存什么 | 映射体保存什么 | 是否需要 Mapping Body | +|-------------|---------------------|----------------|------------------------| +| **Json (3)** | **`requestBodyParameterList`** 参数树 | **同一棵树** | **否** | +| **Xml (4)** | **`requestBodyParameterList`** 参数树 | **同一棵树** | **否** | +| **FormUrlEncoded (1)** | **`bodyFormParameterList`** KV | **`mappingBodyParameterList`** 参数树 | **是** | +| **FormData (2)** | 二期:`bodyForm` 或 multipart 描述 | 若字段内嵌 JSON,同 FormUrlEncoded | 二期 | +| **上游接口** | **`inParameterList`** 树(业务入参) | **同一棵树** | **否** | + +### 2.3 结构总图(改造后) + +```text + ┌─ 基本信息 (t_interface) + │ + 任意 Content-Type ├─ queryFormParameterList (t_parameter_form, Query) + ├─ headerFormParameterList (t_parameter_form, Header) + │ + ├─ Body 区 ─────────────────────────────────────┐ + │ │ + │ Json/Xml FormUrlEncoded │ + │ requestBody bodyFormParameterList │ + │ ParameterList (KV 传输) │ + │ (树=映射体) mappingBodyParameterList │ + │ (树=仅映射体) │ + └────────────────────────────────────────────────┘ + │ + ├─ outParameterList (出参树) + └─ inParams (cURL 示例) +``` + +### 2.4 页面 Tab(下游接口 · 按 Content-Type 显示) + +| Tab | Json / Xml | FormUrlEncoded | +|-----|------------|----------------| +| Query | 显示 KV | 显示 KV | +| Header | 显示 KV | 显示 KV | +| **Body** | **参数树**(= 映射体) | **KV 表**(传输) | +| **Mapping Body** | **隐藏** | **显示**(参数树) | +| cURL | 显示 | 显示 | +| Response | 显示 | 显示 | +| ~~固定配置~~ | 删除(接口级) | 删除 | + +与 ApiTest 对齐方式: + +- ApiTest 的 Request Tab:Json 时常用 Raw;**接口配置**里 Json 仍用 **参数树** 定义结构(itfx 现有能力,且即映射体)。 +- ApiTest 的 FormUrlEncoded:URL Encoded KV = 我们的 **bodyForm**;映射结构不在调试页里,在接口配置里用 **Mapping Body** 维护。 + +--- + +## 3. Mapping Body 何时存在(规则定稿) + +| 条件 | 结论 | +|------|------| +| `ContentType = Json` 或 `Xml` | **没有** `mappingBodyParameterList`;`requestBodyParameterList` 参与 `InParamRestructure` 且被 `SerializationBody` 序列化 | +| `ContentType = FormUrlEncoded` | **有** `bodyFormParameterList` + **有** `mappingBodyParameterList`;映射只对 Mapping Body 树;表单字段值可为 `$FromMappingBody` | +| Query 上也有「值为 JSON」的罕见情况 | 仍用 Mapping Body 树做映射,Query KV 里字段填 `$FromMappingBody`(与 Body 表单同理) | +| 上游 `IsUpStream=true` | 始终只有 **`inParameterList`**,无 Mapping Body、无 bodyForm | + +**占位符** + +| 占位符 | 用于 | Content-Type | +|--------|------|----------------| +| `$FromMappingBody` | Form 的 Query/Body(及必要时 Header)字段引用映射结果 | **仅 Form 类** | +| `$FromBody` | **删除**下游表单中的用法 | — | +| Json Body 树字段 | 直接映射到 alias,**不需要**占位符塞整段 JSON | Json / Xml | + +--- + +## 4. 数据库(修订) + +### 4.1 `t_parameter_form` — 传输层 KV + +**新增** `FieldPosition`:Header=1, Body=2, Query=3。 + +| FieldPosition | 何时有数据 | +|---------------|------------| +| Query | 任意 Content-Type,有 URL 参数就配 | +| Header | 任意 Content-Type | +| Body | **主要 FormUrlEncoded**;Json/Xml 通常 **无 body 行**(Body 在参数树) | + +### 4.2 `t_parameter` — 参数树 + +**新增** `ParaKind`: + +| ParaKind | 含义 | 用于 | +|----------|------|------| +| **0 Normal** | 默认 | 出参;**上游入参**;Json/Xml 的 **RequestBody** | +| **1 MappingBody** | 仅映射、不直接当 HTTP Json Body | **仅** FormUrlEncoded(及日后 FormData)且需要独立映射体时 | + +| 列表字段 | ParaKind | IsInPara | +|----------|----------|----------| +| `requestBodyParameterList` | Normal(或枚举值 RequestBody=0) | true | +| `mappingBodyParameterList` | **MappingBody** | true | +| `outParameterList` | Normal | false | + +**删除的用法** + +- 不再用「一棵 in 树」同时表示 Form 映射 + Json Body(改造前混用)。 + +### 4.3 `t_parameter_fixed` + +- **删**:接口明细维护接口级 Header/Query/Body KV。 +- **留**:服务商级、Url、脚本动态 Header。 + +--- + +## 5. SaveInterface / GetInterfaceById(请求结构) + +`POST /api/Interface/SaveInterface` · 响应为接口 `id`(number)。 +`GetInterfaceById` 字段与保存一致。已删除:`formParameterList`、下游 `inParameterList`。 + +### 5.1 根对象 + +| 字段 | 说明 | +|------|------| +| `optionType` | `1` 增 / `3` 改 / `2` 删 | +| `isUpStream` | `true` 上游 / `false` 下游 | +| `serviceProviderId` | 上游 `0` | +| `id` | 改、删必填 | +| `name`, `requestAddress`, `requestProtocol`, `requestMethod`, `contentType` | 基本信息 | +| `code`, `category`, `description`, `isBatch`, `limit`, `responseContentType`, `flowCode`, `isDisableAuth`, `qps`, `mappedParamType`, `inParams` | 可选 | +| `queryFormParameterList` | Query KV,`fieldPosition=3` | +| `headerFormParameterList` | Header KV,`fieldPosition=1` | +| `outParameterList` | 出参树 | +| `requestBodyParameterList` | **仅下游 Json/Xml**,Body 树 = 映射体 | +| `bodyFormParameterList` | **仅下游 FormUrlEncoded**,Body KV | +| `mappingBodyParameterList` | **仅下游 FormUrlEncoded**,映射树 | +| `inParameterList` | **仅上游**入参树 | + +### 5.2 Body 字段互斥 + +| 场景 | 传 | 不传 | +|------|-----|------| +| 下游 Json/Xml (`contentType` 3/4) | `requestBodyParameterList` | `bodyForm*`、`mappingBody*`、`inParameterList` | +| 下游 FormUrlEncoded (`1`) | `bodyFormParameterList` + `mappingBodyParameterList` | `requestBody*`、`inParameterList` | +| 上游 | `inParameterList` | `requestBody*`、`bodyForm*`、`mappingBody*` | + +### 5.3 子结构 + +**FormParameterInput**(Query / Header / Body 行) + +```json +{ "optionType": 1, "id": 0, "name": "字段名", "value": "$FromMappingBody", "description": "", "fieldPosition": 2 } +``` + +`fieldPosition`:`1` Header · `2` Body · `3` Query。Form 业务 JSON 字段用 `$FromMappingBody`。 + +**ParameterInput**(树节点:requestBody / mappingBody / in / out) + +```json +{ "optionType": 1, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3, "isRequired": true, "sort": "1", "paraKind": 0 } +``` + +`paraKind`:`0` Normal(requestBody、上游 in、出参)· `1` MappingBody(仅 mappingBody 列表)。新增时 `id`/`pId` 需表达父子关系。 + +--- + +## 5.4 示例 + +**下游 · Json**(`contentType: 3`) + +```json +{ + "optionType": 1, + "isUpStream": false, + "serviceProviderId": 5, + "name": "创建订单", + "requestAddress": "https://api.example.com/order", + "requestProtocol": 2, + "requestMethod": 2, + "contentType": 3, + "queryFormParameterList": [ + { "optionType": 1, "id": 0, "name": "access_token", "value": "$FromAccount", "fieldPosition": 3 } + ], + "headerFormParameterList": [], + "requestBodyParameterList": [ + { "optionType": 1, "id": 1, "pId": 0, "name": "orderId", "alias": "orderId", "type": 1, "paraKind": 0 } + ], + "outParameterList": [] +} +``` + +**下游 · FormUrlEncoded**(`contentType: 1`) + +```json +{ + "optionType": 3, + "isUpStream": false, + "serviceProviderId": 5, + "id": 1001, + "name": "1688回调", + "requestAddress": "https://gw.open.1688.com/openapi/http", + "requestProtocol": 2, + "requestMethod": 2, + "contentType": 1, + "queryFormParameterList": [ + { "optionType": 3, "id": 11, "name": "_aop_timestamp", "value": "$TimeStamp", "description": "ms", "fieldPosition": 3 } + ], + "headerFormParameterList": [], + "bodyFormParameterList": [ + { "optionType": 3, "id": 10, "name": "notifyBizEventDTO", "value": "$FromMappingBody", "fieldPosition": 2 }, + { "optionType": 3, "id": 13, "name": "_aop_signature", "value": "", "fieldPosition": 2 } + ], + "mappingBodyParameterList": [ + { "optionType": 3, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3, "paraKind": 1 }, + { "optionType": 3, "id": 2, "pId": 1, "name": "orderId", "alias": "orderId", "type": 1, "paraKind": 1 } + ], + "outParameterList": [] +} +``` + +**上游**(`isUpStream: true`) + +```json +{ + "optionType": 1, + "isUpStream": true, + "serviceProviderId": 0, + "name": "标准创建订单", + "requestAddress": "/api/order/create", + "requestProtocol": 2, + "requestMethod": 2, + "contentType": 3, + "queryFormParameterList": [], + "headerFormParameterList": [], + "inParameterList": [ + { "optionType": 1, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3, "paraKind": 0 } + ], + "outParameterList": [] +} +``` + +--- + +## 6. 转发逻辑(修订) + +### 6.1 映射树来源(InParamTreeList) + +| ContentType | InParamTreeList 来源 | +|-------------|----------------------| +| Json / Xml | **`requestBodyParameterList`**(整棵树) | +| FormUrlEncoded | **`mappingBodyParameterList`** | +| 上游 | 系统接口 **inParameterList**(映射配置页不变) | + +### 6.2 HTTP 组装 + +| 部分 | 来源 | +|------|------| +| URL Query | `queryFormParameterList`(+ 服务商 Fixed Query) | +| Header | `headerFormParameterList`(+ 服务商 Fixed Header) | +| Body Json | `Serialize(requestBodyParameterList` 映射结果 `)` | +| Body FormUrlEncoded | 仅 **`bodyFormParameterList`**;其中 `$FromMappingBody` ← 映射结果序列化 | +| Body Xml | 同 Json,Xml 序列化 | + +### 6.3 流程对比(两张) + +**Json 下游(无 Mapping Body)** + +```text +上游 JSON → Restructure(requestBodyParameterList) → OriginalInterfaceMappedInput + → ReplacePlaceholder(Query/Header form + Fixed) + → SerializationBody(Json) → HTTP Body + → 拼 Query / Header +``` + +**FormUrlEncoded 下游(有 Mapping Body)** + +```text +上游 JSON → Restructure(mappingBodyParameterList) → OriginalInterfaceMappedInput + → ReplacePlaceholder(含 bodyForm 的 $FromMappingBody) + → bodyForm 字典 → HTTP Body + → 拼 Query / Header +``` + +--- + +## 7. 前后对比总表(增删改) + +### 7.1 删除了什么 + +| 类别 | 内容 | +|------|------| +| API | 下游 `formParameterList`、下游笼统的 `inParameterList`(拆开后互斥) | +| UI | 固定配置 Tab;接口级 Fixed API | +| 语义 | 所有下游共用一棵 in 树;Form 无 position;Json 也搞 Mapping Body Tab | +| 占位 | 下游表单用 `$FromBody` 表示整段 JSON | + +### 7.2 新增了什么 + +| 类别 | 内容 | +|------|------| +| 表 | `t_parameter_form.FieldPosition`;`t_parameter.ParaKind` | +| API | `queryFormParameterList`、`headerFormParameterList`(**全类型共有**) | +| API | `bodyFormParameterList`(**仅 Form 类**) | +| API | `requestBodyParameterList`(**仅 Json/Xml**,=映射体) | +| API | `mappingBodyParameterList`(**仅 Form 类**) | +| UI | Header Tab;Form 时 Mapping Body Tab;Json 时 Body=树 | +| 占位 | `$FromMappingBody`(**仅 Form KV**) | + +### 7.3 改了什么 + +| 项 | 改造前 | 改造后 | +|----|--------|--------| +| Query | 假 Query(实为 form body) | 真 Query KV,**所有 Content-Type** | +| Body Tab | 永远是参数树 | **Json=树**;**Form=KV** | +| 映射体 | 永远 in 树 | **Json=Body 树**;**Form=Mapping Body 树** | +| 保存 | form + in 混传 | 按 ContentType 传 **互斥** 的 Body 相关列表 | +| 映射 SQL | 下游 IsInPara 全量 | Json:RequestBody 树;Form:**ParaKind=MappingBody** | + +--- + +## 8. Content-Type 全量对照(改造后) + +| ContentType | queryForm | headerForm | bodyForm | requestBody 树 | mappingBody 树 | 映射目标 | HTTP Body | +|-------------|-----------|------------|----------|----------------|----------------|----------|-----------| +| **Json** | 可选 | 可选 | **不用** | **必填** | **不用** | requestBody 树 | 序列化该树 | +| **Xml** | 可选 | 可选 | **不用** | **必填** | **不用** | requestBody 树 | Xml 序列化 | +| **FormUrlEncoded** | 常用 | 常用 | **必填** | **不用** | **必填** | mappingBody 树 | bodyForm 字典 | +| **FormData** | 二期 | 二期 | 二期 | **不用** | 二期 | 二期 | 二期 | +| **上游 Json** | — | — | — | **inParameterList** | — | in 树 | — | + +--- + +## 9. 一句话 + +| | 改造前 | 改造后 | +|---|--------|--------| +| 先保存什么 | form + in 混在一起 | **先** Query/Header(通用)+ **再按 Content-Type 存 Body** | +| Json | in 树 = 映射 + Body | **requestBody 树 = 映射体**,不要 Mapping Body | +| Form | in 树 + form 表 | **bodyForm 传输** + **mappingBody 映射** | +| Mapping Body | (之前文档)每个下游都有 | **仅 Form 类需要** | + +--- + +*文档版本:2026-06-02 · 含 SaveInterface 结构与示例(§5)*