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:
parent
052dd7ff4c
commit
d1c8988054
24
src/modules/scripting/Domain/Entities/ScriptExecutionLog.cs
Normal file
24
src/modules/scripting/Domain/Entities/ScriptExecutionLog.cs
Normal 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;
|
||||||
|
}
|
||||||
51
src/modules/scripting/Domain/Entities/ScriptFunction.cs
Normal file
51
src/modules/scripting/Domain/Entities/ScriptFunction.cs
Normal 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";
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
namespace Scripting.Domain.Enums;
|
||||||
|
|
||||||
|
/// <summary>脚本执行结果状态。</summary>
|
||||||
|
public enum ScriptExecutionStatus
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
Failed = 1,
|
||||||
|
Timeout = 2,
|
||||||
|
}
|
||||||
9
src/modules/scripting/Domain/Enums/ScriptStatus.cs
Normal file
9
src/modules/scripting/Domain/Enums/ScriptStatus.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Scripting.Domain.Enums;
|
||||||
|
|
||||||
|
/// <summary>脚本函数生命周期:Draft 草稿、Published 可运行、Disabled 停用阻断。</summary>
|
||||||
|
public enum ScriptStatus
|
||||||
|
{
|
||||||
|
Draft = 0,
|
||||||
|
Published = 1,
|
||||||
|
Disabled = 2,
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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_<table> / ix_<table>_<col> 命名约定,操作顺序对齐
|
||||||
|
/// 既有的 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,37 @@
|
|||||||
|
using BuildFlow.Shared.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Scripting.Domain;
|
using Scripting.Domain;
|
||||||
|
using Scripting.Infrastructure.Persistence;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
|
|
||||||
namespace Scripting.Infrastructure;
|
namespace Scripting.Infrastructure;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scripting Infrastructure 层模块。T1 骨架:仅声明对 Domain 的依赖,
|
/// Scripting Infrastructure 层模块。注册 ScriptingDbContext(挂载内核审计拦截器)。
|
||||||
/// DbContext 等基础设施注册留待后续任务。
|
/// 过渡期 ScriptingDbContext 与 WorkflowDbContext 共用 workflow 数据库,
|
||||||
|
/// 本任务的迁移会把 b_funcs / b_func_execution_logs 重命名为 funcs / func_execution_logs(保数据)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DependsOn(typeof(ScriptingDomainModule))]
|
[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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user