286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
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()
|
||
};
|
||
}
|
||
}
|