feat: Add tags and endpoint summaries to various controllers for better API documentation

- Added tags to BrowserAuthController for browser authentication endpoints.
- Added tags to CatalogController for public catalog access.
- Added tags to CommerceController for student transaction operations.
- Added tags to CommissionController for tenant commission management.
- Added tags to CrmController for tenant CRM functionalities.
- Added tags to HealthController for system health checks.
- Added tags to LearningController for student learning resources.
- Added tags to MeController for current user information.
- Added tags to PlatformAdminController for platform management.
- Introduced PlatformBackofficeController for backend permissions management.
- Added tags to PlatformBillingCallbackController for billing callbacks.
- Added tags to PlatformPaymentSettingsController for payment settings management.
- Added tags to PlatformSaasController for SaaS package management.
- Added tags to PlatformTenantCapabilitiesController for tenant capabilities.
- Added tags to PointsController for student points management.
- Added tags to ProfileController for student profile management.
- Added tags to QuestionVideosController for question video resources.
- Added tags to ReferralController for referral growth management.
- Added tags to RuntimeController for runtime configurations.
- Added tags to ScorelineController for scoreline management.
- Added tags to TaxonomyController for category management.
- Added tags to TenantAdminDirectController for tenant operations management.
- Introduced TenantBackofficeController for tenant backend permissions.
- Added tags to TenantBillingController for tenant billing operations.
- Added tags to TenantCommerceController for tenant commerce operations.
- Added tags to TenantContentController for tenant content management.
- Added tags to TenantContentDirectController for direct content management.
- Added tags to TenantFrontendConfigController for frontend configurations.
- Added tags to TenantOnboardingController for onboarding guidance.
- Added tags to TenantPublicController for public tenant configurations.
- Added tags to TenantsController for current tenant information.
- Added tags to VideosController for student video resources.
This commit is contained in:
2026-07-29 16:16:27 +08:00
parent 11c6c74634
commit 4bea745b79
37 changed files with 490 additions and 138 deletions

View File

@@ -8,166 +8,397 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Contracts;
/// <summary>
/// 平台租户能力通用查询参数。
/// </summary>
public class PlatformCapabilityQueryDto
{
/// <summary>
/// 租户 ID。
/// </summary>
public Guid? TenantId { get; set; }
/// <summary>
/// 状态。
/// </summary>
[StringLength(50)]
public string? Status { get; set; }
/// <summary>
/// 返回数量上限。
/// </summary>
[Range(1, 500)]
public int Limit { get; set; } = 100;
public PlatformCapabilityQuery ToQuery() => new(TenantId, Status, Limit);
}
/// <summary>
/// 新增或更新租户 CRM 配置请求。
/// </summary>
public sealed class UpsertPlatformCrmConfigDto
{
/// <summary>
/// ID。
/// </summary>
public Guid? Id { get; set; }
/// <summary>
/// 租户 ID。
/// </summary>
[Required]
public Guid TenantId { get; set; }
/// <summary>
/// 是否启用。
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// 回调地址。
/// </summary>
[StringLength(2048)]
public string? Url { get; set; }
/// <summary>
/// 密钥引用。
/// </summary>
[StringLength(300)]
public string? SecretRef { get; set; }
/// <summary>
/// 表单名称。
/// </summary>
[StringLength(200)]
public string? FormName { get; set; }
/// <summary>
/// 考试类型。
/// </summary>
[StringLength(100)]
public string? ExamType { get; set; }
/// <summary>
/// 超时时长,单位为秒。
/// </summary>
[Range(1, 120)]
public int? TimeoutSeconds { get; set; }
/// <summary>
/// 延迟秒数。
/// </summary>
[Range(0, 86400)]
public int? DelaySeconds { get; set; }
/// <summary>
/// 分配模式。
/// </summary>
[StringLength(50)]
public string? AssignmentMode { get; set; }
/// <summary>
/// 分配池。
/// </summary>
public JsonElement? AssignmentPool { get; set; }
/// <summary>
/// 分配配置。
/// </summary>
public JsonElement? AssignmentConfig { get; set; }
public UpsertPlatformCrmConfigCommand ToCommand() =>
new(Id, TenantId, Enabled, Url, SecretRef, FormName, ExamType, TimeoutSeconds, DelaySeconds, AssignmentMode, AssignmentPool, AssignmentConfig);
}
/// <summary>
/// 重试租户 CRM 线索推送请求。
/// </summary>
public sealed class RetryPlatformCrmLeadDto
{
/// <summary>
/// 队列任务 ID。
/// </summary>
[Required]
public Guid QueueId { get; set; }
/// <summary>
/// 备注。
/// </summary>
[StringLength(500)]
public string? Note { get; set; }
public PlatformCrmLeadRetryCommand ToCommand() => new(QueueId, Note);
}
/// <summary>
/// 平台租户 CRM 日志查询参数。
/// </summary>
public sealed class PlatformCrmLogQueryDto : PlatformCapabilityQueryDto
{
/// <summary>
/// 队列任务 ID。
/// </summary>
public Guid? QueueId { get; set; }
}
/// <summary>
/// 新增或更新租户短信渠道请求。
/// </summary>
public sealed class UpsertPlatformSmsChannelDto
{
/// <summary>
/// ID。
/// </summary>
public Guid? Id { get; set; }
/// <summary>
/// 租户 ID。
/// </summary>
[Required]
public Guid TenantId { get; set; }
/// <summary>
/// 服务提供方。
/// </summary>
[Required, StringLength(80)]
public string Provider { get; set; } = "generic";
/// <summary>
/// 名称。
/// </summary>
[Required, StringLength(200)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 短信签名。
/// </summary>
[Required, StringLength(100)]
public string Signature { get; set; } = string.Empty;
/// <summary>
/// 使用场景。
/// </summary>
[Required, StringLength(100)]
public string Scene { get; set; } = "login";
/// <summary>
/// 状态。
/// </summary>
public TenantExternalProviderStatus Status { get; set; } = TenantExternalProviderStatus.Disabled;
/// <summary>
/// 密钥引用。
/// </summary>
[StringLength(300)]
public string? SecretRef { get; set; }
/// <summary>
/// 优先级。
/// </summary>
public int? Priority { get; set; }
/// <summary>
/// 月度配额。
/// </summary>
[Range(0, int.MaxValue)]
public int? MonthlyQuota { get; set; }
/// <summary>
/// 公开配置内容。
/// </summary>
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
/// <summary>
/// 扩展元数据。
/// </summary>
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformSmsChannelCommand ToCommand() =>
new(Id, TenantId, Provider, Name, Signature, Scene, Status, SecretRef, Priority, MonthlyQuota, ConfigPublic, Metadata);
}
/// <summary>
/// 新增或更新租户短信模板请求。
/// </summary>
public sealed class UpsertPlatformSmsTemplateDto
{
/// <summary>
/// ID。
/// </summary>
public Guid? Id { get; set; }
/// <summary>
/// 租户 ID。
/// </summary>
[Required]
public Guid TenantId { get; set; }
/// <summary>
/// 短信渠道 ID。
/// </summary>
[Required]
public Guid ChannelId { get; set; }
/// <summary>
/// 编码。
/// </summary>
[Required, StringLength(120)]
public string Code { get; set; } = string.Empty;
/// <summary>
/// 名称。
/// </summary>
[Required, StringLength(200)]
public string Name { get; set; } = string.Empty;
/// <summary>
/// 类型。
/// </summary>
public SmsTemplateType Type { get; set; } = SmsTemplateType.Notification;
/// <summary>
/// 审核状态。
/// </summary>
public SmsTemplateAuditStatus AuditStatus { get; set; } = SmsTemplateAuditStatus.Draft;
/// <summary>
/// 状态。
/// </summary>
public SmsTemplateStatus Status { get; set; } = SmsTemplateStatus.Active;
/// <summary>
/// 渠道模板编码。
/// </summary>
[StringLength(120)]
public string? ProviderTemplateCode { get; set; }
/// <summary>
/// 模板内容。
/// </summary>
[Required, StringLength(1000)]
public string Content { get; set; } = string.Empty;
/// <summary>
/// Remark。
/// </summary>
[StringLength(500)]
public string? Remark { get; set; }
/// <summary>
/// 扩展元数据。
/// </summary>
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformSmsTemplateCommand ToCommand() =>
new(Id, TenantId, ChannelId, Code, Name, Type, AuditStatus, Status, ProviderTemplateCode, Content, Remark, Metadata);
}
/// <summary>
/// 新增或更新平台支付应用请求。
/// </summary>
public sealed class UpsertPlatformPaymentAppDto
{
/// <summary>
/// ID。
/// </summary>
public Guid? Id { get; set; }
/// <summary>
/// 支付应用编码。
/// </summary>
[Required, StringLength(100)]
public string AppCode { get; set; } = string.Empty;
/// <summary>
/// 支付应用名称。
/// </summary>
[Required, StringLength(200)]
public string AppName { get; set; } = string.Empty;
/// <summary>
/// 状态。
/// </summary>
public PlatformPaymentAppStatus Status { get; set; } = PlatformPaymentAppStatus.Disabled;
/// <summary>
/// 结算模式。
/// </summary>
[StringLength(100)]
public string SettlementMode { get; set; } = "PlatformCollect";
/// <summary>
/// 说明。
/// </summary>
[StringLength(500)]
public string? Description { get; set; }
/// <summary>
/// 扩展元数据。
/// </summary>
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformPaymentAppCommand ToCommand() =>
new(Id, AppCode, AppName, Status, SettlementMode, Description, Metadata);
}
/// <summary>
/// 新增或更新平台支付渠道请求。
/// </summary>
public sealed class UpsertPlatformPaymentChannelDto
{
/// <summary>
/// ID。
/// </summary>
public Guid? Id { get; set; }
/// <summary>
/// 支付应用 ID。
/// </summary>
[Required]
public Guid AppId { get; set; }
/// <summary>
/// 服务提供方。
/// </summary>
[Required, StringLength(80)]
public string Provider { get; set; } = "manual";
/// <summary>
/// 模式。
/// </summary>
[StringLength(100)]
public string Mode { get; set; } = "PlatformCollect";
/// <summary>
/// 状态。
/// </summary>
public PlatformPaymentChannelStatus Status { get; set; } = PlatformPaymentChannelStatus.Disabled;
/// <summary>
/// 显示名称。
/// </summary>
[Required, StringLength(200)]
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// 密钥引用。
/// </summary>
[StringLength(300)]
public string? SecretRef { get; set; }
/// <summary>
/// 回调路径。
/// </summary>
[StringLength(500)]
public string? CallbackPath { get; set; }
/// <summary>
/// 优先级。
/// </summary>
public int? Priority { get; set; }
/// <summary>
/// 公开配置内容。
/// </summary>
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
/// <summary>
/// 扩展元数据。
/// </summary>
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformPaymentChannelCommand ToCommand() =>
new(Id, AppId, Provider, Mode, Status, DisplayName, SecretRef, CallbackPath, Priority, ConfigPublic, Metadata);
}
/// <summary>
/// 新增或更新租户支付应用请求。
/// </summary>
public sealed class UpsertPlatformTenantPaymentAppDto
{
/// <summary>
/// 租户 ID。
/// </summary>
[Required]
public Guid TenantId { get; set; }
/// <summary>
/// 服务提供方。
/// </summary>
[Required, StringLength(80)]
public string Provider { get; set; } = "manual";
/// <summary>
/// 状态。
/// </summary>
public TenantExternalProviderStatus Status { get; set; } = TenantExternalProviderStatus.Disabled;
/// <summary>
/// 显示名称。
/// </summary>
[StringLength(200)]
public string? DisplayName { get; set; }
/// <summary>
/// 密钥引用。
/// </summary>
[StringLength(300)]
public string? SecretRef { get; set; }
/// <summary>
/// 优先级。
/// </summary>
public int? Priority { get; set; }
/// <summary>
/// 公开配置内容。
/// </summary>
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
/// <summary>
/// 扩展元数据。
/// </summary>
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertTenantExternalProviderCommand ToCommand() =>

View File

@@ -10,6 +10,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-资源访问")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/assets")]

View File

@@ -13,6 +13,7 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-认证")]
[Route("api/auth")]
[Produces("application/json")]
public sealed class AuthController(

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-后台任务")]
[Route("api/backoffice/tenant/jobs")]
[Authorize(Policy = BackendPermissions.TenantJobManage)]
public sealed class BackgroundJobsController(

View File

@@ -1,138 +0,0 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Contracts;
using Tiku.Application.Backoffice;
using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Route("api/backoffice")]
public sealed class BackofficeController(
IBackofficeService backofficeService,
ICurrentAccessContext currentAccessContext) : ControllerBase
{
[HttpGet("tenant/ui-bootstrap")]
[Authorize(Policy = TikuPolicies.TenantBackofficeBootstrap)]
[EndpointSummary("查询租户后台菜单与权限")]
[EndpointDescription("返回当前租户管理员可见的后台菜单、权限和模块启用状态。")]
[ProducesResponseType<BackofficeUiBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeUiBootstrap>> GetTenantUiBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetTenantUiBootstrapAsync(
await currentAccessContext.GetAsync(cancellationToken),
cancellationToken));
}
[HttpGet("tenant/bootstrap")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("查询租户角色管理初始化数据")]
[ProducesResponseType<BackofficeBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeBootstrap>> GetTenantBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetTenantBootstrapAsync(await ResolveTenantActorAsync(cancellationToken), cancellationToken));
}
[HttpGet("platform/ui-bootstrap")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[EndpointSummary("查询平台后台菜单与权限")]
[EndpointDescription("返回当前平台管理员可见的后台菜单、权限和模块启用状态。")]
[ProducesResponseType<BackofficeUiBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeUiBootstrap>> GetPlatformUiBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetPlatformUiBootstrapAsync(
await currentAccessContext.GetAsync(cancellationToken),
cancellationToken));
}
[HttpPost("tenant/roles")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("创建或更新租户后台角色")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> UpsertTenantRole(
UpsertBackofficeRoleDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.UpsertTenantRoleAsync(await ResolveTenantActorAsync(cancellationToken), request.ToCommand(), cancellationToken));
}
[HttpPut("tenant/roles/{roleId:guid}/bindings")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("替换租户后台角色权限绑定")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> ReplaceTenantRoleBindings(
Guid roleId,
ReplaceRoleBindingsDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.ReplaceTenantRoleBindingsAsync(await ResolveTenantActorAsync(cancellationToken), request.ToCommand(roleId), cancellationToken));
}
[HttpPut("tenant/users/{userId:guid}/roles")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("替换租户用户后台角色")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> ReplaceTenantUserRoles(
Guid userId,
ReplaceUserRolesDto request,
CancellationToken cancellationToken)
{
await backofficeService.ReplaceTenantUserRolesAsync(await ResolveTenantActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
return NoContent();
}
[HttpGet("platform/bootstrap")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("查询平台角色管理初始化数据")]
[ProducesResponseType<BackofficeBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeBootstrap>> GetPlatformBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetPlatformBootstrapAsync(await ResolvePlatformActorAsync(cancellationToken), cancellationToken));
}
[HttpPost("platform/roles")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("创建或更新平台后台角色")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> UpsertPlatformRole(
UpsertBackofficeRoleDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.UpsertPlatformRoleAsync(await ResolvePlatformActorAsync(cancellationToken), request.ToCommand(), cancellationToken));
}
[HttpPut("platform/roles/{roleId:guid}/bindings")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("替换平台后台角色权限绑定")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> ReplacePlatformRoleBindings(
Guid roleId,
ReplaceRoleBindingsDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.ReplacePlatformRoleBindingsAsync(await ResolvePlatformActorAsync(cancellationToken), request.ToCommand(roleId), cancellationToken));
}
[HttpPut("platform/users/{userId:guid}/roles")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("替换平台用户后台角色")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> ReplacePlatformUserRoles(
Guid userId,
ReplaceUserRolesDto request,
CancellationToken cancellationToken)
{
await backofficeService.ReplacePlatformUserRolesAsync(await ResolvePlatformActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
return NoContent();
}
private async Task<BackofficeActor> ResolveTenantActorAsync(CancellationToken cancellationToken)
{
return BackofficeActor.FromTenantAccess(await currentAccessContext.GetAsync(cancellationToken));
}
private async Task<BackofficeActor> ResolvePlatformActorAsync(CancellationToken cancellationToken)
{
return BackofficeActor.FromPlatformAccess(await currentAccessContext.GetAsync(cancellationToken));
}
}

View File

@@ -14,6 +14,7 @@ using Tiku.Infrastructure.Content;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-浏览器认证")]
[Route("api/browser-auth")]
[Produces("application/json")]
public sealed class BrowserAuthController(
@@ -28,6 +29,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[EnableRateLimiting(AuthRateLimitPolicies.Sms)]
[HttpPost("sms/send")]
[EndpointSummary("发送浏览器短信验证码")]
public async Task<ActionResult<SmsSendResult>> SendSmsCode(
[FromBody] SendSmsCodeDto request,
CancellationToken cancellationToken)
@@ -52,6 +54,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("login/password")]
[EndpointSummary("浏览器手机号密码登录")]
public async Task<ActionResult<object>> LoginWithPassword(
[FromBody] PasswordLoginDto request,
CancellationToken cancellationToken)
@@ -73,6 +76,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[EnableRateLimiting(AuthRateLimitPolicies.Sms)]
[HttpPost("login/sms")]
[EndpointSummary("浏览器短信验证码登录")]
public async Task<ActionResult<object>> LoginWithSms(
[FromBody] SmsLoginDto request,
CancellationToken cancellationToken)
@@ -91,6 +95,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("oauth/wechat")]
[EndpointSummary("浏览器微信网页 OAuth 登录")]
public async Task<ActionResult<object>> LoginWithWechatWeb(
[FromBody] OAuthCodeDto request,
CancellationToken cancellationToken)
@@ -108,6 +113,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("oauth/wechat-miniapp")]
[EndpointSummary("浏览器微信小程序登录")]
public async Task<ActionResult<object>> LoginWithWechatMiniApp(
[FromBody] OAuthCodeDto request,
CancellationToken cancellationToken)
@@ -125,6 +131,8 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("refresh")]
[EndpointSummary("刷新浏览器登录会话")]
[EndpointDescription("读取浏览器 refresh cookie轮换会话并重新写入认证 cookie。")]
public async Task<ActionResult<object>> Refresh(CancellationToken cancellationToken)
{
var refreshToken = Request.Cookies[BrowserAuthOptions.RefreshCookie];
@@ -139,6 +147,8 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("logout")]
[EndpointSummary("退出浏览器登录")]
[EndpointDescription("撤销浏览器 refresh cookie 对应会话,并清理 access、refresh 与 CSRF cookie。")]
public async Task<IActionResult> Logout(CancellationToken cancellationToken)
{
var refreshToken = Request.Cookies[BrowserAuthOptions.RefreshCookie];
@@ -152,6 +162,7 @@ public sealed class BrowserAuthController(
[Authorize]
[HttpPost("logout-all")]
[EndpointSummary("退出全部浏览器登录会话")]
public async Task<IActionResult> LogoutAll(CancellationToken cancellationToken)
{
if (currentUser.UserId is not { } userId) return Unauthorized();

View File

@@ -14,6 +14,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-公开目录")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/catalog")]

View File

@@ -12,6 +12,7 @@ using Tiku.Domain.Commerce;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-交易")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
[Produces("application/json")]

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-佣金")]
[Authorize(Policy = BackendPermissions.TenantCommissionManage)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.ReferralCommission)]
[Produces("application/json")]

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-CRM")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Crm)]
[Produces("application/json")]

View File

@@ -11,6 +11,7 @@ using Tiku.Infrastructure.Messaging;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-系统健康")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/health")]

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-学习")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)]
[Produces("application/json")]

View File

@@ -8,6 +8,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-当前用户")]
[Authorize(Policy = TikuPolicies.AuthenticatedUser)]
[Route("api/me")]
public sealed class MeController(

View File

@@ -8,6 +8,7 @@ using Tiku.Domain.Platform;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-平台管理")]
[Authorize(Policy = BackendPermissions.PlatformDashboardView)]
[Produces("application/json")]
[Route("api/platform-admin")]

View File

@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Contracts;
using Tiku.Application.Backoffice;
using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-后台权限")]
[Route("api/backoffice/platform")]
public sealed class PlatformBackofficeController(
IBackofficeService backofficeService,
ICurrentAccessContext currentAccessContext) : ControllerBase
{
[HttpGet("ui-bootstrap")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[EndpointSummary("查询平台后台菜单与权限")]
[EndpointDescription("返回当前平台管理员可见的后台菜单、权限和模块启用状态。")]
[ProducesResponseType<BackofficeUiBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeUiBootstrap>> GetUiBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetPlatformUiBootstrapAsync(
await currentAccessContext.GetAsync(cancellationToken),
cancellationToken));
}
[HttpGet("bootstrap")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("查询平台角色管理初始化数据")]
[ProducesResponseType<BackofficeBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeBootstrap>> GetBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetPlatformBootstrapAsync(await ResolveActorAsync(cancellationToken), cancellationToken));
}
[HttpPost("roles")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("创建或更新平台后台角色")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> UpsertRole(
UpsertBackofficeRoleDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.UpsertPlatformRoleAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(), cancellationToken));
}
[HttpPut("roles/{roleId:guid}/bindings")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("替换平台后台角色权限绑定")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> ReplaceRoleBindings(
Guid roleId,
ReplaceRoleBindingsDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.ReplacePlatformRoleBindingsAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(roleId), cancellationToken));
}
[HttpPut("users/{userId:guid}/roles")]
[Authorize(Policy = BackendPermissions.PlatformRoleManage)]
[EndpointSummary("替换平台用户后台角色")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> ReplaceUserRoles(
Guid userId,
ReplaceUserRolesDto request,
CancellationToken cancellationToken)
{
await backofficeService.ReplacePlatformUserRolesAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
return NoContent();
}
private async Task<BackofficeActor> ResolveActorAsync(CancellationToken cancellationToken)
{
return BackofficeActor.FromPlatformAccess(await currentAccessContext.GetAsync(cancellationToken));
}
}

View File

@@ -7,12 +7,15 @@ using Tiku.Application.PlatformBilling;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-账务回调")]
[Route("api/platform-billing/callbacks")]
public sealed class PlatformBillingCallbackController(
IPlatformBillingNotificationService notificationService) : ControllerBase
{
[AllowAnonymous]
[HttpPost("{provider}")]
[EndpointSummary("处理平台账务支付回调")]
[EndpointDescription("接收平台账务支付渠道回调,按 provider 归一化后写入通知处理流程。")]
public async Task<IActionResult> Notify(string provider, CancellationToken cancellationToken)
{
Request.EnableBuffering();

View File

@@ -8,6 +8,7 @@ using Tiku.Domain.Platform;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-支付设置")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[Produces("application/json")]
[Route("api/platform-admin/payment-settings")]
@@ -16,36 +17,43 @@ public sealed class PlatformPaymentSettingsController(
ICurrentUser currentUser) : ControllerBase
{
[HttpGet("apps")]
[EndpointSummary("查询平台支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<IReadOnlyCollection<PlatformPaymentApp>> Apps(string? status, int limit = 100, CancellationToken cancellationToken = default) =>
paymentSettingsService.GetAppsAsync(Actor(), status, limit, cancellationToken);
[HttpPut("apps")]
[EndpointSummary("新增或更新平台支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task<PlatformPaymentApp> UpsertApp(UpsertPlatformPaymentAppDto request, CancellationToken cancellationToken) =>
paymentSettingsService.UpsertAppAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("channels")]
[EndpointSummary("查询平台支付渠道")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<IReadOnlyCollection<PlatformPaymentChannel>> Channels(Guid? appId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
paymentSettingsService.GetChannelsAsync(Actor(), appId, status, limit, cancellationToken);
[HttpPut("channels")]
[EndpointSummary("新增或更新平台支付渠道")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task<PlatformPaymentChannel> UpsertChannel(UpsertPlatformPaymentChannelDto request, CancellationToken cancellationToken) =>
paymentSettingsService.UpsertChannelAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("channels/{id:guid}/disable")]
[EndpointSummary("禁用平台支付渠道")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task<PlatformPaymentChannel> DisableChannel(Guid id, CancellationToken cancellationToken) =>
paymentSettingsService.DisableChannelAsync(Actor(), id, cancellationToken);
[HttpGet("events")]
[EndpointSummary("查询平台支付事件")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<IReadOnlyCollection<PlatformBillingPaymentEvent>> Events(string? status, int limit = 100, CancellationToken cancellationToken = default) =>
paymentSettingsService.GetEventsAsync(Actor(), status, limit, cancellationToken);
[HttpGet("rebates/summary")]
[EndpointSummary("查询平台返佣汇总")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<PlatformRebateSummary> RebateSummary(CancellationToken cancellationToken) =>
paymentSettingsService.GetRebateSummaryAsync(Actor(), cancellationToken);

View File

@@ -8,6 +8,7 @@ using Tiku.Domain.Platform;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-SaaS 套餐")]
[Route("api/platform-admin/saas")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
public sealed class PlatformSaasController(
@@ -16,16 +17,19 @@ public sealed class PlatformSaasController(
ICurrentUser currentUser) : ControllerBase
{
[HttpGet("catalog")]
[EndpointSummary("查询平台 SaaS 商品目录")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasCatalogSnapshot> Catalog(CancellationToken cancellationToken) =>
catalogService.GetCatalogAsync(Actor(), cancellationToken);
[HttpPut("features")]
[EndpointSummary("新增或更新 SaaS 功能")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasFeature> UpsertFeature(UpsertSaasFeatureDto request, CancellationToken cancellationToken) =>
catalogService.UpsertFeatureAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("feature-limits")]
[EndpointSummary("新增或更新 SaaS 功能限额")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasFeatureLimitDefinition> UpsertFeatureLimit(
UpsertSaasFeatureLimitDto request,
@@ -33,61 +37,73 @@ public sealed class PlatformSaasController(
catalogService.UpsertLimitDefinitionAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("offerings")]
[EndpointSummary("新增或更新 SaaS 套餐")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasOffering> UpsertOffering(UpsertSaasOfferingDto request, CancellationToken cancellationToken) =>
catalogService.UpsertOfferingAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("offering-versions")]
[EndpointSummary("新增或更新 SaaS 套餐版本草稿")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasOfferingVersionItem> UpsertVersion(UpsertSaasOfferingVersionDto request, CancellationToken cancellationToken) =>
catalogService.UpsertDraftVersionAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("offering-versions/{versionId:guid}/publish")]
[EndpointSummary("发布 SaaS 套餐版本")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasOfferingVersionItem> PublishVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.PublishVersionAsync(Actor(), versionId, cancellationToken);
[HttpPost("offering-versions/{versionId:guid}/clone")]
[EndpointSummary("克隆 SaaS 套餐版本")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasOfferingVersionItem> CloneVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.CloneVersionAsync(Actor(), versionId, cancellationToken);
[HttpPost("offering-versions/{versionId:guid}/retire")]
[EndpointSummary("下架 SaaS 套餐版本")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task<SaasOfferingVersionItem> RetireVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.RetireVersionAsync(Actor(), versionId, cancellationToken);
[HttpGet("orders")]
[EndpointSummary("查询平台 SaaS 订单")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<IReadOnlyCollection<PlatformBillingOrder>> Orders(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
billingService.GetOrdersAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken);
[HttpGet("payments")]
[EndpointSummary("查询平台 SaaS 支付记录")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<IReadOnlyCollection<PlatformBillingPayment>> Payments(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
billingService.GetPaymentsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken);
[HttpGet("refunds")]
[EndpointSummary("查询平台 SaaS 退款记录")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<IReadOnlyCollection<PlatformBillingRefund>> Refunds(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
billingService.GetRefundsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken);
[HttpGet("invoices")]
[EndpointSummary("查询平台 SaaS 发票")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<IReadOnlyCollection<PlatformBillingInvoice>> Invoices(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
billingService.GetInvoicesAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken);
[HttpGet("subscriptions")]
[EndpointSummary("查询租户 SaaS 订阅")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<IReadOnlyCollection<TenantSaasSubscription>> Subscriptions(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) =>
billingService.GetSubscriptionsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken);
[HttpPost("payments/manual/confirm")]
[EndpointSummary("确认平台手工支付")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<PlatformBillingPayment> ConfirmManualPayment(ConfirmManualPlatformPaymentDto request, CancellationToken cancellationToken) =>
billingService.ConfirmManualPaymentAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("tenant-feature-overrides")]
[EndpointSummary("新增或更新租户功能覆盖规则")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task<TenantFeatureOverride> UpsertFeatureOverride(UpsertTenantFeatureOverrideDto request, CancellationToken cancellationToken) =>
billingService.UpsertFeatureOverrideAsync(Actor(), request.ToCommand(), cancellationToken);

View File

@@ -10,6 +10,7 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("平台端-租户 CRM 能力")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[Produces("application/json")]
[Route("api/platform-admin/tenant-capabilities/crm")]
@@ -18,26 +19,31 @@ public sealed class PlatformAdminCrmController(
ICurrentUser currentUser) : ControllerBase
{
[HttpGet("configs")]
[EndpointSummary("查询租户 CRM 配置")]
[Authorize(Policy = BackendPermissions.PlatformCrmRead)]
public Task<PlatformTenantCapabilityList<PlatformCrmConfigItem>> Configs([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
crmService.GetConfigsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("configs")]
[EndpointSummary("新增或更新租户 CRM 配置")]
[Authorize(Policy = BackendPermissions.PlatformCrmWrite)]
public Task<PlatformCrmConfigItem> UpsertConfig(UpsertPlatformCrmConfigDto request, CancellationToken cancellationToken) =>
crmService.UpsertConfigAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("leads")]
[EndpointSummary("查询租户 CRM 线索队列")]
[Authorize(Policy = BackendPermissions.PlatformCrmRead)]
public Task<PlatformTenantCapabilityList<CrmWebhookQueueItem>> Leads([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
crmService.GetLeadsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPost("leads/retry")]
[EndpointSummary("重试租户 CRM 线索推送")]
[Authorize(Policy = BackendPermissions.PlatformCrmWrite)]
public Task<CrmWebhookQueueItem> RetryLead(RetryPlatformCrmLeadDto request, CancellationToken cancellationToken) =>
crmService.RetryLeadAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("logs")]
[EndpointSummary("查询租户 CRM 推送日志")]
[Authorize(Policy = BackendPermissions.PlatformCrmRead)]
public Task<PlatformTenantCapabilityList<CrmWebhookLog>> Logs([FromQuery] PlatformCrmLogQueryDto query, CancellationToken cancellationToken) =>
crmService.GetLogsAsync(Actor(), query.TenantId, query.QueueId, query.Limit, cancellationToken);
@@ -48,6 +54,7 @@ public sealed class PlatformAdminCrmController(
}
[ApiController]
[Tags("平台端-租户短信能力")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[Produces("application/json")]
[Route("api/platform-admin/tenant-capabilities/sms")]
@@ -56,41 +63,49 @@ public sealed class PlatformAdminSmsController(
ICurrentUser currentUser) : ControllerBase
{
[HttpGet("channels")]
[EndpointSummary("查询租户短信渠道")]
[Authorize(Policy = BackendPermissions.PlatformSmsRead)]
public Task<PlatformTenantCapabilityList<PlatformSmsChannelItem>> Channels([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
smsService.GetChannelsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("channels")]
[EndpointSummary("新增或更新租户短信渠道")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task<PlatformSmsChannelItem> UpsertChannel(UpsertPlatformSmsChannelDto request, CancellationToken cancellationToken) =>
smsService.UpsertChannelAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("channels/{id:guid}/disable")]
[EndpointSummary("禁用租户短信渠道")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task<PlatformSmsChannelItem> DisableChannel(Guid id, CancellationToken cancellationToken) =>
smsService.DisableChannelAsync(Actor(), id, cancellationToken);
[HttpGet("templates")]
[EndpointSummary("查询租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsRead)]
public Task<PlatformTenantCapabilityList<PlatformSmsTemplateItem>> Templates([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
smsService.GetTemplatesAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("templates")]
[EndpointSummary("新增或更新租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task<PlatformSmsTemplateItem> UpsertTemplate(UpsertPlatformSmsTemplateDto request, CancellationToken cancellationToken) =>
smsService.UpsertTemplateAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("templates/{id:guid}/submit-review")]
[EndpointSummary("提交租户短信模板审核")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task<PlatformSmsTemplateItem> SubmitTemplateReview(Guid id, CancellationToken cancellationToken) =>
smsService.SubmitTemplateReviewAsync(Actor(), id, cancellationToken);
[HttpPost("templates/{id:guid}/disable")]
[EndpointSummary("禁用租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task<PlatformSmsTemplateItem> DisableTemplate(Guid id, CancellationToken cancellationToken) =>
smsService.DisableTemplateAsync(Actor(), id, cancellationToken);
[HttpGet("logs")]
[EndpointSummary("查询租户短信发送日志")]
[Authorize(Policy = BackendPermissions.PlatformSmsRead)]
public Task<PlatformTenantCapabilityList<SmsSendLog>> Logs([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
smsService.GetLogsAsync(Actor(), query.ToQuery(), cancellationToken);
@@ -101,6 +116,7 @@ public sealed class PlatformAdminSmsController(
}
[ApiController]
[Tags("平台端-租户支付能力")]
[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
[Produces("application/json")]
[Route("api/platform-admin/tenant-capabilities/payments")]
@@ -109,16 +125,19 @@ public sealed class PlatformAdminTenantPaymentSettingsController(
ICurrentUser currentUser) : ControllerBase
{
[HttpGet("apps")]
[EndpointSummary("查询租户支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<PlatformTenantCapabilityList<TenantExternalProviderItem>> Apps([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
paymentService.GetAppsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("apps")]
[EndpointSummary("新增或更新租户支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task<TenantExternalProviderItem> UpsertApp(UpsertPlatformTenantPaymentAppDto request, CancellationToken cancellationToken) =>
paymentService.UpsertAppAsync(Actor(), request.TenantId, request.ToCommand(), cancellationToken);
[HttpGet("events")]
[EndpointSummary("查询租户支付事件")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task<PlatformTenantCapabilityList<object>> Events([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
paymentService.GetEventsAsync(Actor(), query.ToQuery(), cancellationToken);

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-积分")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
[Produces("application/json")]

View File

@@ -8,6 +8,7 @@ using Tiku.Infrastructure.Profile;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-个人中心")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Produces("application/json")]
[Route("api/profile")]

View File

@@ -8,6 +8,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-题目视频")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)]
[Produces("application/json")]

View File

@@ -11,6 +11,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-推荐增长")]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.ReferralCommission)]
[Produces("application/json")]
[Route("api/referral")]
@@ -96,6 +97,7 @@ public sealed class ReferralController(
}
[HttpGet("stats")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐人个人统计")]
[ProducesResponseType<ReferralStatsItem>(StatusCodes.Status200OK)]
@@ -110,6 +112,7 @@ public sealed class ReferralController(
}
[HttpGet("sales-stats")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询销售推荐统计排行")]
[ProducesResponseType<ReferralList<ReferralStatsItem>>(StatusCodes.Status200OK)]
@@ -124,6 +127,7 @@ public sealed class ReferralController(
}
[HttpGet("conversion-report")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐转化报告")]
[ProducesResponseType<ReferralConversionReport>(StatusCodes.Status200OK)]
@@ -138,6 +142,7 @@ public sealed class ReferralController(
}
[HttpGet("sales-clients")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐人名下客户")]
[ProducesResponseType<ReferralList<ReferralLeadItem>>(StatusCodes.Status200OK)]
@@ -152,6 +157,7 @@ public sealed class ReferralController(
}
[HttpPost("manual-bind")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("人工调整学生推荐归属")]
[ProducesResponseType<ReferralBindResult>(StatusCodes.Status200OK)]
@@ -166,6 +172,7 @@ public sealed class ReferralController(
}
[HttpGet("team")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐团队成员")]
[ProducesResponseType<ReferralList<ReferralTeamItem>>(StatusCodes.Status200OK)]
@@ -180,6 +187,7 @@ public sealed class ReferralController(
}
[HttpPut("team")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("新增或更新推荐团队关系")]
[ProducesResponseType<ReferralTeamItem>(StatusCodes.Status200OK)]

View File

@@ -6,6 +6,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-运行时配置")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/runtime")]

View File

@@ -11,6 +11,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-分数线")]
[AllowAnonymous]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)]
[Produces("application/json")]

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-分类管理")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Produces("application/json")]
[Route("api/taxonomy/nodes")]

View File

@@ -10,6 +10,7 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-运营管理")]
[Produces("application/json")]
[Route("api/tenant-admin")]
public sealed class TenantAdminDirectController(

View File

@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Contracts;
using Tiku.Application.Backoffice;
using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-后台权限")]
[Route("api/backoffice/tenant")]
public sealed class TenantBackofficeController(
IBackofficeService backofficeService,
ICurrentAccessContext currentAccessContext) : ControllerBase
{
[HttpGet("ui-bootstrap")]
[Authorize(Policy = TikuPolicies.TenantBackofficeBootstrap)]
[EndpointSummary("查询租户后台菜单与权限")]
[EndpointDescription("返回当前租户管理员可见的后台菜单、权限和模块启用状态。")]
[ProducesResponseType<BackofficeUiBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeUiBootstrap>> GetUiBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetTenantUiBootstrapAsync(
await currentAccessContext.GetAsync(cancellationToken),
cancellationToken));
}
[HttpGet("bootstrap")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("查询租户角色管理初始化数据")]
[ProducesResponseType<BackofficeBootstrap>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeBootstrap>> GetBootstrap(CancellationToken cancellationToken)
{
return Ok(await backofficeService.GetTenantBootstrapAsync(await ResolveActorAsync(cancellationToken), cancellationToken));
}
[HttpPost("roles")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("创建或更新租户后台角色")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> UpsertRole(
UpsertBackofficeRoleDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.UpsertTenantRoleAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(), cancellationToken));
}
[HttpPut("roles/{roleId:guid}/bindings")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("替换租户后台角色权限绑定")]
[ProducesResponseType<BackofficeRoleItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<BackofficeRoleItem>> ReplaceRoleBindings(
Guid roleId,
ReplaceRoleBindingsDto request,
CancellationToken cancellationToken)
{
return Ok(await backofficeService.ReplaceTenantRoleBindingsAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(roleId), cancellationToken));
}
[HttpPut("users/{userId:guid}/roles")]
[Authorize(Policy = BackendPermissions.TenantRoleManage)]
[EndpointSummary("替换租户用户后台角色")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> ReplaceUserRoles(
Guid userId,
ReplaceUserRolesDto request,
CancellationToken cancellationToken)
{
await backofficeService.ReplaceTenantUserRolesAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
return NoContent();
}
private async Task<BackofficeActor> ResolveActorAsync(CancellationToken cancellationToken)
{
return BackofficeActor.FromTenantAccess(await currentAccessContext.GetAsync(cancellationToken));
}
}

View File

@@ -8,6 +8,7 @@ using Tiku.Domain.Platform;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-账务")]
[Route("api/tenant-billing")]
[Authorize(Policy = BackendPermissions.TenantBillingManage)]
public sealed class TenantBillingController(
@@ -15,50 +16,62 @@ public sealed class TenantBillingController(
ICurrentAccessContext accessContext) : ControllerBase
{
[HttpGet("catalog")]
[EndpointSummary("查询租户可购买 SaaS 目录")]
public async Task<TenantBillingCatalog> Catalog(CancellationToken cancellationToken) =>
await billingService.GetCatalogAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpPost("quotes")]
[EndpointSummary("创建租户账务报价")]
public async Task<PlatformBillingQuoteView> Quote(CreatePlatformBillingQuoteDto request, CancellationToken cancellationToken) =>
await billingService.CreateQuoteAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken);
[HttpPost("orders")]
[EndpointSummary("创建租户账务订单")]
public async Task<PlatformBillingOrderView> CreateOrder(CreatePlatformBillingOrderDto request, CancellationToken cancellationToken) =>
await billingService.CreateOrderAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken);
[HttpPost("orders/{orderNo}/payments")]
[EndpointSummary("创建租户账务订单支付")]
public async Task<PlatformBillingPaymentView> CreatePayment(string orderNo, CreatePlatformBillingPaymentDto request, CancellationToken cancellationToken) =>
await billingService.CreatePaymentAsync(await ActorAsync(cancellationToken), request.ToCommand(orderNo), cancellationToken);
[HttpGet("orders")]
[EndpointSummary("查询租户账务订单")]
public async Task<IReadOnlyCollection<PlatformBillingOrderView>> Orders(int limit = 100, CancellationToken cancellationToken = default) =>
await billingService.GetOrdersAsync(await ActorAsync(cancellationToken), limit, cancellationToken);
[HttpGet("orders/{orderNo}")]
[EndpointSummary("查询租户账务订单详情")]
public async Task<PlatformBillingOrderView> Order(string orderNo, CancellationToken cancellationToken) =>
await billingService.GetOrderAsync(await ActorAsync(cancellationToken), orderNo, cancellationToken);
[HttpGet("subscription")]
[EndpointSummary("查询当前租户 SaaS 订阅")]
public async Task<TenantSubscriptionView?> Subscription(CancellationToken cancellationToken) =>
await billingService.GetSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpPost("subscription/change")]
[EndpointSummary("变更当前租户 SaaS 订阅")]
public async Task<PlatformBillingOrderView> Change(ChangeTenantSubscriptionDto request, CancellationToken cancellationToken) =>
await billingService.ChangeSubscriptionAsync(await ActorAsync(cancellationToken), request.ToCommand(), request.IdempotencyKey, cancellationToken);
[HttpPost("subscription/renew")]
[EndpointSummary("续费当前租户 SaaS 订阅")]
public async Task<PlatformBillingOrderView> Renew(IdempotentTenantBillingDto request, CancellationToken cancellationToken) =>
await billingService.RenewSubscriptionAsync(await ActorAsync(cancellationToken), request.IdempotencyKey, cancellationToken);
[HttpPost("subscription/cancel")]
[EndpointSummary("取消当前租户 SaaS 订阅")]
public async Task<TenantSubscriptionView> Cancel(CancellationToken cancellationToken) =>
await billingService.CancelSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpGet("usage")]
[EndpointSummary("查询当前租户功能用量")]
public async Task<IReadOnlyCollection<FeatureQuotaSnapshot>> Usage(CancellationToken cancellationToken) =>
await billingService.GetUsageAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpGet("invoices")]
[EndpointSummary("查询当前租户发票")]
public async Task<IReadOnlyCollection<PlatformBillingInvoice>> Invoices(int limit = 100, CancellationToken cancellationToken = default) =>
await billingService.GetInvoicesAsync(await ActorAsync(cancellationToken), limit, cancellationToken);

View File

@@ -9,6 +9,7 @@ using Tiku.Domain.Commerce;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-交易运营")]
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
[Produces("application/json")]

View File

@@ -9,6 +9,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-内容管理")]
[Authorize(Policy = BackendPermissions.TenantContentManage)]
[Produces("application/json")]
[Route("api/tenant-content")]

View File

@@ -12,6 +12,7 @@ using Tiku.Domain.Content;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-内容直接管理")]
[Produces("application/json")]
[Route("api/tenant-content")]
public sealed class TenantContentDirectController(

View File

@@ -7,6 +7,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-前端配置")]
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
[Produces("application/json")]
[Route("api/tenant-admin/frontend-config")]

View File

@@ -6,6 +6,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-开通引导")]
[Route("api/tenant-onboarding")]
[Authorize(Policy = BackendPermissions.TenantSettingsManage)]
public sealed class TenantOnboardingController(
@@ -13,6 +14,7 @@ public sealed class TenantOnboardingController(
ICurrentAccessContext accessContext) : ControllerBase
{
[HttpGet("status")]
[EndpointSummary("查询租户开通引导状态")]
public async Task<TenantOnboardingStatus> Status(CancellationToken cancellationToken)
{
var access = await accessContext.GetAsync(cancellationToken);

View File

@@ -13,6 +13,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-公开配置")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/tenant")]

View File

@@ -9,6 +9,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("租户端-当前租户")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Route("api/tenants")]
public sealed class TenantsController(

View File

@@ -8,6 +8,7 @@ using Tiku.Application.Security;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-视频")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)]
[Produces("application/json")]