feat: script mcp
This commit is contained in:
parent
4f31bba29d
commit
432ea493d0
@ -1,9 +1,6 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.AppHost", "src\InterfaceForward.AppHost\InterfaceForward.AppHost.csproj", "{9609CC2D-CC83-4C01-BE36-C0429A06B238}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.ServiceDefaults", "src\InterfaceForward.ServiceDefaults\InterfaceForward.ServiceDefaults.csproj", "{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A}"
|
||||
EndProject
|
||||
#
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EA8DF4A8-7618-4C28-BEDC-21629B8B219B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Api", "src\InterfaceForward.Api\InterfaceForward.Api.csproj", "{0E2F3A09-4EC4-4A61-85DC-9F9F770C3B65}"
|
||||
@ -20,28 +17,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Repositories", "src\InterfaceForward.Repositories\InterfaceForward.Repositories.csproj", "{AB53F0CC-DE7A-4AE9-B6AA-2A8283B4EB23}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E78C7664-0AED-43DB-8169-5414EBD945E7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Application", "src\InterfaceForward.Application\InterfaceForward.Application.csproj", "{4FE95CF4-F22A-45D6-B7DA-5B9DAE39ECED}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Domain.Shared", "src\InterfaceForward.Domain.Shared\InterfaceForward.Domain.Shared.csproj", "{9DCF9B05-832C-4258-B7C9-0A389734F853}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Application.Contracts", "src\InterfaceForward.Application.Contracts\InterfaceForward.Application.Contracts.csproj", "{79ED9047-0141-4B15-94E1-30FFD1566BAE}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DCD9E3A4-7B28-4756-A2AA-272796356C52}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceForward.Application.Tests", "tests\InterfaceForward.Application.Tests\InterfaceForward.Application.Tests.csproj", "{48C656F0-F879-420B-82C8-F00A8206EADE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9609CC2D-CC83-4C01-BE36-C0429A06B238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9609CC2D-CC83-4C01-BE36-C0429A06B238}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9609CC2D-CC83-4C01-BE36-C0429A06B238}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9609CC2D-CC83-4C01-BE36-C0429A06B238}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E2F3A09-4EC4-4A61-85DC-9F9F770C3B65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E2F3A09-4EC4-4A61-85DC-9F9F770C3B65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E2F3A09-4EC4-4A61-85DC-9F9F770C3B65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -62,14 +53,17 @@ Global
|
||||
{79ED9047-0141-4B15-94E1-30FFD1566BAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{79ED9047-0141-4B15-94E1-30FFD1566BAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{79ED9047-0141-4B15-94E1-30FFD1566BAE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{48C656F0-F879-420B-82C8-F00A8206EADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{48C656F0-F879-420B-82C8-F00A8206EADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{48C656F0-F879-420B-82C8-F00A8206EADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{48C656F0-F879-420B-82C8-F00A8206EADE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{9609CC2D-CC83-4C01-BE36-C0429A06B238} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{6DAA9CF1-155A-4FC7-9EAE-7E8FA22CD23A} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{0E2F3A09-4EC4-4A61-85DC-9F9F770C3B65} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{AB53F0CC-DE7A-4AE9-B6AA-2A8283B4EB23} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{4FE95CF4-F22A-45D6-B7DA-5B9DAE39ECED} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{9DCF9B05-832C-4258-B7C9-0A389734F853} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{79ED9047-0141-4B15-94E1-30FFD1566BAE} = {EA8DF4A8-7618-4C28-BEDC-21629B8B219B}
|
||||
{48C656F0-F879-420B-82C8-F00A8206EADE} = {DCD9E3A4-7B28-4756-A2AA-272796356C52}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InterfaceForward.Application\InterfaceForward.Application.csproj" />
|
||||
<ProjectReference Include="..\InterfaceForward.ServiceDefaults\InterfaceForward.ServiceDefaults.csproj"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.18" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
|
||||
|
||||
@ -106,9 +106,6 @@ public class InterfaceForwardApiModule : FakeModule
|
||||
|
||||
app.UseFakeExceptionHandling();
|
||||
|
||||
// Add Aspire default endpoints.
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
// 虚拟路由-网关适配
|
||||
app.UsePathBase(app.Configuration["ASPNETCORE_PATHBASE"]);
|
||||
|
||||
|
||||
@ -15,9 +15,6 @@ try
|
||||
builder.Host.UseAutofac().UseSerilog();
|
||||
builder.Services.AddApplication<InterfaceForwardApiModule>();
|
||||
|
||||
// Add service defaults & Aspire components.
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
var app = builder.Build();
|
||||
app.InitializeApplication();
|
||||
app.Run();
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireHost>true</IsAspireHost>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting" Version="8.2.2" />
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InterfaceForward.Api\InterfaceForward.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -1,5 +0,0 @@
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddProject<Projects.InterfaceForward_Api>("apiservice");
|
||||
|
||||
builder.Build().Run();
|
||||
@ -1,16 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:15177",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16294"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Aspire.Hosting.Dcp": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class ExplainForwardScriptOutput
|
||||
{
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public List<string> Warnings { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class GenerateForwardScriptInput
|
||||
{
|
||||
public string Position { get; set; } = "prefix";
|
||||
|
||||
public string Requirement { get; set; } = string.Empty;
|
||||
|
||||
public List<string> AccountFields { get; set; } = [];
|
||||
|
||||
public List<string> Headers { get; set; } = [];
|
||||
|
||||
public List<string> Queries { get; set; } = [];
|
||||
|
||||
public List<string> FormFields { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class GenerateForwardScriptOutput
|
||||
{
|
||||
public string Script { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public List<string> Warnings { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class PreviewForwardScriptInput
|
||||
{
|
||||
public string Script { get; set; } = string.Empty;
|
||||
|
||||
public JObject? OriginalInput { get; set; }
|
||||
|
||||
public JObject? MappedInput { get; set; }
|
||||
|
||||
public JObject? TargetOutput { get; set; }
|
||||
|
||||
public Dictionary<string, string> AccountFields { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using InterfaceForward.Domain.Shared.Dtos;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class PreviewForwardScriptOutput
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string? Error { get; set; }
|
||||
|
||||
public JToken? OriginalInput { get; set; }
|
||||
|
||||
public JToken? MappedInput { get; set; }
|
||||
|
||||
public JToken? TargetOutput { get; set; }
|
||||
|
||||
public List<FixedFieldWithValueDto> FixedFields { get; set; } = [];
|
||||
|
||||
public List<InterfaceFormFieldDto> FormFields { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class ValidateForwardScriptInput
|
||||
{
|
||||
public string Script { get; set; } = string.Empty;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
|
||||
public class ValidateForwardScriptOutput
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string? Error { get; set; }
|
||||
|
||||
public List<string> Warnings { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,229 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
using InterfaceForward.Application.Contracts.ForwardCore;
|
||||
using InterfaceForward.Application.ForwardCore;
|
||||
using InterfaceForward.Domain.Shared.Dtos;
|
||||
using InterfaceForward.Domain.Shared.Enum;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InterfaceForward.Application.Services.ScriptAssistant;
|
||||
|
||||
[ApiExplorerSettings(GroupName = "脚本助手")]
|
||||
public class ScriptAssistantService : ApplicationService
|
||||
{
|
||||
static readonly string[] DangerousTokens =
|
||||
[
|
||||
"System.IO", "File", "Directory", "Path.", "Environment", "Process", "System.Diagnostics",
|
||||
"Reflection", "Assembly", "Activator", "HttpClient", "Socket", "TcpClient", "UdpClient",
|
||||
"Task.Run", "Thread", "while (true", "for (;;"
|
||||
];
|
||||
|
||||
[HttpPost("ScriptAssistant/Generate")]
|
||||
public GenerateForwardScriptOutput Generate([FromBody] [Required] GenerateForwardScriptInput input)
|
||||
{
|
||||
var requirement = input.Requirement.Trim();
|
||||
if (requirement.IsNullOrWhiteSpace())
|
||||
{
|
||||
throw new BusinessException(message: "请输入脚本需求");
|
||||
}
|
||||
|
||||
var script = GenerateTemplateScript(input);
|
||||
var output = new GenerateForwardScriptOutput
|
||||
{
|
||||
Script = script,
|
||||
Description = "已根据需求生成可编辑脚本模板,请先校验并预览后再保存到接口配置。"
|
||||
};
|
||||
|
||||
output.Warnings.AddRange(GetWarnings(script));
|
||||
return output;
|
||||
}
|
||||
|
||||
[HttpPost("ScriptAssistant/Validate")]
|
||||
public ValidateForwardScriptOutput Validate([FromBody] [Required] ValidateForwardScriptInput input)
|
||||
{
|
||||
var output = new ValidateForwardScriptOutput();
|
||||
output.Warnings.AddRange(GetWarnings(input.Script));
|
||||
|
||||
try
|
||||
{
|
||||
NatashaScriptCompiler.CompileAsyncFunc(NormalizeScript(input.Script));
|
||||
output.Success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
output.Success = false;
|
||||
output.Error = ex.Message;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[HttpPost("ScriptAssistant/Preview")]
|
||||
public async Task<PreviewForwardScriptOutput> Preview([FromBody] [Required] PreviewForwardScriptInput input)
|
||||
{
|
||||
var output = new PreviewForwardScriptOutput();
|
||||
try
|
||||
{
|
||||
var context = BuildPreviewContext(input);
|
||||
var func = NatashaScriptCompiler.CompileAsyncFunc(NormalizeScript(input.Script));
|
||||
await func(context);
|
||||
|
||||
output.Success = true;
|
||||
output.OriginalInput = context.OriginalInterfaceInput;
|
||||
output.MappedInput = context.OriginalInterfaceMappedInput;
|
||||
output.TargetOutput = context.TargetInterfaceOutput;
|
||||
output.FixedFields = context.FixedFieldList;
|
||||
output.FormFields = context.FormFieldList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
output.Success = false;
|
||||
output.Error = ex.Message;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[HttpPost("ScriptAssistant/Explain")]
|
||||
public ExplainForwardScriptOutput Explain([FromBody] [Required] ValidateForwardScriptInput input)
|
||||
{
|
||||
var output = new ExplainForwardScriptOutput
|
||||
{
|
||||
Description = BuildExplanation(input.Script)
|
||||
};
|
||||
output.Warnings.AddRange(GetWarnings(input.Script));
|
||||
return output;
|
||||
}
|
||||
|
||||
static string GenerateTemplateScript(GenerateForwardScriptInput input)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("// 请按需修改生成的模板脚本,保存前先执行校验和预览。");
|
||||
|
||||
foreach (var field in input.AccountFields.Where(x => !x.IsNullOrWhiteSpace()).Distinct())
|
||||
{
|
||||
var variableName = ToVariableName(field);
|
||||
builder.AppendLine($"var {variableName} = context.GetAccountFieldValueOrNull(\"{Escape(field)}\") ?? string.Empty;");
|
||||
}
|
||||
|
||||
foreach (var header in input.Headers.Where(x => !x.IsNullOrWhiteSpace()).Distinct())
|
||||
{
|
||||
builder.AppendLine($"context.AddHeader(\"{Escape(header)}\", string.Empty);");
|
||||
}
|
||||
|
||||
foreach (var query in input.Queries.Where(x => !x.IsNullOrWhiteSpace()).Distinct())
|
||||
{
|
||||
builder.AppendLine("context.FixedFieldList.Add(new FixedFieldWithValueDto");
|
||||
builder.AppendLine("{");
|
||||
builder.AppendLine($" FieldName = \"{Escape(query)}\",");
|
||||
builder.AppendLine(" FieldValue = string.Empty,");
|
||||
builder.AppendLine(" FieldPosition = FieldPosition.Query");
|
||||
builder.AppendLine("});");
|
||||
}
|
||||
|
||||
foreach (var formField in input.FormFields.Where(x => !x.IsNullOrWhiteSpace()).Distinct())
|
||||
{
|
||||
builder.AppendLine("context.FormFieldList.Add(new InterfaceFormFieldDto");
|
||||
builder.AppendLine("{");
|
||||
builder.AppendLine($" Name = \"{Escape(formField)}\",");
|
||||
builder.AppendLine(" Value = string.Empty");
|
||||
builder.AppendLine("});");
|
||||
}
|
||||
|
||||
if (builder.Length == 0 || builder.ToString().Split(Environment.NewLine).Length <= 2)
|
||||
{
|
||||
builder.AppendLine("// 示例:读取账号字段并添加到 Header。");
|
||||
builder.AppendLine("var appKey = context.GetAccountFieldValueOrNull(\"appKey\") ?? string.Empty;");
|
||||
builder.AppendLine("context.AddHeader(\"appKey\", appKey);");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
static ForwardCoreContext BuildPreviewContext(PreviewForwardScriptInput input)
|
||||
{
|
||||
return new ForwardCoreContext
|
||||
{
|
||||
OriginalInterfaceInput = input.OriginalInput ?? new JObject(),
|
||||
OriginalInterfaceMappedInput = input.MappedInput ?? new JObject(),
|
||||
TargetInterfaceOutput = input.TargetOutput ?? new JObject(),
|
||||
TargetInterfaceInput = string.Empty,
|
||||
TargetInterface = new InterfaceDto(),
|
||||
ServiceProviderRequestLog = new RequestLogDto(Guid.NewGuid()),
|
||||
AccountFieldList = input.AccountFields.Select(x => new ServiceProviderAccountFieldWithValueDto
|
||||
{
|
||||
FieldName = x.Key,
|
||||
FieldValue = x.Value
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
static string NormalizeScript(string script)
|
||||
{
|
||||
return script.StartsWith("//async") || script.Contains("await") ? script : "//async" + Environment.NewLine + script;
|
||||
}
|
||||
|
||||
static List<string> GetWarnings(string script)
|
||||
{
|
||||
var warnings = new List<string>();
|
||||
foreach (var token in DangerousTokens)
|
||||
{
|
||||
if (script.Contains(token, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
warnings.Add($"脚本包含高风险内容:{token}");
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
static string BuildExplanation(string script)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
if (script.Contains("GetAccountFieldValueOrNull", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lines.Add("会读取服务商账号字段。");
|
||||
}
|
||||
|
||||
if (script.Contains("AddHeader", StringComparison.OrdinalIgnoreCase) || script.Contains("FieldPosition.Header", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lines.Add("会向请求 Header 添加字段。");
|
||||
}
|
||||
|
||||
if (script.Contains("FieldPosition.Query", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lines.Add("会向请求 Query 添加字段。");
|
||||
}
|
||||
|
||||
if (script.Contains("FormFieldList", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lines.Add("会修改表单字段。");
|
||||
}
|
||||
|
||||
if (script.Contains("TargetInterfaceOutput", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
lines.Add("会读取或修改服务商响应结果。");
|
||||
}
|
||||
|
||||
return lines.Count == 0 ? "暂未识别到具体操作,请查看脚本文本。" : string.Join(Environment.NewLine, lines);
|
||||
}
|
||||
|
||||
static string ToVariableName(string value)
|
||||
{
|
||||
var name = Regex.Replace(value, "[^a-zA-Z0-9_]", "_");
|
||||
if (name.Length == 0 || char.IsDigit(name[0]))
|
||||
{
|
||||
name = "field_" + name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static string Escape(string value)
|
||||
{
|
||||
return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace Microsoft.Extensions.Hosting;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.ConfigureOpenTelemetry();
|
||||
|
||||
builder.AddDefaultHealthChecks();
|
||||
|
||||
builder.Services.AddServiceDiscovery();
|
||||
|
||||
builder.Services.ConfigureHttpClientDefaults(http =>
|
||||
{
|
||||
// Turn on resilience by default
|
||||
http.AddStandardResilienceHandler();
|
||||
|
||||
// Turn on service discovery by default
|
||||
http.UseServiceDiscovery();
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.Logging.AddOpenTelemetry(logging =>
|
||||
{
|
||||
logging.IncludeFormattedMessage = true;
|
||||
logging.IncludeScopes = true;
|
||||
});
|
||||
|
||||
builder.Services.AddOpenTelemetry()
|
||||
.WithMetrics(metrics =>
|
||||
{
|
||||
metrics.AddRuntimeInstrumentation()
|
||||
.AddBuiltInMeters();
|
||||
})
|
||||
.WithTracing(tracing =>
|
||||
{
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
// We want to view all traces in development
|
||||
tracing.SetSampler(new AlwaysOnSampler());
|
||||
}
|
||||
|
||||
tracing.AddAspNetCoreInstrumentation()
|
||||
.AddGrpcClientInstrumentation()
|
||||
.AddHttpClientInstrumentation();
|
||||
});
|
||||
|
||||
builder.AddOpenTelemetryExporters();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
|
||||
{
|
||||
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
|
||||
|
||||
if (useOtlpExporter)
|
||||
{
|
||||
builder.Services.Configure<OpenTelemetryLoggerOptions>(logging => logging.AddOtlpExporter());
|
||||
builder.Services.ConfigureOpenTelemetryMeterProvider(metrics => metrics.AddOtlpExporter());
|
||||
builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());
|
||||
}
|
||||
|
||||
// Uncomment the following lines to enable the Prometheus exporter (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
|
||||
// builder.Services.AddOpenTelemetry()
|
||||
// .WithMetrics(metrics => metrics.AddPrometheusExporter());
|
||||
|
||||
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.Exporter package)
|
||||
// builder.Services.AddOpenTelemetry()
|
||||
// .UseAzureMonitor();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddHealthChecks()
|
||||
// Add a default liveness check to ensure app is responsive
|
||||
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplication MapDefaultEndpoints(this WebApplication app)
|
||||
{
|
||||
// Uncomment the following line to enable the Prometheus endpoint (requires the OpenTelemetry.Exporter.Prometheus.AspNetCore package)
|
||||
// app.MapPrometheusScrapingEndpoint();
|
||||
|
||||
// All health checks must pass for app to be considered ready to accept traffic after starting
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
// Only health checks tagged with the "live" tag must pass for app to be considered alive
|
||||
app.MapHealthChecks("/alive", new HealthCheckOptions
|
||||
{
|
||||
Predicate = r => r.Tags.Contains("live")
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static MeterProviderBuilder AddBuiltInMeters(this MeterProviderBuilder meterProviderBuilder) =>
|
||||
meterProviderBuilder.AddMeter(
|
||||
"Microsoft.AspNetCore.Hosting",
|
||||
"Microsoft.AspNetCore.Server.Kestrel",
|
||||
"System.Net.Http");
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAspireSharedProject>true</IsAspireSharedProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.0-preview.1.23557.2"/>
|
||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.7.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.7.0-beta.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.7.1" />
|
||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\InterfaceForward.Application\InterfaceForward.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
74
tests/InterfaceForward.Application.Tests/UnitTest1.cs
Normal file
74
tests/InterfaceForward.Application.Tests/UnitTest1.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using InterfaceForward.Application.Contracts.Dtos.ScriptAssistant;
|
||||
using InterfaceForward.Application.Services.ScriptAssistant;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace InterfaceForward.Application.Tests;
|
||||
|
||||
public class ScriptAssistantServiceTests
|
||||
{
|
||||
static ScriptAssistantServiceTests()
|
||||
{
|
||||
NatashaManagement.Preheating<NatashaDomainCreator>(false, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_ShouldCreateTemplateFromFields()
|
||||
{
|
||||
var service = new ScriptAssistantService();
|
||||
|
||||
var output = service.Generate(new GenerateForwardScriptInput
|
||||
{
|
||||
Requirement = "把 appKey 放到 header",
|
||||
AccountFields = ["appKey"],
|
||||
Headers = ["appKey"]
|
||||
});
|
||||
|
||||
Assert.Contains("GetAccountFieldValueOrNull(\"appKey\")", output.Script);
|
||||
Assert.Contains("context.AddHeader(\"appKey\"", output.Script);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ShouldCompileGeneratedScript()
|
||||
{
|
||||
var service = new ScriptAssistantService();
|
||||
|
||||
var output = service.Validate(new ValidateForwardScriptInput
|
||||
{
|
||||
Script = "var appKey = context.GetAccountFieldValueOrNull(\"appKey\") ?? string.Empty;\ncontext.AddHeader(\"appKey\", appKey);"
|
||||
});
|
||||
|
||||
Assert.True(output.Success, output.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Preview_ShouldRunScriptAndReturnChangedFields()
|
||||
{
|
||||
var service = new ScriptAssistantService();
|
||||
|
||||
var output = await service.Preview(new PreviewForwardScriptInput
|
||||
{
|
||||
Script = "var appKey = context.GetAccountFieldValueOrNull(\"appKey\") ?? string.Empty;\ncontext.AddHeader(\"appKey\", appKey);",
|
||||
OriginalInput = new JObject { ["orderNo"] = "A001" },
|
||||
MappedInput = new JObject { ["targetOrderNo"] = "A001" },
|
||||
AccountFields = new Dictionary<string, string> { ["appKey"] = "test-key" }
|
||||
});
|
||||
|
||||
Assert.True(output.Success, output.Error);
|
||||
var field = Assert.Single(output.FixedFields);
|
||||
Assert.Equal("appKey", field.FieldName);
|
||||
Assert.Equal("test-key", field.FieldValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ShouldReturnWarningForDangerousToken()
|
||||
{
|
||||
var service = new ScriptAssistantService();
|
||||
|
||||
var output = service.Validate(new ValidateForwardScriptInput
|
||||
{
|
||||
Script = "System.IO.File.ReadAllText(\"a.txt\");"
|
||||
});
|
||||
|
||||
Assert.Contains(output.Warnings, x => x.Contains("System.IO"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user