diff --git a/src/modules/scripting/Domain/Entities/ScriptExecutionLog.cs b/src/modules/scripting/Domain/Entities/ScriptExecutionLog.cs
new file mode 100644
index 0000000..2774839
--- /dev/null
+++ b/src/modules/scripting/Domain/Entities/ScriptExecutionLog.cs
@@ -0,0 +1,24 @@
+using BuildFlow.Shared.Domain.Common;
+using Scripting.Domain.Enums;
+
+namespace Scripting.Domain.Entities;
+
+/// 脚本执行日志,记录输入、输出、耗时和异常,便于审计脚本运行结果。
+public class ScriptExecutionLog : BaseEntity, IAuditable
+{
+ public Guid FunctionId { get; set; }
+ public string FunctionName { get; set; } = string.Empty;
+ public ScriptExecutionStatus Status { get; set; }
+ public string InputJson { get; set; } = "{}";
+ public string? ResultJson { get; set; }
+ public string? ErrorMessage { get; set; }
+ public long DurationMs { get; set; }
+
+ /// 执行期间由 Logger 收集的输出日志(多行文本)。
+ public string? LogLines { get; set; }
+
+ public string CreatedBy { get; set; } = default!;
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public string UpdatedBy { get; set; } = default!;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+}
diff --git a/src/modules/scripting/Domain/Entities/ScriptFunction.cs b/src/modules/scripting/Domain/Entities/ScriptFunction.cs
new file mode 100644
index 0000000..d11615d
--- /dev/null
+++ b/src/modules/scripting/Domain/Entities/ScriptFunction.cs
@@ -0,0 +1,51 @@
+using BuildFlow.Shared.Domain.Common;
+using Scripting.Domain.Enums;
+
+namespace Scripting.Domain.Entities;
+
+///
+/// 脚本函数定义。Name 为英文标识,Code 保存受控 C# 脚本内容,由 Roslyn 编译执行。
+/// 按钮通过 runFunction/executeFunctions 引用。本模块仅承载 C# 脚本类型(不含 HTTP 请求函数)。
+///
+public class ScriptFunction : BaseEntity, IFullAudit
+{
+ /// 绑定的自定义表单定义 ID。为空表示全局可复用函数。
+ public Guid? FormDefinitionId { get; set; }
+
+ /// 函数英文标识,供按钮动作链稳定引用。
+ public string Name { get; set; } = string.Empty;
+
+ /// 函数显示名称。
+ public string DisplayName { get; set; } = string.Empty;
+
+ /// 函数说明。
+ public string? Description { get; set; }
+
+ /// 受控 C# 脚本代码。
+ public string Code { get; set; } = string.Empty;
+
+ /// 函数输入 Schema JSON。
+ public string? InputSchema { get; set; }
+
+ /// 函数输出 Schema JSON。
+ public string? OutputSchema { get; set; }
+
+ /// 函数发布状态。
+ public ScriptStatus Status { get; set; } = ScriptStatus.Draft;
+
+ /// 函数版本号。
+ public int Version { get; set; } = 1;
+
+ /// 执行超时时间,单位毫秒。
+ public int TimeoutMs { get; set; }
+
+ /// 测试运行期间由 Logger 收集的输出日志(多行文本)。
+ public string? LogLines { get; set; }
+
+ public string CreatedBy { get; set; } = default!;
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public string UpdatedBy { get; set; } = default!;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ public bool IsDeleted { get; set; }
+ public string OperatorIP { get; set; } = "unknown";
+}
diff --git a/src/modules/scripting/Domain/Enums/ScriptExecutionStatus.cs b/src/modules/scripting/Domain/Enums/ScriptExecutionStatus.cs
new file mode 100644
index 0000000..0421941
--- /dev/null
+++ b/src/modules/scripting/Domain/Enums/ScriptExecutionStatus.cs
@@ -0,0 +1,9 @@
+namespace Scripting.Domain.Enums;
+
+/// 脚本执行结果状态。
+public enum ScriptExecutionStatus
+{
+ Success = 0,
+ Failed = 1,
+ Timeout = 2,
+}
diff --git a/src/modules/scripting/Domain/Enums/ScriptStatus.cs b/src/modules/scripting/Domain/Enums/ScriptStatus.cs
new file mode 100644
index 0000000..4b73197
--- /dev/null
+++ b/src/modules/scripting/Domain/Enums/ScriptStatus.cs
@@ -0,0 +1,9 @@
+namespace Scripting.Domain.Enums;
+
+/// 脚本函数生命周期:Draft 草稿、Published 可运行、Disabled 停用阻断。
+public enum ScriptStatus
+{
+ Draft = 0,
+ Published = 1,
+ Disabled = 2,
+}
diff --git a/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.Designer.cs b/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.Designer.cs
new file mode 100644
index 0000000..eda7e6f
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.Designer.cs
@@ -0,0 +1,236 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Scripting.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace Scripting.Infrastructure.Migrations
+{
+ [DbContext(typeof(ScriptingDbContext))]
+ [Migration("20260706095113_RenameTablesForScriptingModule")]
+ partial class RenameTablesForScriptingModule
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Scripting.Domain.Entities.ScriptExecutionLog", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasComment("主键ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasComment("创建时间");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("created_by")
+ .HasComment("创建人ID");
+
+ b.Property("DurationMs")
+ .HasColumnType("bigint")
+ .HasColumnName("duration_ms")
+ .HasComment("执行耗时(毫秒)");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)")
+ .HasColumnName("error_message")
+ .HasComment("错误信息");
+
+ b.Property("FunctionId")
+ .HasColumnType("uuid")
+ .HasColumnName("function_id")
+ .HasComment("函数ID");
+
+ b.Property("FunctionName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("function_name")
+ .HasComment("函数英文标识");
+
+ b.Property("InputJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("input_json")
+ .HasComment("执行输入JSON");
+
+ b.Property("LogLines")
+ .HasColumnType("text")
+ .HasColumnName("log_lines")
+ .HasComment("执行期间 Logger 收集的输出日志");
+
+ b.Property("ResultJson")
+ .HasColumnType("jsonb")
+ .HasColumnName("result_json")
+ .HasComment("执行结果JSON");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status")
+ .HasComment("执行状态");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasComment("更新时间");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("updated_by")
+ .HasComment("更新人ID");
+
+ b.HasKey("Id")
+ .HasName("pk_func_execution_logs");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("ix_func_execution_logs_created_at");
+
+ b.HasIndex("FunctionId")
+ .HasDatabaseName("ix_func_execution_logs_function_id");
+
+ b.ToTable("func_execution_logs", (string)null);
+ });
+
+ modelBuilder.Entity("Scripting.Domain.Entities.ScriptFunction", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasComment("主键ID");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("code")
+ .HasComment("C# 脚本代码");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasComment("创建时间");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("created_by")
+ .HasComment("创建人ID");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("description")
+ .HasComment("函数说明");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("display_name")
+ .HasComment("函数显示名称");
+
+ b.Property("FormDefinitionId")
+ .HasColumnType("uuid")
+ .HasColumnName("form_definition_id")
+ .HasComment("绑定的自定义表单定义ID,为空表示全局可复用函数");
+
+ b.Property("InputSchema")
+ .HasColumnType("jsonb")
+ .HasColumnName("input_schema")
+ .HasComment("函数输入 Schema JSON");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted")
+ .HasComment("是否软删除");
+
+ b.Property("LogLines")
+ .HasColumnType("text")
+ .HasColumnName("log_lines")
+ .HasComment("测试运行期间 Logger 收集的输出日志");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name")
+ .HasComment("函数英文标识");
+
+ b.Property("OperatorIP")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)")
+ .HasColumnName("operator_ip")
+ .HasComment("操作人IP地址");
+
+ b.Property("OutputSchema")
+ .HasColumnType("jsonb")
+ .HasColumnName("output_schema")
+ .HasComment("函数输出 Schema JSON");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status")
+ .HasComment("函数状态");
+
+ b.Property("TimeoutMs")
+ .HasColumnType("integer")
+ .HasColumnName("timeout_ms")
+ .HasComment("执行超时时间(毫秒)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasComment("更新时间");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("updated_by")
+ .HasComment("更新人ID");
+
+ b.Property("Version")
+ .HasColumnType("integer")
+ .HasColumnName("version")
+ .HasComment("函数版本");
+
+ b.HasKey("Id")
+ .HasName("pk_funcs");
+
+ b.HasIndex("FormDefinitionId")
+ .HasDatabaseName("ix_funcs_form_definition_id");
+
+ b.HasIndex("Name")
+ .IsUnique()
+ .HasDatabaseName("ix_funcs_name");
+
+ b.ToTable("funcs", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.cs b/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.cs
new file mode 100644
index 0000000..cc24988
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Migrations/20260706095113_RenameTablesForScriptingModule.cs
@@ -0,0 +1,146 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Scripting.Infrastructure.Migrations
+{
+ ///
+ ///
+ /// 数据保留式迁移:将 workflow 模块下的 b_funcs / b_func_execution_logs 表连同主键、索引
+ /// 重命名为 funcs / func_execution_logs,并新增 log_lines 列(可空,已有行落 NULL)。
+ /// 不进行 Drop/Create,避免数据丢失。Scripting 模块从 CustomFunction 拆分而来(见 spec §11 YAGNI)。
+ /// 主键/索引沿用 pk_<table> / ix_<table>_<col> 命名约定,操作顺序对齐
+ /// 既有的 RenameTablesToBPrefix 迁移。
+ ///
+ public partial class RenameTablesForScriptingModule : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ // 1) 先卸下旧主键约束(RenameTable 之前)
+ migrationBuilder.DropPrimaryKey(
+ name: "pk_b_funcs",
+ table: "b_funcs");
+
+ migrationBuilder.DropPrimaryKey(
+ name: "pk_b_func_execution_logs",
+ table: "b_func_execution_logs");
+
+ // 2) 重命名表(保留数据)
+ migrationBuilder.RenameTable(
+ name: "b_funcs",
+ newName: "funcs");
+
+ migrationBuilder.RenameTable(
+ name: "b_func_execution_logs",
+ newName: "func_execution_logs");
+
+ // 3) 重建主键约束(新表名)
+ migrationBuilder.AddPrimaryKey(
+ name: "pk_funcs",
+ table: "funcs",
+ column: "id");
+
+ migrationBuilder.AddPrimaryKey(
+ name: "pk_func_execution_logs",
+ table: "func_execution_logs",
+ column: "id");
+
+ // 4) 重命名索引(沿用 ix__ 命名约定)
+ migrationBuilder.RenameIndex(
+ name: "ix_b_funcs_name",
+ table: "funcs",
+ newName: "ix_funcs_name");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_b_funcs_form_definition_id",
+ table: "funcs",
+ newName: "ix_funcs_form_definition_id");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_b_func_execution_logs_function_id",
+ table: "func_execution_logs",
+ newName: "ix_func_execution_logs_function_id");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_b_func_execution_logs_created_at",
+ table: "func_execution_logs",
+ newName: "ix_func_execution_logs_created_at");
+
+ // 5) 新增 LogLines 列(可空 text,已有行落 NULL,无数据损失)
+ migrationBuilder.AddColumn(
+ name: "log_lines",
+ table: "funcs",
+ type: "text",
+ nullable: true,
+ comment: "测试运行期间 Logger 收集的输出日志");
+
+ migrationBuilder.AddColumn(
+ name: "log_lines",
+ table: "func_execution_logs",
+ type: "text",
+ nullable: true,
+ comment: "执行期间 Logger 收集的输出日志");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // 回滚:移除 log_lines 列并还原表名/主键/索引
+ migrationBuilder.DropColumn(
+ name: "log_lines",
+ table: "func_execution_logs");
+
+ migrationBuilder.DropColumn(
+ name: "log_lines",
+ table: "funcs");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_func_execution_logs_created_at",
+ table: "func_execution_logs",
+ newName: "ix_b_func_execution_logs_created_at");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_func_execution_logs_function_id",
+ table: "func_execution_logs",
+ newName: "ix_b_func_execution_logs_function_id");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_funcs_form_definition_id",
+ table: "funcs",
+ newName: "ix_b_funcs_form_definition_id");
+
+ migrationBuilder.RenameIndex(
+ name: "ix_funcs_name",
+ table: "funcs",
+ newName: "ix_b_funcs_name");
+
+ migrationBuilder.DropPrimaryKey(
+ name: "pk_funcs",
+ table: "funcs");
+
+ migrationBuilder.DropPrimaryKey(
+ name: "pk_func_execution_logs",
+ table: "func_execution_logs");
+
+ migrationBuilder.RenameTable(
+ name: "func_execution_logs",
+ newName: "b_func_execution_logs");
+
+ migrationBuilder.RenameTable(
+ name: "funcs",
+ newName: "b_funcs");
+
+ migrationBuilder.AddPrimaryKey(
+ name: "pk_b_funcs",
+ table: "b_funcs",
+ column: "id");
+
+ migrationBuilder.AddPrimaryKey(
+ name: "pk_b_func_execution_logs",
+ table: "b_func_execution_logs",
+ column: "id");
+ }
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Migrations/ScriptingDbContextModelSnapshot.cs b/src/modules/scripting/Infrastructure/Migrations/ScriptingDbContextModelSnapshot.cs
new file mode 100644
index 0000000..7b6a38c
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Migrations/ScriptingDbContextModelSnapshot.cs
@@ -0,0 +1,233 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Scripting.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace Scripting.Infrastructure.Migrations
+{
+ [DbContext(typeof(ScriptingDbContext))]
+ partial class ScriptingDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Scripting.Domain.Entities.ScriptExecutionLog", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasComment("主键ID");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasComment("创建时间");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("created_by")
+ .HasComment("创建人ID");
+
+ b.Property("DurationMs")
+ .HasColumnType("bigint")
+ .HasColumnName("duration_ms")
+ .HasComment("执行耗时(毫秒)");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(4000)
+ .HasColumnType("character varying(4000)")
+ .HasColumnName("error_message")
+ .HasComment("错误信息");
+
+ b.Property("FunctionId")
+ .HasColumnType("uuid")
+ .HasColumnName("function_id")
+ .HasComment("函数ID");
+
+ b.Property("FunctionName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("function_name")
+ .HasComment("函数英文标识");
+
+ b.Property("InputJson")
+ .IsRequired()
+ .HasColumnType("jsonb")
+ .HasColumnName("input_json")
+ .HasComment("执行输入JSON");
+
+ b.Property("LogLines")
+ .HasColumnType("text")
+ .HasColumnName("log_lines")
+ .HasComment("执行期间 Logger 收集的输出日志");
+
+ b.Property("ResultJson")
+ .HasColumnType("jsonb")
+ .HasColumnName("result_json")
+ .HasComment("执行结果JSON");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status")
+ .HasComment("执行状态");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasComment("更新时间");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("updated_by")
+ .HasComment("更新人ID");
+
+ b.HasKey("Id")
+ .HasName("pk_func_execution_logs");
+
+ b.HasIndex("CreatedAt")
+ .HasDatabaseName("ix_func_execution_logs_created_at");
+
+ b.HasIndex("FunctionId")
+ .HasDatabaseName("ix_func_execution_logs_function_id");
+
+ b.ToTable("func_execution_logs", (string)null);
+ });
+
+ modelBuilder.Entity("Scripting.Domain.Entities.ScriptFunction", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid")
+ .HasColumnName("id")
+ .HasComment("主键ID");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("text")
+ .HasColumnName("code")
+ .HasComment("C# 脚本代码");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at")
+ .HasComment("创建时间");
+
+ b.Property("CreatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("created_by")
+ .HasComment("创建人ID");
+
+ b.Property("Description")
+ .HasMaxLength(1000)
+ .HasColumnType("character varying(1000)")
+ .HasColumnName("description")
+ .HasComment("函数说明");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("display_name")
+ .HasComment("函数显示名称");
+
+ b.Property("FormDefinitionId")
+ .HasColumnType("uuid")
+ .HasColumnName("form_definition_id")
+ .HasComment("绑定的自定义表单定义ID,为空表示全局可复用函数");
+
+ b.Property("InputSchema")
+ .HasColumnType("jsonb")
+ .HasColumnName("input_schema")
+ .HasComment("函数输入 Schema JSON");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted")
+ .HasComment("是否软删除");
+
+ b.Property("LogLines")
+ .HasColumnType("text")
+ .HasColumnName("log_lines")
+ .HasComment("测试运行期间 Logger 收集的输出日志");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)")
+ .HasColumnName("name")
+ .HasComment("函数英文标识");
+
+ b.Property("OperatorIP")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)")
+ .HasColumnName("operator_ip")
+ .HasComment("操作人IP地址");
+
+ b.Property("OutputSchema")
+ .HasColumnType("jsonb")
+ .HasColumnName("output_schema")
+ .HasComment("函数输出 Schema JSON");
+
+ b.Property("Status")
+ .HasColumnType("integer")
+ .HasColumnName("status")
+ .HasComment("函数状态");
+
+ b.Property("TimeoutMs")
+ .HasColumnType("integer")
+ .HasColumnName("timeout_ms")
+ .HasComment("执行超时时间(毫秒)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("updated_at")
+ .HasComment("更新时间");
+
+ b.Property("UpdatedBy")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)")
+ .HasColumnName("updated_by")
+ .HasComment("更新人ID");
+
+ b.Property("Version")
+ .HasColumnType("integer")
+ .HasColumnName("version")
+ .HasComment("函数版本");
+
+ b.HasKey("Id")
+ .HasName("pk_funcs");
+
+ b.HasIndex("FormDefinitionId")
+ .HasDatabaseName("ix_funcs_form_definition_id");
+
+ b.HasIndex("Name")
+ .IsUnique()
+ .HasDatabaseName("ix_funcs_name");
+
+ b.ToTable("funcs", (string)null);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptExecutionLogConfiguration.cs b/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptExecutionLogConfiguration.cs
new file mode 100644
index 0000000..2ef1375
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptExecutionLogConfiguration.cs
@@ -0,0 +1,30 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Scripting.Domain.Entities;
+
+namespace Scripting.Infrastructure.Persistence.Configurations;
+
+/// 脚本执行日志 EF 配置,用于审计脚本运行结果。
+public class ScriptExecutionLogConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("func_execution_logs");
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).ValueGeneratedNever().HasComment("主键ID");
+ builder.Property(e => e.FunctionId).HasComment("函数ID");
+ builder.Property(e => e.FunctionName).HasMaxLength(200).IsRequired().HasComment("函数英文标识");
+ builder.Property(e => e.Status).HasComment("执行状态");
+ builder.Property(e => e.InputJson).HasColumnType("jsonb").IsRequired().HasComment("执行输入JSON");
+ builder.Property(e => e.ResultJson).HasColumnType("jsonb").HasComment("执行结果JSON");
+ builder.Property(e => e.ErrorMessage).HasMaxLength(4000).HasComment("错误信息");
+ builder.Property(e => e.DurationMs).HasComment("执行耗时(毫秒)");
+ builder.Property(e => e.LogLines).HasColumnType("text").HasComment("执行期间 Logger 收集的输出日志");
+ builder.Property(e => e.CreatedBy).HasMaxLength(100).HasComment("创建人ID");
+ builder.Property(e => e.CreatedAt).HasComment("创建时间");
+ builder.Property(e => e.UpdatedBy).HasMaxLength(100).HasComment("更新人ID");
+ builder.Property(e => e.UpdatedAt).HasComment("更新时间");
+ builder.HasIndex(e => e.FunctionId);
+ builder.HasIndex(e => e.CreatedAt);
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptFunctionConfiguration.cs b/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptFunctionConfiguration.cs
new file mode 100644
index 0000000..9276c00
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Persistence/Configurations/ScriptFunctionConfiguration.cs
@@ -0,0 +1,35 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Scripting.Domain.Entities;
+
+namespace Scripting.Infrastructure.Persistence.Configurations;
+
+/// 脚本函数 EF 配置,函数代码和输入输出 Schema 均由后端受控执行。
+public class ScriptFunctionConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("funcs");
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).ValueGeneratedNever().HasComment("主键ID");
+ builder.Property(e => e.FormDefinitionId).HasComment("绑定的自定义表单定义ID,为空表示全局可复用函数");
+ builder.Property(e => e.Name).HasMaxLength(200).IsRequired().HasComment("函数英文标识");
+ builder.Property(e => e.DisplayName).HasMaxLength(200).IsRequired().HasComment("函数显示名称");
+ builder.Property(e => e.Description).HasMaxLength(1000).HasComment("函数说明");
+ builder.Property(e => e.Code).HasColumnType("text").HasComment("C# 脚本代码");
+ builder.Property(e => e.InputSchema).HasColumnType("jsonb").HasComment("函数输入 Schema JSON");
+ builder.Property(e => e.OutputSchema).HasColumnType("jsonb").HasComment("函数输出 Schema JSON");
+ builder.Property(e => e.Status).HasComment("函数状态");
+ builder.Property(e => e.Version).HasComment("函数版本");
+ builder.Property(e => e.TimeoutMs).HasComment("执行超时时间(毫秒)");
+ builder.Property(e => e.LogLines).HasColumnType("text").HasComment("测试运行期间 Logger 收集的输出日志");
+ builder.Property(e => e.CreatedBy).HasMaxLength(100).HasComment("创建人ID");
+ builder.Property(e => e.CreatedAt).HasComment("创建时间");
+ builder.Property(e => e.UpdatedBy).HasMaxLength(100).HasComment("更新人ID");
+ builder.Property(e => e.UpdatedAt).HasComment("更新时间");
+ builder.Property(e => e.IsDeleted).HasDefaultValue(false).HasComment("是否软删除");
+ builder.Property(e => e.OperatorIP).HasMaxLength(500).HasComment("操作人IP地址");
+ builder.HasIndex(e => e.FormDefinitionId);
+ builder.HasIndex(e => e.Name).IsUnique();
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContext.cs b/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContext.cs
new file mode 100644
index 0000000..f444af5
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContext.cs
@@ -0,0 +1,63 @@
+using System.Linq.Expressions;
+using BuildFlow.Shared.Domain.Common;
+using BuildFlow.Shared.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Scripting.Domain.Entities;
+
+namespace Scripting.Infrastructure.Persistence;
+
+///
+/// Scripting 持久化上下文。承载脚本函数定义与执行日志。
+/// 注册 AuditInterceptor 自动维护审计字段;对实现 ISoftDelete 的实体统一生成全局查询过滤器,
+/// 自动排除已软删除记录。约定与 WorkflowDbContext 保持一致。
+///
+public class ScriptingDbContext : DbContext
+{
+ private readonly ICurrentUserContext? _currentUserContext;
+
+ public DbSet Scripts => Set();
+ public DbSet ScriptExecutionLogs => Set();
+
+ public ScriptingDbContext(DbContextOptions options) : base(options) { }
+
+ public ScriptingDbContext(DbContextOptions options, ICurrentUserContext currentUserContext)
+ : base(options)
+ {
+ _currentUserContext = currentUserContext;
+ }
+
+ // 注意:此处通过 OnConfiguring 注册拦截器(而非 DI AddInterceptors),与 rag-backend 约定一致,
+ // 保证无参构造(如设计时工厂、测试夹具)也能正常工作。
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (_currentUserContext is not null)
+ {
+ optionsBuilder.AddInterceptors(new AuditInterceptor(_currentUserContext));
+ }
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ // 自动发现本程序集内所有 IEntityTypeConfiguration 实现
+ modelBuilder.ApplyConfigurationsFromAssembly(typeof(ScriptingDbContext).Assembly);
+
+ // 为所有实现 ISoftDelete 的实体动态生成 HasQueryFilter(e => !e.IsDeleted)
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
+ {
+ if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
+ {
+ modelBuilder.Entity(entityType.ClrType).HasQueryFilter(
+ CreateSoftDeleteFilter(entityType.ClrType));
+ }
+ }
+ }
+
+ /// 用表达式树动态构造 e => !e.IsDeleted,避免对每个实体类型手写泛型过滤。
+ private static LambdaExpression CreateSoftDeleteFilter(Type entityType)
+ {
+ var param = Expression.Parameter(entityType, "e");
+ var isDeletedProp = Expression.Property(param, nameof(ISoftDelete.IsDeleted));
+ var filter = Expression.Lambda(Expression.Not(isDeletedProp), param);
+ return filter;
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContextDesignTimeFactory.cs b/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContextDesignTimeFactory.cs
new file mode 100644
index 0000000..cd6255d
--- /dev/null
+++ b/src/modules/scripting/Infrastructure/Persistence/ScriptingDbContextDesignTimeFactory.cs
@@ -0,0 +1,17 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+
+namespace Scripting.Infrastructure.Persistence;
+
+/// EF Core 设计时工厂。硬编码连接串,不依赖 appsettings 路径。
+public class ScriptingDbContextDesignTimeFactory : IDesignTimeDbContextFactory
+{
+ public ScriptingDbContext CreateDbContext(string[] args)
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseNpgsql("Host=localhost;Port=15432;Database=workflow;Username=rag;Password=dev")
+ .UseSnakeCaseNamingConvention()
+ .Options;
+ return new ScriptingDbContext(options);
+ }
+}
diff --git a/src/modules/scripting/Infrastructure/ScriptingInfrastructureModule.cs b/src/modules/scripting/Infrastructure/ScriptingInfrastructureModule.cs
index 4ac3242..9310af0 100644
--- a/src/modules/scripting/Infrastructure/ScriptingInfrastructureModule.cs
+++ b/src/modules/scripting/Infrastructure/ScriptingInfrastructureModule.cs
@@ -1,11 +1,37 @@
+using BuildFlow.Shared.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Scripting.Domain;
+using Scripting.Infrastructure.Persistence;
using Volo.Abp.Modularity;
namespace Scripting.Infrastructure;
///
-/// Scripting Infrastructure 层模块。T1 骨架:仅声明对 Domain 的依赖,
-/// DbContext 等基础设施注册留待后续任务。
+/// Scripting Infrastructure 层模块。注册 ScriptingDbContext(挂载内核审计拦截器)。
+/// 过渡期 ScriptingDbContext 与 WorkflowDbContext 共用 workflow 数据库,
+/// 本任务的迁移会把 b_funcs / b_func_execution_logs 重命名为 funcs / func_execution_logs(保数据)。
///
[DependsOn(typeof(ScriptingDomainModule))]
-public class ScriptingInfrastructureModule : AbpModule;
+public class ScriptingInfrastructureModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ var services = context.Services;
+ var config = services.GetConfiguration();
+
+ // 过渡期连接串与 WorkflowDbContext 一致(同名 workflow 库),迁移完成后由后续任务切换独立库。
+ var conn = $"{config.GetConnectionString("Default")!};Database={config["DatabaseNames:Workflow"]}";
+ services.AddDbContext((sp, options) =>
+ {
+ options.UseNpgsql(conn, npgsql =>
+ {
+ npgsql.MigrationsAssembly(typeof(ScriptingDbContext).Assembly.FullName);
+ });
+ options.UseSnakeCaseNamingConvention();
+ });
+
+ services.AddScoped();
+ }
+}