fix(identity-service): 内部接口改用 accountSequence 查询

- identity-service InternalController 改用 accountSequence 批量/单个查询
- 添加 findByAccountSequences 批量查询方法
- authorization-service 调用改为 batchGetUserInfoBySequence/getUserInfoBySequence
- 系统间通信统一使用 accountSequence 作为标识符

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-23 02:16:12 -08:00
parent 5fa195e4bc
commit a194fcad72
5 changed files with 75 additions and 29 deletions

View File

@ -329,7 +329,8 @@
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\n\nasync function mint\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function mint\\(uint256 amount\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)'', ''function totalSupply\\(\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n // 2,000,000,000,000 USDT \\(2万亿\\) = 2000000000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(2000000000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Minting 2,000,000,000,000 USDT \\(2万亿\\)...''\\);\n const tx = await contract.mint\\(amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const totalSupply = await contract.totalSupply\\(\\);\n const balance = await contract.balanceOf\\(wallet.address\\);\n console.log\\(''New Total Supply:'', Number\\(totalSupply\\) / 1e6, ''USDT''\\);\n console.log\\(''Deployer Balance:'', Number\\(balance\\) / 1e6, ''USDT''\\);\n}\n\nmint\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")",
"Bash(npx prisma migrate diff:*)",
"Bash(git revert:*)",
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x0ec001ed6233b7959d7a251e2792621e4707c35f'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n // 1,020,000,000 USDT \\(10亿2千万\\) = 1020000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(1020000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,020,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")"
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x0ec001ed6233b7959d7a251e2792621e4707c35f'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n // 1,020,000,000 USDT \\(10亿2千万\\) = 1020000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(1020000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 1,020,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")",
"Bash(node -e \"\nconst { ethers } = require\\(''ethers''\\);\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x323AA5bd8101Ad97B724dc1584479219c7660628'';\n\nasync function transfer\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(KAVA_TESTNET_RPC\\);\n const wallet = new ethers.Wallet\\(privateKey, provider\\);\n \n const abi = [''function transfer\\(address to, uint256 amount\\) returns \\(bool\\)'', ''function balanceOf\\(address\\) view returns \\(uint256\\)''];\n const contract = new ethers.Contract\\(USDT_CONTRACT, abi, wallet\\);\n \n // 2,000,000,000 USDT \\(20亿\\) = 2000000000 * 1e6 \\(6 decimals\\)\n const amount = BigInt\\(2000000000\\) * BigInt\\(1000000\\);\n \n console.log\\(''Transferring 2,000,000,000 USDT to'', TO_ADDRESS\\);\n const tx = await contract.transfer\\(TO_ADDRESS, amount, { gasLimit: 100000 }\\);\n console.log\\(''TX Hash:'', tx.hash\\);\n await tx.wait\\(\\);\n \n const newBalance = await contract.balanceOf\\(TO_ADDRESS\\);\n console.log\\(''New balance:'', Number\\(newBalance\\) / 1e6, ''USDT''\\);\n}\n\ntransfer\\(\\).catch\\(e => console.error\\(''Error:'', e.message\\)\\);\n\")"
],
"deny": [],
"ask": []

View File

@ -683,15 +683,15 @@ export class AuthorizationApplicationService {
}
}
// 批量获取用户信息
const userIds = assessments.map(a => a.userId.value)
const userInfoMap = await this.identityServiceClient.batchGetUserInfo(userIds)
// 批量获取用户信息(使用 accountSequence
const accountSequences = assessments.map(a => a.userId.accountSequence)
const userInfoMap = await this.identityServiceClient.batchGetUserInfoBySequence(accountSequences)
const rankings: StickmanRankingDTO[] = []
const finalTarget = LadderTargetRule.getFinalTarget(roleType)
for (const assessment of assessments) {
const userInfo = userInfoMap.get(assessment.userId.value)
const userInfo = userInfoMap.get(assessment.userId.accountSequence)
this.logger.debug(
`[getStickmanRanking] 处理评估记录: userId=${assessment.userId.value}, ` +
`regionCode=${assessment.regionCode.value}, cumulativeCompleted=${assessment.cumulativeCompleted}`,
@ -3155,8 +3155,8 @@ export class AuthorizationApplicationService {
)
for (const auth of matchingAuths) {
// 获取用户昵称
const userInfo = await this.identityServiceClient.getUserInfo(auth.userId.value)
// 获取用户昵称(使用 accountSequence
const userInfo = await this.identityServiceClient.getUserInfoBySequence(auth.userId.accountSequence)
occupiedRegions.push({
accountSequence: ancestorAccountSeq,
nickname: userInfo?.nickname || `用户${ancestorAccountSeq}`,

View File

@ -15,6 +15,7 @@ export interface UserInfo {
/**
* Identity Service HTTP
* identity-service
* 使 accountSequence
*/
@Injectable()
export class IdentityServiceClient implements OnModuleInit {
@ -41,26 +42,26 @@ export class IdentityServiceClient implements OnModuleInit {
}
/**
*
* accountSequence
*/
async batchGetUserInfo(userIds: string[]): Promise<Map<string, UserInfo>> {
async batchGetUserInfoBySequence(accountSequences: string[]): Promise<Map<string, UserInfo>> {
const result = new Map<string, UserInfo>();
if (!this.enabled || userIds.length === 0) {
if (!this.enabled || accountSequences.length === 0) {
return result;
}
try {
this.logger.debug(`[HTTP] POST /internal/users/batch - ${userIds.length} users`);
this.logger.debug(`[HTTP] POST /internal/users/batch - ${accountSequences.length} users`);
const response = await this.httpClient.post<UserInfo[]>(
`/api/v1/internal/users/batch`,
{ userIds },
{ accountSequences },
);
if (response.data && Array.isArray(response.data)) {
for (const user of response.data) {
result.set(user.userId, user);
result.set(user.accountSequence, user);
}
}
@ -73,25 +74,25 @@ export class IdentityServiceClient implements OnModuleInit {
}
/**
*
* accountSequence
*/
async getUserInfo(userId: string): Promise<UserInfo | null> {
async getUserInfoBySequence(accountSequence: string): Promise<UserInfo | null> {
if (!this.enabled) {
return null;
}
try {
this.logger.debug(`[HTTP] GET /internal/users/${userId}`);
this.logger.debug(`[HTTP] GET /internal/users/${accountSequence}`);
const response = await this.httpClient.get<UserInfo>(
`/api/v1/internal/users/${userId}`,
`/api/v1/internal/users/${accountSequence}`,
);
if (response.data) {
return response.data;
}
} catch (error) {
this.logger.error(`[HTTP] Failed to get user info for ${userId}:`, error);
this.logger.error(`[HTTP] Failed to get user info for ${accountSequence}:`, error);
}
return null;

View File

@ -1,14 +1,14 @@
import { Controller, Post, Body, Logger } from '@nestjs/common';
import { Controller, Post, Body, Get, Param, Logger } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Public } from '@/shared/guards/jwt-auth.guard';
import { UserAccountRepositoryImpl } from '@/infrastructure/persistence/repositories/user-account.repository.impl';
import { UserId } from '@/domain/value-objects';
import { AccountSequence } from '@/domain/value-objects';
/**
* DTO
* DTO ( accountSequence)
*/
class BatchGetUsersDto {
userIds: string[];
class BatchGetUsersBySequenceDto {
accountSequences: string[];
}
/**
@ -36,18 +36,18 @@ export class InternalController {
@Public()
@Post('users/batch')
@ApiOperation({ summary: '批量获取用户信息(内部调用' })
@ApiOperation({ summary: '批量获取用户信息(内部调用,按 accountSequence' })
@ApiResponse({ status: 200, description: '返回用户信息列表' })
async batchGetUsers(@Body() dto: BatchGetUsersDto): Promise<UserBasicInfo[]> {
this.logger.debug(`[batchGetUsers] 请求用户数量: ${dto.userIds?.length || 0}`);
async batchGetUsers(@Body() dto: BatchGetUsersBySequenceDto): Promise<UserBasicInfo[]> {
this.logger.debug(`[batchGetUsers] 请求用户数量: ${dto.accountSequences?.length || 0}`);
if (!dto.userIds || dto.userIds.length === 0) {
if (!dto.accountSequences || dto.accountSequences.length === 0) {
return [];
}
try {
const userIds = dto.userIds.map((id) => UserId.create(id));
const users = await this.userRepository.findByUserIds(userIds);
const sequences = dto.accountSequences.map((seq) => AccountSequence.create(seq));
const users = await this.userRepository.findByAccountSequences(sequences);
const result = users.map((user) => ({
userId: user.userId.value.toString(),
@ -63,4 +63,31 @@ export class InternalController {
return [];
}
}
@Public()
@Get('users/:accountSequence')
@ApiOperation({ summary: '获取单个用户信息(内部调用,按 accountSequence' })
@ApiResponse({ status: 200, description: '返回用户信息' })
async getUserBySequence(@Param('accountSequence') accountSequence: string): Promise<UserBasicInfo | null> {
this.logger.debug(`[getUserBySequence] 查询用户: ${accountSequence}`);
try {
const sequence = AccountSequence.create(accountSequence);
const user = await this.userRepository.findByAccountSequence(sequence);
if (!user) {
return null;
}
return {
userId: user.userId.value.toString(),
accountSequence: user.accountSequence.value,
nickname: user.nickname,
avatarUrl: user.avatarUrl || undefined,
};
} catch (error) {
this.logger.error(`[getUserBySequence] 查询失败:`, error);
return null;
}
}
}

View File

@ -395,6 +395,23 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
return data.map((d) => this.toDomain(d));
}
async findByAccountSequences(sequences: AccountSequence[]): Promise<UserAccount[]> {
if (sequences.length === 0) {
return [];
}
const data = await this.prisma.userAccount.findMany({
where: {
accountSequence: {
in: sequences.map((seq) => seq.value),
},
},
include: { devices: true, walletAddresses: true },
});
return data.map((d) => this.toDomain(d));
}
// ============ 推荐相关 ============
async findByInviterSequence(