refactor(jobs): modularize background job handlers
Some checks failed
ci / release-gate (push) Has been cancelled

This commit is contained in:
2026-08-03 13:28:21 +08:00
parent c497a3ca8d
commit 7adcb6a3d5
21 changed files with 871 additions and 645 deletions

View File

@@ -0,0 +1,70 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Jobs;
using Tiku.Domain.Content;
using Tiku.Infrastructure.Jobs;
using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Content.Exports;
internal sealed class ContentExportJobHandler(TikuDbContext dbContext) : IBackgroundJobHandler
{
public string JobType => "content_export";
public async Task<BackgroundJobHandlerResult> HandleAsync(
BackgroundJobExecutionContext context,
CancellationToken cancellationToken = default)
{
var exportType = BackgroundJobPayload.GetString(context.Payload, "exportType") ?? "summary";
var assetKey = $"background-jobs/{context.JobId:N}/content-export.json";
var asset = await dbContext.ContentAssets.SingleOrDefaultAsync(
item => item.TenantId == context.TenantId && item.AssetKey == assetKey,
cancellationToken);
if (asset is null)
{
asset = new ContentAsset
{
TenantId = context.TenantId,
AssetKey = assetKey,
AssetType = ContentAssetType.Document,
StorageProvider = AssetStorageProvider.ExternalUrl,
UploadStatus = AssetUploadStatus.Verified,
SecurityScanStatus = AssetSecurityScanStatus.NotRequired,
Source = "background_job"
};
dbContext.ContentAssets.Add(asset);
}
var questionBankCount =
await dbContext.QuestionBanks.CountAsync(item => item.TenantId == context.TenantId, cancellationToken);
var questionCount =
await dbContext.Questions.CountAsync(item => item.TenantId == context.TenantId, cancellationToken);
var studentCount =
await dbContext.StudentProfiles.CountAsync(item => item.TenantId == context.TenantId, cancellationToken);
asset.FileName = $"content-export-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}.json";
asset.Title = "Content export manifest";
asset.Description = $"Generated content export manifest for {exportType}.";
asset.ObjectKey = assetKey;
asset.MimeType = "application/json";
asset.Metadata = JsonSerializer.SerializeToElement(new
{
exportType,
generatedAt = DateTimeOffset.UtcNow,
questionBankCount,
questionCount,
studentCount,
payload = context.Payload
});
await dbContext.SaveChangesAsync(cancellationToken);
return new BackgroundJobHandlerResult(
JsonSerializer.SerializeToElement(new
{
outputAssetId = asset.Id,
asset.AssetKey,
questionBankCount,
questionCount,
studentCount
}),
asset.Id);
}
}

View File

@@ -0,0 +1,48 @@
using System.Text.Json;
using Tiku.Application.Content;
using Tiku.Application.Jobs;
using Tiku.Infrastructure.Jobs;
namespace Tiku.Infrastructure.Content.Imports;
internal sealed class ContentImportJobHandler(IDirectContentService directContentService) : IBackgroundJobHandler
{
public string JobType => "content_import";
public async Task<BackgroundJobHandlerResult> HandleAsync(
BackgroundJobExecutionContext context,
CancellationToken cancellationToken = default)
{
var createdBy = BackgroundJobPayload.GetGuid(context.Payload, "createdBy") ?? Guid.Empty;
if (createdBy == Guid.Empty) throw new InvalidOperationException("content_import job requires createdBy.");
var command = new SimpleImportCommand(
BackgroundJobPayload.GetString(context.Payload, "importType") ??
throw new InvalidOperationException("content_import job requires importType."),
BackgroundJobPayload.GetString(context.Payload, "sourceFormat"),
BackgroundJobPayload.GetString(context.Payload, "sourceName"),
BackgroundJobPayload.GetGuid(context.Payload, "regionId"),
BackgroundJobPayload.GetGuid(context.Payload, "entryId"),
BackgroundJobPayload.GetGuid(context.Payload, "contentNodeId"),
BackgroundJobPayload.GetGuid(context.Payload, "subjectId"),
BackgroundJobPayload.GetGuid(context.Payload, "categoryId"),
BackgroundJobPayload.GetGuid(context.Payload, "questionBankId"),
BackgroundJobPayload.GetGuid(context.Payload, "collectionId"),
BackgroundJobPayload.GetArray(context.Payload, "items"),
false);
var result = await directContentService.ExecuteImportAsync(
new DirectContentActor(context.TenantId, createdBy),
command,
cancellationToken);
return new BackgroundJobHandlerResult(JsonSerializer.SerializeToElement(new
{
importJobId = result.Job.Id,
result.Job.ImportType,
result.Job.Status,
result.Job.TotalCount,
result.Job.InsertedCount,
result.Job.UpdatedCount,
result.Job.ErrorCount
}));
}
}