feat(scripting): 迁移 Domain 实体+EF 配置+表重命名迁移

- ScriptFunction/ScriptExecutionLog 实体(从 CustomFunction 迁移)
- ScriptStatus/ScriptExecutionStatus 枚举
- ScriptingDbContext + 两个 Configuration(表名 funcs/func_execution_logs)
- EF Core 迁移:RenameTablesForScriptingModule(b_funcs→funcs 保数据)
- 新增 LogLines 字段(Logger 输出收集)

Task T2 of scripting module refactor
This commit is contained in:
xiangning 2026-07-06 17:56:40 +08:00
parent 052dd7ff4c
commit d1c8988054
12 changed files with 882 additions and 3 deletions

View File

@ -0,0 +1,24 @@
using BuildFlow.Shared.Domain.Common;
using Scripting.Domain.Enums;
namespace Scripting.Domain.Entities;
/// <summary>脚本执行日志,记录输入、输出、耗时和异常,便于审计脚本运行结果。</summary>
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; }
/// <summary>执行期间由 Logger 收集的输出日志(多行文本)。</summary>
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;
}

View File

@ -0,0 +1,51 @@
using BuildFlow.Shared.Domain.Common;
using Scripting.Domain.Enums;
namespace Scripting.Domain.Entities;
/// <summary>
/// 脚本函数定义。Name 为英文标识,Code 保存受控 C# 脚本内容,由 Roslyn 编译执行。
/// 按钮通过 runFunction/executeFunctions 引用。本模块仅承载 C# 脚本类型(不含 HTTP 请求函数)。
/// </summary>
public class ScriptFunction : BaseEntity, IFullAudit
{
/// <summary>绑定的自定义表单定义 ID。为空表示全局可复用函数。</summary>
public Guid? FormDefinitionId { get; set; }
/// <summary>函数英文标识,供按钮动作链稳定引用。</summary>
public string Name { get; set; } = string.Empty;
/// <summary>函数显示名称。</summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>函数说明。</summary>
public string? Description { get; set; }
/// <summary>受控 C# 脚本代码。</summary>
public string Code { get; set; } = string.Empty;
/// <summary>函数输入 Schema JSON。</summary>
public string? InputSchema { get; set; }
/// <summary>函数输出 Schema JSON。</summary>
public string? OutputSchema { get; set; }
/// <summary>函数发布状态。</summary>
public ScriptStatus Status { get; set; } = ScriptStatus.Draft;
/// <summary>函数版本号。</summary>
public int Version { get; set; } = 1;
/// <summary>执行超时时间,单位毫秒。</summary>
public int TimeoutMs { get; set; }
/// <summary>测试运行期间由 Logger 收集的输出日志(多行文本)。</summary>
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";
}

View File

@ -0,0 +1,9 @@
namespace Scripting.Domain.Enums;
/// <summary>脚本执行结果状态。</summary>
public enum ScriptExecutionStatus
{
Success = 0,
Failed = 1,
Timeout = 2,
}

View File

@ -0,0 +1,9 @@
namespace Scripting.Domain.Enums;
/// <summary>脚本函数生命周期:Draft 草稿、Published 可运行、Disabled 停用阻断。</summary>
public enum ScriptStatus
{
Draft = 0,
Published = 1,
Disabled = 2,
}

View File

@ -0,0 +1,236 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id")
.HasComment("主键ID");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasComment("创建时间");
b.Property<string>("CreatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("created_by")
.HasComment("创建人ID");
b.Property<long>("DurationMs")
.HasColumnType("bigint")
.HasColumnName("duration_ms")
.HasComment("执行耗时(毫秒)");
b.Property<string>("ErrorMessage")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("error_message")
.HasComment("错误信息");
b.Property<Guid>("FunctionId")
.HasColumnType("uuid")
.HasColumnName("function_id")
.HasComment("函数ID");
b.Property<string>("FunctionName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("function_name")
.HasComment("函数英文标识");
b.Property<string>("InputJson")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("input_json")
.HasComment("执行输入JSON");
b.Property<string>("LogLines")
.HasColumnType("text")
.HasColumnName("log_lines")
.HasComment("执行期间 Logger 收集的输出日志");
b.Property<string>("ResultJson")
.HasColumnType("jsonb")
.HasColumnName("result_json")
.HasComment("执行结果JSON");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status")
.HasComment("执行状态");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasComment("更新时间");
b.Property<string>("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<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id")
.HasComment("主键ID");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text")
.HasColumnName("code")
.HasComment("C# 脚本代码");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasComment("创建时间");
b.Property<string>("CreatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("created_by")
.HasComment("创建人ID");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description")
.HasComment("函数说明");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("display_name")
.HasComment("函数显示名称");
b.Property<Guid?>("FormDefinitionId")
.HasColumnType("uuid")
.HasColumnName("form_definition_id")
.HasComment("绑定的自定义表单定义ID,为空表示全局可复用函数");
b.Property<string>("InputSchema")
.HasColumnType("jsonb")
.HasColumnName("input_schema")
.HasComment("函数输入 Schema JSON");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted")
.HasComment("是否软删除");
b.Property<string>("LogLines")
.HasColumnType("text")
.HasColumnName("log_lines")
.HasComment("测试运行期间 Logger 收集的输出日志");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name")
.HasComment("函数英文标识");
b.Property<string>("OperatorIP")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("operator_ip")
.HasComment("操作人IP地址");
b.Property<string>("OutputSchema")
.HasColumnType("jsonb")
.HasColumnName("output_schema")
.HasComment("函数输出 Schema JSON");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status")
.HasComment("函数状态");
b.Property<int>("TimeoutMs")
.HasColumnType("integer")
.HasColumnName("timeout_ms")
.HasComment("执行超时时间(毫秒)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasComment("更新时间");
b.Property<string>("UpdatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("updated_by")
.HasComment("更新人ID");
b.Property<int>("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
}
}
}

View File

@ -0,0 +1,146 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Scripting.Infrastructure.Migrations
{
/// <inheritdoc />
/// <summary>
/// 数据保留式迁移:将 workflow 模块下的 b_funcs / b_func_execution_logs 表连同主键、索引
/// 重命名为 funcs / func_execution_logs,并新增 log_lines 列(可空,已有行落 NULL)。
/// 不进行 Drop/Create,避免数据丢失。Scripting 模块从 CustomFunction 拆分而来(见 spec §11 YAGNI)。
/// 主键/索引沿用 pk_&lt;table&gt; / ix_&lt;table&gt;_&lt;col&gt; 命名约定,操作顺序对齐
/// 既有的 RenameTablesToBPrefix 迁移。
/// </summary>
public partial class RenameTablesForScriptingModule : Migration
{
/// <inheritdoc />
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_<table>_<col> 命名约定)
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<string>(
name: "log_lines",
table: "funcs",
type: "text",
nullable: true,
comment: "测试运行期间 Logger 收集的输出日志");
migrationBuilder.AddColumn<string>(
name: "log_lines",
table: "func_execution_logs",
type: "text",
nullable: true,
comment: "执行期间 Logger 收集的输出日志");
}
/// <inheritdoc />
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");
}
}
}

View File

@ -0,0 +1,233 @@
// <auto-generated />
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<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id")
.HasComment("主键ID");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasComment("创建时间");
b.Property<string>("CreatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("created_by")
.HasComment("创建人ID");
b.Property<long>("DurationMs")
.HasColumnType("bigint")
.HasColumnName("duration_ms")
.HasComment("执行耗时(毫秒)");
b.Property<string>("ErrorMessage")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("error_message")
.HasComment("错误信息");
b.Property<Guid>("FunctionId")
.HasColumnType("uuid")
.HasColumnName("function_id")
.HasComment("函数ID");
b.Property<string>("FunctionName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("function_name")
.HasComment("函数英文标识");
b.Property<string>("InputJson")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("input_json")
.HasComment("执行输入JSON");
b.Property<string>("LogLines")
.HasColumnType("text")
.HasColumnName("log_lines")
.HasComment("执行期间 Logger 收集的输出日志");
b.Property<string>("ResultJson")
.HasColumnType("jsonb")
.HasColumnName("result_json")
.HasComment("执行结果JSON");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status")
.HasComment("执行状态");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasComment("更新时间");
b.Property<string>("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<Guid>("Id")
.HasColumnType("uuid")
.HasColumnName("id")
.HasComment("主键ID");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("text")
.HasColumnName("code")
.HasComment("C# 脚本代码");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasComment("创建时间");
b.Property<string>("CreatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("created_by")
.HasComment("创建人ID");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description")
.HasComment("函数说明");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("display_name")
.HasComment("函数显示名称");
b.Property<Guid?>("FormDefinitionId")
.HasColumnType("uuid")
.HasColumnName("form_definition_id")
.HasComment("绑定的自定义表单定义ID,为空表示全局可复用函数");
b.Property<string>("InputSchema")
.HasColumnType("jsonb")
.HasColumnName("input_schema")
.HasComment("函数输入 Schema JSON");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(false)
.HasColumnName("is_deleted")
.HasComment("是否软删除");
b.Property<string>("LogLines")
.HasColumnType("text")
.HasColumnName("log_lines")
.HasComment("测试运行期间 Logger 收集的输出日志");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name")
.HasComment("函数英文标识");
b.Property<string>("OperatorIP")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasColumnName("operator_ip")
.HasComment("操作人IP地址");
b.Property<string>("OutputSchema")
.HasColumnType("jsonb")
.HasColumnName("output_schema")
.HasComment("函数输出 Schema JSON");
b.Property<int>("Status")
.HasColumnType("integer")
.HasColumnName("status")
.HasComment("函数状态");
b.Property<int>("TimeoutMs")
.HasColumnType("integer")
.HasColumnName("timeout_ms")
.HasComment("执行超时时间(毫秒)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasComment("更新时间");
b.Property<string>("UpdatedBy")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("updated_by")
.HasComment("更新人ID");
b.Property<int>("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
}
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Scripting.Domain.Entities;
namespace Scripting.Infrastructure.Persistence.Configurations;
/// <summary>脚本执行日志 EF 配置,用于审计脚本运行结果。</summary>
public class ScriptExecutionLogConfiguration : IEntityTypeConfiguration<ScriptExecutionLog>
{
public void Configure(EntityTypeBuilder<ScriptExecutionLog> 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);
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Scripting.Domain.Entities;
namespace Scripting.Infrastructure.Persistence.Configurations;
/// <summary>脚本函数 EF 配置,函数代码和输入输出 Schema 均由后端受控执行。</summary>
public class ScriptFunctionConfiguration : IEntityTypeConfiguration<ScriptFunction>
{
public void Configure(EntityTypeBuilder<ScriptFunction> 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();
}
}

View File

@ -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;
/// <summary>
/// Scripting 持久化上下文。承载脚本函数定义与执行日志。
/// 注册 AuditInterceptor 自动维护审计字段;对实现 ISoftDelete 的实体统一生成全局查询过滤器,
/// 自动排除已软删除记录。约定与 WorkflowDbContext 保持一致。
/// </summary>
public class ScriptingDbContext : DbContext
{
private readonly ICurrentUserContext? _currentUserContext;
public DbSet<ScriptFunction> Scripts => Set<ScriptFunction>();
public DbSet<ScriptExecutionLog> ScriptExecutionLogs => Set<ScriptExecutionLog>();
public ScriptingDbContext(DbContextOptions<ScriptingDbContext> options) : base(options) { }
public ScriptingDbContext(DbContextOptions<ScriptingDbContext> 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));
}
}
}
/// <summary>用表达式树动态构造 e => !e.IsDeleted,避免对每个实体类型手写泛型过滤。</summary>
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;
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace Scripting.Infrastructure.Persistence;
/// <summary>EF Core 设计时工厂。硬编码连接串,不依赖 appsettings 路径。</summary>
public class ScriptingDbContextDesignTimeFactory : IDesignTimeDbContextFactory<ScriptingDbContext>
{
public ScriptingDbContext CreateDbContext(string[] args)
{
var options = new DbContextOptionsBuilder<ScriptingDbContext>()
.UseNpgsql("Host=localhost;Port=15432;Database=workflow;Username=rag;Password=dev")
.UseSnakeCaseNamingConvention()
.Options;
return new ScriptingDbContext(options);
}
}

View File

@ -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;
/// <summary>
/// Scripting Infrastructure 层模块。T1 骨架:仅声明对 Domain 的依赖,
/// DbContext 等基础设施注册留待后续任务。
/// Scripting Infrastructure 层模块。注册 ScriptingDbContext(挂载内核审计拦截器)。
/// 过渡期 ScriptingDbContext 与 WorkflowDbContext 共用 workflow 数据库,
/// 本任务的迁移会把 b_funcs / b_func_execution_logs 重命名为 funcs / func_execution_logs(保数据)。
/// </summary>
[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<ScriptingDbContext>((sp, options) =>
{
options.UseNpgsql(conn, npgsql =>
{
npgsql.MigrationsAssembly(typeof(ScriptingDbContext).Assembly.FullName);
});
options.UseSnakeCaseNamingConvention();
});
services.AddScoped<AuditInterceptor>();
}
}