2026-06-04 15:54:30 +08:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.IService;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Models.Entity;
|
2026-06-08 16:57:44 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Models.Enum;
|
2026-06-04 15:54:30 +08:00
|
|
|
|
using QYZH.InteractiveMagazine.Repository;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Service;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 补偿任务服务实现(仅负责记录,处理逻辑由外部 Hangfire 项目完成)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class CompensationTaskService(
|
|
|
|
|
|
BaseRepository<CompensationTask> taskRepository,
|
|
|
|
|
|
ILogger<CompensationTaskService> logger)
|
|
|
|
|
|
: BaseRepository<CompensationTask>, ICompensationTaskService
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 创建补偿任务 — 记录失败操作,供后续补偿处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<long> CreateTaskAsync(CreateCompensationTaskInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning(
|
|
|
|
|
|
"创建补偿任务,TaskType: {TaskType}, BusinessSource: {BusinessSource}, UserId: {UserId}, ErrorSource: {ErrorSource}, Error: {ErrorMessage}",
|
|
|
|
|
|
input.TaskType, input.BusinessSource, input.UserId, input.ErrorSource, input.ErrorMessage);
|
|
|
|
|
|
|
|
|
|
|
|
var payloadJson = input.Payload is string str ? str : JsonConvert.SerializeObject(input.Payload);
|
|
|
|
|
|
|
|
|
|
|
|
var task = new CompensationTask
|
|
|
|
|
|
{
|
2026-06-08 16:57:44 +08:00
|
|
|
|
TaskType = (int)input.TaskType,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
BusinessSource = input.BusinessSource,
|
|
|
|
|
|
BusinessId = input.BusinessId,
|
|
|
|
|
|
UserId = input.UserId,
|
|
|
|
|
|
Payload = payloadJson,
|
|
|
|
|
|
ErrorMessage = input.ErrorMessage,
|
|
|
|
|
|
ErrorSource = input.ErrorSource,
|
|
|
|
|
|
RetryCount = 0,
|
|
|
|
|
|
MaxRetries = input.MaxRetries > 0 ? input.MaxRetries : 3,
|
2026-06-08 17:54:36 +08:00
|
|
|
|
Status = (int)CompensationTaskStatusEnum.Pending,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
ScheduledAt = DateTime.Now,
|
|
|
|
|
|
IsDeleted = false,
|
|
|
|
|
|
CreatedBy = "System",
|
|
|
|
|
|
CreatedAt = DateTime.Now,
|
|
|
|
|
|
UpdatedBy = "System",
|
|
|
|
|
|
UpdatedAt = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var result = await taskRepository.InsertReturnEntityAsync(task);
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("补偿任务创建成功,TaskId: {TaskId}", result.Id);
|
|
|
|
|
|
|
|
|
|
|
|
return result.Id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取待处理的补偿任务列表
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<List<CompensationTaskOutput>> GetPendingTasksAsync(int limit = 50)
|
|
|
|
|
|
{
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
|
|
|
|
|
|
|
|
|
var tasks = await taskRepository.Queryable()
|
2026-06-08 17:54:36 +08:00
|
|
|
|
.Where(t => (t.Status == (int)CompensationTaskStatusEnum.Pending || t.Status == (int)CompensationTaskStatusEnum.Processing)
|
2026-06-04 15:54:30 +08:00
|
|
|
|
&& !t.IsDeleted
|
|
|
|
|
|
&& (t.ScheduledAt == null || t.ScheduledAt <= now))
|
|
|
|
|
|
.OrderBy(t => t.CreatedAt)
|
|
|
|
|
|
.Take(limit)
|
|
|
|
|
|
.Select(t => new CompensationTaskOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = t.Id,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
TaskType = (CompensationTaskTypeEnum)t.TaskType,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
BusinessSource = t.BusinessSource,
|
|
|
|
|
|
BusinessId = t.BusinessId,
|
|
|
|
|
|
UserId = t.UserId,
|
|
|
|
|
|
Payload = t.Payload,
|
|
|
|
|
|
ErrorMessage = t.ErrorMessage,
|
|
|
|
|
|
ErrorSource = t.ErrorSource,
|
|
|
|
|
|
RetryCount = t.RetryCount,
|
|
|
|
|
|
MaxRetries = t.MaxRetries,
|
2026-06-08 17:54:36 +08:00
|
|
|
|
Status = (CompensationTaskStatusEnum)t.Status,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
ProcessedAt = t.ProcessedAt,
|
|
|
|
|
|
ScheduledAt = t.ScheduledAt,
|
|
|
|
|
|
ResultMessage = t.ResultMessage,
|
|
|
|
|
|
CreatedAt = t.CreatedAt
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return tasks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据业务来源和类型查询补偿任务(用于外部项目按条件拉取)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<List<CompensationTaskOutput>> GetTasksAsync(GetCompensationTasksInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var query = taskRepository.Queryable()
|
|
|
|
|
|
.Where(t => !t.IsDeleted);
|
|
|
|
|
|
|
2026-06-09 16:15:22 +08:00
|
|
|
|
if (input.UserId.HasValue)
|
|
|
|
|
|
query = query.Where(t => t.UserId == input.UserId.Value);
|
|
|
|
|
|
|
2026-06-08 16:57:44 +08:00
|
|
|
|
if (input.Status.HasValue)
|
2026-06-08 17:54:36 +08:00
|
|
|
|
query = query.Where(t => t.Status == (int)input.Status);
|
2026-06-04 15:54:30 +08:00
|
|
|
|
|
2026-06-08 16:57:44 +08:00
|
|
|
|
if (input.TaskType.HasValue)
|
2026-06-08 17:54:36 +08:00
|
|
|
|
query = query.Where(t => t.TaskType == (int)input.TaskType);
|
2026-06-04 15:54:30 +08:00
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(input.BusinessSource))
|
|
|
|
|
|
query = query.Where(t => t.BusinessSource == input.BusinessSource);
|
|
|
|
|
|
|
|
|
|
|
|
var tasks = await query
|
|
|
|
|
|
.OrderBy(t => t.CreatedAt)
|
|
|
|
|
|
.Take(input.Limit > 0 ? input.Limit : 50)
|
|
|
|
|
|
.Select(t => new CompensationTaskOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = t.Id,
|
2026-06-08 16:57:44 +08:00
|
|
|
|
TaskType = (CompensationTaskTypeEnum)t.TaskType,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
BusinessSource = t.BusinessSource,
|
|
|
|
|
|
BusinessId = t.BusinessId,
|
|
|
|
|
|
UserId = t.UserId,
|
|
|
|
|
|
Payload = t.Payload,
|
|
|
|
|
|
ErrorMessage = t.ErrorMessage,
|
|
|
|
|
|
ErrorSource = t.ErrorSource,
|
|
|
|
|
|
RetryCount = t.RetryCount,
|
|
|
|
|
|
MaxRetries = t.MaxRetries,
|
2026-06-08 17:54:36 +08:00
|
|
|
|
Status = (CompensationTaskStatusEnum)t.Status,
|
2026-06-04 15:54:30 +08:00
|
|
|
|
ProcessedAt = t.ProcessedAt,
|
|
|
|
|
|
ScheduledAt = t.ScheduledAt,
|
|
|
|
|
|
ResultMessage = t.ResultMessage,
|
|
|
|
|
|
CreatedAt = t.CreatedAt
|
|
|
|
|
|
})
|
|
|
|
|
|
.ToListAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return tasks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新补偿任务状态(供外部处理项目回调更新结果)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task UpdateTaskStatusAsync(long taskId, UpdateCompensationStatusInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var update = taskRepository.Context.Updateable<CompensationTask>()
|
2026-06-08 17:54:36 +08:00
|
|
|
|
.SetColumns(t => t.Status == (int)input.Status)
|
2026-06-04 15:54:30 +08:00
|
|
|
|
.SetColumns(t => t.ResultMessage == input.ResultMessage)
|
|
|
|
|
|
.SetColumns(t => t.ProcessedAt == DateTime.Now)
|
|
|
|
|
|
.SetColumns(t => t.UpdatedAt == DateTime.Now);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果外部传入了重试相关字段,一并更新
|
|
|
|
|
|
if (input.RetryCount.HasValue)
|
|
|
|
|
|
update = update.SetColumns(t => t.RetryCount == input.RetryCount.Value);
|
|
|
|
|
|
|
|
|
|
|
|
if (input.ScheduledAt.HasValue)
|
|
|
|
|
|
update = update.SetColumns(t => t.ScheduledAt == input.ScheduledAt.Value);
|
|
|
|
|
|
|
|
|
|
|
|
await update.Where(t => t.Id == taskId && !t.IsDeleted)
|
|
|
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("补偿任务状态更新,TaskId: {TaskId}, Status: {Status}", taskId, input.Status);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 取消补偿任务
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task CancelTaskAsync(long taskId, string reason)
|
|
|
|
|
|
{
|
|
|
|
|
|
await taskRepository.Context.Updateable<CompensationTask>()
|
2026-06-08 17:54:36 +08:00
|
|
|
|
.SetColumns(t => t.Status == (int)CompensationTaskStatusEnum.Cancelled)
|
2026-06-04 15:54:30 +08:00
|
|
|
|
.SetColumns(t => t.ResultMessage == reason)
|
|
|
|
|
|
.SetColumns(t => t.UpdatedAt == DateTime.Now)
|
|
|
|
|
|
.Where(t => t.Id == taskId && !t.IsDeleted)
|
|
|
|
|
|
.ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("补偿任务已取消,TaskId: {TaskId}, Reason: {Reason}", taskId, reason);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|