fix(scripting): T3 code quality 修复
- C1: 脚本运行时异常未捕获导致 500(新增 catch-all Failed 日志+包装) - I1: DateTime.TryParse 误判日期样字符串(改为严格 ISO-8601 正则前置) - M1: IScriptExecutionService XML 注释补 Logger - M2: TestExecuteAsync → ExecuteTestAsync 命名一致性 - M7: 递归深度 ++ 移入 try 块保证 finally 配对 T3 code quality review fixes
This commit is contained in:
parent
624579cf78
commit
bec5bfd0dd
@ -2,7 +2,7 @@ using Scripting.Application.DTOs;
|
||||
|
||||
namespace Scripting.Application.Services;
|
||||
|
||||
/// <summary>脚本执行服务:编译+运行 C# 脚本,注入 MediatR/CurrentUser/CancellationToken,并写执行日志。</summary>
|
||||
/// <summary>脚本执行服务:编译+运行 C# 脚本,注入 MediatR/CurrentUser/CancellationToken/Logger,并写执行日志。</summary>
|
||||
public interface IScriptExecutionService
|
||||
{
|
||||
/// <summary>按 Id 执行已发布脚本(按钮动作链用)。</summary>
|
||||
@ -12,5 +12,5 @@ public interface IScriptExecutionService
|
||||
Task<ScriptExecutionResult> ExecuteByNameAsync(string scriptName, string inputJson, CancellationToken ct);
|
||||
|
||||
/// <summary>测试运行:不校验 Published 状态,允许草稿直接测,仍写日志(FunctionName 标 [TEST])。</summary>
|
||||
Task<ScriptExecutionResult> TestExecuteAsync(Guid scriptId, string inputJson, CancellationToken ct);
|
||||
Task<ScriptExecutionResult> ExecuteTestAsync(Guid scriptId, string inputJson, CancellationToken ct);
|
||||
}
|
||||
|
||||
@ -57,6 +57,12 @@ public partial class ScriptExecutionService(
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant)]
|
||||
private static partial Regex ForbiddenApiRegex { get; }
|
||||
|
||||
/// <summary>严格 ISO-8601 日期正则:要求 YYYY-MM-DD(可带时间/时区)。防止 "2026"/"2026-07" 等日期样字符串误判为 DateTime。</summary>
|
||||
[GeneratedRegex(
|
||||
@"^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$",
|
||||
RegexOptions.Compiled | RegexOptions.CultureInvariant)]
|
||||
private static partial Regex Iso8601Regex { get; }
|
||||
|
||||
// ── 公共入口 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -68,7 +74,7 @@ public partial class ScriptExecutionService(
|
||||
=> ExecuteAsync(null, scriptName, inputJson, requirePublished: true, isTest: false, ct);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ScriptExecutionResult> TestExecuteAsync(Guid scriptId, string inputJson, CancellationToken ct)
|
||||
public Task<ScriptExecutionResult> ExecuteTestAsync(Guid scriptId, string inputJson, CancellationToken ct)
|
||||
=> ExecuteAsync(scriptId, null, inputJson, requirePublished: false, isTest: true, ct);
|
||||
|
||||
// ── 核心执行 ──────────────────────────────────────────────────────────
|
||||
@ -117,12 +123,13 @@ public partial class ScriptExecutionService(
|
||||
throw new BusinessException($"脚本包含禁用 API: {forbiddenMatch.Value}");
|
||||
}
|
||||
|
||||
// 深度计数 +1(finally 归位)
|
||||
_recursionDepth.Value++;
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var logger = new ScriptLogger();
|
||||
try
|
||||
{
|
||||
// 深度计数 +1(finally 归位),置于 try 内保证与 finally 配对
|
||||
_recursionDepth.Value++;
|
||||
|
||||
// 超时令牌:默认 5s,上限 30s
|
||||
var timeoutMs = function.TimeoutMs <= 0 ? DefaultTimeoutMs : Math.Min(function.TimeoutMs, MaxTimeoutMs);
|
||||
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
@ -170,6 +177,17 @@ public partial class ScriptExecutionService(
|
||||
inputJson, null, ex.Message, stopwatch.ElapsedMilliseconds, logger.ToText(), ct);
|
||||
throw new BusinessException($"脚本执行失败: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 脚本运行时异常(NullReference/DivideByZero/IndexOutOfRange/InvalidCast/KeyNotFound 等):
|
||||
// 统一记录 Failed 日志并包装为 BusinessException,避免原始异常冒泡成 500 带堆栈。
|
||||
// 超时场景下 ct 可能已取消,用 CancellationToken.None 保证日志落库
|
||||
stopwatch.Stop();
|
||||
await AddLogAsync(function, isTest, ScriptExecutionStatus.Failed,
|
||||
inputJson, null, ex.Message, stopwatch.ElapsedMilliseconds, logger.ToText(),
|
||||
ct.IsCancellationRequested ? CancellationToken.None : ct);
|
||||
throw new BusinessException($"脚本执行失败: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_recursionDepth.Value--;
|
||||
@ -301,16 +319,17 @@ public partial class ScriptExecutionService(
|
||||
{
|
||||
var str = element.GetString() ?? string.Empty;
|
||||
|
||||
// ISO 8601 日期优先
|
||||
if (DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dt))
|
||||
{
|
||||
return dt;
|
||||
}
|
||||
// Guid
|
||||
// Guid 优先(Guid 串不会匹配 ISO-8601 日期正则)
|
||||
if (Guid.TryParse(str, out var guid))
|
||||
{
|
||||
return guid;
|
||||
}
|
||||
// 严格 ISO-8601 日期:先正则白名单,再 TryParse,避免 "2026"/"2026-07" 等日期样字符串误判
|
||||
if (Iso8601Regex.IsMatch(str)
|
||||
&& DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dt))
|
||||
{
|
||||
return dt;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user