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 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 GetWarnings(string script) { var warnings = new List(); 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(); 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("\"", "\\\""); } }