using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Api.Security; using Tiku.Application.Content; using Tiku.Application.Security; namespace Tiku.Api.Controllers; [ApiController] [Tags("租户端-内容 V2 录题")] [Authorize(Policy = BackendPermissions.TenantContentManage)] [Authorize(Policy = TikuPolicies.TenantAllDataScope)] [RequireSaasFeature(SaasFeatureCatalog.PrivateQuestionBank)] [Produces("application/json")] [Route("api/tenant/content-v2")] public sealed class ContentV2AuthoringController( IContentV2AuthoringService service, IContentReleaseCompiler releaseCompiler, DirectContentActorResolver actorResolver) : ControllerBase { [HttpPost("questions/duplicate-search")] [EndpointSummary("查询当前租户和平台公共范围内的精确/相似题")] public Task SearchDuplicates( QuestionDuplicateSearchDto request, CancellationToken cancellationToken) { return service.SearchDuplicatesAsync(Actor(), request.Draft.ToCommand(), request.Limit, cancellationToken); } [HttpPost("questions/execute")] [EndpointSummary("按复用、新建、变体、跳过或人工复核决策执行录题")] public Task Execute( ExecuteQuestionAuthoringDto request, CancellationToken cancellationToken) { return service.ExecuteAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPost("questions/{questionAssetId:guid}/revisions")] [EndpointSummary("为租户题目资产创建不可变 Revision")] public Task CreateRevision( Guid questionAssetId, CreateQuestionRevisionV2Dto request, CancellationToken cancellationToken) { return service.CreateRevisionAsync( Actor(), questionAssetId, request.Draft.ToCommand(), request.PublishAsset, cancellationToken); } [HttpPost("questions/placements")] [EndpointSummary("把租户私题或平台公共题放置到大纲并配置适用规则")] public Task CreatePlacement( CreateQuestionPlacementDto request, CancellationToken cancellationToken) { return service.CreatePlacementAsync(Actor(), request.ToCommand(), cancellationToken); } [HttpPost("imports/questions/preview")] [EndpointSummary("预览 Excel/Word 解析后的题目行并给出精确复用或人工复核建议")] public Task PreviewImport( PreviewQuestionImportV2Dto request, CancellationToken cancellationToken) { return service.PreviewImportAsync( Actor(), request.Rows.Select(row => (row.RowKey, row.Draft.ToCommand())).ToArray(), cancellationToken); } [HttpPost("imports/questions/execute")] [EndpointSummary("按逐行复用、新建、变体、跳过或人工复核决策执行导入")] public Task ExecuteImport( ExecuteQuestionImportV2Dto request, CancellationToken cancellationToken) { return service.ExecuteImportAsync( Actor(), request.Rows.Select(row => new ExecuteQuestionImportRow( row.RowKey, row.Command.ToCommand())).ToArray(), cancellationToken); } [HttpPost("releases/publish")] [EndpointSummary("校验并发布不可变内容 Release")] public Task PublishRelease( PublishContentReleaseDto request, CancellationToken cancellationToken) { var actor = Actor(); return releaseCompiler.PublishAsync( new PublishContentReleaseCommand( actor.TenantId, actor.UserId, request.CurriculumVersionId, request.Name), cancellationToken); } private ContentAuthorActor Actor() { var actor = actorResolver.Resolve(); return new ContentAuthorActor(actor.TenantId, actor.UserId); } }