using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Api.OpenApi; using Tiku.Application.Auth; using Tiku.Application.PlatformAdmin; using Tiku.Application.Security; using Tiku.Domain.Platform; namespace Tiku.Api.Controllers; [ApiController] [Tags("平台端-平台管理")] [Authorize(Policy = BackendPermissions.PlatformDashboardView)] [Produces("application/json")] [Route("api/platform")] public sealed class PlatformAdminController( IPlatformAdminService platformAdminService, IPlatformApprovalService approvalService, IAuthAdministrationService authAdministrationService, ICurrentUser currentUser) : ControllerBase { [HttpGet("overview")] [EndpointSummary("查询平台经营概览")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Overview(CancellationToken cancellationToken) { return Ok(await platformAdminService.GetOverviewAsync(ResolveActor(), cancellationToken)); } [HttpGet("tenants")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("查询平台租户列表")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Tenants( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetTenantsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpPost("tenants")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("创建平台租户")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> CreateTenant( CreatePlatformTenantDto request, [FromHeader(Name = "Idempotency-Key")] [Required] string idempotencyKey, CancellationToken cancellationToken) { return Ok(await platformAdminService.CreateTenantAsync( ResolveActor(), request.ToCommand(idempotencyKey), cancellationToken)); } [HttpPut("tenants/{tenantId:guid}/primary-domain")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("更正租户主域名")] public Task ReplacePrimaryDomain( Guid tenantId, ReplacePlatformPrimaryDomainDto request, CancellationToken cancellationToken) { return platformAdminService.ReplacePrimaryDomainAsync(ResolveActor(), request.ToCommand(tenantId), cancellationToken); } [HttpPost("tenants/{tenantId:guid}/owner-activation-links")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("一次性领取租户 Owner 激活链接")] [EndpointDescription("仅在主域名和试用/订阅有效时签发;幂等重放不会再次返回明文链接。")] public Task IssueOwnerActivationLink( Guid tenantId, IssuePlatformOwnerActivationLinkDto request, [FromHeader(Name = "Idempotency-Key")] [Required] string idempotencyKey, CancellationToken cancellationToken) { return platformAdminService.IssueOwnerActivationLinkAsync( ResolveActor(), request.ToCommand(tenantId, idempotencyKey), cancellationToken); } [HttpGet("tenants/{tenantId:guid}/billing-policy")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("查询租户收款策略")] public Task GetBillingPolicy(Guid tenantId, CancellationToken cancellationToken) { return platformAdminService.GetTenantBillingPolicyAsync(ResolveActor(), tenantId, cancellationToken); } [HttpPut("tenants/{tenantId:guid}/billing-policy")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("更新租户收款策略")] public Task UpsertBillingPolicy( Guid tenantId, UpsertTenantBillingPolicyDto request, CancellationToken cancellationToken) { return platformAdminService.UpsertTenantBillingPolicyAsync(ResolveActor(), request.ToCommand(tenantId), cancellationToken); } [HttpGet("tenants/detail")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("查询平台租户详情")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> TenantDetail( [FromQuery] Guid tenantId, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetTenantDetailAsync(ResolveActor(), tenantId, cancellationToken)); } [HttpPatch("tenants/status")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("更新租户业务状态")] [PlatformOperationRisk("critical", PlatformApprovalPolicyCodes.TenantArchive)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task> TenantStatus( UpdatePlatformTenantStatusDto request, [FromHeader(Name = "Idempotency-Key")] [Required] string idempotencyKey, CancellationToken cancellationToken) { var result = await approvalService.UpdateTenantStatusAsync(ResolveActor(), request.ToCommand(), idempotencyKey, cancellationToken); return result.ExecutionStatus == "pending_approval" ? Accepted(result) : Ok(result); } [HttpPut("tenants/billing-profile")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("保存租户账务与开票资料")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> BillingProfile( UpsertPlatformTenantBillingProfileDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.UpsertTenantBillingProfileAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpGet("domains")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("查询租户域名状态")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Domains( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetDomainsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpPost("domains/{domainId:guid}/recheck")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("重新触发租户域名 DNS/TLS 验证")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> RecheckDomain( Guid domainId, CancellationToken cancellationToken) { return Ok(await platformAdminService.RecheckDomainAsync(ResolveActor(), domainId, cancellationToken)); } [HttpGet("staff")] [Authorize(Policy = BackendPermissions.PlatformStaffManage)] [EndpointSummary("查询平台员工列表")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> Staff( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetStaffAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpPut("staff")] [Authorize(Policy = BackendPermissions.PlatformStaffManage)] [EndpointSummary("创建或更新平台员工")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpsertStaff( UpsertPlatformStaffDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.UpsertStaffAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpPatch("staff/status")] [Authorize(Policy = BackendPermissions.PlatformStaffManage)] [EndpointSummary("启用或禁用平台员工")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> StaffStatus( UpdatePlatformStaffStatusDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.UpdateStaffStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpPost("staff/{userId:guid}/password-reset")] [Authorize(Policy = BackendPermissions.PlatformStaffManage)] [EndpointSummary("为平台员工设置一次性临时密码")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task ResetStaffPassword( Guid userId, AdministrativePasswordResetDto request, CancellationToken cancellationToken) { var actor = ResolveActor(); await authAdministrationService.ResetPasswordAsync( new AdministrativePasswordResetRequest( actor.UserId, userId, null, request.TemporaryPassword, request.Reason), cancellationToken); return NoContent(); } [HttpGet("audit-logs")] [Authorize(Policy = BackendPermissions.PlatformAuditView)] [EndpointSummary("查询平台审计日志")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AuditLogs( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetAuditLogsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpGet("audit-alerts")] [Authorize(Policy = BackendPermissions.PlatformAuditView)] [EndpointSummary("查询平台审计告警")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AuditAlerts( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetAuditAlertsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpPost("audit-alerts/status")] [Authorize(Policy = BackendPermissions.PlatformAuditView)] [EndpointSummary("更新平台审计告警状态")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AuditAlertStatus( UpdatePlatformAuditAlertStatusDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.UpdateAuditAlertStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpGet("saas/dunning/channels")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知渠道")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> BillingDunningChannels( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetBillingDunningChannelsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpPut("saas/dunning/channels")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("创建或更新平台催缴通知渠道")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> UpsertBillingDunningChannel( UpsertPlatformBillingDunningChannelDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.UpsertBillingDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpPost("saas/dunning/channels/disable")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("禁用平台催缴通知渠道")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> DisableBillingDunningChannel( DisablePlatformBillingDunningChannelDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.DisableBillingDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpGet("saas/dunning/events")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知事件")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> BillingDunningEvents( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetBillingDunningEventsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } [HttpGet("saas/dunning/events/detail")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知事件详情")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> BillingDunningEventDetail( [FromQuery] Guid eventId, CancellationToken cancellationToken) { return Ok(await platformAdminService.GetBillingDunningEventDetailAsync(ResolveActor(), eventId, cancellationToken)); } [HttpPost("saas/dunning/events/retry")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("重新标记平台催缴通知事件待发送")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> RetryBillingDunningEvent( RetryPlatformBillingDunningEventDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.RetryBillingDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpPost("saas/dunning/events/acknowledge")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("人工确认平台催缴通知事件")] public async Task> AcknowledgeBillingDunningEvent( ResolvePlatformBillingDunningEventDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.AcknowledgeBillingDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } [HttpPost("saas/dunning/events/ignore")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("人工忽略平台催缴通知事件")] public async Task> IgnoreBillingDunningEvent( ResolvePlatformBillingDunningEventDto request, CancellationToken cancellationToken) { return Ok(await platformAdminService.IgnoreBillingDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } private PlatformAdminActor ResolveActor() { if (currentUser.UserId is not { } userId) throw new PlatformAdminException("Platform admin actor was not resolved.", "platform_access_denied"); return new PlatformAdminActor(userId); } }