166 lines
7.7 KiB
C#
166 lines
7.7 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Application.Assets;
|
|
using Tiku.Application.Learning;
|
|
using Tiku.Application.PlatformAdmin;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.QuestionBanks;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.PlatformAdmin;
|
|
|
|
internal abstract partial class PlatformQuestionBankServiceBase
|
|
{
|
|
protected Task<IReadOnlyCollection<PlatformQuestionBankItem>> GetBanksCoreAsync(
|
|
PlatformAdminActor actor,
|
|
PlatformQuestionBankFilter filter,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync<IReadOnlyCollection<PlatformQuestionBankItem>>(actor, "查询平台公共题库",
|
|
async (_, dbContext, tenantId, token) =>
|
|
{
|
|
var query = dbContext.QuestionBanks.AsNoTracking().Where(item => item.TenantId == tenantId);
|
|
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
|
{
|
|
var keyword = filter.Keyword.Trim();
|
|
query = query.Where(item => item.Name.Contains(keyword));
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(filter.Status) &&
|
|
!filter.Status.Equals("all", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var status = ParseBankStatus(filter.Status);
|
|
query = query.Where(item => item.Status == status);
|
|
}
|
|
|
|
var banks = await query.OrderBy(item => item.Name).ThenBy(item => item.CreatedAt).ToArrayAsync(token);
|
|
var entryIds = banks.Where(item => item.ContentEntryId.HasValue)
|
|
.Select(item => item.ContentEntryId!.Value).ToArray();
|
|
var nodeCounts = await dbContext.ContentNodes.AsNoTracking()
|
|
.Where(item => item.TenantId == tenantId && entryIds.Contains(item.EntryId) && item.IsActive)
|
|
.GroupBy(item => item.EntryId)
|
|
.Select(group => new { EntryId = group.Key, Count = group.Count() })
|
|
.ToDictionaryAsync(item => item.EntryId, item => item.Count, token);
|
|
var questionCounts = await dbContext.Questions.AsNoTracking()
|
|
.Where(item =>
|
|
item.TenantId == tenantId && item.QuestionBankId.HasValue &&
|
|
item.Status != QuestionStatus.Archived)
|
|
.GroupBy(item => item.QuestionBankId!.Value)
|
|
.Select(group => new { BankId = group.Key, Count = group.Count() })
|
|
.ToDictionaryAsync(item => item.BankId, item => item.Count, token);
|
|
return banks.Select(item => ToBankItem(
|
|
item,
|
|
item.ContentEntryId.HasValue && nodeCounts.TryGetValue(item.ContentEntryId.Value, out var nodes)
|
|
? nodes
|
|
: 0,
|
|
questionCounts.TryGetValue(item.Id, out var questions) ? questions : 0)).ToArray();
|
|
}, cancellationToken);
|
|
}
|
|
|
|
protected Task<PlatformQuestionBankItem> UpsertBankCoreAsync(
|
|
PlatformAdminActor actor,
|
|
UpsertPlatformQuestionBankCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync(actor, "保存平台公共题库", async (_, dbContext, tenantId, token) =>
|
|
{
|
|
if (string.IsNullOrWhiteSpace(command.Name)) throw Error("题库名称不能为空。", "question_bank_name_required");
|
|
|
|
var bank = command.Id.HasValue
|
|
? await dbContext.QuestionBanks.SingleOrDefaultAsync(
|
|
item => item.TenantId == tenantId && item.Id == command.Id.Value, token)
|
|
: null;
|
|
if (command.Id.HasValue && bank is null) throw Error("公共题库不存在。", "question_bank_not_found");
|
|
|
|
ContentEntry? trackedEntry = null;
|
|
if (bank is null)
|
|
{
|
|
var entry = new ContentEntry
|
|
{
|
|
TenantId = tenantId,
|
|
EntryKey = $"public-question-bank-{Guid.NewGuid():N}",
|
|
Name = command.Name.Trim(),
|
|
EntryType = ContentEntryType.QuestionPractice,
|
|
Visibility = ContentVisibility.Public,
|
|
IsActive = true,
|
|
CreatedBy = actor.UserId,
|
|
UpdatedBy = actor.UserId
|
|
};
|
|
trackedEntry = entry;
|
|
bank = new QuestionBank
|
|
{
|
|
TenantId = tenantId,
|
|
ContentEntryId = entry.Id,
|
|
Status = QuestionBankStatus.Active
|
|
};
|
|
dbContext.ContentEntries.Add(entry);
|
|
dbContext.QuestionBanks.Add(bank);
|
|
}
|
|
else if (!bank.ContentEntryId.HasValue)
|
|
{
|
|
var entry = new ContentEntry
|
|
{
|
|
TenantId = tenantId,
|
|
EntryKey = $"public-question-bank-{bank.Id:N}",
|
|
Name = command.Name.Trim(),
|
|
EntryType = ContentEntryType.QuestionPractice,
|
|
Visibility = ContentVisibility.Public,
|
|
IsActive = true,
|
|
CreatedBy = actor.UserId,
|
|
UpdatedBy = actor.UserId
|
|
};
|
|
trackedEntry = entry;
|
|
dbContext.ContentEntries.Add(entry);
|
|
bank.ContentEntryId = entry.Id;
|
|
}
|
|
|
|
bank.Name = command.Name.Trim();
|
|
bank.Metadata = ObjectOrDefault(command.Metadata);
|
|
if (bank.ContentEntryId.HasValue)
|
|
{
|
|
var entry = trackedEntry ??
|
|
await dbContext.ContentEntries.SingleAsync(
|
|
item => item.TenantId == tenantId && item.Id == bank.ContentEntryId.Value, token);
|
|
entry.Name = bank.Name;
|
|
entry.UpdatedBy = actor.UserId;
|
|
}
|
|
|
|
AddAudit(dbContext, actor, "platform.question_bank.saved", bank.Id, new { bank.Name });
|
|
await dbContext.SaveChangesAsync(token);
|
|
return ToBankItem(bank, 0, 0);
|
|
}, cancellationToken);
|
|
}
|
|
|
|
protected Task<PlatformQuestionBankItem> ArchiveBankCoreAsync(
|
|
PlatformAdminActor actor,
|
|
Guid bankId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync(actor, "归档平台公共题库", async (_, dbContext, tenantId, token) =>
|
|
{
|
|
var bank = await RequireBankAsync(dbContext, tenantId, bankId, token);
|
|
if (await dbContext.Questions.AnyAsync(
|
|
item => item.TenantId == tenantId && item.QuestionBankId == bankId &&
|
|
item.Status != QuestionStatus.Archived, token))
|
|
throw Error("题库中仍有未归档题目,不能归档题库。", "question_bank_not_empty");
|
|
|
|
if (bank.ContentEntryId.HasValue && await dbContext.ContentNodes.AnyAsync(
|
|
item => item.TenantId == tenantId && item.EntryId == bank.ContentEntryId && item.IsActive, token))
|
|
throw Error("题库中仍有启用的内容层级,不能归档题库。", "question_bank_nodes_not_archived");
|
|
|
|
bank.Status = QuestionBankStatus.Archived;
|
|
AddAudit(dbContext, actor, "platform.question_bank.archived", bank.Id, new { bank.Name });
|
|
await dbContext.SaveChangesAsync(token);
|
|
return ToBankItem(bank, 0, 0);
|
|
}, cancellationToken);
|
|
}
|
|
|
|
}
|