itfx/src/InterfaceForward.Application/Services/ScriptAssistant/ScriptAssistantService.cs
2026-06-12 14:15:54 +08:00

229 lines
8.2 KiB
C#

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.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("请输入脚本需求");
}
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("\"", "\\\"");
}
}