using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Application.Assets; using Tiku.Application.Security; using Tiku.Application.Tenancy; namespace Tiku.Api.Controllers; [ApiController] [Tags("学生端-资源访问")] [AllowAnonymous] [Produces("application/json")] [Route("api/student/assets")] public sealed class AssetsController( IAssetAccessService assetAccessService, ITenantContext currentTenant, ICurrentUser currentUser, ITenantDirectory tenantDirectory) : ControllerBase { [HttpGet("{assetId:guid}/download")] [EndpointSummary("获取资源下载地址")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public async Task> Download( Guid assetId, [FromQuery] AssetAccessQueryDto query, CancellationToken cancellationToken) { var result = await assetAccessService.DownloadAsync( ToRequest(assetId, query, await ResolveTenantIdAsync(query.TenantCode, cancellationToken)), cancellationToken); return Ok(AssetAccessResponseDto.FromApplication(result)); } [HttpGet("{assetId:guid}/preview")] [EndpointSummary("获取资源预览地址")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public async Task> Preview( Guid assetId, [FromQuery] AssetAccessQueryDto query, CancellationToken cancellationToken) { var result = await assetAccessService.PreviewAsync( ToRequest(assetId, query, await ResolveTenantIdAsync(query.TenantCode, cancellationToken)), cancellationToken); return Ok(AssetAccessResponseDto.FromApplication(result)); } private AssetAccessRequest ToRequest(Guid assetId, AssetAccessQueryDto query, Guid tenantId) { return new AssetAccessRequest( tenantId, assetId, currentUser.UserId, HttpContext.Connection.RemoteIpAddress?.ToString(), Request.Headers.UserAgent.ToString(), query.ExpiresInSeconds.HasValue ? TimeSpan.FromSeconds(query.ExpiresInSeconds.Value) : null); } private async Task ResolveTenantIdAsync(string? tenantCode, CancellationToken cancellationToken) { if (currentTenant.TenantId.HasValue) return currentTenant.TenantId.Value; var resolvedTenantCode = tenantCode ?? Request.Headers["x-tenant-code"].FirstOrDefault(); if (string.IsNullOrWhiteSpace(resolvedTenantCode)) throw new TenantNotFoundException(); var tenant = await tenantDirectory.FindByCodeAsync(resolvedTenantCode, cancellationToken); return tenant?.TenantId ?? throw new TenantNotFoundException(); } }