229 lines
8.2 KiB
C#
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("\"", "\\\"");
|
|
}
|
|
}
|