feat(referral): integrate authorization-service for community/province/city rights allocation
## Problem 社区/省/市权益分配一直返回 null,导致所有权益都分配给系统账户而非正确的授权用户。 原因:referral-service 的 getReferralContext 接口中 nearestCommunity、nearestProvinceAuth、 nearestCityAuth 三个字段硬编码为 null,注释说"需要后续实现"但一直未实现。 ## Solution 1. 新建 AuthorizationServiceClient 调用 authorization-service 的内部 API - /api/v1/authorization/nearest-community - /api/v1/authorization/nearest-province - /api/v1/authorization/nearest-city 2. 修改 InternalReferralController 使用并行查询获取授权信息 3. 添加 fallback 机制:authorization-service 不可用时返回 null(保持现有行为) 4. docker-compose.yml 添加 AUTHORIZATION_SERVICE_URL 环境变量 ## Files Changed - backend/services/referral-service/src/infrastructure/external/authorization-service.client.ts (new) - backend/services/referral-service/src/api/controllers/referral.controller.ts - backend/services/referral-service/src/modules/infrastructure.module.ts - backend/services/docker-compose.yml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7d9837fc22
commit
1a5925494e
|
|
@ -275,6 +275,7 @@ services:
|
|||
- KAFKA_BROKERS=kafka:29092
|
||||
- KAFKA_CLIENT_ID=referral-service
|
||||
- KAFKA_GROUP_ID=referral-service-group
|
||||
- AUTHORIZATION_SERVICE_URL=http://rwa-authorization-service:3009
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
@ -282,6 +283,8 @@ services:
|
|||
condition: service_healthy
|
||||
kafka:
|
||||
condition: service_started
|
||||
authorization-service:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3004/api/v1/health"]
|
||||
interval: 30s
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
HttpCode,
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
|
|
@ -36,6 +37,7 @@ import {
|
|||
TEAM_STATISTICS_REPOSITORY,
|
||||
ITeamStatisticsRepository,
|
||||
} from '../../domain';
|
||||
import { AuthorizationServiceClient } from '../../infrastructure/external';
|
||||
|
||||
@ApiTags('Referral')
|
||||
@Controller('referral')
|
||||
|
|
@ -181,7 +183,12 @@ export class ReferralController {
|
|||
@ApiTags('Internal Referral API')
|
||||
@Controller('referrals')
|
||||
export class InternalReferralController {
|
||||
constructor(private readonly referralService: ReferralService) {}
|
||||
private readonly logger = new Logger(InternalReferralController.name);
|
||||
|
||||
constructor(
|
||||
private readonly referralService: ReferralService,
|
||||
private readonly authorizationClient: AuthorizationServiceClient,
|
||||
) {}
|
||||
|
||||
@Get(':accountSequence/context')
|
||||
@ApiOperation({ summary: '获取用户推荐上下文信息(内部API)' })
|
||||
|
|
@ -192,19 +199,37 @@ export class InternalReferralController {
|
|||
@Query('provinceCode') provinceCode: string,
|
||||
@Query('cityCode') cityCode: string,
|
||||
) {
|
||||
// 获取用户的推荐链
|
||||
const query = new GetUserReferralInfoQuery(Number(accountSequence));
|
||||
const accountSeqNum = Number(accountSequence);
|
||||
|
||||
// 1. 获取用户的推荐链
|
||||
const query = new GetUserReferralInfoQuery(accountSeqNum);
|
||||
const referralInfo = await this.referralService.getUserReferralInfo(query);
|
||||
|
||||
// 返回推荐上下文信息
|
||||
// 目前返回基础信息,后续可以扩展省市授权等信息
|
||||
// 2. 并行查询授权信息(省/市/社区)
|
||||
// 使用 fallback 机制:如果 authorization-service 不可用,返回 null
|
||||
const authorizations = await this.authorizationClient.findAllNearestAuthorizations(
|
||||
accountSeqNum,
|
||||
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: null, // 省代账户ID - 需要后续实现
|
||||
nearestCityAuth: null, // 市代账户ID - 需要后续实现
|
||||
nearestCommunity: null, // 社区账户ID - 需要后续实现
|
||||
nearestProvinceAuth: authorizations.nearestProvinceAuth?.toString() ?? null,
|
||||
nearestCityAuth: authorizations.nearestCityAuth?.toString() ?? null,
|
||||
nearestCommunity: authorizations.nearestCommunity?.toString() ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
178
backend/services/referral-service/src/infrastructure/external/authorization-service.client.ts
vendored
Normal file
178
backend/services/referral-service/src/infrastructure/external/authorization-service.client.ts
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { firstValueFrom, timeout, catchError } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
export interface NearestAuthorizationResult {
|
||||
accountSequence: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorization Service 客户端
|
||||
* 用于查询用户推荐链中最近的省/市/社区授权用户
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuthorizationServiceClient {
|
||||
private readonly logger = new Logger(AuthorizationServiceClient.name);
|
||||
private readonly baseUrl: string;
|
||||
private readonly timeoutMs: number;
|
||||
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly httpService: HttpService,
|
||||
) {
|
||||
this.baseUrl =
|
||||
this.configService.get<string>('AUTHORIZATION_SERVICE_URL') ||
|
||||
'http://localhost:3009';
|
||||
this.timeoutMs = this.configService.get<number>('AUTHORIZATION_SERVICE_TIMEOUT_MS') || 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找用户推荐链中最近的社区授权用户
|
||||
* @param accountSequence 用户的 accountSequence
|
||||
* @returns 最近社区授权用户的 accountSequence,如果没有则返回 null
|
||||
*/
|
||||
async findNearestCommunity(accountSequence: number): Promise<number | null> {
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.get<NearestAuthorizationResult>(
|
||||
`${this.baseUrl}/api/v1/authorization/nearest-community`,
|
||||
{
|
||||
params: { accountSequence },
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
timeout(this.timeoutMs),
|
||||
catchError((error) => {
|
||||
this.logger.warn(
|
||||
`Failed to find nearest community for accountSequence=${accountSequence}: ${error.message}`,
|
||||
);
|
||||
return of({ data: { accountSequence: null } });
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return response.data.accountSequence;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error finding nearest community for accountSequence=${accountSequence}`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找用户推荐链中最近的省公司授权用户(匹配指定省份)
|
||||
* @param accountSequence 用户的 accountSequence
|
||||
* @param provinceCode 省份代码
|
||||
* @returns 最近省公司授权用户的 accountSequence,如果没有则返回 null
|
||||
*/
|
||||
async findNearestProvince(
|
||||
accountSequence: number,
|
||||
provinceCode: string,
|
||||
): Promise<number | null> {
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.get<NearestAuthorizationResult>(
|
||||
`${this.baseUrl}/api/v1/authorization/nearest-province`,
|
||||
{
|
||||
params: { accountSequence, provinceCode },
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
timeout(this.timeoutMs),
|
||||
catchError((error) => {
|
||||
this.logger.warn(
|
||||
`Failed to find nearest province for accountSequence=${accountSequence}, provinceCode=${provinceCode}: ${error.message}`,
|
||||
);
|
||||
return of({ data: { accountSequence: null } });
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return response.data.accountSequence;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error finding nearest province for accountSequence=${accountSequence}, provinceCode=${provinceCode}`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找用户推荐链中最近的市公司授权用户(匹配指定城市)
|
||||
* @param accountSequence 用户的 accountSequence
|
||||
* @param cityCode 城市代码
|
||||
* @returns 最近市公司授权用户的 accountSequence,如果没有则返回 null
|
||||
*/
|
||||
async findNearestCity(
|
||||
accountSequence: number,
|
||||
cityCode: string,
|
||||
): Promise<number | null> {
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.httpService
|
||||
.get<NearestAuthorizationResult>(
|
||||
`${this.baseUrl}/api/v1/authorization/nearest-city`,
|
||||
{
|
||||
params: { accountSequence, cityCode },
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
timeout(this.timeoutMs),
|
||||
catchError((error) => {
|
||||
this.logger.warn(
|
||||
`Failed to find nearest city for accountSequence=${accountSequence}, cityCode=${cityCode}: ${error.message}`,
|
||||
);
|
||||
return of({ data: { accountSequence: null } });
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return response.data.accountSequence;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error finding nearest city for accountSequence=${accountSequence}, cityCode=${cityCode}`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 并行查询所有授权信息
|
||||
* 优化性能:同时发起三个请求
|
||||
*/
|
||||
async findAllNearestAuthorizations(
|
||||
accountSequence: number,
|
||||
provinceCode: string,
|
||||
cityCode: string,
|
||||
): Promise<{
|
||||
nearestCommunity: number | null;
|
||||
nearestProvinceAuth: number | null;
|
||||
nearestCityAuth: number | null;
|
||||
}> {
|
||||
const [nearestCommunity, nearestProvinceAuth, nearestCityAuth] =
|
||||
await Promise.all([
|
||||
this.findNearestCommunity(accountSequence),
|
||||
this.findNearestProvince(accountSequence, provinceCode),
|
||||
this.findNearestCity(accountSequence, cityCode),
|
||||
]);
|
||||
|
||||
this.logger.debug(
|
||||
`Authorization lookup for accountSequence=${accountSequence}: ` +
|
||||
`community=${nearestCommunity}, province=${nearestProvinceAuth}, city=${nearestCityAuth}`,
|
||||
);
|
||||
|
||||
return {
|
||||
nearestCommunity,
|
||||
nearestProvinceAuth,
|
||||
nearestCityAuth,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export * from './authorization-service.client';
|
||||
|
|
@ -3,3 +3,4 @@ export * from './repositories';
|
|||
export * from './messaging';
|
||||
export * from './cache';
|
||||
export * from './kafka';
|
||||
export * from './external';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Module, Global } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import {
|
||||
PrismaService,
|
||||
ReferralRelationshipRepository,
|
||||
|
|
@ -8,6 +9,7 @@ import {
|
|||
EventPublisherService,
|
||||
RedisService,
|
||||
EventAckPublisher,
|
||||
AuthorizationServiceClient,
|
||||
} from '../infrastructure';
|
||||
import {
|
||||
REFERRAL_RELATIONSHIP_REPOSITORY,
|
||||
|
|
@ -16,13 +18,14 @@ import {
|
|||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
imports: [ConfigModule, HttpModule],
|
||||
providers: [
|
||||
PrismaService,
|
||||
KafkaService,
|
||||
RedisService,
|
||||
EventPublisherService,
|
||||
EventAckPublisher,
|
||||
AuthorizationServiceClient,
|
||||
{
|
||||
provide: REFERRAL_RELATIONSHIP_REPOSITORY,
|
||||
useClass: ReferralRelationshipRepository,
|
||||
|
|
@ -38,6 +41,7 @@ import {
|
|||
RedisService,
|
||||
EventPublisherService,
|
||||
EventAckPublisher,
|
||||
AuthorizationServiceClient,
|
||||
REFERRAL_RELATIONSHIP_REPOSITORY,
|
||||
TEAM_STATISTICS_REPOSITORY,
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue