refactor(monitoring): 重构监控模块数据库表名和查询处理
- 将监控模块表前缀从 mon_ 改为 b_ - 为工作流审批任务处理器添加流程引擎图加载逻辑 - 移除外部系统相关DTO和查询类的直接引用改为查询对象模式 - 为脚本执行服务添加模块程序集诊断日志 - 为分享表单提交功能增加密码验证支持 - 集成脚本模块数据库迁移支持 - 更新通知服务测试以兼容IOptionsMonitor接口变化
This commit is contained in:
parent
ec06a95bc5
commit
5332ff3867
@ -6,6 +6,7 @@ using IM.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using MongoDB.Entities;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
using Scripting.Infrastructure.Persistence;
|
||||
using Workflow.Infrastructure.Persistence;
|
||||
using PlatformSeedData = BuildFlow.Platform.Infrastructure.Persistence.SeedData;
|
||||
using WorkflowSeedData = Workflow.Infrastructure.Persistence.SeedData;
|
||||
@ -36,6 +37,9 @@ public static class DatabaseInitializer
|
||||
await MigrateAsync<ImDbContext>(sp, logger);
|
||||
await MigrateAsync<WorkflowDbContext>(sp, logger, suppressPendingChanges: true);
|
||||
|
||||
// Scripting 与 Workflow 共用 workflow 库,迁移创建 funcs / func_execution_logs 表
|
||||
await MigrateAsync<ScriptingDbContext>(sp, logger, suppressPendingChanges: true);
|
||||
|
||||
await MigrateAsync<MonitoringDbContext>(sp, logger);
|
||||
|
||||
// IM 列补丁:AddPinAndPinnedAtColumns 迁移因 ImDbContext Snapshot 与实体不匹配
|
||||
|
||||
@ -23,13 +23,3 @@ public class CreateExternalSystemEndpoint(IMediator mediator)
|
||||
await Send.ResponseAsync(result, 201, ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>创建外部系统请求体。</summary>
|
||||
public class CreateExternalSystemRequest
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
public int SystemType { get; set; }
|
||||
public string BaseUrl { get; set; } = default!;
|
||||
public int PollIntervalSeconds { get; set; }
|
||||
public string? ParametersJson { get; set; }
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>创建外部系统请求体。</summary>
|
||||
public class CreateExternalSystemRequest
|
||||
{
|
||||
public string Name { get; set; } = default!;
|
||||
public int SystemType { get; set; }
|
||||
public string BaseUrl { get; set; } = default!;
|
||||
public int PollIntervalSeconds { get; set; }
|
||||
public string? ParametersJson { get; set; }
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
using Monitoring.Application.ExternalSystems.Queries;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
@ -20,5 +20,3 @@ public class GetExternalSystemDetailEndpoint(IMediator mediator)
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record GetExternalSystemDetailRequest(Guid Id);
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>查询外部系统详情请求。Id 由路由 {id} 绑定。</summary>
|
||||
public sealed record GetExternalSystemDetailRequest(Guid Id);
|
||||
@ -1,6 +1,6 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Reports;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
using Monitoring.Application.Reports.Queries;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
@ -21,10 +21,3 @@ public class GetSummaryEndpoint(IMediator mediator) : Endpoint<GetSummaryRequest
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public class GetSummaryRequest
|
||||
{
|
||||
public Guid SystemId { get; set; }
|
||||
[QueryParam]
|
||||
public int RecentJobs { get; set; } = 10;
|
||||
}
|
||||
|
||||
11
src/BuildFlow.Host/Endpoints/Monitoring/GetSummaryRequest.cs
Normal file
11
src/BuildFlow.Host/Endpoints/Monitoring/GetSummaryRequest.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>同步汇总请求。SystemId 由路由 {systemId} 绑定。</summary>
|
||||
public class GetSummaryRequest
|
||||
{
|
||||
public Guid SystemId { get; set; }
|
||||
|
||||
/// <summary>要返回的最近任务条数,默认 10。</summary>
|
||||
[QueryParam]
|
||||
public int RecentJobs { get; set; } = 10;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Reports;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
using Monitoring.Application.Reports.Queries;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
@ -21,10 +21,3 @@ public class GetTrendEndpoint(IMediator mediator)
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public class GetTrendRequest
|
||||
{
|
||||
public Guid SystemId { get; set; }
|
||||
[QueryParam]
|
||||
public int Days { get; set; } = 14;
|
||||
}
|
||||
|
||||
11
src/BuildFlow.Host/Endpoints/Monitoring/GetTrendRequest.cs
Normal file
11
src/BuildFlow.Host/Endpoints/Monitoring/GetTrendRequest.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>按天趋势请求。SystemId 由路由 {systemId} 绑定。</summary>
|
||||
public class GetTrendRequest
|
||||
{
|
||||
public Guid SystemId { get; set; }
|
||||
|
||||
/// <summary>最近 N 天,默认 14。</summary>
|
||||
[QueryParam]
|
||||
public int Days { get; set; } = 14;
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
using Monitoring.Application.ExternalSystems.Queries;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>外部系统列表。GET /monitoring/systems。</summary>
|
||||
public class ListExternalSystemsEndpoint(MonitoringDbContext db) : EndpointWithoutRequest<List<ExternalSystemDto>>
|
||||
public class ListExternalSystemsEndpoint(IMediator mediator)
|
||||
: EndpointWithoutRequest<IReadOnlyList<ExternalSystemDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
@ -16,21 +16,7 @@ public class ListExternalSystemsEndpoint(MonitoringDbContext db) : EndpointWitho
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
// json_data 在表达式树里翻译不了,先物化实体再在内存映射 DTO(外部系统数量很少,无需担心性能)
|
||||
var systems = await db.ExternalSystems
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
var dtos = systems.Select(s =>
|
||||
{
|
||||
var d = s.Data;
|
||||
return new ExternalSystemDto(
|
||||
s.Id, d.Name, (int)d.SystemType, d.SystemType.ToString(),
|
||||
d.IsEnabled, d.BaseUrl, d.PollIntervalSeconds,
|
||||
JsonSerializer.Serialize(d.Parameters),
|
||||
d.LastSyncedAt, d.LastSyncJobId);
|
||||
}).ToList();
|
||||
|
||||
await Send.OkAsync(dtos, ct);
|
||||
var result = await mediator.Send(new ListExternalSystemsQuery(), ct);
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems;
|
||||
using Monitoring.Application.ExternalSystems.Commands;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
@ -21,10 +22,3 @@ public class LoginExternalSystemEndpoint(IMediator mediator)
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record LoginExternalSystemRequest(
|
||||
Guid Id,
|
||||
string Account,
|
||||
string Password,
|
||||
string Ticket,
|
||||
string Randstr);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>登录外部系统请求。Id 由路由 {id} 绑定。</summary>
|
||||
public sealed record LoginExternalSystemRequest(
|
||||
Guid Id,
|
||||
string Account,
|
||||
string Password,
|
||||
string Ticket,
|
||||
string Randstr);
|
||||
@ -1,5 +1,5 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Sync;
|
||||
using Monitoring.Application.Sync.Commands;
|
||||
using Monitoring.Application.Sync.DTOs;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
@ -19,6 +19,3 @@ public class TriggerSyncEndpoint(IMediator mediator) : Endpoint<TriggerSyncReque
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>触发同步请求。SystemId 由路由 {systemId} 绑定(与 PublishScriptRequest 同构)。</summary>
|
||||
public sealed record TriggerSyncRequest(Guid SystemId);
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>触发同步请求。SystemId 由路由 {systemId} 绑定。</summary>
|
||||
public sealed record TriggerSyncRequest(Guid SystemId);
|
||||
@ -24,12 +24,3 @@ public class UpdateExternalSystemEndpoint(IMediator mediator)
|
||||
await Send.OkAsync(result, ct);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record UpdateExternalSystemRequest(
|
||||
Guid Id,
|
||||
string? Name,
|
||||
string? BaseUrl,
|
||||
int? PollIntervalSeconds,
|
||||
bool? IsEnabled,
|
||||
string? ParametersJson,
|
||||
List<CredentialInput>? Credentials);
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
namespace BuildFlow.Host.Endpoints.Monitoring;
|
||||
|
||||
/// <summary>更新外部系统请求。Id 由路由 {id} 绑定,其余字段可选(null 表示不改)。</summary>
|
||||
public sealed record UpdateExternalSystemRequest(
|
||||
Guid Id,
|
||||
string? Name,
|
||||
string? BaseUrl,
|
||||
int? PollIntervalSeconds,
|
||||
bool? IsEnabled,
|
||||
string? ParametersJson,
|
||||
List<CredentialInput>? Credentials);
|
||||
@ -18,8 +18,10 @@ public class SubmitSharedFormEndpoint(IMediator mediator)
|
||||
|
||||
public override async Task HandleAsync(SubmitSharedFormRequest req, CancellationToken ct)
|
||||
{
|
||||
// 密码优先取 body,兜底取 query(前端 form-share.vue 通过 query 传 password)
|
||||
var password = req.Password ?? Query<string?>("password", isRequired: false);
|
||||
var formDataId = await mediator.Send(
|
||||
new SubmitSharedFormCommand(req.Token, req.DataJson),
|
||||
new SubmitSharedFormCommand(req.Token, req.DataJson, password),
|
||||
ct);
|
||||
await Send.ResponseAsync(new SubmitSharedFormResponse(formDataId), 201, ct);
|
||||
}
|
||||
|
||||
@ -6,4 +6,6 @@ public sealed class SubmitSharedFormRequest
|
||||
public string Token { get; init; } = default!;
|
||||
/// <summary>表单数据 JSON 字符串。</summary>
|
||||
public string DataJson { get; init; } = default!;
|
||||
/// <summary>分享密码(分享设置了密码时必传)。通过 query 的 password 或 body 均可。</summary>
|
||||
public string? Password { get; init; }
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
namespace Monitoring.Application.ExternalSystems;
|
||||
namespace Monitoring.Application.ExternalSystems.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 登录外部系统命令。用账号密码 + 腾讯验证码票据调目标系统登录,
|
||||
@ -17,9 +18,3 @@ public record LoginExternalSystemCommand(
|
||||
string Password,
|
||||
string Ticket,
|
||||
string Randstr) : IRequest<LoginResult>;
|
||||
|
||||
/// <summary>登录结果。</summary>
|
||||
/// <param name="Success">是否成功。</param>
|
||||
/// <param name="Message">提示信息。</param>
|
||||
/// <param name="CredentialCount">获取到的凭证数量。</param>
|
||||
public record LoginResult(bool Success, string? Message, int CredentialCount);
|
||||
@ -22,6 +22,3 @@ public record UpdateExternalSystemCommand(
|
||||
bool? IsEnabled,
|
||||
string? ParametersJson,
|
||||
List<CredentialInput>? Credentials) : IRequest<ExternalSystemDto>;
|
||||
|
||||
/// <summary>凭证输入(前端传入,写入 json_data.Credentials)。</summary>
|
||||
public record CredentialInput(string Type, string Key, string Value, bool IsEnabled, string? Remark);
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
namespace Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
/// <summary>凭证输入(前端传入,写入 json_data.Credentials)。</summary>
|
||||
public record CredentialInput(string Type, string Key, string Value, bool IsEnabled, string? Remark);
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
/// <summary>登录结果。</summary>
|
||||
/// <param name="Success">是否成功。</param>
|
||||
/// <param name="Message">提示信息。</param>
|
||||
/// <param name="CredentialCount">获取到的凭证数量。</param>
|
||||
public record LoginResult(bool Success, string? Message, int CredentialCount);
|
||||
@ -1,18 +1,20 @@
|
||||
using System.Text.Json;
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using BuildFlow.Shared.Domain.Errors;
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
using Monitoring.Application.ExternalSystems.Queries;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
namespace Monitoring.Application.ExternalSystems;
|
||||
namespace Monitoring.Application.ExternalSystems.Handlers;
|
||||
|
||||
/// <summary>外部系统详情查询处理器(含凭证,供前端编辑回填)。</summary>
|
||||
public class GetExternalSystemDetailQueryHandler(MonitoringDbContext db)
|
||||
: IRequestHandler<GetExternalSystemDetailQuery, ExternalSystemDetailDto>
|
||||
{
|
||||
public async Task<ExternalSystemDetailDto> Handle(GetExternalSystemDetailQuery request, CancellationToken ct)
|
||||
public async Task<ExternalSystemDetailDto> Handle(
|
||||
GetExternalSystemDetailQuery request, CancellationToken ct)
|
||||
{
|
||||
var system = await db.ExternalSystems.FirstOrDefaultAsync(s => s.Id == request.Id, ct)
|
||||
?? throw new NotFoundException(ErrorCode.NotFound, $"外部系统 {request.Id} 不存在");
|
||||
@ -0,0 +1,36 @@
|
||||
using System.Text.Json;
|
||||
using BuildFlow.Shared.Domain.Errors;
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
using Monitoring.Application.ExternalSystems.Queries;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
namespace Monitoring.Application.ExternalSystems.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 外部系统列表查询处理器。按创建时间倒序返回全部系统。
|
||||
/// json_data 在表达式树里翻译不了,先物化实体再在内存映射 DTO(外部系统数量很少,无需担心性能)。
|
||||
/// </summary>
|
||||
public class ListExternalSystemsQueryHandler(MonitoringDbContext db)
|
||||
: IRequestHandler<ListExternalSystemsQuery, IReadOnlyList<ExternalSystemDto>>
|
||||
{
|
||||
public async Task<IReadOnlyList<ExternalSystemDto>> Handle(
|
||||
ListExternalSystemsQuery request, CancellationToken ct)
|
||||
{
|
||||
var systems = await db.ExternalSystems
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return systems.Select(s =>
|
||||
{
|
||||
var d = s.Data;
|
||||
return new ExternalSystemDto(
|
||||
s.Id, d.Name, (int)d.SystemType, d.SystemType.ToString(),
|
||||
d.IsEnabled, d.BaseUrl, d.PollIntervalSeconds,
|
||||
JsonSerializer.Serialize(d.Parameters),
|
||||
d.LastSyncedAt, d.LastSyncJobId);
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,8 @@ using BuildFlow.Shared.Domain.Exceptions;
|
||||
using BuildFlow.Shared.Domain.Errors;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.ExternalSystems;
|
||||
using Monitoring.Application.ExternalSystems.Commands;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
using Monitoring.Domain.Enums;
|
||||
using Monitoring.Infrastructure.Connectors;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
namespace Monitoring.Application.ExternalSystems;
|
||||
namespace Monitoring.Application.ExternalSystems.Queries;
|
||||
|
||||
/// <summary>查询外部系统详情(含凭证,供前端凭证编辑回填)。</summary>
|
||||
/// <param name="Id">系统 Id。</param>
|
||||
@ -0,0 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.ExternalSystems.DTOs;
|
||||
|
||||
namespace Monitoring.Application.ExternalSystems.Queries;
|
||||
|
||||
/// <summary>查询全部外部系统列表(按创建时间倒序)。供列表页展示。</summary>
|
||||
public record ListExternalSystemsQuery : IRequest<IReadOnlyList<ExternalSystemDto>>;
|
||||
@ -1,9 +1,10 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
using Monitoring.Application.Reports.Queries;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
namespace Monitoring.Application.Reports;
|
||||
namespace Monitoring.Application.Reports.Handlers;
|
||||
|
||||
/// <summary>按天趋势查询处理器。在数据库侧按 occurred_at 日期分组聚合记录数。</summary>
|
||||
public class GetDailyTrendQueryHandler(MonitoringDbContext db)
|
||||
@ -1,12 +1,13 @@
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using BuildFlow.Shared.Domain.Errors;
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
using Monitoring.Application.Reports.Queries;
|
||||
using Monitoring.Domain.Enums;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
namespace Monitoring.Application.Reports;
|
||||
namespace Monitoring.Application.Reports.Handlers;
|
||||
|
||||
/// <summary>同步汇总查询处理器。</summary>
|
||||
public class GetSyncSummaryQueryHandler(MonitoringDbContext db)
|
||||
@ -1,7 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
|
||||
namespace Monitoring.Application.Reports;
|
||||
namespace Monitoring.Application.Reports.Queries;
|
||||
|
||||
/// <summary>查询某外部系统按天的记录数趋势(基于 SyncedRecord.OccurredAt 聚合)。</summary>
|
||||
/// <param name="ExternalSystemId">外部系统 Id。</param>
|
||||
@ -1,7 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Reports.DTOs;
|
||||
|
||||
namespace Monitoring.Application.Reports;
|
||||
namespace Monitoring.Application.Reports.Queries;
|
||||
|
||||
/// <summary>查询某外部系统的同步汇总:最近一次成功的汇总指标 + 最近 N 次任务执行情况。</summary>
|
||||
/// <param name="ExternalSystemId">外部系统 Id。</param>
|
||||
@ -1,7 +1,7 @@
|
||||
using MediatR;
|
||||
using Monitoring.Application.Sync.DTOs;
|
||||
|
||||
namespace Monitoring.Application.Sync;
|
||||
namespace Monitoring.Application.Sync.Commands;
|
||||
|
||||
/// <summary>手动触发某外部系统立即同步。返回本次 SyncJob 概览。</summary>
|
||||
/// <param name="ExternalSystemId">目标外部系统 Id。</param>
|
||||
@ -1,11 +1,12 @@
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using BuildFlow.Shared.Domain.Errors;
|
||||
using BuildFlow.Shared.Domain.Exceptions;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Monitoring.Application.Sync.Commands;
|
||||
using Monitoring.Application.Sync.DTOs;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
|
||||
namespace Monitoring.Application.Sync;
|
||||
namespace Monitoring.Application.Sync.Handlers;
|
||||
|
||||
/// <summary>手动触发同步处理器。</summary>
|
||||
public class TriggerSyncCommandHandler(
|
||||
@ -1,111 +0,0 @@
|
||||
using Monitoring.Domain.Enums;
|
||||
|
||||
namespace Monitoring.Domain.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 外部系统 json_data 投影:承载可变/扩展字段。
|
||||
/// 凭证(Cookie/Header/Token)也内聚于此,避免单独建表;Credentials 列表为空时表示无凭证。
|
||||
/// Parameters 承载 Connector 专属配置(如 u号租的 sellerId/lookbackDays)。
|
||||
/// </summary>
|
||||
public class SystemData
|
||||
{
|
||||
/// <summary>系统显示名称。</summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>系统类型:1=UHaoZu, 100=GenericHttp。</summary>
|
||||
public ExternalSystemType SystemType { get; set; }
|
||||
|
||||
/// <summary>Base URL。</summary>
|
||||
public string BaseUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>轮询间隔(秒),0=用默认。</summary>
|
||||
public int PollIntervalSeconds { get; set; }
|
||||
|
||||
/// <summary>是否启用。</summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>Connector 专属参数(如 sellerId/lookbackDays)。</summary>
|
||||
public Dictionary<string, string?> Parameters { get; set; } = new();
|
||||
|
||||
/// <summary>凭证列表(Cookie/Header/Token),不再单独建表。</summary>
|
||||
public List<CredentialEntry> Credentials { get; set; } = new();
|
||||
|
||||
/// <summary>最近同步时间(UTC 毫秒时间戳)。</summary>
|
||||
public long? LastSyncedAt { get; set; }
|
||||
|
||||
/// <summary>最近同步任务 Id。</summary>
|
||||
public Guid? LastSyncJobId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>凭证条目(内聚于 SystemData,无独立表)。</summary>
|
||||
public class CredentialEntry
|
||||
{
|
||||
/// <summary>类型:Cookie / Header / BearerToken / Query。</summary>
|
||||
public string Type { get; set; } = default!;
|
||||
|
||||
/// <summary>键名(Cookie 名/Header 名/参数名)。</summary>
|
||||
public string Key { get; set; } = default!;
|
||||
|
||||
/// <summary>值。TODO 后续加密。</summary>
|
||||
public string Value { get; set; } = default!;
|
||||
|
||||
/// <summary>是否启用。</summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>备注。</summary>
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>同步任务 json_data 投影。</summary>
|
||||
public class JobData
|
||||
{
|
||||
/// <summary>触发来源:scheduler / manual。</summary>
|
||||
public string TriggerSource { get; set; } = "scheduler";
|
||||
|
||||
/// <summary>汇总指标(Connector 产出,如 {order_count:8283, actual_rent:9813.80})。</summary>
|
||||
public Dictionary<string, object?> SummaryMetrics { get; set; } = new();
|
||||
|
||||
/// <summary>错误信息(成功时为 null)。</summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>同步记录 json_data 投影:拉取到的外部原始数据。</summary>
|
||||
public class RecordData
|
||||
{
|
||||
/// <summary>记录类型:order / finance / metric(由 Connector 定义)。</summary>
|
||||
public string RecordType { get; set; } = default!;
|
||||
|
||||
/// <summary>外部业务 Id(订单号/流水号)。</summary>
|
||||
public string ExternalId { get; set; } = default!;
|
||||
|
||||
/// <summary>原始数据(已换算后的业务字段,如金额已从分转元)。</summary>
|
||||
public Dictionary<string, object?> Payload { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>告警规则 json_data 投影。</summary>
|
||||
public class RuleData
|
||||
{
|
||||
/// <summary>规则名称。</summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>监控指标名(对应 JobData.SummaryMetrics 的键)。</summary>
|
||||
public string MetricName { get; set; } = default!;
|
||||
|
||||
/// <summary>比较运算符:gt/gte/lt/lte/eq。</summary>
|
||||
public string Operator { get; set; } = "gt";
|
||||
|
||||
/// <summary>阈值。</summary>
|
||||
public string Threshold { get; set; } = default!;
|
||||
|
||||
/// <summary>严重级别。</summary>
|
||||
public AlertSeverity Severity { get; set; } = AlertSeverity.Warning;
|
||||
|
||||
/// <summary>是否启用。</summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>触发冷却(秒)。</summary>
|
||||
public int CooldownSeconds { get; set; } = 3600;
|
||||
|
||||
/// <summary>最近触发时间(UTC 毫秒时间戳)。</summary>
|
||||
public long? LastTriggeredAt { get; set; }
|
||||
}
|
||||
@ -9,7 +9,7 @@ public class AlertRuleConfiguration : IEntityTypeConfiguration<AlertRule>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AlertRule> builder)
|
||||
{
|
||||
builder.ToTable("mon_alert_rules");
|
||||
builder.ToTable("b_alert_rules");
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).ValueGeneratedNever().HasComment("规则ID");
|
||||
builder.Property(e => e.ExternalSystemId).HasComment("绑定的外部系统ID");
|
||||
|
||||
@ -9,7 +9,7 @@ public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSyst
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ExternalSystem> builder)
|
||||
{
|
||||
builder.ToTable("mon_external_systems");
|
||||
builder.ToTable("b_external_systems");
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).ValueGeneratedNever().HasComment("系统ID");
|
||||
builder.Property(e => e.JsonData).HasColumnType("jsonb").IsRequired().HasComment("业务数据: 名称/类型/URL/参数/凭证/同步状态");
|
||||
|
||||
@ -9,7 +9,7 @@ public class SyncJobConfiguration : IEntityTypeConfiguration<SyncJob>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SyncJob> builder)
|
||||
{
|
||||
builder.ToTable("mon_sync_jobs");
|
||||
builder.ToTable("b_sync_jobs");
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).ValueGeneratedNever().HasComment("任务ID");
|
||||
builder.Property(e => e.ExternalSystemId).HasComment("外部系统ID");
|
||||
|
||||
@ -9,7 +9,7 @@ public class SyncedRecordConfiguration : IEntityTypeConfiguration<SyncedRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SyncedRecord> builder)
|
||||
{
|
||||
builder.ToTable("mon_synced_records");
|
||||
builder.ToTable("b_synced_records");
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).ValueGeneratedNever().HasComment("记录ID");
|
||||
builder.Property(e => e.ExternalSystemId).HasComment("来源外部系统ID");
|
||||
|
||||
@ -0,0 +1,350 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Monitoring.Infrastructure.Persistence;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(MonitoringDbContext))]
|
||||
[Migration("20260719044352_RenameTablesToBPrefix")]
|
||||
partial class RenameTablesToBPrefix
|
||||
{
|
||||
/// <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("Monitoring.Domain.Entities.AlertRule", 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("创建人");
|
||||
|
||||
b.Property<Guid>("ExternalSystemId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("external_system_id")
|
||||
.HasComment("绑定的外部系统ID");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted")
|
||||
.HasComment("是否已软删除");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("json_data")
|
||||
.HasComment("业务数据: 名称/指标/阈值/运算符/严重级别/启用/冷却");
|
||||
|
||||
b.Property<string>("OperatorIP")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("operator_ip")
|
||||
.HasComment("操作者IP");
|
||||
|
||||
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("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_b_alert_rules");
|
||||
|
||||
b.HasIndex("ExternalSystemId")
|
||||
.HasDatabaseName("ix_b_alert_rules_external_system_id");
|
||||
|
||||
b.ToTable("b_alert_rules", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.ExternalSystem", 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("创建人");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted")
|
||||
.HasComment("是否已软删除");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("json_data")
|
||||
.HasComment("业务数据: 名称/类型/URL/参数/凭证/同步状态");
|
||||
|
||||
b.Property<string>("OperatorIP")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("operator_ip")
|
||||
.HasComment("操作者IP");
|
||||
|
||||
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("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_b_external_systems");
|
||||
|
||||
b.HasIndex("JsonData")
|
||||
.HasDatabaseName("ix_b_external_systems_json_data");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonData"), "gin");
|
||||
NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonData"), new[] { "jsonb_path_ops" });
|
||||
|
||||
b.ToTable("b_external_systems", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.SyncJob", 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("创建人");
|
||||
|
||||
b.Property<long?>("DurationMs")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("duration_ms")
|
||||
.HasComment("耗时(毫秒)");
|
||||
|
||||
b.Property<Guid>("ExternalSystemId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("external_system_id")
|
||||
.HasComment("外部系统ID");
|
||||
|
||||
b.Property<DateTime?>("FinishedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("finished_at")
|
||||
.HasComment("结束时间UTC");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted")
|
||||
.HasComment("是否已软删除");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("json_data")
|
||||
.HasComment("业务数据: 触发来源/汇总指标/错误信息");
|
||||
|
||||
b.Property<string>("OperatorIP")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("operator_ip")
|
||||
.HasComment("操作者IP");
|
||||
|
||||
b.Property<int>("RecordCount")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("record_count")
|
||||
.HasComment("拉取记录数");
|
||||
|
||||
b.Property<DateTime?>("StartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("started_at")
|
||||
.HasComment("开始时间UTC");
|
||||
|
||||
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("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_b_sync_jobs");
|
||||
|
||||
b.HasIndex("Status")
|
||||
.HasDatabaseName("ix_b_sync_jobs_status");
|
||||
|
||||
b.HasIndex("ExternalSystemId", "CreatedAt")
|
||||
.HasDatabaseName("ix_b_sync_jobs_external_system_id_created_at");
|
||||
|
||||
b.ToTable("b_sync_jobs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.SyncedRecord", 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("创建人");
|
||||
|
||||
b.Property<string>("ExternalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("external_id")
|
||||
.HasComment("外部业务Id(订单号等)");
|
||||
|
||||
b.Property<Guid>("ExternalSystemId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("external_system_id")
|
||||
.HasComment("来源外部系统ID");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted")
|
||||
.HasComment("是否已软删除");
|
||||
|
||||
b.Property<string>("JsonData")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("json_data")
|
||||
.HasComment("原始业务数据JSON");
|
||||
|
||||
b.Property<DateTime?>("OccurredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("occurred_at")
|
||||
.HasComment("业务发生时间UTC");
|
||||
|
||||
b.Property<string>("OperatorIP")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("operator_ip")
|
||||
.HasComment("操作者IP");
|
||||
|
||||
b.Property<string>("RecordType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)")
|
||||
.HasColumnName("record_type")
|
||||
.HasComment("记录类型:order/finance/metric");
|
||||
|
||||
b.Property<Guid>("SyncJobId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sync_job_id")
|
||||
.HasComment("来源同步任务ID");
|
||||
|
||||
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("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_b_synced_records");
|
||||
|
||||
b.HasIndex("JsonData")
|
||||
.HasDatabaseName("ix_b_synced_records_json_data");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonData"), "gin");
|
||||
NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonData"), new[] { "jsonb_path_ops" });
|
||||
|
||||
b.HasIndex("ExternalSystemId", "OccurredAt")
|
||||
.HasDatabaseName("ix_b_synced_records_external_system_id_occurred_at");
|
||||
|
||||
b.HasIndex("ExternalSystemId", "ExternalId", "RecordType")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_b_synced_records_external_system_id_external_id_record_type");
|
||||
|
||||
b.ToTable("b_synced_records", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameTablesToBPrefix : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_mon_synced_records",
|
||||
table: "mon_synced_records");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_mon_sync_jobs",
|
||||
table: "mon_sync_jobs");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_mon_external_systems",
|
||||
table: "mon_external_systems");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_mon_alert_rules",
|
||||
table: "mon_alert_rules");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "mon_synced_records",
|
||||
newName: "b_synced_records");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "mon_sync_jobs",
|
||||
newName: "b_sync_jobs");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "mon_external_systems",
|
||||
newName: "b_external_systems");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "mon_alert_rules",
|
||||
newName: "b_alert_rules");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_synced_records_json_data",
|
||||
table: "b_synced_records",
|
||||
newName: "ix_b_synced_records_json_data");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_synced_records_external_system_id_occurred_at",
|
||||
table: "b_synced_records",
|
||||
newName: "ix_b_synced_records_external_system_id_occurred_at");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_synced_records_external_system_id_external_id_record_ty",
|
||||
table: "b_synced_records",
|
||||
newName: "ix_b_synced_records_external_system_id_external_id_record_type");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_sync_jobs_status",
|
||||
table: "b_sync_jobs",
|
||||
newName: "ix_b_sync_jobs_status");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_sync_jobs_external_system_id_created_at",
|
||||
table: "b_sync_jobs",
|
||||
newName: "ix_b_sync_jobs_external_system_id_created_at");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_external_systems_json_data",
|
||||
table: "b_external_systems",
|
||||
newName: "ix_b_external_systems_json_data");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_mon_alert_rules_external_system_id",
|
||||
table: "b_alert_rules",
|
||||
newName: "ix_b_alert_rules_external_system_id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_b_synced_records",
|
||||
table: "b_synced_records",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_b_sync_jobs",
|
||||
table: "b_sync_jobs",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_b_external_systems",
|
||||
table: "b_external_systems",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_b_alert_rules",
|
||||
table: "b_alert_rules",
|
||||
column: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_b_synced_records",
|
||||
table: "b_synced_records");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_b_sync_jobs",
|
||||
table: "b_sync_jobs");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_b_external_systems",
|
||||
table: "b_external_systems");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "pk_b_alert_rules",
|
||||
table: "b_alert_rules");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "b_synced_records",
|
||||
newName: "mon_synced_records");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "b_sync_jobs",
|
||||
newName: "mon_sync_jobs");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "b_external_systems",
|
||||
newName: "mon_external_systems");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "b_alert_rules",
|
||||
newName: "mon_alert_rules");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_synced_records_json_data",
|
||||
table: "mon_synced_records",
|
||||
newName: "ix_mon_synced_records_json_data");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_synced_records_external_system_id_occurred_at",
|
||||
table: "mon_synced_records",
|
||||
newName: "ix_mon_synced_records_external_system_id_occurred_at");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_synced_records_external_system_id_external_id_record_type",
|
||||
table: "mon_synced_records",
|
||||
newName: "ix_mon_synced_records_external_system_id_external_id_record_ty");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_sync_jobs_status",
|
||||
table: "mon_sync_jobs",
|
||||
newName: "ix_mon_sync_jobs_status");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_sync_jobs_external_system_id_created_at",
|
||||
table: "mon_sync_jobs",
|
||||
newName: "ix_mon_sync_jobs_external_system_id_created_at");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_external_systems_json_data",
|
||||
table: "mon_external_systems",
|
||||
newName: "ix_mon_external_systems_json_data");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "ix_b_alert_rules_external_system_id",
|
||||
table: "mon_alert_rules",
|
||||
newName: "ix_mon_alert_rules_external_system_id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_mon_synced_records",
|
||||
table: "mon_synced_records",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_mon_sync_jobs",
|
||||
table: "mon_sync_jobs",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_mon_external_systems",
|
||||
table: "mon_external_systems",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "pk_mon_alert_rules",
|
||||
table: "mon_alert_rules",
|
||||
column: "id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,12 +79,12 @@ namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
.HasComment("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_mon_alert_rules");
|
||||
.HasName("pk_b_alert_rules");
|
||||
|
||||
b.HasIndex("ExternalSystemId")
|
||||
.HasDatabaseName("ix_mon_alert_rules_external_system_id");
|
||||
.HasDatabaseName("ix_b_alert_rules_external_system_id");
|
||||
|
||||
b.ToTable("mon_alert_rules", (string)null);
|
||||
b.ToTable("b_alert_rules", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.ExternalSystem", b =>
|
||||
@ -139,15 +139,15 @@ namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
.HasComment("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_mon_external_systems");
|
||||
.HasName("pk_b_external_systems");
|
||||
|
||||
b.HasIndex("JsonData")
|
||||
.HasDatabaseName("ix_mon_external_systems_json_data");
|
||||
.HasDatabaseName("ix_b_external_systems_json_data");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonData"), "gin");
|
||||
NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonData"), new[] { "jsonb_path_ops" });
|
||||
|
||||
b.ToTable("mon_external_systems", (string)null);
|
||||
b.ToTable("b_external_systems", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.SyncJob", b =>
|
||||
@ -232,15 +232,15 @@ namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
.HasComment("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_mon_sync_jobs");
|
||||
.HasName("pk_b_sync_jobs");
|
||||
|
||||
b.HasIndex("Status")
|
||||
.HasDatabaseName("ix_mon_sync_jobs_status");
|
||||
.HasDatabaseName("ix_b_sync_jobs_status");
|
||||
|
||||
b.HasIndex("ExternalSystemId", "CreatedAt")
|
||||
.HasDatabaseName("ix_mon_sync_jobs_external_system_id_created_at");
|
||||
.HasDatabaseName("ix_b_sync_jobs_external_system_id_created_at");
|
||||
|
||||
b.ToTable("mon_sync_jobs", (string)null);
|
||||
b.ToTable("b_sync_jobs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Monitoring.Domain.Entities.SyncedRecord", b =>
|
||||
@ -324,22 +324,22 @@ namespace Monitoring.Infrastructure.Persistence.Migrations
|
||||
.HasComment("更新人");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_mon_synced_records");
|
||||
.HasName("pk_b_synced_records");
|
||||
|
||||
b.HasIndex("JsonData")
|
||||
.HasDatabaseName("ix_mon_synced_records_json_data");
|
||||
.HasDatabaseName("ix_b_synced_records_json_data");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("JsonData"), "gin");
|
||||
NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex("JsonData"), new[] { "jsonb_path_ops" });
|
||||
|
||||
b.HasIndex("ExternalSystemId", "OccurredAt")
|
||||
.HasDatabaseName("ix_mon_synced_records_external_system_id_occurred_at");
|
||||
.HasDatabaseName("ix_b_synced_records_external_system_id_occurred_at");
|
||||
|
||||
b.HasIndex("ExternalSystemId", "ExternalId", "RecordType")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_mon_synced_records_external_system_id_external_id_record_ty");
|
||||
.HasDatabaseName("ix_b_synced_records_external_system_id_external_id_record_type");
|
||||
|
||||
b.ToTable("mon_synced_records", (string)null);
|
||||
b.ToTable("b_synced_records", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
@ -272,7 +272,11 @@ public partial class ScriptExecutionService(
|
||||
|| name.EndsWith(".Application")
|
||||
|| name.EndsWith(".Domain"))
|
||||
&& !name.EndsWith(".Infrastructure")
|
||||
&& !name.Contains(".Tests"));
|
||||
&& !name.Contains(".Tests"))
|
||||
.ToList();
|
||||
|
||||
// 诊断日志:看脚本编译引用了哪些模块程序集
|
||||
Console.WriteLine($"[ScriptAssemblies] 白名单匹配 {moduleAssemblies.Count} 个: {string.Join(", ", moduleAssemblies.Select(a => a.GetName().Name))}");
|
||||
|
||||
return baseline.Concat(moduleAssemblies).Distinct()
|
||||
.Select(a => MetadataReference.CreateFromFile(a.Location))
|
||||
|
||||
@ -5,7 +5,9 @@ namespace Workflow.Application.BuildFlow.Shares.Commands;
|
||||
/// <summary>
|
||||
/// 匿名通过分享 token 提交表单数据命令。
|
||||
/// 仅当分享 Permission 允许写入(Edit/Admin)时才能成功。
|
||||
/// 若分享设置了密码,需通过 Password 校验后才能提交。
|
||||
/// </summary>
|
||||
public sealed record SubmitSharedFormCommand(
|
||||
string Token,
|
||||
string DataJson) : IRequest<Guid>;
|
||||
string DataJson,
|
||||
string? Password = null) : IRequest<Guid>;
|
||||
|
||||
@ -33,6 +33,16 @@ public class SubmitSharedFormCommandHandler(WorkflowDbContext db) : IRequestHand
|
||||
throw new BusinessException("当前分享链接为只读,不允许提交数据");
|
||||
}
|
||||
|
||||
// 若分享设置了密码,提交时也必须校验(防止绕过密码直接提交)
|
||||
if (!string.IsNullOrEmpty(share.Password))
|
||||
{
|
||||
if (string.IsNullOrEmpty(req.Password) ||
|
||||
!BCrypt.Net.BCrypt.Verify(req.Password, share.Password))
|
||||
{
|
||||
throw new BusinessException("需要密码或密码错误");
|
||||
}
|
||||
}
|
||||
|
||||
// 基于 share.Id 构造确定性 InstanceId,便于同一分享下的多笔提交聚合到一个虚拟实例。
|
||||
var instanceId = DeterministicInstanceId(share.Id);
|
||||
|
||||
|
||||
@ -35,6 +35,9 @@ public static class WorkflowGraphAdapter
|
||||
Config = gn.Data is { ValueKind: JsonValueKind.Object or JsonValueKind.Array } data
|
||||
? data.GetRawText()
|
||||
: null,
|
||||
// 审批节点绑定的表单定义 id(钉钉式树编译进 data.formDefinitionId)。
|
||||
// ApproveTaskCommandHandler.SaveNodeFormDataAsync 据此加载表单做字段权限校验。
|
||||
FormDefinitionId = TryGetFormDefinitionId(gn.Data),
|
||||
PositionX = (int)Math.Round(gn.X),
|
||||
PositionY = (int)Math.Round(gn.Y),
|
||||
// 审计字段填充非空默认值(实体是 IFullAudit,避免下游 null 引用)
|
||||
@ -78,4 +81,13 @@ public static class WorkflowGraphAdapter
|
||||
if (string.IsNullOrWhiteSpace(id)) return Guid.Empty;
|
||||
return Guid.TryParse(id, out var g) ? g : Guid.Empty;
|
||||
}
|
||||
|
||||
/// <summary>从节点 data.formDefinitionId(钉钉式树编译产物)提取表单定义 id。</summary>
|
||||
private static Guid? TryGetFormDefinitionId(System.Text.Json.JsonElement? data)
|
||||
{
|
||||
if (data is not { ValueKind: System.Text.Json.JsonValueKind.Object } obj) return null;
|
||||
if (!obj.TryGetProperty("formDefinitionId", out var prop)) return null;
|
||||
var raw = prop.ValueKind == System.Text.Json.JsonValueKind.String ? prop.GetString() : null;
|
||||
return Guid.TryParse(raw, out var g) ? g : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +56,18 @@ public class ApproveTaskCommandHandler(
|
||||
string formDataJson,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var node = await db.WorkflowNodes
|
||||
// 先确保 ProcessEngine 已加载该实例的图(从 DefinitionJson/快照),
|
||||
// 否则下方 FindNodePublicAsync 在新请求里拿不到内存图。优先用图查 node(含 config),
|
||||
// 回退关系表(迁移期兼容)。
|
||||
var instanceForGraph = await db.WorkflowInstances
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(n => n.Id == task.NodeId, ct)
|
||||
.FirstOrDefaultAsync(i => i.Id == task.InstanceId, ct);
|
||||
if (instanceForGraph is not null)
|
||||
{
|
||||
await processEngine.EnsureGraphLoadedForAsync(instanceForGraph);
|
||||
}
|
||||
var node = await processEngine.FindNodePublicAsync(task.NodeId)
|
||||
?? await db.WorkflowNodes.AsNoTracking().FirstOrDefaultAsync(n => n.Id == task.NodeId, ct)
|
||||
?? throw new BusinessException("Task node not found");
|
||||
|
||||
if (!node.FormDefinitionId.HasValue)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Workflow.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixWorkflowTaskAttachmentsColumn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// 模型漂移修复:WorkflowTask.Attachments 字段早已存在于实体与 ModelSnapshot,
|
||||
// 但建表迁移遗漏了它(RenameTablesToBPrefix 只 rename,更早的建表迁移在 attachments 加入前)。
|
||||
// 用幂等 SQL 补列:全新部署建列;已 ALTER 补过的库跳过。
|
||||
// attachments 为自由 JSON 数组字符串(引用文件分享链接/S3 key),无校验约束,text 类型。
|
||||
migrationBuilder.Sql(
|
||||
"ALTER TABLE b_workflow_tasks ADD COLUMN IF NOT EXISTS attachments text");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"ALTER TABLE b_workflow_tasks DROP COLUMN IF EXISTS attachments");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,17 @@ public class NotificationServiceTests
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>包装 IOptionsMonitor(NotificationService 现用 IOptionsMonitor 而非 IOptions)。</summary>
|
||||
private static IOptionsMonitor<NotificationOptions> OptionsMonitorOf(NotificationOptions options) =>
|
||||
new ConstantOptionsMonitor(options);
|
||||
|
||||
private sealed class ConstantOptionsMonitor(NotificationOptions options) : IOptionsMonitor<NotificationOptions>
|
||||
{
|
||||
public NotificationOptions CurrentValue => options;
|
||||
public NotificationOptions Get(string? name) => options;
|
||||
public IDisposable? OnChange(Action<NotificationOptions, string?> listener) => null!;
|
||||
}
|
||||
|
||||
private static WorkflowTask NewTask(Guid? assigneeId = null, string? role = null)
|
||||
{
|
||||
var instanceId = Guid.NewGuid();
|
||||
@ -66,7 +77,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, Guid.NewGuid()));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskArrivedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -87,7 +98,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, Guid.NewGuid()));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskArrivedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -106,7 +117,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, initiatorId));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskApprovedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -126,7 +137,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, userId));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskApprovedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -143,7 +154,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, initiatorId));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskRejectedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -159,7 +170,7 @@ public class NotificationServiceTests
|
||||
db.WorkflowInstances.Add(NewInstance(task.InstanceId, Guid.NewGuid()));
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var svc = new NotificationService(db, Options.Create(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(DefaultOptions()), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskTimeoutAsync(task, autoApproved: true);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -185,7 +196,7 @@ public class NotificationServiceTests
|
||||
DefaultUrl = "https://example.com/hook"
|
||||
}
|
||||
};
|
||||
var svc = new NotificationService(db, Options.Create(options), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(options), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskArrivedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
@ -213,7 +224,7 @@ public class NotificationServiceTests
|
||||
AllowedHosts = ["example.com"]
|
||||
}
|
||||
};
|
||||
var svc = new NotificationService(db, Options.Create(options), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(options), new NoOpPlatformNotificationPublisher());
|
||||
|
||||
svc.IsHostAllowed(url).Should().Be(expected);
|
||||
}
|
||||
@ -235,7 +246,7 @@ public class NotificationServiceTests
|
||||
DefaultUrl = "https://evil.com/hook"
|
||||
}
|
||||
};
|
||||
var svc = new NotificationService(db, Options.Create(options), new NoOpPlatformNotificationPublisher());
|
||||
var svc = new NotificationService(db, OptionsMonitorOf(options), new NoOpPlatformNotificationPublisher());
|
||||
await svc.NotifyTaskArrivedAsync(task);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user