using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Api.Security; using Tiku.Application.Profile; using Tiku.Application.Security; namespace Tiku.Api.Controllers; [ApiController] [Tags("学生端-个人中心")] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] [Produces("application/json")] [Route("api/student/profile")] public sealed class ProfileController( IProfileService profileService, ICurrentUser currentUser, ITenantContext currentTenant) : ControllerBase { [HttpGet("me")] [EndpointSummary("获取当前学生资料")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Me( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetMeAsync( ResolveActor(), new ProfileQuery(query.RecentLimit), cancellationToken)); } [HttpPatch("me")] [EndpointSummary("更新当前学生资料")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpdateMe( UpdateProfileDto request, CancellationToken cancellationToken) { return Ok(await profileService.UpdateMeAsync( ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpGet("exam-countdowns")] [EndpointSummary("查询考试倒计时")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> ExamCountdowns( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetExamCountdownsAsync( ResolveActor(), new ProfileQuery(Limit: query.Limit), cancellationToken)); } [HttpGet("notifications")] [EndpointSummary("查询用户通知")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Notifications( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetNotificationsAsync( ResolveActor(), new ProfileNotificationQuery(query.Status, query.Limit), cancellationToken)); } [HttpPost("notifications/status")] [EndpointSummary("更新通知状态")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpdateNotificationStatus( NotificationStatusDto request, CancellationToken cancellationToken) { return Ok(await profileService.UpdateNotificationStatusAsync( ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpGet("badges")] [EndpointSummary("查询徽章列表")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Badges( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetBadgesAsync( ResolveActor(), new BadgeQuery(query.IncludeLocked, query.Category, query.Limit), cancellationToken)); } [HttpGet("feedbacks")] [EndpointSummary("查询反馈记录")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> Feedbacks( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetFeedbacksAsync( ResolveActor(), new FeedbackQuery(query.Status, query.Type, query.Limit), cancellationToken)); } [HttpPost("check-in")] [RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("每日签到")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> CheckIn(CancellationToken cancellationToken) { return Ok(await profileService.CheckInAsync(ResolveActor(), cancellationToken)); } [HttpGet("score-events")] [RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("查询积分流水")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> ScoreEvents( [FromQuery] ProfileQueryDto query, CancellationToken cancellationToken) { return Ok(await profileService.GetScoreEventsAsync( ResolveActor(), new ProfileScoreEventQuery(query.Limit, query.SourceType, query.From, query.To), cancellationToken)); } [HttpPost("feedbacks")] [EndpointSummary("提交意见反馈")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> SubmitFeedback( SubmitFeedbackDto request, CancellationToken cancellationToken) { return Ok(await profileService.SubmitFeedbackAsync( ResolveActor(), request.ToCommand(), cancellationToken)); } private ProfileActor ResolveActor() { if (currentTenant.TenantId is null || currentUser.UserId is null) throw new ProfileException("Current profile actor was not resolved.", "profile_access_denied"); return new ProfileActor(currentTenant.TenantId.Value, currentUser.UserId.Value); } }