Files
tiku-backend.net/Tiku.Api/Controllers/ReferralController.cs
xiong 33375a38d7
Some checks failed
ci / release-gate (push) Has been cancelled
refactor(architecture): harden module boundaries
2026-08-04 12:10:36 +08:00

233 lines
9.4 KiB
C#

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Tiku.Api.Contracts;
using Tiku.Api.Security;
using Tiku.Application.Growth;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
namespace Tiku.Api.Controllers;
[ApiController]
[Tags("学生端-推荐增长")]
[RequireSaasFeature(SaasFeatureCatalog.ReferralCommission)]
[Produces("application/json")]
[Route("api/student/referral")]
public sealed class ReferralController(
IStudentReferralService studentReferralService,
IReferralAdministrationService referralAdministrationService,
IReferralAnalyticsService referralAnalyticsService,
ICurrentUser currentUser,
ITenantContext currentTenant,
ITenantContextInitializer tenantInitializer,
ITenantDirectory tenantDirectory) : ControllerBase
{
[HttpPost("invite-code")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[EndpointSummary("生成或查询当前成员邀请码")]
[ProducesResponseType<ReferralInviteItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralInviteItem>> InviteCode(
ReferralInviteDto request,
CancellationToken cancellationToken)
{
return Ok(await studentReferralService.GetOrCreateInviteCodeAsync(
ResolveUserActor(),
request.ToCommand(),
cancellationToken));
}
[HttpPost("resolve")]
[AllowAnonymous]
[EndpointSummary("解析推荐邀请码")]
[ProducesResponseType<ReferralResolutionItem>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ReferralResolutionItem>> Resolve(
ResolveReferralDto request,
CancellationToken cancellationToken)
{
return Ok(await studentReferralService.ResolveAsync(
new ReferralActor(await ResolveTenantIdAsync(request.TenantCode, cancellationToken), currentUser.UserId),
request.ToCommand(),
cancellationToken));
}
[HttpPost("track-event")]
[AllowAnonymous]
[EndpointSummary("记录推荐行为")]
[ProducesResponseType<ReferralTrackResult>(StatusCodes.Status200OK)]
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ReferralTrackResult>> TrackEvent(
TrackReferralEventDto request,
CancellationToken cancellationToken)
{
return Ok(await studentReferralService.TrackEventAsync(
new ReferralActor(await ResolveTenantIdAsync(request.TenantCode, cancellationToken), currentUser.UserId),
request.ToCommand(),
HttpContext.Connection.RemoteIpAddress?.ToString(),
Request.Headers.UserAgent.FirstOrDefault(),
cancellationToken));
}
[HttpPost("bind")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[EndpointSummary("绑定当前用户推荐归属")]
[ProducesResponseType<ReferralBindResult>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralBindResult>> Bind(
BindReferralDto request,
CancellationToken cancellationToken)
{
return Ok(await studentReferralService.BindAsync(
ResolveUserActor(),
request.ToCommand(),
cancellationToken));
}
[HttpPost("qrcode")]
[Authorize(Policy = TikuPolicies.CurrentTenantMember)]
[EndpointSummary("生成或查询推广二维码")]
[ProducesResponseType<ReferralQrcodeItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralQrcodeItem>> Qrcode(
ReferralQrcodeDto request,
CancellationToken cancellationToken)
{
return Ok(await studentReferralService.GetOrCreateQrcodeAsync(
ResolveUserActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("/api/tenant/referral/stats")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐人个人统计")]
[ProducesResponseType<ReferralStatsItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralStatsItem>> Stats(
[FromQuery] ReferralStatsQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await referralAnalyticsService.GetStatsAsync(
ResolveAdminActor(),
query.ToQuery(),
cancellationToken));
}
[HttpGet("/api/tenant/referral/sales-stats")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询销售推荐统计排行")]
[ProducesResponseType<ReferralList<ReferralStatsItem>>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralList<ReferralStatsItem>>> SalesStats(
[FromQuery] ReferralStatsQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await referralAnalyticsService.GetSalesStatsAsync(
ResolveAdminActor(),
query.ToQuery(),
cancellationToken));
}
[HttpGet("/api/tenant/referral/conversion-report")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐转化报告")]
[ProducesResponseType<ReferralConversionReport>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralConversionReport>> ConversionReport(
[FromQuery] ReferralConversionQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await referralAnalyticsService.GetConversionReportAsync(
ResolveAdminActor(),
query.ToQuery(),
cancellationToken));
}
[HttpGet("/api/tenant/referral/sales-clients")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐人名下客户")]
[ProducesResponseType<ReferralList<ReferralLeadItem>>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralList<ReferralLeadItem>>> SalesClients(
[FromQuery] ReferralStatsQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await referralAnalyticsService.GetClientsAsync(
ResolveAdminActor(),
query.ToQuery(),
cancellationToken));
}
[HttpPost("/api/tenant/referral/manual-bind")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("人工调整学生推荐归属")]
[ProducesResponseType<ReferralBindResult>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralBindResult>> ManualBind(
ManualBindReferralDto request,
CancellationToken cancellationToken)
{
return Ok(await referralAdministrationService.ManualBindAsync(
ResolveAdminActor(),
request.ToCommand(),
cancellationToken));
}
[HttpGet("/api/tenant/referral/team")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("查询推荐团队成员")]
[ProducesResponseType<ReferralList<ReferralTeamItem>>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralList<ReferralTeamItem>>> Team(
[FromQuery] ReferralTeamQueryDto query,
CancellationToken cancellationToken)
{
return Ok(await referralAdministrationService.GetTeamAsync(
ResolveAdminActor(),
query.ToQuery(),
cancellationToken));
}
[HttpPut("/api/tenant/referral/team")]
[Tags("租户端-推荐增长")]
[Authorize(Policy = BackendPermissions.TenantCrmManage)]
[EndpointSummary("新增或更新推荐团队关系")]
[ProducesResponseType<ReferralTeamItem>(StatusCodes.Status200OK)]
public async Task<ActionResult<ReferralTeamItem>> UpsertTeam(
UpsertReferralTeamDto request,
CancellationToken cancellationToken)
{
return Ok(await referralAdministrationService.UpsertTeamAsync(
ResolveAdminActor(),
request.ToCommand(),
cancellationToken));
}
private ReferralActor ResolveUserActor()
{
if (currentTenant.TenantId is null || currentUser.UserId is null)
throw new ReferralException("Current referral actor was not resolved.", "referral_access_denied");
return new ReferralActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
}
private ReferralAdminActor ResolveAdminActor()
{
if (currentTenant.TenantId is null || currentUser.UserId is null)
throw new ReferralException("Referral admin actor was not resolved.", "referral_access_denied");
return new ReferralAdminActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
}
private async Task<Guid> ResolveTenantIdAsync(string? tenantCode, CancellationToken cancellationToken)
{
if (currentTenant.TenantId.HasValue) return currentTenant.TenantId.Value;
var resolvedTenantCode = tenantCode ?? Request.Headers["x-tenant-code"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(resolvedTenantCode)) throw new TenantNotFoundException();
var tenant = await tenantDirectory.FindByCodeAsync(resolvedTenantCode.Trim(), cancellationToken)
?? throw new TenantNotFoundException();
tenantInitializer.Initialize(tenant.TenantId, tenant.TenantCode, TenantResolutionSource.TenantCode);
return tenant.TenantId;
}
}