rwadurian/backend/services/referral-service/src/api/controllers/referral.controller.ts

286 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Controller,
Get,
Post,
Query,
Param,
Body,
UseGuards,
HttpCode,
HttpStatus,
Inject,
Logger,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiParam,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../guards';
import { CurrentUser } from '../decorators';
import { ReferralService } from '../../application/services';
import {
ValidateReferralCodeDto,
GetDirectReferralsDto,
ReferralInfoResponseDto,
DirectReferralsResponseDto,
ValidateCodeResponseDto,
CreateReferralDto,
} from '../dto';
import { CreateReferralRelationshipCommand } from '../../application/commands';
import { GetUserReferralInfoQuery, GetDirectReferralsQuery } from '../../application/queries';
import {
REFERRAL_RELATIONSHIP_REPOSITORY,
IReferralRelationshipRepository,
TEAM_STATISTICS_REPOSITORY,
ITeamStatisticsRepository,
} from '../../domain';
import { AuthorizationServiceClient } from '../../infrastructure/external';
@ApiTags('Referral')
@Controller('referral')
export class ReferralController {
constructor(
private readonly referralService: ReferralService,
@Inject(REFERRAL_RELATIONSHIP_REPOSITORY)
private readonly referralRepo: IReferralRelationshipRepository,
@Inject(TEAM_STATISTICS_REPOSITORY)
private readonly teamStatsRepo: ITeamStatisticsRepository,
) {}
@Get('me')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: '获取当前用户推荐信息' })
@ApiResponse({ status: 200, type: ReferralInfoResponseDto })
async getMyReferralInfo(@CurrentUser('accountSequence') accountSequence: string): Promise<ReferralInfoResponseDto> {
this.logger.log(`[getMyReferralInfo] accountSequence from JWT: ${accountSequence}`);
// 直接使用JWT中的accountSequence查询
const query = new GetUserReferralInfoQuery(accountSequence);
return this.referralService.getUserReferralInfo(query);
}
@Get('me/direct-referrals')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: '获取当前用户直推列表' })
@ApiResponse({ status: 200, type: DirectReferralsResponseDto })
async getMyDirectReferrals(
@CurrentUser('accountSequence') accountSequence: string,
@Query() dto: GetDirectReferralsDto,
): Promise<DirectReferralsResponseDto> {
const query = new GetDirectReferralsQuery(accountSequence, dto.limit, dto.offset);
return this.referralService.getDirectReferrals(query);
}
@Get('validate/:code')
@ApiOperation({ summary: '验证推荐码是否有效' })
@ApiParam({ name: 'code', description: '推荐码' })
@ApiResponse({ status: 200, type: ValidateCodeResponseDto })
async validateCode(@Param('code') code: string): Promise<ValidateCodeResponseDto> {
const referrer = await this.referralService.getReferrerByCode(code.toUpperCase());
return {
valid: referrer !== null,
referrerId: referrer?.userId,
};
}
@Post('validate')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '验证推荐码 (POST方式)' })
@ApiResponse({ status: 200, type: ValidateCodeResponseDto })
async validateCodePost(@Body() dto: ValidateReferralCodeDto): Promise<ValidateCodeResponseDto> {
const referrer = await this.referralService.getReferrerByCode(dto.code.toUpperCase());
return {
valid: referrer !== null,
referrerId: referrer?.userId,
};
}
private readonly logger = new Logger(ReferralController.name);
/**
* 获取用户的推荐链内部API供 reward-service 调用)
* 返回直接推荐人及其认种状态
*
* @param accountSequence 支持两种格式:
* - accountSequence 格式: D25121300006 或 25121300006 (去掉D前缀)
* - 纯数字 userId 格式: 2 (旧格式,不推荐)
*/
@Get('chain/:accountSequence')
@ApiOperation({ summary: '获取用户推荐链内部API' })
@ApiParam({ name: 'accountSequence', description: '用户标识 (accountSequence 或 userId)' })
@ApiResponse({
status: 200,
description: '推荐链数据',
schema: {
type: 'object',
properties: {
ancestors: {
type: 'array',
items: {
type: 'object',
properties: {
userId: { type: 'string' },
accountSequence: { type: 'string' },
hasPlanted: { type: 'boolean' },
},
},
},
},
},
})
async getReferralChainForReward(
@Param('accountSequence') accountSequence: string,
): Promise<{ ancestors: Array<{ userId: string; accountSequence: string; hasPlanted: boolean }> }> {
this.logger.log(`[INTERNAL] getReferralChain: accountSequence=${accountSequence}`);
let relationship;
// 判断传入的是 accountSequence 还是 userId
// accountSequence 格式: D25121300006 或 25121300006 (11位数字或D+11位数字)
if (accountSequence.startsWith('D') || accountSequence.length >= 11) {
// 使用 accountSequence 查询
const normalizedSequence = accountSequence.startsWith('D') ? accountSequence : `D${accountSequence}`;
this.logger.log(`Using accountSequence query: ${normalizedSequence}`);
relationship = await this.referralRepo.findByAccountSequence(normalizedSequence);
} else {
// 尝试作为 userId 查询 (向后兼容)
this.logger.log(`Using userId query: ${accountSequence}`);
try {
const userIdBigInt = BigInt(accountSequence);
relationship = await this.referralRepo.findByUserId(userIdBigInt);
} catch {
this.logger.warn(`Invalid userId format: ${accountSequence}`);
return { ancestors: [] };
}
}
if (!relationship || !relationship.referrerId) {
this.logger.log(`No referral found for accountSequence: ${accountSequence}`);
return { ancestors: [] };
}
// 获取直接推荐人的认种状态
const referrerId = relationship.referrerId;
const referrerStats = await this.teamStatsRepo.findByUserId(referrerId);
const hasPlanted = referrerStats ? referrerStats.personalPlantingCount > 0 : false;
// 获取推荐人的 accountSequence
const referrerRelationship = await this.referralRepo.findByUserId(referrerId);
const referrerAccountSequence = referrerRelationship?.accountSequence || referrerId.toString();
this.logger.log(`Found referrer: userId=${referrerId}, accountSequence=${referrerAccountSequence}, hasPlanted=${hasPlanted}`);
return {
ancestors: [
{
userId: referrerId.toString(),
accountSequence: referrerAccountSequence,
hasPlanted,
},
],
};
}
@Post()
@ApiOperation({ summary: '创建推荐关系 (内部接口)' })
@ApiResponse({ status: 201, description: '创建成功' })
async createReferralRelationship(
@Body() dto: CreateReferralDto,
): Promise<{ referralCode: string }> {
const command = new CreateReferralRelationshipCommand(
BigInt(dto.userId),
dto.accountSequence,
dto.referralCode,
dto.referrerCode ?? null,
dto.inviterAccountSequence ?? null,
);
return this.referralService.createReferralRelationship(command);
}
@Get('user/:userId')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: '获取指定用户推荐信息' })
@ApiParam({ name: 'userId', description: '用户ID' })
@ApiResponse({ status: 200, type: ReferralInfoResponseDto })
async getUserReferralInfo(@Param('userId') userId: string): Promise<ReferralInfoResponseDto> {
const query = new GetUserReferralInfoQuery(userId); // userId 已经是字符串
return this.referralService.getUserReferralInfo(query);
}
@Get('user/:accountSequence/direct-referrals')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: '获取指定用户的直推列表(用于伞下树懒加载)' })
@ApiParam({ name: 'accountSequence', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
@ApiResponse({ status: 200, type: DirectReferralsResponseDto })
async getUserDirectReferrals(
@Param('accountSequence') accountSequence: string,
@Query() dto: GetDirectReferralsDto,
): Promise<DirectReferralsResponseDto> {
this.logger.log(`[getUserDirectReferrals] accountSequence=${accountSequence}, limit=${dto.limit}, offset=${dto.offset}`);
const query = new GetDirectReferralsQuery(accountSequence, dto.limit, dto.offset);
return this.referralService.getDirectReferrals(query);
}
}
/**
* 内部API控制器 - 供其他微服务调用
* 不需要JWT认证
*/
@ApiTags('Internal Referral API')
@Controller('referrals')
export class InternalReferralController {
private readonly logger = new Logger(InternalReferralController.name);
constructor(
private readonly referralService: ReferralService,
private readonly authorizationClient: AuthorizationServiceClient,
) {}
@Get(':accountSequence/context')
@ApiOperation({ summary: '获取用户推荐上下文信息(内部API)' })
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
@ApiResponse({ status: 200, description: '推荐上下文' })
async getReferralContext(
@Param('accountSequence') accountSequence: string,
@Query('provinceCode') provinceCode: string,
@Query('cityCode') cityCode: string,
) {
// 1. 获取用户的推荐链
const query = new GetUserReferralInfoQuery(accountSequence); // accountSequence 现在是字符串
const referralInfo = await this.referralService.getUserReferralInfo(query);
// 2. 并行查询授权信息(省/市/社区)
// 使用 fallback 机制:如果 authorization-service 不可用,返回 null
const authorizations = await this.authorizationClient.findAllNearestAuthorizations(
accountSequence, // accountSequence 现在是字符串
provinceCode,
cityCode,
);
this.logger.debug(
`[getReferralContext] accountSequence=${accountSequence}, ` +
`provinceCode=${provinceCode}, cityCode=${cityCode}, ` +
`referrerId=${referralInfo.referrerId}, ` +
`nearestCommunity=${authorizations.nearestCommunity}, ` +
`nearestProvinceAuth=${authorizations.nearestProvinceAuth}, ` +
`nearestCityAuth=${authorizations.nearestCityAuth}`,
);
// 3. 返回完整的推荐上下文信息
return {
accountSequence,
referralChain: referralInfo.referrerId ? [referralInfo.referrerId] : [],
referrerId: referralInfo.referrerId,
nearestProvinceAuth: authorizations.nearestProvinceAuth ?? null, // 已经是字符串,不需要 toString()
nearestCityAuth: authorizations.nearestCityAuth ?? null, // 已经是字符串,不需要 toString()
nearestCommunity: authorizations.nearestCommunity ?? null, // 已经是字符串,不需要 toString()
};
}
}