本次提交完成了多个核心业务模块的开发与优化: 1. 宠物模块:新增宠物实体、服务接口与实现,支持创建默认宠物、激活、喂养、进化以及喂养记录查询 2. 签到模块:新增签到实体、服务接口、控制器以及相关DTO,支持用户签到和签到信息查询,新增成长值奖励字段 3. 期刊绑定模块:新增用户期刊关联实体、服务接口与控制器,支持扫码绑定期刊、解绑和查询绑定列表 4. 补偿任务模块:新增补偿任务实体、服务接口与实现,用于处理业务失败后的异步重试补偿 5. 优化微信登录流程:拆分登录与快捷登录接口,支持手机号获取,新增首次登录自动创建默认宠物逻辑 6. 调整基础路由与实体状态:修改微信控制器路由前缀,更新宠物状态枚举与默认值
174 lines
6.6 KiB
C#
174 lines
6.6 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Newtonsoft.Json;
|
||
using QYZH.InteractiveMagazine.IService;
|
||
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
|
||
using QYZH.InteractiveMagazine.Models.Entity;
|
||
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
|
||
{
|
||
TaskType = input.TaskType,
|
||
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,
|
||
Status = CompensationTaskStatus.Pending,
|
||
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()
|
||
.Where(t => (t.Status == CompensationTaskStatus.Pending || t.Status == CompensationTaskStatus.Processing)
|
||
&& !t.IsDeleted
|
||
&& (t.ScheduledAt == null || t.ScheduledAt <= now))
|
||
.OrderBy(t => t.CreatedAt)
|
||
.Take(limit)
|
||
.Select(t => new CompensationTaskOutput
|
||
{
|
||
Id = t.Id,
|
||
TaskType = t.TaskType,
|
||
BusinessSource = t.BusinessSource,
|
||
BusinessId = t.BusinessId,
|
||
UserId = t.UserId,
|
||
Payload = t.Payload,
|
||
ErrorMessage = t.ErrorMessage,
|
||
ErrorSource = t.ErrorSource,
|
||
RetryCount = t.RetryCount,
|
||
MaxRetries = t.MaxRetries,
|
||
Status = t.Status,
|
||
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);
|
||
|
||
if (!string.IsNullOrEmpty(input.Status))
|
||
query = query.Where(t => t.Status == input.Status);
|
||
|
||
if (!string.IsNullOrEmpty(input.TaskType))
|
||
query = query.Where(t => t.TaskType == input.TaskType);
|
||
|
||
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,
|
||
TaskType = t.TaskType,
|
||
BusinessSource = t.BusinessSource,
|
||
BusinessId = t.BusinessId,
|
||
UserId = t.UserId,
|
||
Payload = t.Payload,
|
||
ErrorMessage = t.ErrorMessage,
|
||
ErrorSource = t.ErrorSource,
|
||
RetryCount = t.RetryCount,
|
||
MaxRetries = t.MaxRetries,
|
||
Status = t.Status,
|
||
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>()
|
||
.SetColumns(t => t.Status == input.Status)
|
||
.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>()
|
||
.SetColumns(t => t.Status == CompensationTaskStatus.Cancelled)
|
||
.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);
|
||
}
|
||
}
|