diff --git a/Tiku.Api/Contracts/PlatformTenantCapabilitiesDtos.cs b/Tiku.Api/Contracts/PlatformTenantCapabilitiesDtos.cs
index b4fd196..4e77481 100644
--- a/Tiku.Api/Contracts/PlatformTenantCapabilitiesDtos.cs
+++ b/Tiku.Api/Contracts/PlatformTenantCapabilitiesDtos.cs
@@ -8,166 +8,397 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Contracts;
+///
+/// 平台租户能力通用查询参数。
+///
public class PlatformCapabilityQueryDto
{
+ ///
+ /// 租户 ID。
+ ///
public Guid? TenantId { get; set; }
+ ///
+ /// 状态。
+ ///
[StringLength(50)]
public string? Status { get; set; }
+ ///
+ /// 返回数量上限。
+ ///
[Range(1, 500)]
public int Limit { get; set; } = 100;
public PlatformCapabilityQuery ToQuery() => new(TenantId, Status, Limit);
}
+///
+/// 新增或更新租户 CRM 配置请求。
+///
public sealed class UpsertPlatformCrmConfigDto
{
+ ///
+ /// ID。
+ ///
public Guid? Id { get; set; }
+ ///
+ /// 租户 ID。
+ ///
[Required]
public Guid TenantId { get; set; }
+ ///
+ /// 是否启用。
+ ///
public bool Enabled { get; set; }
+ ///
+ /// 回调地址。
+ ///
[StringLength(2048)]
public string? Url { get; set; }
+ ///
+ /// 密钥引用。
+ ///
[StringLength(300)]
public string? SecretRef { get; set; }
+ ///
+ /// 表单名称。
+ ///
[StringLength(200)]
public string? FormName { get; set; }
+ ///
+ /// 考试类型。
+ ///
[StringLength(100)]
public string? ExamType { get; set; }
+ ///
+ /// 超时时长,单位为秒。
+ ///
[Range(1, 120)]
public int? TimeoutSeconds { get; set; }
+ ///
+ /// 延迟秒数。
+ ///
[Range(0, 86400)]
public int? DelaySeconds { get; set; }
+ ///
+ /// 分配模式。
+ ///
[StringLength(50)]
public string? AssignmentMode { get; set; }
+ ///
+ /// 分配池。
+ ///
public JsonElement? AssignmentPool { get; set; }
+ ///
+ /// 分配配置。
+ ///
public JsonElement? AssignmentConfig { get; set; }
public UpsertPlatformCrmConfigCommand ToCommand() =>
new(Id, TenantId, Enabled, Url, SecretRef, FormName, ExamType, TimeoutSeconds, DelaySeconds, AssignmentMode, AssignmentPool, AssignmentConfig);
}
+///
+/// 重试租户 CRM 线索推送请求。
+///
public sealed class RetryPlatformCrmLeadDto
{
+ ///
+ /// 队列任务 ID。
+ ///
[Required]
public Guid QueueId { get; set; }
+ ///
+ /// 备注。
+ ///
[StringLength(500)]
public string? Note { get; set; }
public PlatformCrmLeadRetryCommand ToCommand() => new(QueueId, Note);
}
+///
+/// 平台租户 CRM 日志查询参数。
+///
public sealed class PlatformCrmLogQueryDto : PlatformCapabilityQueryDto
{
+ ///
+ /// 队列任务 ID。
+ ///
public Guid? QueueId { get; set; }
}
+///
+/// 新增或更新租户短信渠道请求。
+///
public sealed class UpsertPlatformSmsChannelDto
{
+ ///
+ /// ID。
+ ///
public Guid? Id { get; set; }
+ ///
+ /// 租户 ID。
+ ///
[Required]
public Guid TenantId { get; set; }
+ ///
+ /// 服务提供方。
+ ///
[Required, StringLength(80)]
public string Provider { get; set; } = "generic";
+ ///
+ /// 名称。
+ ///
[Required, StringLength(200)]
public string Name { get; set; } = string.Empty;
+ ///
+ /// 短信签名。
+ ///
[Required, StringLength(100)]
public string Signature { get; set; } = string.Empty;
+ ///
+ /// 使用场景。
+ ///
[Required, StringLength(100)]
public string Scene { get; set; } = "login";
+ ///
+ /// 状态。
+ ///
public TenantExternalProviderStatus Status { get; set; } = TenantExternalProviderStatus.Disabled;
+ ///
+ /// 密钥引用。
+ ///
[StringLength(300)]
public string? SecretRef { get; set; }
+ ///
+ /// 优先级。
+ ///
public int? Priority { get; set; }
+ ///
+ /// 月度配额。
+ ///
[Range(0, int.MaxValue)]
public int? MonthlyQuota { get; set; }
+ ///
+ /// 公开配置内容。
+ ///
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
+ ///
+ /// 扩展元数据。
+ ///
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformSmsChannelCommand ToCommand() =>
new(Id, TenantId, Provider, Name, Signature, Scene, Status, SecretRef, Priority, MonthlyQuota, ConfigPublic, Metadata);
}
+///
+/// 新增或更新租户短信模板请求。
+///
public sealed class UpsertPlatformSmsTemplateDto
{
+ ///
+ /// ID。
+ ///
public Guid? Id { get; set; }
+ ///
+ /// 租户 ID。
+ ///
[Required]
public Guid TenantId { get; set; }
+ ///
+ /// 短信渠道 ID。
+ ///
[Required]
public Guid ChannelId { get; set; }
+ ///
+ /// 编码。
+ ///
[Required, StringLength(120)]
public string Code { get; set; } = string.Empty;
+ ///
+ /// 名称。
+ ///
[Required, StringLength(200)]
public string Name { get; set; } = string.Empty;
+ ///
+ /// 类型。
+ ///
public SmsTemplateType Type { get; set; } = SmsTemplateType.Notification;
+ ///
+ /// 审核状态。
+ ///
public SmsTemplateAuditStatus AuditStatus { get; set; } = SmsTemplateAuditStatus.Draft;
+ ///
+ /// 状态。
+ ///
public SmsTemplateStatus Status { get; set; } = SmsTemplateStatus.Active;
+ ///
+ /// 渠道模板编码。
+ ///
[StringLength(120)]
public string? ProviderTemplateCode { get; set; }
+ ///
+ /// 模板内容。
+ ///
[Required, StringLength(1000)]
public string Content { get; set; } = string.Empty;
+ ///
+ /// Remark。
+ ///
[StringLength(500)]
public string? Remark { get; set; }
+ ///
+ /// 扩展元数据。
+ ///
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformSmsTemplateCommand ToCommand() =>
new(Id, TenantId, ChannelId, Code, Name, Type, AuditStatus, Status, ProviderTemplateCode, Content, Remark, Metadata);
}
+///
+/// 新增或更新平台支付应用请求。
+///
public sealed class UpsertPlatformPaymentAppDto
{
+ ///
+ /// ID。
+ ///
public Guid? Id { get; set; }
+ ///
+ /// 支付应用编码。
+ ///
[Required, StringLength(100)]
public string AppCode { get; set; } = string.Empty;
+ ///
+ /// 支付应用名称。
+ ///
[Required, StringLength(200)]
public string AppName { get; set; } = string.Empty;
+ ///
+ /// 状态。
+ ///
public PlatformPaymentAppStatus Status { get; set; } = PlatformPaymentAppStatus.Disabled;
+ ///
+ /// 结算模式。
+ ///
[StringLength(100)]
public string SettlementMode { get; set; } = "PlatformCollect";
+ ///
+ /// 说明。
+ ///
[StringLength(500)]
public string? Description { get; set; }
+ ///
+ /// 扩展元数据。
+ ///
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformPaymentAppCommand ToCommand() =>
new(Id, AppCode, AppName, Status, SettlementMode, Description, Metadata);
}
+///
+/// 新增或更新平台支付渠道请求。
+///
public sealed class UpsertPlatformPaymentChannelDto
{
+ ///
+ /// ID。
+ ///
public Guid? Id { get; set; }
+ ///
+ /// 支付应用 ID。
+ ///
[Required]
public Guid AppId { get; set; }
+ ///
+ /// 服务提供方。
+ ///
[Required, StringLength(80)]
public string Provider { get; set; } = "manual";
+ ///
+ /// 模式。
+ ///
[StringLength(100)]
public string Mode { get; set; } = "PlatformCollect";
+ ///
+ /// 状态。
+ ///
public PlatformPaymentChannelStatus Status { get; set; } = PlatformPaymentChannelStatus.Disabled;
+ ///
+ /// 显示名称。
+ ///
[Required, StringLength(200)]
public string DisplayName { get; set; } = string.Empty;
+ ///
+ /// 密钥引用。
+ ///
[StringLength(300)]
public string? SecretRef { get; set; }
+ ///
+ /// 回调路径。
+ ///
[StringLength(500)]
public string? CallbackPath { get; set; }
+ ///
+ /// 优先级。
+ ///
public int? Priority { get; set; }
+ ///
+ /// 公开配置内容。
+ ///
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
+ ///
+ /// 扩展元数据。
+ ///
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertPlatformPaymentChannelCommand ToCommand() =>
new(Id, AppId, Provider, Mode, Status, DisplayName, SecretRef, CallbackPath, Priority, ConfigPublic, Metadata);
}
+///
+/// 新增或更新租户支付应用请求。
+///
public sealed class UpsertPlatformTenantPaymentAppDto
{
+ ///
+ /// 租户 ID。
+ ///
[Required]
public Guid TenantId { get; set; }
+ ///
+ /// 服务提供方。
+ ///
[Required, StringLength(80)]
public string Provider { get; set; } = "manual";
+ ///
+ /// 状态。
+ ///
public TenantExternalProviderStatus Status { get; set; } = TenantExternalProviderStatus.Disabled;
+ ///
+ /// 显示名称。
+ ///
[StringLength(200)]
public string? DisplayName { get; set; }
+ ///
+ /// 密钥引用。
+ ///
[StringLength(300)]
public string? SecretRef { get; set; }
+ ///
+ /// 优先级。
+ ///
public int? Priority { get; set; }
+ ///
+ /// 公开配置内容。
+ ///
public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object();
+ ///
+ /// 扩展元数据。
+ ///
public JsonElement Metadata { get; set; } = JsonDefaults.Object();
public UpsertTenantExternalProviderCommand ToCommand() =>
diff --git a/Tiku.Api/Controllers/AssetsController.cs b/Tiku.Api/Controllers/AssetsController.cs
index f281f4a..b13f93e 100644
--- a/Tiku.Api/Controllers/AssetsController.cs
+++ b/Tiku.Api/Controllers/AssetsController.cs
@@ -10,6 +10,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("学生端-资源访问")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/assets")]
diff --git a/Tiku.Api/Controllers/AuthController.cs b/Tiku.Api/Controllers/AuthController.cs
index 9a77211..e61742f 100644
--- a/Tiku.Api/Controllers/AuthController.cs
+++ b/Tiku.Api/Controllers/AuthController.cs
@@ -13,6 +13,7 @@ using Tiku.Domain.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("租户端-认证")]
[Route("api/auth")]
[Produces("application/json")]
public sealed class AuthController(
diff --git a/Tiku.Api/Controllers/BackgroundJobsController.cs b/Tiku.Api/Controllers/BackgroundJobsController.cs
index aaaaf74..3ba048a 100644
--- a/Tiku.Api/Controllers/BackgroundJobsController.cs
+++ b/Tiku.Api/Controllers/BackgroundJobsController.cs
@@ -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(
diff --git a/Tiku.Api/Controllers/BackofficeController.cs b/Tiku.Api/Controllers/BackofficeController.cs
deleted file mode 100644
index 77d519b..0000000
--- a/Tiku.Api/Controllers/BackofficeController.cs
+++ /dev/null
@@ -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(StatusCodes.Status200OK)]
- public async Task> GetTenantUiBootstrap(CancellationToken cancellationToken)
- {
- return Ok(await backofficeService.GetTenantUiBootstrapAsync(
- await currentAccessContext.GetAsync(cancellationToken),
- cancellationToken));
- }
-
- [HttpGet("tenant/bootstrap")]
- [Authorize(Policy = BackendPermissions.TenantRoleManage)]
- [EndpointSummary("查询租户角色管理初始化数据")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetTenantBootstrap(CancellationToken cancellationToken)
- {
- return Ok(await backofficeService.GetTenantBootstrapAsync(await ResolveTenantActorAsync(cancellationToken), cancellationToken));
- }
-
- [HttpGet("platform/ui-bootstrap")]
- [Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)]
- [EndpointSummary("查询平台后台菜单与权限")]
- [EndpointDescription("返回当前平台管理员可见的后台菜单、权限和模块启用状态。")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetPlatformUiBootstrap(CancellationToken cancellationToken)
- {
- return Ok(await backofficeService.GetPlatformUiBootstrapAsync(
- await currentAccessContext.GetAsync(cancellationToken),
- cancellationToken));
- }
-
- [HttpPost("tenant/roles")]
- [Authorize(Policy = BackendPermissions.TenantRoleManage)]
- [EndpointSummary("创建或更新租户后台角色")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> 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(StatusCodes.Status200OK)]
- public async Task> 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 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(StatusCodes.Status200OK)]
- public async Task> GetPlatformBootstrap(CancellationToken cancellationToken)
- {
- return Ok(await backofficeService.GetPlatformBootstrapAsync(await ResolvePlatformActorAsync(cancellationToken), cancellationToken));
- }
-
- [HttpPost("platform/roles")]
- [Authorize(Policy = BackendPermissions.PlatformRoleManage)]
- [EndpointSummary("创建或更新平台后台角色")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> 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(StatusCodes.Status200OK)]
- public async Task> 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 ReplacePlatformUserRoles(
- Guid userId,
- ReplaceUserRolesDto request,
- CancellationToken cancellationToken)
- {
- await backofficeService.ReplacePlatformUserRolesAsync(await ResolvePlatformActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
- return NoContent();
- }
-
- private async Task ResolveTenantActorAsync(CancellationToken cancellationToken)
- {
- return BackofficeActor.FromTenantAccess(await currentAccessContext.GetAsync(cancellationToken));
- }
-
- private async Task ResolvePlatformActorAsync(CancellationToken cancellationToken)
- {
- return BackofficeActor.FromPlatformAccess(await currentAccessContext.GetAsync(cancellationToken));
- }
-}
diff --git a/Tiku.Api/Controllers/BrowserAuthController.cs b/Tiku.Api/Controllers/BrowserAuthController.cs
index c2b77fd..85a2245 100644
--- a/Tiku.Api/Controllers/BrowserAuthController.cs
+++ b/Tiku.Api/Controllers/BrowserAuthController.cs
@@ -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> SendSmsCode(
[FromBody] SendSmsCodeDto request,
CancellationToken cancellationToken)
@@ -52,6 +54,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("login/password")]
+ [EndpointSummary("浏览器手机号密码登录")]
public async Task> 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> LoginWithSms(
[FromBody] SmsLoginDto request,
CancellationToken cancellationToken)
@@ -91,6 +95,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("oauth/wechat")]
+ [EndpointSummary("浏览器微信网页 OAuth 登录")]
public async Task> LoginWithWechatWeb(
[FromBody] OAuthCodeDto request,
CancellationToken cancellationToken)
@@ -108,6 +113,7 @@ public sealed class BrowserAuthController(
[AllowAnonymous]
[HttpPost("oauth/wechat-miniapp")]
+ [EndpointSummary("浏览器微信小程序登录")]
public async Task> 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> 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 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 LogoutAll(CancellationToken cancellationToken)
{
if (currentUser.UserId is not { } userId) return Unauthorized();
diff --git a/Tiku.Api/Controllers/CatalogController.cs b/Tiku.Api/Controllers/CatalogController.cs
index da4f379..1dff021 100644
--- a/Tiku.Api/Controllers/CatalogController.cs
+++ b/Tiku.Api/Controllers/CatalogController.cs
@@ -14,6 +14,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("学生端-公开目录")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/catalog")]
diff --git a/Tiku.Api/Controllers/CommerceController.cs b/Tiku.Api/Controllers/CommerceController.cs
index 1d2cc88..ca83e6a 100644
--- a/Tiku.Api/Controllers/CommerceController.cs
+++ b/Tiku.Api/Controllers/CommerceController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/CommissionController.cs b/Tiku.Api/Controllers/CommissionController.cs
index 91075a5..8689d3c 100644
--- a/Tiku.Api/Controllers/CommissionController.cs
+++ b/Tiku.Api/Controllers/CommissionController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/CrmController.cs b/Tiku.Api/Controllers/CrmController.cs
index 9c2b678..b9c7065 100644
--- a/Tiku.Api/Controllers/CrmController.cs
+++ b/Tiku.Api/Controllers/CrmController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/HealthController.cs b/Tiku.Api/Controllers/HealthController.cs
index 12e4799..e49aba5 100644
--- a/Tiku.Api/Controllers/HealthController.cs
+++ b/Tiku.Api/Controllers/HealthController.cs
@@ -11,6 +11,7 @@ using Tiku.Infrastructure.Messaging;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("平台端-系统健康")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/health")]
diff --git a/Tiku.Api/Controllers/LearningController.cs b/Tiku.Api/Controllers/LearningController.cs
index 06835aa..f12522d 100644
--- a/Tiku.Api/Controllers/LearningController.cs
+++ b/Tiku.Api/Controllers/LearningController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/MeController.cs b/Tiku.Api/Controllers/MeController.cs
index bbd81e3..b5f540b 100644
--- a/Tiku.Api/Controllers/MeController.cs
+++ b/Tiku.Api/Controllers/MeController.cs
@@ -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(
diff --git a/Tiku.Api/Controllers/PlatformAdminController.cs b/Tiku.Api/Controllers/PlatformAdminController.cs
index 8613580..58448aa 100644
--- a/Tiku.Api/Controllers/PlatformAdminController.cs
+++ b/Tiku.Api/Controllers/PlatformAdminController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/PlatformBackofficeController.cs b/Tiku.Api/Controllers/PlatformBackofficeController.cs
new file mode 100644
index 0000000..ede9e67
--- /dev/null
+++ b/Tiku.Api/Controllers/PlatformBackofficeController.cs
@@ -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(StatusCodes.Status200OK)]
+ public async Task> GetUiBootstrap(CancellationToken cancellationToken)
+ {
+ return Ok(await backofficeService.GetPlatformUiBootstrapAsync(
+ await currentAccessContext.GetAsync(cancellationToken),
+ cancellationToken));
+ }
+
+ [HttpGet("bootstrap")]
+ [Authorize(Policy = BackendPermissions.PlatformRoleManage)]
+ [EndpointSummary("查询平台角色管理初始化数据")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetBootstrap(CancellationToken cancellationToken)
+ {
+ return Ok(await backofficeService.GetPlatformBootstrapAsync(await ResolveActorAsync(cancellationToken), cancellationToken));
+ }
+
+ [HttpPost("roles")]
+ [Authorize(Policy = BackendPermissions.PlatformRoleManage)]
+ [EndpointSummary("创建或更新平台后台角色")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> 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(StatusCodes.Status200OK)]
+ public async Task> 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 ReplaceUserRoles(
+ Guid userId,
+ ReplaceUserRolesDto request,
+ CancellationToken cancellationToken)
+ {
+ await backofficeService.ReplacePlatformUserRolesAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
+ return NoContent();
+ }
+
+ private async Task ResolveActorAsync(CancellationToken cancellationToken)
+ {
+ return BackofficeActor.FromPlatformAccess(await currentAccessContext.GetAsync(cancellationToken));
+ }
+}
diff --git a/Tiku.Api/Controllers/PlatformBillingCallbackController.cs b/Tiku.Api/Controllers/PlatformBillingCallbackController.cs
index 668fa62..796adbf 100644
--- a/Tiku.Api/Controllers/PlatformBillingCallbackController.cs
+++ b/Tiku.Api/Controllers/PlatformBillingCallbackController.cs
@@ -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 Notify(string provider, CancellationToken cancellationToken)
{
Request.EnableBuffering();
diff --git a/Tiku.Api/Controllers/PlatformPaymentSettingsController.cs b/Tiku.Api/Controllers/PlatformPaymentSettingsController.cs
index dc65aac..f976736 100644
--- a/Tiku.Api/Controllers/PlatformPaymentSettingsController.cs
+++ b/Tiku.Api/Controllers/PlatformPaymentSettingsController.cs
@@ -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> Apps(string? status, int limit = 100, CancellationToken cancellationToken = default) =>
paymentSettingsService.GetAppsAsync(Actor(), status, limit, cancellationToken);
[HttpPut("apps")]
+ [EndpointSummary("新增或更新平台支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task UpsertApp(UpsertPlatformPaymentAppDto request, CancellationToken cancellationToken) =>
paymentSettingsService.UpsertAppAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("channels")]
+ [EndpointSummary("查询平台支付渠道")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task> 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 UpsertChannel(UpsertPlatformPaymentChannelDto request, CancellationToken cancellationToken) =>
paymentSettingsService.UpsertChannelAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("channels/{id:guid}/disable")]
+ [EndpointSummary("禁用平台支付渠道")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task DisableChannel(Guid id, CancellationToken cancellationToken) =>
paymentSettingsService.DisableChannelAsync(Actor(), id, cancellationToken);
[HttpGet("events")]
+ [EndpointSummary("查询平台支付事件")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task> 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 RebateSummary(CancellationToken cancellationToken) =>
paymentSettingsService.GetRebateSummaryAsync(Actor(), cancellationToken);
diff --git a/Tiku.Api/Controllers/PlatformSaasController.cs b/Tiku.Api/Controllers/PlatformSaasController.cs
index 2d39449..9189c74 100644
--- a/Tiku.Api/Controllers/PlatformSaasController.cs
+++ b/Tiku.Api/Controllers/PlatformSaasController.cs
@@ -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 Catalog(CancellationToken cancellationToken) =>
catalogService.GetCatalogAsync(Actor(), cancellationToken);
[HttpPut("features")]
+ [EndpointSummary("新增或更新 SaaS 功能")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task UpsertFeature(UpsertSaasFeatureDto request, CancellationToken cancellationToken) =>
catalogService.UpsertFeatureAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("feature-limits")]
+ [EndpointSummary("新增或更新 SaaS 功能限额")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task 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 UpsertOffering(UpsertSaasOfferingDto request, CancellationToken cancellationToken) =>
catalogService.UpsertOfferingAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("offering-versions")]
+ [EndpointSummary("新增或更新 SaaS 套餐版本草稿")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task 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 PublishVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.PublishVersionAsync(Actor(), versionId, cancellationToken);
[HttpPost("offering-versions/{versionId:guid}/clone")]
+ [EndpointSummary("克隆 SaaS 套餐版本")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task CloneVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.CloneVersionAsync(Actor(), versionId, cancellationToken);
[HttpPost("offering-versions/{versionId:guid}/retire")]
+ [EndpointSummary("下架 SaaS 套餐版本")]
[Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)]
public Task RetireVersion(Guid versionId, CancellationToken cancellationToken) =>
catalogService.RetireVersionAsync(Actor(), versionId, cancellationToken);
[HttpGet("orders")]
+ [EndpointSummary("查询平台 SaaS 订单")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task> 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> 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> 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> 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> 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 ConfirmManualPayment(ConfirmManualPlatformPaymentDto request, CancellationToken cancellationToken) =>
billingService.ConfirmManualPaymentAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPut("tenant-feature-overrides")]
+ [EndpointSummary("新增或更新租户功能覆盖规则")]
[Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)]
public Task UpsertFeatureOverride(UpsertTenantFeatureOverrideDto request, CancellationToken cancellationToken) =>
billingService.UpsertFeatureOverrideAsync(Actor(), request.ToCommand(), cancellationToken);
diff --git a/Tiku.Api/Controllers/PlatformTenantCapabilitiesController.cs b/Tiku.Api/Controllers/PlatformTenantCapabilitiesController.cs
index 99b45f4..309e8d3 100644
--- a/Tiku.Api/Controllers/PlatformTenantCapabilitiesController.cs
+++ b/Tiku.Api/Controllers/PlatformTenantCapabilitiesController.cs
@@ -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> Configs([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
crmService.GetConfigsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("configs")]
+ [EndpointSummary("新增或更新租户 CRM 配置")]
[Authorize(Policy = BackendPermissions.PlatformCrmWrite)]
public Task UpsertConfig(UpsertPlatformCrmConfigDto request, CancellationToken cancellationToken) =>
crmService.UpsertConfigAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("leads")]
+ [EndpointSummary("查询租户 CRM 线索队列")]
[Authorize(Policy = BackendPermissions.PlatformCrmRead)]
public Task> Leads([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
crmService.GetLeadsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPost("leads/retry")]
+ [EndpointSummary("重试租户 CRM 线索推送")]
[Authorize(Policy = BackendPermissions.PlatformCrmWrite)]
public Task RetryLead(RetryPlatformCrmLeadDto request, CancellationToken cancellationToken) =>
crmService.RetryLeadAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpGet("logs")]
+ [EndpointSummary("查询租户 CRM 推送日志")]
[Authorize(Policy = BackendPermissions.PlatformCrmRead)]
public Task> 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> Channels([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
smsService.GetChannelsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("channels")]
+ [EndpointSummary("新增或更新租户短信渠道")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task UpsertChannel(UpsertPlatformSmsChannelDto request, CancellationToken cancellationToken) =>
smsService.UpsertChannelAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("channels/{id:guid}/disable")]
+ [EndpointSummary("禁用租户短信渠道")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task DisableChannel(Guid id, CancellationToken cancellationToken) =>
smsService.DisableChannelAsync(Actor(), id, cancellationToken);
[HttpGet("templates")]
+ [EndpointSummary("查询租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsRead)]
public Task> Templates([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
smsService.GetTemplatesAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("templates")]
+ [EndpointSummary("新增或更新租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task UpsertTemplate(UpsertPlatformSmsTemplateDto request, CancellationToken cancellationToken) =>
smsService.UpsertTemplateAsync(Actor(), request.ToCommand(), cancellationToken);
[HttpPost("templates/{id:guid}/submit-review")]
+ [EndpointSummary("提交租户短信模板审核")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task SubmitTemplateReview(Guid id, CancellationToken cancellationToken) =>
smsService.SubmitTemplateReviewAsync(Actor(), id, cancellationToken);
[HttpPost("templates/{id:guid}/disable")]
+ [EndpointSummary("禁用租户短信模板")]
[Authorize(Policy = BackendPermissions.PlatformSmsWrite)]
public Task DisableTemplate(Guid id, CancellationToken cancellationToken) =>
smsService.DisableTemplateAsync(Actor(), id, cancellationToken);
[HttpGet("logs")]
+ [EndpointSummary("查询租户短信发送日志")]
[Authorize(Policy = BackendPermissions.PlatformSmsRead)]
public Task> 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> Apps([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
paymentService.GetAppsAsync(Actor(), query.ToQuery(), cancellationToken);
[HttpPut("apps")]
+ [EndpointSummary("新增或更新租户支付应用")]
[Authorize(Policy = BackendPermissions.PlatformPaymentWrite)]
public Task UpsertApp(UpsertPlatformTenantPaymentAppDto request, CancellationToken cancellationToken) =>
paymentService.UpsertAppAsync(Actor(), request.TenantId, request.ToCommand(), cancellationToken);
[HttpGet("events")]
+ [EndpointSummary("查询租户支付事件")]
[Authorize(Policy = BackendPermissions.PlatformPaymentRead)]
public Task> Events([FromQuery] PlatformCapabilityQueryDto query, CancellationToken cancellationToken) =>
paymentService.GetEventsAsync(Actor(), query.ToQuery(), cancellationToken);
diff --git a/Tiku.Api/Controllers/PointsController.cs b/Tiku.Api/Controllers/PointsController.cs
index 8154a28..f49bfb9 100644
--- a/Tiku.Api/Controllers/PointsController.cs
+++ b/Tiku.Api/Controllers/PointsController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/ProfileController.cs b/Tiku.Api/Controllers/ProfileController.cs
index f58420e..a2022d3 100644
--- a/Tiku.Api/Controllers/ProfileController.cs
+++ b/Tiku.Api/Controllers/ProfileController.cs
@@ -8,6 +8,7 @@ using Tiku.Infrastructure.Profile;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("学生端-个人中心")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[Produces("application/json")]
[Route("api/profile")]
diff --git a/Tiku.Api/Controllers/QuestionVideosController.cs b/Tiku.Api/Controllers/QuestionVideosController.cs
index ff498e3..0268fed 100644
--- a/Tiku.Api/Controllers/QuestionVideosController.cs
+++ b/Tiku.Api/Controllers/QuestionVideosController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/ReferralController.cs b/Tiku.Api/Controllers/ReferralController.cs
index bc6205e..3193a96 100644
--- a/Tiku.Api/Controllers/ReferralController.cs
+++ b/Tiku.Api/Controllers/ReferralController.cs
@@ -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(StatusCodes.Status200OK)]
@@ -110,6 +112,7 @@ public sealed class ReferralController(
}
[HttpGet("sales-stats")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询销售推荐统计排行")]
[ProducesResponseType>(StatusCodes.Status200OK)]
@@ -124,6 +127,7 @@ public sealed class ReferralController(
}
[HttpGet("conversion-report")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐转化报告")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -138,6 +142,7 @@ public sealed class ReferralController(
}
[HttpGet("sales-clients")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐人名下客户")]
[ProducesResponseType>(StatusCodes.Status200OK)]
@@ -152,6 +157,7 @@ public sealed class ReferralController(
}
[HttpPost("manual-bind")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("人工调整学生推荐归属")]
[ProducesResponseType(StatusCodes.Status200OK)]
@@ -166,6 +172,7 @@ public sealed class ReferralController(
}
[HttpGet("team")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐团队成员")]
[ProducesResponseType>(StatusCodes.Status200OK)]
@@ -180,6 +187,7 @@ public sealed class ReferralController(
}
[HttpPut("team")]
+ [Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("新增或更新推荐团队关系")]
[ProducesResponseType(StatusCodes.Status200OK)]
diff --git a/Tiku.Api/Controllers/RuntimeController.cs b/Tiku.Api/Controllers/RuntimeController.cs
index 005c24a..cfe6db2 100644
--- a/Tiku.Api/Controllers/RuntimeController.cs
+++ b/Tiku.Api/Controllers/RuntimeController.cs
@@ -6,6 +6,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("租户端-运行时配置")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/runtime")]
diff --git a/Tiku.Api/Controllers/ScorelineController.cs b/Tiku.Api/Controllers/ScorelineController.cs
index f0801b0..e0a4e12 100644
--- a/Tiku.Api/Controllers/ScorelineController.cs
+++ b/Tiku.Api/Controllers/ScorelineController.cs
@@ -11,6 +11,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("学生端-分数线")]
[AllowAnonymous]
[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)]
[Produces("application/json")]
diff --git a/Tiku.Api/Controllers/TaxonomyController.cs b/Tiku.Api/Controllers/TaxonomyController.cs
index 46044bc..d6de84a 100644
--- a/Tiku.Api/Controllers/TaxonomyController.cs
+++ b/Tiku.Api/Controllers/TaxonomyController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/TenantAdminDirectController.cs b/Tiku.Api/Controllers/TenantAdminDirectController.cs
index 5f9b669..f28759f 100644
--- a/Tiku.Api/Controllers/TenantAdminDirectController.cs
+++ b/Tiku.Api/Controllers/TenantAdminDirectController.cs
@@ -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(
diff --git a/Tiku.Api/Controllers/TenantBackofficeController.cs b/Tiku.Api/Controllers/TenantBackofficeController.cs
new file mode 100644
index 0000000..681a3bf
--- /dev/null
+++ b/Tiku.Api/Controllers/TenantBackofficeController.cs
@@ -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(StatusCodes.Status200OK)]
+ public async Task> GetUiBootstrap(CancellationToken cancellationToken)
+ {
+ return Ok(await backofficeService.GetTenantUiBootstrapAsync(
+ await currentAccessContext.GetAsync(cancellationToken),
+ cancellationToken));
+ }
+
+ [HttpGet("bootstrap")]
+ [Authorize(Policy = BackendPermissions.TenantRoleManage)]
+ [EndpointSummary("查询租户角色管理初始化数据")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetBootstrap(CancellationToken cancellationToken)
+ {
+ return Ok(await backofficeService.GetTenantBootstrapAsync(await ResolveActorAsync(cancellationToken), cancellationToken));
+ }
+
+ [HttpPost("roles")]
+ [Authorize(Policy = BackendPermissions.TenantRoleManage)]
+ [EndpointSummary("创建或更新租户后台角色")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> 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(StatusCodes.Status200OK)]
+ public async Task> 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 ReplaceUserRoles(
+ Guid userId,
+ ReplaceUserRolesDto request,
+ CancellationToken cancellationToken)
+ {
+ await backofficeService.ReplaceTenantUserRolesAsync(await ResolveActorAsync(cancellationToken), request.ToCommand(userId), cancellationToken);
+ return NoContent();
+ }
+
+ private async Task ResolveActorAsync(CancellationToken cancellationToken)
+ {
+ return BackofficeActor.FromTenantAccess(await currentAccessContext.GetAsync(cancellationToken));
+ }
+}
diff --git a/Tiku.Api/Controllers/TenantBillingController.cs b/Tiku.Api/Controllers/TenantBillingController.cs
index 3f057a9..3ed50c1 100644
--- a/Tiku.Api/Controllers/TenantBillingController.cs
+++ b/Tiku.Api/Controllers/TenantBillingController.cs
@@ -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 Catalog(CancellationToken cancellationToken) =>
await billingService.GetCatalogAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpPost("quotes")]
+ [EndpointSummary("创建租户账务报价")]
public async Task Quote(CreatePlatformBillingQuoteDto request, CancellationToken cancellationToken) =>
await billingService.CreateQuoteAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken);
[HttpPost("orders")]
+ [EndpointSummary("创建租户账务订单")]
public async Task CreateOrder(CreatePlatformBillingOrderDto request, CancellationToken cancellationToken) =>
await billingService.CreateOrderAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken);
[HttpPost("orders/{orderNo}/payments")]
+ [EndpointSummary("创建租户账务订单支付")]
public async Task CreatePayment(string orderNo, CreatePlatformBillingPaymentDto request, CancellationToken cancellationToken) =>
await billingService.CreatePaymentAsync(await ActorAsync(cancellationToken), request.ToCommand(orderNo), cancellationToken);
[HttpGet("orders")]
+ [EndpointSummary("查询租户账务订单")]
public async Task> Orders(int limit = 100, CancellationToken cancellationToken = default) =>
await billingService.GetOrdersAsync(await ActorAsync(cancellationToken), limit, cancellationToken);
[HttpGet("orders/{orderNo}")]
+ [EndpointSummary("查询租户账务订单详情")]
public async Task Order(string orderNo, CancellationToken cancellationToken) =>
await billingService.GetOrderAsync(await ActorAsync(cancellationToken), orderNo, cancellationToken);
[HttpGet("subscription")]
+ [EndpointSummary("查询当前租户 SaaS 订阅")]
public async Task Subscription(CancellationToken cancellationToken) =>
await billingService.GetSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpPost("subscription/change")]
+ [EndpointSummary("变更当前租户 SaaS 订阅")]
public async Task Change(ChangeTenantSubscriptionDto request, CancellationToken cancellationToken) =>
await billingService.ChangeSubscriptionAsync(await ActorAsync(cancellationToken), request.ToCommand(), request.IdempotencyKey, cancellationToken);
[HttpPost("subscription/renew")]
+ [EndpointSummary("续费当前租户 SaaS 订阅")]
public async Task Renew(IdempotentTenantBillingDto request, CancellationToken cancellationToken) =>
await billingService.RenewSubscriptionAsync(await ActorAsync(cancellationToken), request.IdempotencyKey, cancellationToken);
[HttpPost("subscription/cancel")]
+ [EndpointSummary("取消当前租户 SaaS 订阅")]
public async Task Cancel(CancellationToken cancellationToken) =>
await billingService.CancelSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpGet("usage")]
+ [EndpointSummary("查询当前租户功能用量")]
public async Task> Usage(CancellationToken cancellationToken) =>
await billingService.GetUsageAsync(await ActorAsync(cancellationToken), cancellationToken);
[HttpGet("invoices")]
+ [EndpointSummary("查询当前租户发票")]
public async Task> Invoices(int limit = 100, CancellationToken cancellationToken = default) =>
await billingService.GetInvoicesAsync(await ActorAsync(cancellationToken), limit, cancellationToken);
diff --git a/Tiku.Api/Controllers/TenantCommerceController.cs b/Tiku.Api/Controllers/TenantCommerceController.cs
index 5c16cb6..c58b1d1 100644
--- a/Tiku.Api/Controllers/TenantCommerceController.cs
+++ b/Tiku.Api/Controllers/TenantCommerceController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/TenantContentController.cs b/Tiku.Api/Controllers/TenantContentController.cs
index 4579efe..be97f09 100644
--- a/Tiku.Api/Controllers/TenantContentController.cs
+++ b/Tiku.Api/Controllers/TenantContentController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/TenantContentDirectController.cs b/Tiku.Api/Controllers/TenantContentDirectController.cs
index 470ca8a..4bed5b7 100644
--- a/Tiku.Api/Controllers/TenantContentDirectController.cs
+++ b/Tiku.Api/Controllers/TenantContentDirectController.cs
@@ -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(
diff --git a/Tiku.Api/Controllers/TenantFrontendConfigController.cs b/Tiku.Api/Controllers/TenantFrontendConfigController.cs
index 0ab213c..93c0a31 100644
--- a/Tiku.Api/Controllers/TenantFrontendConfigController.cs
+++ b/Tiku.Api/Controllers/TenantFrontendConfigController.cs
@@ -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")]
diff --git a/Tiku.Api/Controllers/TenantOnboardingController.cs b/Tiku.Api/Controllers/TenantOnboardingController.cs
index ba3fdc7..3d8cfdf 100644
--- a/Tiku.Api/Controllers/TenantOnboardingController.cs
+++ b/Tiku.Api/Controllers/TenantOnboardingController.cs
@@ -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 Status(CancellationToken cancellationToken)
{
var access = await accessContext.GetAsync(cancellationToken);
diff --git a/Tiku.Api/Controllers/TenantPublicController.cs b/Tiku.Api/Controllers/TenantPublicController.cs
index 4d702cd..3696b56 100644
--- a/Tiku.Api/Controllers/TenantPublicController.cs
+++ b/Tiku.Api/Controllers/TenantPublicController.cs
@@ -13,6 +13,7 @@ using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
+[Tags("租户端-公开配置")]
[AllowAnonymous]
[Produces("application/json")]
[Route("api/tenant")]
diff --git a/Tiku.Api/Controllers/TenantsController.cs b/Tiku.Api/Controllers/TenantsController.cs
index 39c3250..b324c77 100644
--- a/Tiku.Api/Controllers/TenantsController.cs
+++ b/Tiku.Api/Controllers/TenantsController.cs
@@ -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(
diff --git a/Tiku.Api/Controllers/VideosController.cs b/Tiku.Api/Controllers/VideosController.cs
index 361aa8b..4cd9861 100644
--- a/Tiku.Api/Controllers/VideosController.cs
+++ b/Tiku.Api/Controllers/VideosController.cs
@@ -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")]