refactor(monitoring): 重构领域实体命名和枚举类型

- 将 AlertRule 重命名为 AlertRuleEntity
- 将 ExternalSystem 重命名为 ExternalSystemEntity
- 将 SyncJob 重命名为 SyncJobEntity
- 将 SyncedRecord 重命名为 SyncedRecordEntity
- 将 AlertSeverity 重命名为 AlertSeverityEnum
- 将 ExternalSystemType 重命名为 ExternalSystemTypeEnum
- 将 SyncJobStatus 重命名为 SyncJobStatusEnum
- 将相关实体的命名空间从 Entities 移至 Values
- 更新所有引用这些类型的代码以使用新名称
- 修改数据访问层配置以使用新的实体类型名称
This commit is contained in:
xiangning 2026-07-19 13:13:24 +08:00
parent a11330936a
commit 6b7116fafd
31 changed files with 159 additions and 103 deletions

View File

@ -7,7 +7,7 @@ namespace BuildFlow.Host.Services.BackgroundServices;
/// <summary> /// <summary>
/// 监控同步后台调度器:按 MonitoringOptions.Scheduler.PollIntervalSeconds 轮询, /// 监控同步后台调度器:按 MonitoringOptions.Scheduler.PollIntervalSeconds 轮询,
/// 找出到期的启用 ExternalSystem(LastSyncedAt + PollInterval &lt;= now), /// 找出到期的启用 ExternalSystemEntity(LastSyncedAt + PollInterval &lt;= now),
/// 调用 SyncProcessor 执行同步。 /// 调用 SyncProcessor 执行同步。
/// ///
/// 设计要点(与 WebhookDispatcherService 一致): /// 设计要点(与 WebhookDispatcherService 一致):

View File

@ -1,4 +1,8 @@
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Application.ExternalSystems.DTOs; namespace Monitoring.Application.ExternalSystems.DTOs;

View File

@ -3,7 +3,11 @@ using BuildFlow.Shared.Domain.Errors;
using MediatR; using MediatR;
using Monitoring.Application.ExternalSystems.Commands; using Monitoring.Application.ExternalSystems.Commands;
using Monitoring.Application.ExternalSystems.DTOs; using Monitoring.Application.ExternalSystems.DTOs;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
using Monitoring.Infrastructure.Persistence; using Monitoring.Infrastructure.Persistence;
using System.Text.Json; using System.Text.Json;
@ -16,15 +20,15 @@ public class CreateExternalSystemCommandHandler(MonitoringDbContext db)
{ {
public async Task<ExternalSystemDto> Handle(CreateExternalSystemCommand request, CancellationToken ct) public async Task<ExternalSystemDto> Handle(CreateExternalSystemCommand request, CancellationToken ct)
{ {
if (!Enum.IsDefined(typeof(ExternalSystemType), request.SystemType)) if (!Enum.IsDefined(typeof(ExternalSystemTypeEnum), request.SystemType))
throw new BusinessException(ErrorCode.InvalidParam, $"无效的系统类型: {request.SystemType}"); throw new BusinessException(ErrorCode.InvalidParam, $"无效的系统类型: {request.SystemType}");
var systemType = (ExternalSystemType)request.SystemType; var systemType = (ExternalSystemTypeEnum)request.SystemType;
var parameters = string.IsNullOrEmpty(request.ParametersJson) var parameters = string.IsNullOrEmpty(request.ParametersJson)
? new Dictionary<string, string?>() ? new Dictionary<string, string?>()
: JsonSerializer.Deserialize<Dictionary<string, string?>>(request.ParametersJson) ?? new(); : JsonSerializer.Deserialize<Dictionary<string, string?>>(request.ParametersJson) ?? new();
var system = new ExternalSystem var system = new ExternalSystemEntity
{ {
Data = new SystemData Data = new SystemData
{ {

View File

@ -26,7 +26,7 @@ public class LoginExternalSystemCommandHandler(
?? throw new NotFoundException(ErrorCode.NotFound, $"外部系统 {request.SystemId} 不存在"); ?? throw new NotFoundException(ErrorCode.NotFound, $"外部系统 {request.SystemId} 不存在");
// 目前仅支持 u号租自动登录 // 目前仅支持 u号租自动登录
if (system.Data.SystemType != ExternalSystemType.UHaoZu) if (system.Data.SystemType != ExternalSystemTypeEnum.UHaoZu)
{ {
return new LoginResult(false, $"{system.Data.SystemType} 类型暂不支持自动登录", 0); return new LoginResult(false, $"{system.Data.SystemType} 类型暂不支持自动登录", 0);
} }

View File

@ -5,7 +5,11 @@ using MediatR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Monitoring.Application.ExternalSystems.Commands; using Monitoring.Application.ExternalSystems.Commands;
using Monitoring.Application.ExternalSystems.DTOs; using Monitoring.Application.ExternalSystems.DTOs;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Infrastructure.Persistence; using Monitoring.Infrastructure.Persistence;
namespace Monitoring.Application.ExternalSystems.Handlers; namespace Monitoring.Application.ExternalSystems.Handlers;

View File

@ -12,7 +12,7 @@ public class MonitoringOptions
/// <summary>调度器配置节。</summary> /// <summary>调度器配置节。</summary>
public class SchedulerSection public class SchedulerSection
{ {
/// <summary>轮询间隔(秒)。调度器按此频率扫描到期的 ExternalSystem。</summary> /// <summary>轮询间隔(秒)。调度器按此频率扫描到期的 ExternalSystemEntity。</summary>
public int PollIntervalSeconds { get; set; } = 60; public int PollIntervalSeconds { get; set; } = 60;
/// <summary>应用启动后延迟多少秒开始首次扫描,避免与启动初始化争抢资源。</summary> /// <summary>应用启动后延迟多少秒开始首次扫描,避免与启动初始化争抢资源。</summary>

View File

@ -23,7 +23,7 @@ public class GetSyncSummaryQueryHandler(MonitoringDbContext db)
// 最近一次成功的汇总指标(json_data 在表达式树里翻译不了,先物化再读 Data) // 最近一次成功的汇总指标(json_data 在表达式树里翻译不了,先物化再读 Data)
var latestSuccess = await db.SyncJobs var latestSuccess = await db.SyncJobs
.Where(j => j.ExternalSystemId == request.ExternalSystemId && j.Status == SyncJobStatus.Succeeded) .Where(j => j.ExternalSystemId == request.ExternalSystemId && j.Status == SyncJobStatusEnum.Succeeded)
.OrderByDescending(j => j.FinishedAt) .OrderByDescending(j => j.FinishedAt)
.FirstOrDefaultAsync(ct); .FirstOrDefaultAsync(ct);
var metrics = latestSuccess?.Data.SummaryMetrics ?? new Dictionary<string, object?>(); var metrics = latestSuccess?.Data.SummaryMetrics ?? new Dictionary<string, object?>();

View File

@ -3,7 +3,7 @@ using Monitoring.Application.Reports.DTOs;
namespace Monitoring.Application.Reports.Queries; namespace Monitoring.Application.Reports.Queries;
/// <summary>查询某外部系统按天的记录数趋势(基于 SyncedRecord.OccurredAt 聚合)。</summary> /// <summary>查询某外部系统按天的记录数趋势(基于 SyncedRecordEntity.OccurredAt 聚合)。</summary>
/// <param name="ExternalSystemId">外部系统 Id。</param> /// <param name="ExternalSystemId">外部系统 Id。</param>
/// <param name="Days">最近 N 天,默认 14。</param> /// <param name="Days">最近 N 天,默认 14。</param>
public record GetDailyTrendQuery(Guid ExternalSystemId, int Days = 14) public record GetDailyTrendQuery(Guid ExternalSystemId, int Days = 14)

View File

@ -3,6 +3,6 @@ using Monitoring.Application.Sync.DTOs;
namespace Monitoring.Application.Sync.Commands; namespace Monitoring.Application.Sync.Commands;
/// <summary>手动触发某外部系统立即同步。返回本次 SyncJob 概览。</summary> /// <summary>手动触发某外部系统立即同步。返回本次 SyncJobEntity 概览。</summary>
/// <param name="ExternalSystemId">目标外部系统 Id。</param> /// <param name="ExternalSystemId">目标外部系统 Id。</param>
public record TriggerSyncCommand(Guid ExternalSystemId) : IRequest<SyncJobDto>; public record TriggerSyncCommand(Guid ExternalSystemId) : IRequest<SyncJobDto>;

View File

@ -2,7 +2,11 @@ using System.Text.Json;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
using Monitoring.Domain.Services; using Monitoring.Domain.Services;
using Monitoring.Infrastructure.Persistence; using Monitoring.Infrastructure.Persistence;
@ -10,12 +14,12 @@ using Monitoring.Infrastructure.Persistence;
namespace Monitoring.Application.Sync; namespace Monitoring.Application.Sync;
/// <summary> /// <summary>
/// 同步编排核心。从 ExternalSystem 路由到对应 IExternalConnector,执行拉取→落库→记 SyncJob, /// 同步编排核心。从 ExternalSystemEntity 路由到对应 IExternalConnector,执行拉取→落库→记 SyncJobEntity,
/// 并在成功后触发告警评估。纯业务逻辑、无 HostedService 依赖,便于单元测试。 /// 并在成功后触发告警评估。纯业务逻辑、无 HostedService 依赖,便于单元测试。
/// ///
/// 设计要点: /// 设计要点:
/// 1. Connector 路由:从 IServiceProvider 取 IEnumerable&lt;IExternalConnector&gt;,按 SystemType 选实现; /// 1. Connector 路由:从 IServiceProvider 取 IEnumerable&lt;IExternalConnector&gt;,按 SystemType 选实现;
/// 2. 幂等落库:SyncedRecord 按 (ExternalSystemId, ExternalId, RecordType) UPSERT,重复拉取只保留最新快照; /// 2. 幂等落库:SyncedRecordEntity 按 (ExternalSystemId, ExternalId, RecordType) UPSERT,重复拉取只保留最新快照;
/// 3. 失败隔离:异常记录为 Failed 不抛出,单个系统失败不影响调度器后续系统。 /// 3. 失败隔离:异常记录为 Failed 不抛出,单个系统失败不影响调度器后续系统。
/// json_data 模式:所有业务字段经实体 .Data 强类型视图读写,自动序列化进 JsonData 列。 /// json_data 模式:所有业务字段经实体 .Data 强类型视图读写,自动序列化进 JsonData 列。
/// </summary> /// </summary>
@ -24,8 +28,8 @@ public class SyncProcessor(
IServiceProvider services, IServiceProvider services,
ILogger<SyncProcessor> logger) ILogger<SyncProcessor> logger)
{ {
/// <summary>对单个外部系统执行一次同步。返回本次 SyncJob 记录(已落库)。</summary> /// <summary>对单个外部系统执行一次同步。返回本次 SyncJobEntity 记录(已落库)。</summary>
public async Task<SyncJob> ExecuteAsync(ExternalSystem system, string triggerSource, CancellationToken ct) public async Task<SyncJobEntity> ExecuteAsync(ExternalSystemEntity system, string triggerSource, CancellationToken ct)
{ {
var systemType = system.Data.SystemType; var systemType = system.Data.SystemType;
@ -38,10 +42,10 @@ public class SyncProcessor(
} }
// 2. 创建并标记 Running // 2. 创建并标记 Running
var job = new SyncJob var job = new SyncJobEntity
{ {
ExternalSystemId = system.Id, ExternalSystemId = system.Id,
Status = SyncJobStatus.Running, Status = SyncJobStatusEnum.Running,
StartedAt = DateTime.UtcNow, StartedAt = DateTime.UtcNow,
Data = new JobData { TriggerSource = triggerSource }, Data = new JobData { TriggerSource = triggerSource },
}; };
@ -53,11 +57,11 @@ public class SyncProcessor(
{ {
var result = await connector.FetchAsync(system, ct); var result = await connector.FetchAsync(system, ct);
// 4. 幂等落库 SyncedRecord // 4. 幂等落库 SyncedRecordEntity
await PersistRecordsAsync(system.Id, job.Id, result.Records, ct); await PersistRecordsAsync(system.Id, job.Id, result.Records, ct);
// 5. 更新 SyncJob 为 Succeeded(汇总指标写入 json_data) // 5. 更新 SyncJobEntity 为 Succeeded(汇总指标写入 json_data)
job.Status = SyncJobStatus.Succeeded; job.Status = SyncJobStatusEnum.Succeeded;
job.FinishedAt = DateTime.UtcNow; job.FinishedAt = DateTime.UtcNow;
job.DurationMs = (long)(job.FinishedAt.Value - job.StartedAt!.Value).TotalMilliseconds; job.DurationMs = (long)(job.FinishedAt.Value - job.StartedAt!.Value).TotalMilliseconds;
job.RecordCount = result.RecordCount; job.RecordCount = result.RecordCount;
@ -69,7 +73,7 @@ public class SyncProcessor(
}; };
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
// 6. 更新 ExternalSystem 的最近同步信息(写入 json_data) // 6. 更新 ExternalSystemEntity 的最近同步信息(写入 json_data)
var sysData = system.Data; var sysData = system.Data;
sysData.LastSyncedAt = new DateTimeOffset(job.FinishedAt.Value).ToUnixTimeMilliseconds(); sysData.LastSyncedAt = new DateTimeOffset(job.FinishedAt.Value).ToUnixTimeMilliseconds();
sysData.LastSyncJobId = job.Id; sysData.LastSyncJobId = job.Id;
@ -124,7 +128,7 @@ public class SyncProcessor(
} }
else else
{ {
db.SyncedRecords.Add(new SyncedRecord db.SyncedRecords.Add(new SyncedRecordEntity
{ {
ExternalSystemId = systemId, ExternalSystemId = systemId,
SyncJobId = jobId, SyncJobId = jobId,
@ -171,16 +175,16 @@ public class SyncProcessor(
} }
/// <summary>记录失败任务(job 已存在则更新,否则新建)。</summary> /// <summary>记录失败任务(job 已存在则更新,否则新建)。</summary>
private async Task<SyncJob> RecordFailureAsync( private async Task<SyncJobEntity> RecordFailureAsync(
ExternalSystem system, string triggerSource, string error, SyncJob? job, CancellationToken ct) ExternalSystemEntity system, string triggerSource, string error, SyncJobEntity? job, CancellationToken ct)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
if (job is null) if (job is null)
{ {
job = new SyncJob job = new SyncJobEntity
{ {
ExternalSystemId = system.Id, ExternalSystemId = system.Id,
Status = SyncJobStatus.Failed, Status = SyncJobStatusEnum.Failed,
StartedAt = now, StartedAt = now,
Data = new JobData { TriggerSource = triggerSource }, Data = new JobData { TriggerSource = triggerSource },
}; };
@ -188,7 +192,7 @@ public class SyncProcessor(
} }
else else
{ {
job.Status = SyncJobStatus.Failed; job.Status = SyncJobStatusEnum.Failed;
} }
job.FinishedAt = now; job.FinishedAt = now;
job.DurationMs = job.StartedAt.HasValue ? (long)(now - job.StartedAt.Value).TotalMilliseconds : 0; job.DurationMs = job.StartedAt.HasValue ? (long)(now - job.StartedAt.Value).TotalMilliseconds : 0;

View File

@ -3,7 +3,7 @@ namespace Monitoring.Domain.Enums;
/// <summary> /// <summary>
/// 告警严重级别。影响通知渠道与展示排序(Critical 通常触发即时推送,Info 仅记录)。 /// 告警严重级别。影响通知渠道与展示排序(Critical 通常触发即时推送,Info 仅记录)。
/// </summary> /// </summary>
public enum AlertSeverity public enum AlertSeverityEnum
{ {
/// <summary>信息级:仅记录,不打扰。</summary> /// <summary>信息级:仅记录,不打扰。</summary>
Info = 0, Info = 0,

View File

@ -6,7 +6,7 @@ namespace Monitoring.Domain.Enums;
/// 1) 在此枚举登记; /// 1) 在此枚举登记;
/// 2) 实现 IExternalConnector 并在 DI 注册。 /// 2) 实现 IExternalConnector 并在 DI 注册。
/// </summary> /// </summary>
public enum ExternalSystemType public enum ExternalSystemTypeEnum
{ {
/// <summary>u号租商家后台(订单/资金报表拉取)。</summary> /// <summary>u号租商家后台(订单/资金报表拉取)。</summary>
UHaoZu = 1, UHaoZu = 1,

View File

@ -6,7 +6,7 @@ namespace Monitoring.Domain.Enums;
/// ↘ Failed /// ↘ Failed
/// 由 SyncProcessor 在执行前后驱动转换,SyncedRecord/SyncJob 据此统计成功率与历史。 /// 由 SyncProcessor 在执行前后驱动转换,SyncedRecord/SyncJob 据此统计成功率与历史。
/// </summary> /// </summary>
public enum SyncJobStatus public enum SyncJobStatusEnum
{ {
/// <summary>已入队待执行。</summary> /// <summary>已入队待执行。</summary>
Pending = 0, Pending = 0,

View File

@ -1,4 +1,8 @@
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
namespace Monitoring.Domain.Services; namespace Monitoring.Domain.Services;
@ -11,13 +15,13 @@ namespace Monitoring.Domain.Services;
public interface IExternalConnector public interface IExternalConnector
{ {
/// <summary>本连接器处理的系统类型。ConnectorRegistry 据此建立 SystemType → 实现的路由表。</summary> /// <summary>本连接器处理的系统类型。ConnectorRegistry 据此建立 SystemType → 实现的路由表。</summary>
ExternalSystemType SystemType { get; } ExternalSystemTypeEnum SystemType { get; }
/// <summary> /// <summary>
/// 执行一次拉取。从 <paramref name="system"/> 读取 BaseUrl/参数与关联凭证, /// 执行一次拉取。从 <paramref name="system"/> 读取 BaseUrl/参数与关联凭证,
/// 调用外部接口,返回汇总指标 + 明细记录。失败应抛异常,由 SyncProcessor 统一记录为 Failed。 /// 调用外部接口,返回汇总指标 + 明细记录。失败应抛异常,由 SyncProcessor 统一记录为 Failed。
/// </summary> /// </summary>
Task<FetchResult> FetchAsync(ExternalSystem system, CancellationToken ct); Task<FetchResult> FetchAsync(ExternalSystemEntity system, CancellationToken ct);
} }
/// <summary> /// <summary>
@ -25,7 +29,7 @@ public interface IExternalConnector
/// 明细记录放 Records(如各订单的原始 JSON)。 /// 明细记录放 Records(如各订单的原始 JSON)。
/// </summary> /// </summary>
/// <param name="RecordCount">本次拉取的明细行数。</param> /// <param name="RecordCount">本次拉取的明细行数。</param>
/// <param name="SummaryMetrics">汇总指标键值对(值为数值或字符串,序列化进 SyncJob.SummaryMetricsJson)。</param> /// <param name="SummaryMetrics">汇总指标键值对(值为数值或字符串,序列化进 SyncJobEntity.SummaryMetricsJson)。</param>
/// <param name="Records">明细记录列表;若仅拉汇总可为空。</param> /// <param name="Records">明细记录列表;若仅拉汇总可为空。</param>
public record FetchResult( public record FetchResult(
int RecordCount, int RecordCount,
@ -33,7 +37,7 @@ public record FetchResult(
IReadOnlyList<SyncedRecordData> Records); IReadOnlyList<SyncedRecordData> Records);
/// <summary> /// <summary>
/// 一条明细记录数据(由 Connector 产出,SyncProcessor 据此落库 SyncedRecord)。 /// 一条明细记录数据(由 Connector 产出,SyncProcessor 据此落库 SyncedRecordEntity)。
/// </summary> /// </summary>
/// <param name="ExternalId">外部业务 Id(订单号/流水号),幂等去重依据。</param> /// <param name="ExternalId">外部业务 Id(订单号/流水号),幂等去重依据。</param>
/// <param name="RecordType">记录类型("order" / "finance" 等)。</param> /// <param name="RecordType">记录类型("order" / "finance" 等)。</param>

View File

@ -1,4 +1,8 @@
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Domain.Services; namespace Monitoring.Domain.Services;
@ -15,7 +19,7 @@ public interface IAlertEvaluator
/// <param name="summaryMetrics">最新一次同步的汇总指标。</param> /// <param name="summaryMetrics">最新一次同步的汇总指标。</param>
/// <param name="rules">该系统下启用的告警规则。</param> /// <param name="rules">该系统下启用的告警规则。</param>
/// <returns>命中的规则列表(调用方负责后续通知与 LastTriggeredAt 更新)。</returns> /// <returns>命中的规则列表(调用方负责后续通知与 LastTriggeredAt 更新)。</returns>
IReadOnlyList<AlertRule> Evaluate( IReadOnlyList<AlertRuleEntity> Evaluate(
Dictionary<string, object?> summaryMetrics, Dictionary<string, object?> summaryMetrics,
IReadOnlyList<AlertRule> rules); IReadOnlyList<AlertRuleEntity> rules);
} }

View File

@ -1,4 +1,4 @@
namespace Monitoring.Domain.Entities; namespace Monitoring.Domain.Values;
/// <summary>凭证条目(内聚于 SystemData,无独立表)。</summary> /// <summary>凭证条目(内聚于 SystemData,无独立表)。</summary>
public class CredentialEntry public class CredentialEntry

View File

@ -1,4 +1,4 @@
namespace Monitoring.Domain.Entities; namespace Monitoring.Domain.Values;
/// <summary>同步任务 json_data 投影。</summary> /// <summary>同步任务 json_data 投影。</summary>
public class JobData public class JobData

View File

@ -1,4 +1,4 @@
namespace Monitoring.Domain.Entities; namespace Monitoring.Domain.Values;
/// <summary>同步记录 json_data 投影:拉取到的外部原始数据。</summary> /// <summary>同步记录 json_data 投影:拉取到的外部原始数据。</summary>
public class RecordData public class RecordData

View File

@ -1,6 +1,6 @@
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
namespace Monitoring.Domain.Entities; namespace Monitoring.Domain.Values;
/// <summary>告警规则 json_data 投影。</summary> /// <summary>告警规则 json_data 投影。</summary>
public class RuleData public class RuleData
@ -18,7 +18,7 @@ public class RuleData
public string Threshold { get; set; } = default!; public string Threshold { get; set; } = default!;
/// <summary>严重级别。</summary> /// <summary>严重级别。</summary>
public AlertSeverity Severity { get; set; } = AlertSeverity.Warning; public AlertSeverityEnum Severity { get; set; } = AlertSeverityEnum.Warning;
/// <summary>是否启用。</summary> /// <summary>是否启用。</summary>
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;

View File

@ -1,5 +1,9 @@
using System.Globalization; using System.Globalization;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Services; using Monitoring.Domain.Services;
namespace Monitoring.Infrastructure.Alerting; namespace Monitoring.Infrastructure.Alerting;
@ -7,17 +11,17 @@ namespace Monitoring.Infrastructure.Alerting;
/// <summary> /// <summary>
/// 默认告警评估器:对每条规则,从汇总指标取出 MetricName 的值, /// 默认告警评估器:对每条规则,从汇总指标取出 MetricName 的值,
/// 按 Operator 与 Threshold 比较;命中且度过冷却期则纳入返回列表。 /// 按 Operator 与 Threshold 比较;命中且度过冷却期则纳入返回列表。
/// 字段读取自 AlertRule.Data(json_data 强类型视图)。 /// 字段读取自 AlertRuleEntity.Data(json_data 强类型视图)。
/// 比较失败(指标缺失/非数值)的规则静默跳过,不视为命中也不抛异常。 /// 比较失败(指标缺失/非数值)的规则静默跳过,不视为命中也不抛异常。
/// </summary> /// </summary>
public class DefaultAlertEvaluator : IAlertEvaluator public class DefaultAlertEvaluator : IAlertEvaluator
{ {
public IReadOnlyList<AlertRule> Evaluate( public IReadOnlyList<AlertRuleEntity> Evaluate(
Dictionary<string, object?> summaryMetrics, Dictionary<string, object?> summaryMetrics,
IReadOnlyList<AlertRule> rules) IReadOnlyList<AlertRuleEntity> rules)
{ {
var nowMs = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds(); var nowMs = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds();
var hit = new List<AlertRule>(); var hit = new List<AlertRuleEntity>();
foreach (var rule in rules) foreach (var rule in rules)
{ {

View File

@ -1,13 +1,17 @@
using System.Text.Json; using System.Text.Json;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Connectors; namespace Monitoring.Infrastructure.Connectors;
/// <summary> /// <summary>
/// Connector 公共辅助:从 ExternalSystem.JsonData 解析凭证与参数,构造带凭证的 HttpClient。 /// Connector 公共辅助:从 ExternalSystemEntity.JsonData 解析凭证与参数,构造带凭证的 HttpClient。
/// ///
/// 设计取向:把 PG 当 Mongo 用——业务字段全在 jsonb,本类只做 JSON 反序列化 + HttpClient 拼装, /// 设计取向:把 PG 当 Mongo 用——业务字段全在 jsonb,本类只做 JSON 反序列化 + HttpClient 拼装,
/// 不再访问 DbContext(凭证内聚于 ExternalSystem,无独立表)。 /// 不再访问 DbContext(凭证内聚于 ExternalSystemEntity,无独立表)。
/// ///
/// HttpClient 命名约定:"Monitoring." + SystemType 名(已在 MonitoringInfrastructureModule 注册工厂)。 /// HttpClient 命名约定:"Monitoring." + SystemType 名(已在 MonitoringInfrastructureModule 注册工厂)。
/// </summary> /// </summary>
@ -16,7 +20,7 @@ public class ConnectorCredentialResolver(IHttpClientFactory httpClientFactory)
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
/// <summary>创建挂好 BaseUrl + 凭证的 HttpClient。</summary> /// <summary>创建挂好 BaseUrl + 凭证的 HttpClient。</summary>
public HttpClient CreateClient(ExternalSystem system) public HttpClient CreateClient(ExternalSystemEntity system)
{ {
var clientName = $"Monitoring.{system.Data.SystemType}"; var clientName = $"Monitoring.{system.Data.SystemType}";
var client = httpClientFactory.CreateClient(clientName); var client = httpClientFactory.CreateClient(clientName);

View File

@ -1,6 +1,10 @@
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
using Monitoring.Domain.Services; using Monitoring.Domain.Services;
@ -8,7 +12,7 @@ namespace Monitoring.Infrastructure.Connectors;
/// <summary> /// <summary>
/// 通用 HTTP Connector:POST JSON + 配置化解析。 /// 通用 HTTP Connector:POST JSON + 配置化解析。
/// 通过 ExternalSystem.Data.Parameters 配置 path/body/响应字段路径,覆盖未定制对接的简单接口。 /// 通过 ExternalSystemEntity.Data.Parameters 配置 path/body/响应字段路径,覆盖未定制对接的简单接口。
/// ///
/// 配置示例(Parameters): /// 配置示例(Parameters):
/// { /// {
@ -19,9 +23,9 @@ namespace Monitoring.Infrastructure.Connectors;
/// </summary> /// </summary>
public class GenericHttpConnector(ConnectorCredentialResolver resolver) : IExternalConnector public class GenericHttpConnector(ConnectorCredentialResolver resolver) : IExternalConnector
{ {
public ExternalSystemType SystemType => ExternalSystemType.GenericHttp; public ExternalSystemTypeEnum SystemType => ExternalSystemTypeEnum.GenericHttp;
public async Task<FetchResult> FetchAsync(ExternalSystem system, CancellationToken ct) public async Task<FetchResult> FetchAsync(ExternalSystemEntity system, CancellationToken ct)
{ {
var p = system.Data.Parameters; var p = system.Data.Parameters;
var path = p.GetValueOrDefault("path") ?? "/"; var path = p.GetValueOrDefault("path") ?? "/";

View File

@ -2,7 +2,11 @@ using System.Net.Http.Json;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
using Monitoring.Domain.Enums; using Monitoring.Domain.Enums;
using Monitoring.Domain.Services; using Monitoring.Domain.Services;
@ -16,8 +20,8 @@ namespace Monitoring.Infrastructure.Connectors;
/// 1) POST {BaseUrl}/merchants/orderReport/ajax/selectOrderReport → 汇总指标 /// 1) POST {BaseUrl}/merchants/orderReport/ajax/selectOrderReport → 汇总指标
/// 2) POST {BaseUrl}/merchants/orderReport/ajax/orderReportList → 订单明细(分页全量) /// 2) POST {BaseUrl}/merchants/orderReport/ajax/orderReportList → 订单明细(分页全量)
/// ///
/// 凭证:Cookie(JSESSIONID/uid 等)从 ExternalSystem.Data.Credentials 读取(json_data 内聚)。 /// 凭证:Cookie(JSESSIONID/uid 等)从 ExternalSystemEntity.Data.Credentials 读取(json_data 内聚)。
/// 参数(ExternalSystem.Data.Parameters):reportPath/lookbackDays/maxPages。 /// 参数(ExternalSystemEntity.Data.Parameters):reportPath/lookbackDays/maxPages。
/// 金额单位说明:外部接口金额字段单位为"分",本 Connector 统一除以100转"元"后入库。 /// 金额单位说明:外部接口金额字段单位为"分",本 Connector 统一除以100转"元"后入库。
/// </summary> /// </summary>
public class UHaoZuConnector(ConnectorCredentialResolver resolver, ILogger<UHaoZuConnector> logger) public class UHaoZuConnector(ConnectorCredentialResolver resolver, ILogger<UHaoZuConnector> logger)
@ -29,9 +33,9 @@ public class UHaoZuConnector(ConnectorCredentialResolver resolver, ILogger<UHaoZ
private const int DefaultMaxPages = 50; private const int DefaultMaxPages = 50;
private const int PageSize = 30; private const int PageSize = 30;
public ExternalSystemType SystemType => ExternalSystemType.UHaoZu; public ExternalSystemTypeEnum SystemType => ExternalSystemTypeEnum.UHaoZu;
public async Task<FetchResult> FetchAsync(ExternalSystem system, CancellationToken ct) public async Task<FetchResult> FetchAsync(ExternalSystemEntity system, CancellationToken ct)
{ {
var p = system.Data.Parameters; var p = system.Data.Parameters;
var reportPath = p.GetValueOrDefault("reportPath") ?? DefaultReportPath; var reportPath = p.GetValueOrDefault("reportPath") ?? DefaultReportPath;

View File

@ -2,7 +2,11 @@ using System.Net;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Connectors; namespace Monitoring.Infrastructure.Connectors;
@ -30,7 +34,7 @@ public class UHaoZuLoginService(ILogger<UHaoZuLoginService> logger)
/// 执行登录,返回提取到的凭证列表(失败抛异常)。 /// 执行登录,返回提取到的凭证列表(失败抛异常)。
/// </summary> /// </summary>
public async Task<List<CredentialEntry>> LoginAsync( public async Task<List<CredentialEntry>> LoginAsync(
ExternalSystem system, string account, string password, ExternalSystemEntity system, string account, string password,
string ticket, string randstr, CancellationToken ct) string ticket, string randstr, CancellationToken ct)
{ {
// 用 HttpClientHandler 捕获 Set-Cookie。 // 用 HttpClientHandler 捕获 Set-Cookie。

View File

@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Persistence.Configurations; namespace Monitoring.Infrastructure.Persistence.Configurations;
/// <summary>告警规则表映射(id + 外键索引列 + json_data + 审计)。</summary> /// <summary>告警规则表映射(id + 外键索引列 + json_data + 审计)。</summary>
public class AlertRuleConfiguration : IEntityTypeConfiguration<AlertRule> public class AlertRuleConfiguration : IEntityTypeConfiguration<AlertRuleEntity>
{ {
public void Configure(EntityTypeBuilder<AlertRule> builder) public void Configure(EntityTypeBuilder<AlertRuleEntity> builder)
{ {
builder.ToTable("b_alert_rules"); builder.ToTable("b_alert_rules");
builder.HasKey(e => e.Id); builder.HasKey(e => e.Id);

View File

@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Persistence.Configurations; namespace Monitoring.Infrastructure.Persistence.Configurations;
/// <summary>外部系统配置表映射(极简文档模式:id + json_data + 审计)。</summary> /// <summary>外部系统配置表映射(极简文档模式:id + json_data + 审计)。</summary>
public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystem> public class ExternalSystemConfiguration : IEntityTypeConfiguration<ExternalSystemEntity>
{ {
public void Configure(EntityTypeBuilder<ExternalSystem> builder) public void Configure(EntityTypeBuilder<ExternalSystemEntity> builder)
{ {
builder.ToTable("b_external_systems"); builder.ToTable("b_external_systems");
builder.HasKey(e => e.Id); builder.HasKey(e => e.Id);

View File

@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Persistence.Configurations; namespace Monitoring.Infrastructure.Persistence.Configurations;
/// <summary>同步任务记录表映射(id + 外键索引列 + json_data + 审计)。</summary> /// <summary>同步任务记录表映射(id + 外键索引列 + json_data + 审计)。</summary>
public class SyncJobConfiguration : IEntityTypeConfiguration<SyncJob> public class SyncJobConfiguration : IEntityTypeConfiguration<SyncJobEntity>
{ {
public void Configure(EntityTypeBuilder<SyncJob> builder) public void Configure(EntityTypeBuilder<SyncJobEntity> builder)
{ {
builder.ToTable("b_sync_jobs"); builder.ToTable("b_sync_jobs");
builder.HasKey(e => e.Id); builder.HasKey(e => e.Id);

View File

@ -1,13 +1,17 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Persistence.Configurations; namespace Monitoring.Infrastructure.Persistence.Configurations;
/// <summary>同步记录表映射(id + 去重/分类/时间索引列 + json_data + 审计)。</summary> /// <summary>同步记录表映射(id + 去重/分类/时间索引列 + json_data + 审计)。</summary>
public class SyncedRecordConfiguration : IEntityTypeConfiguration<SyncedRecord> public class SyncedRecordConfiguration : IEntityTypeConfiguration<SyncedRecordEntity>
{ {
public void Configure(EntityTypeBuilder<SyncedRecord> builder) public void Configure(EntityTypeBuilder<SyncedRecordEntity> builder)
{ {
builder.ToTable("b_synced_records"); builder.ToTable("b_synced_records");
builder.HasKey(e => e.Id); builder.HasKey(e => e.Id);

View File

@ -1,6 +1,10 @@
using System.Linq.Expressions; using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Monitoring.Domain.Entities; using Monitoring.Domain.Entities.AlertRule;
using Monitoring.Domain.Entities.ExternalSystem;
using Monitoring.Domain.Entities.SyncJob;
using Monitoring.Domain.Entities.SyncedRecord;
using Monitoring.Domain.Values;
namespace Monitoring.Infrastructure.Persistence; namespace Monitoring.Infrastructure.Persistence;
@ -13,10 +17,10 @@ public class MonitoringDbContext : DbContext
{ {
public MonitoringDbContext(DbContextOptions<MonitoringDbContext> options) : base(options) { } public MonitoringDbContext(DbContextOptions<MonitoringDbContext> options) : base(options) { }
public DbSet<ExternalSystem> ExternalSystems => Set<ExternalSystem>(); public DbSet<ExternalSystemEntity> ExternalSystems => Set<ExternalSystemEntity>();
public DbSet<SyncJob> SyncJobs => Set<SyncJob>(); public DbSet<SyncJobEntity> SyncJobs => Set<SyncJobEntity>();
public DbSet<SyncedRecord> SyncedRecords => Set<SyncedRecord>(); public DbSet<SyncedRecordEntity> SyncedRecords => Set<SyncedRecordEntity>();
public DbSet<AlertRule> AlertRules => Set<AlertRule>(); public DbSet<AlertRuleEntity> AlertRules => Set<AlertRuleEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {

View File

@ -10,19 +10,15 @@ namespace BuildFlow.Shared.Infrastructure.Serialization;
public sealed class TimestampDateTimeConverter : JsonConverter<DateTime> public sealed class TimestampDateTimeConverter : JsonConverter<DateTime>
{ {
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.TokenType switch
{ {
// 兼容数字(毫秒时间戳)与字符串(ISO 8601,向后兼容旧客户端) // 兼容数字(毫秒时间戳)与字符串(ISO 8601,向后兼容旧客户端)
if (reader.TokenType == JsonTokenType.Number) JsonTokenType.Number => DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()).UtcDateTime,
{ JsonTokenType.String => DateTime.Parse(reader.GetString()!, null,
return DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()).UtcDateTime; System.Globalization.DateTimeStyles.RoundtripKind),
} _ => throw new JsonException("Expected a number (ms timestamp) or string (ISO date) for DateTime field.")
};
if (reader.TokenType == JsonTokenType.String)
{
return DateTime.Parse(reader.GetString()!, null, System.Globalization.DateTimeStyles.RoundtripKind);
}
throw new JsonException("Expected a number (ms timestamp) or string (ISO date) for DateTime field.");
} }
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)

View File

@ -11,17 +11,13 @@ public sealed class TimestampDateTimeOffsetConverter : JsonConverter<DateTimeOff
{ {
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
if (reader.TokenType == JsonTokenType.Number) return reader.TokenType switch
{ {
return DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()); JsonTokenType.Number => DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()),
} JsonTokenType.String => DateTimeOffset.Parse(reader.GetString()!),
_ => throw new JsonException(
if (reader.TokenType == JsonTokenType.String) "Expected a number (ms timestamp) or string (ISO date) for DateTimeOffset field.")
{ };
return DateTimeOffset.Parse(reader.GetString()!);
}
throw new JsonException("Expected a number (ms timestamp) or string (ISO date) for DateTimeOffset field.");
} }
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)