refactor!: 重构账户序列号格式 (BREAKING CHANGE)
将 accountSequence 从数字类型改为字符串类型,新格式为: - 普通用户: D + YYMMDD + 5位序号 (例: D2512120001) - 系统账户: S + 10位序号 (例: S0000000001) 主要变更: - identity-service: AccountSequence 值对象改为字符串类型 - identity-service: 序列号生成器改为按日期重置计数 - 所有服务: Prisma schema 字段类型从 BigInt/Int 改为 String - 所有服务: DTO、Command、Event 中的类型定义更新 - Flutter 前端: 相关数据模型类型更新 涉及服务: - identity-service (核心变更) - referral-service - authorization-service - wallet-service - reward-service - blockchain-service - backup-service - planting-service - mpc-service - admin-service - mobile-app (Flutter) 注意: 此为破坏性变更,需要清空数据库并重新运行 migration 🤖 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
8148d1d127
commit
4be9c1fb82
|
|
@ -32,7 +32,20 @@
|
||||||
"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 = ''0xd110112e057d269b41f7dc7dbf1f8eabb896f51a'';\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 // 300,000 USDT = 300000 * 1e6 (6 decimals)\n const amount = BigInt(300000) * BigInt(1000000);\n \n console.log(''Transferring 300,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 = ''0xd110112e057d269b41f7dc7dbf1f8eabb896f51a'';\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 // 300,000 USDT = 300000 * 1e6 (6 decimals)\n const amount = BigInt(300000) * BigInt(1000000);\n \n console.log(''Transferring 300,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 = ''0x6a664488d000e094baa8a055961921bf495c1152'';\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 // 880,000 USDT = 880000 * 1e6 (6 decimals)\n const amount = BigInt(880000) * BigInt(1000000);\n \n console.log(''Transferring 880,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 = ''0x6a664488d000e094baa8a055961921bf495c1152'';\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 // 880,000 USDT = 880000 * 1e6 (6 decimals)\n const amount = BigInt(880000) * BigInt(1000000);\n \n console.log(''Transferring 880,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 = ''0x53fd262ef1a707b80f87581cc64e09800fdbd690'';\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 // 360,000 USDT = 360000 * 1e6 (6 decimals)\n const amount = BigInt(360000) * BigInt(1000000);\n \n console.log(''Transferring 360,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 = ''0x53fd262ef1a707b80f87581cc64e09800fdbd690'';\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 // 360,000 USDT = 360000 * 1e6 (6 decimals)\n const amount = BigInt(360000) * BigInt(1000000);\n \n console.log(''Transferring 360,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(docker exec:*)"
|
"Bash(docker exec:*)",
|
||||||
|
"Bash(node -e:*)",
|
||||||
|
"Bash(dir /s /b c:UsersdongDesktoprwadurianbackendservicesreward-servicesrc*.ts)",
|
||||||
|
"Bash(git tag:*)",
|
||||||
|
"Bash(dir:*)",
|
||||||
|
"Bash(grep:*)",
|
||||||
|
"Bash(npx prisma format)",
|
||||||
|
"Bash(DATABASE_URL=\"postgresql://dummy:dummy@localhost:5432/dummy\" npx prisma generate:*)",
|
||||||
|
"Bash(npx prisma generate)",
|
||||||
|
"Bash(for file in grant-*.dto.ts)",
|
||||||
|
"Bash(do sed -i '/@ApiProperty.*账户序列号/,/accountSequence:/ s/@IsNumber()/@IsString()/' \"$file\")",
|
||||||
|
"Bash(done)",
|
||||||
|
"Bash(git diff:*)",
|
||||||
|
"Bash(npm install:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ datasource db {
|
||||||
// ============ 授权角色表 ============
|
// ============ 授权角色表 ============
|
||||||
model AuthorizationRole {
|
model AuthorizationRole {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId BigInt @map("user_id")
|
userId String @map("user_id")
|
||||||
accountSequence BigInt @map("account_sequence")
|
accountSequence String @map("account_sequence")
|
||||||
roleType RoleType @map("role_type")
|
roleType RoleType @map("role_type")
|
||||||
regionCode String @map("region_code")
|
regionCode String @map("region_code")
|
||||||
regionName String @map("region_name")
|
regionName String @map("region_name")
|
||||||
|
|
@ -23,9 +23,9 @@ model AuthorizationRole {
|
||||||
|
|
||||||
// 授权信息
|
// 授权信息
|
||||||
authorizedAt DateTime? @map("authorized_at")
|
authorizedAt DateTime? @map("authorized_at")
|
||||||
authorizedBy BigInt? @map("authorized_by")
|
authorizedBy String? @map("authorized_by")
|
||||||
revokedAt DateTime? @map("revoked_at")
|
revokedAt DateTime? @map("revoked_at")
|
||||||
revokedBy BigInt? @map("revoked_by")
|
revokedBy String? @map("revoked_by")
|
||||||
revokeReason String? @map("revoke_reason")
|
revokeReason String? @map("revoke_reason")
|
||||||
|
|
||||||
// 考核配置
|
// 考核配置
|
||||||
|
|
@ -65,8 +65,8 @@ model AuthorizationRole {
|
||||||
model MonthlyAssessment {
|
model MonthlyAssessment {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
authorizationId String @map("authorization_id")
|
authorizationId String @map("authorization_id")
|
||||||
userId BigInt @map("user_id")
|
userId String @map("user_id")
|
||||||
accountSequence BigInt @map("account_sequence")
|
accountSequence String @map("account_sequence")
|
||||||
roleType RoleType @map("role_type")
|
roleType RoleType @map("role_type")
|
||||||
regionCode String @map("region_code")
|
regionCode String @map("region_code")
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ model MonthlyAssessment {
|
||||||
|
|
||||||
// 豁免
|
// 豁免
|
||||||
isBypassed Boolean @default(false) @map("is_bypassed")
|
isBypassed Boolean @default(false) @map("is_bypassed")
|
||||||
bypassedBy BigInt? @map("bypassed_by")
|
bypassedBy String? @map("bypassed_by")
|
||||||
bypassedAt DateTime? @map("bypassed_at")
|
bypassedAt DateTime? @map("bypassed_at")
|
||||||
|
|
||||||
// 时间戳
|
// 时间戳
|
||||||
|
|
@ -125,22 +125,22 @@ model MonthlyAssessment {
|
||||||
model MonthlyBypass {
|
model MonthlyBypass {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
authorizationId String @map("authorization_id")
|
authorizationId String @map("authorization_id")
|
||||||
userId BigInt @map("user_id")
|
userId String @map("user_id")
|
||||||
accountSequence BigInt @map("account_sequence")
|
accountSequence String @map("account_sequence")
|
||||||
roleType RoleType @map("role_type")
|
roleType RoleType @map("role_type")
|
||||||
bypassMonth String @map("bypass_month") // YYYY-MM
|
bypassMonth String @map("bypass_month") // YYYY-MM
|
||||||
|
|
||||||
// 授权信息
|
// 授权信息
|
||||||
grantedBy BigInt @map("granted_by")
|
grantedBy String @map("granted_by")
|
||||||
grantedAt DateTime @map("granted_at")
|
grantedAt DateTime @map("granted_at")
|
||||||
reason String?
|
reason String?
|
||||||
|
|
||||||
// 审批信息(三人授权)
|
// 审批信息(三人授权)
|
||||||
approver1Id BigInt @map("approver1_id")
|
approver1Id String @map("approver1_id")
|
||||||
approver1At DateTime @map("approver1_at")
|
approver1At DateTime @map("approver1_at")
|
||||||
approver2Id BigInt? @map("approver2_id")
|
approver2Id String? @map("approver2_id")
|
||||||
approver2At DateTime? @map("approver2_at")
|
approver2At DateTime? @map("approver2_at")
|
||||||
approver3Id BigInt? @map("approver3_id")
|
approver3Id String? @map("approver3_id")
|
||||||
approver3At DateTime? @map("approver3_at")
|
approver3At DateTime? @map("approver3_at")
|
||||||
approvalStatus ApprovalStatus @default(PENDING) @map("approval_status")
|
approvalStatus ApprovalStatus @default(PENDING) @map("approval_status")
|
||||||
|
|
||||||
|
|
@ -281,8 +281,8 @@ model RegionHeatMap {
|
||||||
// ============ 火柴人排名视图数据表 ============
|
// ============ 火柴人排名视图数据表 ============
|
||||||
model StickmanRanking {
|
model StickmanRanking {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId BigInt @map("user_id")
|
userId String @map("user_id")
|
||||||
accountSequence BigInt @map("account_sequence")
|
accountSequence String @map("account_sequence")
|
||||||
authorizationId String @map("authorization_id")
|
authorizationId String @map("authorization_id")
|
||||||
roleType RoleType @map("role_type")
|
roleType RoleType @map("role_type")
|
||||||
regionCode String @map("region_code")
|
regionCode String @map("region_code")
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,133 @@
|
||||||
import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common'
|
import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common'
|
||||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'
|
||||||
import { AuthorizationApplicationService } from '@/application/services'
|
import { AuthorizationApplicationService } from '@/application/services'
|
||||||
import {
|
import {
|
||||||
GrantCommunityCommand,
|
GrantCommunityCommand,
|
||||||
GrantProvinceCompanyCommand,
|
GrantProvinceCompanyCommand,
|
||||||
GrantCityCompanyCommand,
|
GrantCityCompanyCommand,
|
||||||
GrantAuthProvinceCompanyCommand,
|
GrantAuthProvinceCompanyCommand,
|
||||||
GrantAuthCityCompanyCommand,
|
GrantAuthCityCompanyCommand,
|
||||||
} from '@/application/commands'
|
} from '@/application/commands'
|
||||||
import {
|
import {
|
||||||
GrantCommunityDto,
|
GrantCommunityDto,
|
||||||
GrantProvinceCompanyDto,
|
GrantProvinceCompanyDto,
|
||||||
GrantCityCompanyDto,
|
GrantCityCompanyDto,
|
||||||
GrantAuthProvinceCompanyDto,
|
GrantAuthProvinceCompanyDto,
|
||||||
GrantAuthCityCompanyDto,
|
GrantAuthCityCompanyDto,
|
||||||
} from '@/api/dto/request'
|
} from '@/api/dto/request'
|
||||||
import { CurrentUser } from '@/shared/decorators'
|
import { CurrentUser } from '@/shared/decorators'
|
||||||
import { JwtAuthGuard } from '@/shared/guards'
|
import { JwtAuthGuard } from '@/shared/guards'
|
||||||
|
|
||||||
@ApiTags('Admin Authorization')
|
@ApiTags('Admin Authorization')
|
||||||
@Controller('admin/authorizations')
|
@Controller('admin/authorizations')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
export class AdminAuthorizationController {
|
export class AdminAuthorizationController {
|
||||||
constructor(private readonly applicationService: AuthorizationApplicationService) {}
|
constructor(private readonly applicationService: AuthorizationApplicationService) {}
|
||||||
|
|
||||||
@Post('community')
|
@Post('community')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ApiOperation({ summary: '授权社区(管理员)' })
|
@ApiOperation({ summary: '授权社区(管理员)' })
|
||||||
@ApiResponse({ status: 201, description: '授权成功' })
|
@ApiResponse({ status: 201, description: '授权成功' })
|
||||||
async grantCommunity(
|
async grantCommunity(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantCommunityDto,
|
@Body() dto: GrantCommunityDto,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
const command = new GrantCommunityCommand(
|
const command = new GrantCommunityCommand(
|
||||||
dto.userId,
|
dto.userId,
|
||||||
dto.accountSequence,
|
dto.accountSequence,
|
||||||
dto.communityName,
|
dto.communityName,
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.skipAssessment ?? false,
|
dto.skipAssessment ?? false,
|
||||||
)
|
)
|
||||||
await this.applicationService.grantCommunity(command)
|
await this.applicationService.grantCommunity(command)
|
||||||
return { message: '社区授权成功' }
|
return { message: '社区授权成功' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('province-company')
|
@Post('province-company')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ApiOperation({ summary: '授权正式省公司(管理员)' })
|
@ApiOperation({ summary: '授权正式省公司(管理员)' })
|
||||||
@ApiResponse({ status: 201, description: '授权成功' })
|
@ApiResponse({ status: 201, description: '授权成功' })
|
||||||
async grantProvinceCompany(
|
async grantProvinceCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantProvinceCompanyDto,
|
@Body() dto: GrantProvinceCompanyDto,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
const command = new GrantProvinceCompanyCommand(
|
const command = new GrantProvinceCompanyCommand(
|
||||||
dto.userId,
|
dto.userId,
|
||||||
dto.accountSequence,
|
dto.accountSequence,
|
||||||
dto.provinceCode,
|
dto.provinceCode,
|
||||||
dto.provinceName,
|
dto.provinceName,
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.skipAssessment ?? false,
|
dto.skipAssessment ?? false,
|
||||||
)
|
)
|
||||||
await this.applicationService.grantProvinceCompany(command)
|
await this.applicationService.grantProvinceCompany(command)
|
||||||
return { message: '正式省公司授权成功' }
|
return { message: '正式省公司授权成功' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('city-company')
|
@Post('city-company')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ApiOperation({ summary: '授权正式市公司(管理员)' })
|
@ApiOperation({ summary: '授权正式市公司(管理员)' })
|
||||||
@ApiResponse({ status: 201, description: '授权成功' })
|
@ApiResponse({ status: 201, description: '授权成功' })
|
||||||
async grantCityCompany(
|
async grantCityCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantCityCompanyDto,
|
@Body() dto: GrantCityCompanyDto,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
const command = new GrantCityCompanyCommand(
|
const command = new GrantCityCompanyCommand(
|
||||||
dto.userId,
|
dto.userId,
|
||||||
dto.accountSequence,
|
dto.accountSequence,
|
||||||
dto.cityCode,
|
dto.cityCode,
|
||||||
dto.cityName,
|
dto.cityName,
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.skipAssessment ?? false,
|
dto.skipAssessment ?? false,
|
||||||
)
|
)
|
||||||
await this.applicationService.grantCityCompany(command)
|
await this.applicationService.grantCityCompany(command)
|
||||||
return { message: '正式市公司授权成功' }
|
return { message: '正式市公司授权成功' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('auth-province-company')
|
@Post('auth-province-company')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ApiOperation({ summary: '授权省团队(管理员)' })
|
@ApiOperation({ summary: '授权省团队(管理员)' })
|
||||||
@ApiResponse({ status: 201, description: '授权成功' })
|
@ApiResponse({ status: 201, description: '授权成功' })
|
||||||
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同省份授权)' })
|
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同省份授权)' })
|
||||||
async grantAuthProvinceCompany(
|
async grantAuthProvinceCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantAuthProvinceCompanyDto,
|
@Body() dto: GrantAuthProvinceCompanyDto,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
const command = new GrantAuthProvinceCompanyCommand(
|
const command = new GrantAuthProvinceCompanyCommand(
|
||||||
dto.userId,
|
dto.userId,
|
||||||
dto.accountSequence,
|
dto.accountSequence,
|
||||||
dto.provinceCode,
|
dto.provinceCode,
|
||||||
dto.provinceName,
|
dto.provinceName,
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.skipAssessment ?? false,
|
dto.skipAssessment ?? false,
|
||||||
)
|
)
|
||||||
await this.applicationService.grantAuthProvinceCompany(command)
|
await this.applicationService.grantAuthProvinceCompany(command)
|
||||||
return { message: '省团队授权成功' }
|
return { message: '省团队授权成功' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('auth-city-company')
|
@Post('auth-city-company')
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ApiOperation({ summary: '授权市团队(管理员)' })
|
@ApiOperation({ summary: '授权市团队(管理员)' })
|
||||||
@ApiResponse({ status: 201, description: '授权成功' })
|
@ApiResponse({ status: 201, description: '授权成功' })
|
||||||
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同城市授权)' })
|
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同城市授权)' })
|
||||||
async grantAuthCityCompany(
|
async grantAuthCityCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantAuthCityCompanyDto,
|
@Body() dto: GrantAuthCityCompanyDto,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
const command = new GrantAuthCityCompanyCommand(
|
const command = new GrantAuthCityCompanyCommand(
|
||||||
dto.userId,
|
dto.userId,
|
||||||
dto.accountSequence,
|
dto.accountSequence,
|
||||||
dto.cityCode,
|
dto.cityCode,
|
||||||
dto.cityName,
|
dto.cityName,
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.skipAssessment ?? false,
|
dto.skipAssessment ?? false,
|
||||||
)
|
)
|
||||||
await this.applicationService.grantAuthCityCompany(command)
|
await this.applicationService.grantAuthCityCompany(command)
|
||||||
return { message: '市团队授权成功' }
|
return { message: '市团队授权成功' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,172 +1,172 @@
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Post,
|
Post,
|
||||||
Delete,
|
Delete,
|
||||||
Param,
|
Param,
|
||||||
Body,
|
Body,
|
||||||
Query,
|
Query,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import {
|
import {
|
||||||
ApiTags,
|
ApiTags,
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
} from '@nestjs/swagger'
|
} from '@nestjs/swagger'
|
||||||
import { AuthorizationApplicationService } from '@/application/services'
|
import { AuthorizationApplicationService } from '@/application/services'
|
||||||
import {
|
import {
|
||||||
ApplyCommunityAuthCommand,
|
ApplyCommunityAuthCommand,
|
||||||
ApplyAuthProvinceCompanyCommand,
|
ApplyAuthProvinceCompanyCommand,
|
||||||
ApplyAuthCityCompanyCommand,
|
ApplyAuthCityCompanyCommand,
|
||||||
RevokeAuthorizationCommand,
|
RevokeAuthorizationCommand,
|
||||||
GrantMonthlyBypassCommand,
|
GrantMonthlyBypassCommand,
|
||||||
ExemptLocalPercentageCheckCommand,
|
ExemptLocalPercentageCheckCommand,
|
||||||
} from '@/application/commands'
|
} from '@/application/commands'
|
||||||
import {
|
import {
|
||||||
ApplyCommunityAuthDto,
|
ApplyCommunityAuthDto,
|
||||||
ApplyAuthProvinceDto,
|
ApplyAuthProvinceDto,
|
||||||
ApplyAuthCityDto,
|
ApplyAuthCityDto,
|
||||||
RevokeAuthorizationDto,
|
RevokeAuthorizationDto,
|
||||||
GrantMonthlyBypassDto,
|
GrantMonthlyBypassDto,
|
||||||
} from '@/api/dto/request'
|
} from '@/api/dto/request'
|
||||||
import {
|
import {
|
||||||
AuthorizationResponse,
|
AuthorizationResponse,
|
||||||
ApplyAuthorizationResponse,
|
ApplyAuthorizationResponse,
|
||||||
StickmanRankingResponse,
|
StickmanRankingResponse,
|
||||||
CommunityHierarchyResponse,
|
CommunityHierarchyResponse,
|
||||||
} from '@/api/dto/response'
|
} from '@/api/dto/response'
|
||||||
import { CurrentUser } from '@/shared/decorators'
|
import { CurrentUser } from '@/shared/decorators'
|
||||||
import { JwtAuthGuard } from '@/shared/guards'
|
import { JwtAuthGuard } from '@/shared/guards'
|
||||||
import { RoleType } from '@/domain/enums'
|
import { RoleType } from '@/domain/enums'
|
||||||
|
|
||||||
@ApiTags('Authorization')
|
@ApiTags('Authorization')
|
||||||
@Controller('authorizations')
|
@Controller('authorizations')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
export class AuthorizationController {
|
export class AuthorizationController {
|
||||||
constructor(private readonly applicationService: AuthorizationApplicationService) {}
|
constructor(private readonly applicationService: AuthorizationApplicationService) {}
|
||||||
|
|
||||||
@Post('community')
|
@Post('community')
|
||||||
@ApiOperation({ summary: '申请社区授权' })
|
@ApiOperation({ summary: '申请社区授权' })
|
||||||
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
||||||
async applyCommunityAuth(
|
async applyCommunityAuth(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: ApplyCommunityAuthDto,
|
@Body() dto: ApplyCommunityAuthDto,
|
||||||
): Promise<ApplyAuthorizationResponse> {
|
): Promise<ApplyAuthorizationResponse> {
|
||||||
const command = new ApplyCommunityAuthCommand(user.userId, user.accountSequence, dto.communityName)
|
const command = new ApplyCommunityAuthCommand(user.userId, user.accountSequence, dto.communityName)
|
||||||
return await this.applicationService.applyCommunityAuth(command)
|
return await this.applicationService.applyCommunityAuth(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('province')
|
@Post('province')
|
||||||
@ApiOperation({ summary: '申请授权省公司' })
|
@ApiOperation({ summary: '申请授权省公司' })
|
||||||
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
||||||
async applyAuthProvinceCompany(
|
async applyAuthProvinceCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: ApplyAuthProvinceDto,
|
@Body() dto: ApplyAuthProvinceDto,
|
||||||
): Promise<ApplyAuthorizationResponse> {
|
): Promise<ApplyAuthorizationResponse> {
|
||||||
const command = new ApplyAuthProvinceCompanyCommand(
|
const command = new ApplyAuthProvinceCompanyCommand(
|
||||||
user.userId,
|
user.userId,
|
||||||
user.accountSequence,
|
user.accountSequence,
|
||||||
dto.provinceCode,
|
dto.provinceCode,
|
||||||
dto.provinceName,
|
dto.provinceName,
|
||||||
)
|
)
|
||||||
return await this.applicationService.applyAuthProvinceCompany(command)
|
return await this.applicationService.applyAuthProvinceCompany(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('city')
|
@Post('city')
|
||||||
@ApiOperation({ summary: '申请授权市公司' })
|
@ApiOperation({ summary: '申请授权市公司' })
|
||||||
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
@ApiResponse({ status: 201, type: ApplyAuthorizationResponse })
|
||||||
async applyAuthCityCompany(
|
async applyAuthCityCompany(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: ApplyAuthCityDto,
|
@Body() dto: ApplyAuthCityDto,
|
||||||
): Promise<ApplyAuthorizationResponse> {
|
): Promise<ApplyAuthorizationResponse> {
|
||||||
const command = new ApplyAuthCityCompanyCommand(user.userId, user.accountSequence, dto.cityCode, dto.cityName)
|
const command = new ApplyAuthCityCompanyCommand(user.userId, user.accountSequence, dto.cityCode, dto.cityName)
|
||||||
return await this.applicationService.applyAuthCityCompany(command)
|
return await this.applicationService.applyAuthCityCompany(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('my')
|
@Get('my')
|
||||||
@ApiOperation({ summary: '获取我的授权列表' })
|
@ApiOperation({ summary: '获取我的授权列表' })
|
||||||
@ApiResponse({ status: 200, type: [AuthorizationResponse] })
|
@ApiResponse({ status: 200, type: [AuthorizationResponse] })
|
||||||
async getMyAuthorizations(
|
async getMyAuthorizations(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
): Promise<AuthorizationResponse[]> {
|
): Promise<AuthorizationResponse[]> {
|
||||||
return await this.applicationService.getUserAuthorizations(user.accountSequence)
|
return await this.applicationService.getUserAuthorizations(user.accountSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('my/community-hierarchy')
|
@Get('my/community-hierarchy')
|
||||||
@ApiOperation({ summary: '获取我的社区层级(上级社区和下级社区)' })
|
@ApiOperation({ summary: '获取我的社区层级(上级社区和下级社区)' })
|
||||||
@ApiResponse({ status: 200, type: CommunityHierarchyResponse })
|
@ApiResponse({ status: 200, type: CommunityHierarchyResponse })
|
||||||
async getMyCommunityHierarchy(
|
async getMyCommunityHierarchy(
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
): Promise<CommunityHierarchyResponse> {
|
): Promise<CommunityHierarchyResponse> {
|
||||||
return await this.applicationService.getCommunityHierarchy(user.accountSequence)
|
return await this.applicationService.getCommunityHierarchy(user.accountSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: '获取授权详情' })
|
@ApiOperation({ summary: '获取授权详情' })
|
||||||
@ApiParam({ name: 'id', description: '授权ID' })
|
@ApiParam({ name: 'id', description: '授权ID' })
|
||||||
@ApiResponse({ status: 200, type: AuthorizationResponse })
|
@ApiResponse({ status: 200, type: AuthorizationResponse })
|
||||||
async getAuthorizationById(@Param('id') id: string): Promise<AuthorizationResponse | null> {
|
async getAuthorizationById(@Param('id') id: string): Promise<AuthorizationResponse | null> {
|
||||||
return await this.applicationService.getAuthorizationById(id)
|
return await this.applicationService.getAuthorizationById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('ranking/stickman')
|
@Get('ranking/stickman')
|
||||||
@ApiOperation({ summary: '获取火柴人排名' })
|
@ApiOperation({ summary: '获取火柴人排名' })
|
||||||
@ApiQuery({ name: 'month', description: '月份 (YYYY-MM)', example: '2024-01' })
|
@ApiQuery({ name: 'month', description: '月份 (YYYY-MM)', example: '2024-01' })
|
||||||
@ApiQuery({ name: 'roleType', description: '角色类型', enum: RoleType })
|
@ApiQuery({ name: 'roleType', description: '角色类型', enum: RoleType })
|
||||||
@ApiQuery({ name: 'regionCode', description: '区域代码' })
|
@ApiQuery({ name: 'regionCode', description: '区域代码' })
|
||||||
@ApiResponse({ status: 200, type: [StickmanRankingResponse] })
|
@ApiResponse({ status: 200, type: [StickmanRankingResponse] })
|
||||||
async getStickmanRanking(
|
async getStickmanRanking(
|
||||||
@Query('month') month: string,
|
@Query('month') month: string,
|
||||||
@Query('roleType') roleType: RoleType,
|
@Query('roleType') roleType: RoleType,
|
||||||
@Query('regionCode') regionCode: string,
|
@Query('regionCode') regionCode: string,
|
||||||
): Promise<StickmanRankingResponse[]> {
|
): Promise<StickmanRankingResponse[]> {
|
||||||
return await this.applicationService.getStickmanRanking(month, roleType, regionCode)
|
return await this.applicationService.getStickmanRanking(month, roleType, regionCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@ApiOperation({ summary: '撤销授权(管理员)' })
|
@ApiOperation({ summary: '撤销授权(管理员)' })
|
||||||
@ApiParam({ name: 'id', description: '授权ID' })
|
@ApiParam({ name: 'id', description: '授权ID' })
|
||||||
@ApiResponse({ status: 204 })
|
@ApiResponse({ status: 204 })
|
||||||
async revokeAuthorization(
|
async revokeAuthorization(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: RevokeAuthorizationDto,
|
@Body() dto: RevokeAuthorizationDto,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const command = new RevokeAuthorizationCommand(id, user.accountSequence, dto.reason)
|
const command = new RevokeAuthorizationCommand(id, user.accountSequence, dto.reason)
|
||||||
await this.applicationService.revokeAuthorization(command)
|
await this.applicationService.revokeAuthorization(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/bypass')
|
@Post(':id/bypass')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@ApiOperation({ summary: '授予单月豁免(管理员)' })
|
@ApiOperation({ summary: '授予单月豁免(管理员)' })
|
||||||
@ApiParam({ name: 'id', description: '授权ID' })
|
@ApiParam({ name: 'id', description: '授权ID' })
|
||||||
@ApiResponse({ status: 204 })
|
@ApiResponse({ status: 204 })
|
||||||
async grantMonthlyBypass(
|
async grantMonthlyBypass(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
@Body() dto: GrantMonthlyBypassDto,
|
@Body() dto: GrantMonthlyBypassDto,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const command = new GrantMonthlyBypassCommand(id, dto.month, user.accountSequence, dto.reason)
|
const command = new GrantMonthlyBypassCommand(id, dto.month, user.accountSequence, dto.reason)
|
||||||
await this.applicationService.grantMonthlyBypass(command)
|
await this.applicationService.grantMonthlyBypass(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post(':id/exempt-percentage')
|
@Post(':id/exempt-percentage')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
@ApiOperation({ summary: '豁免占比考核(管理员)' })
|
@ApiOperation({ summary: '豁免占比考核(管理员)' })
|
||||||
@ApiParam({ name: 'id', description: '授权ID' })
|
@ApiParam({ name: 'id', description: '授权ID' })
|
||||||
@ApiResponse({ status: 204 })
|
@ApiResponse({ status: 204 })
|
||||||
async exemptLocalPercentageCheck(
|
async exemptLocalPercentageCheck(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
@CurrentUser() user: { userId: string; accountSequence: string },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const command = new ExemptLocalPercentageCheckCommand(id, user.accountSequence)
|
const command = new ExemptLocalPercentageCheckCommand(id, user.accountSequence)
|
||||||
await this.applicationService.exemptLocalPercentageCheck(command)
|
await this.applicationService.exemptLocalPercentageCheck(command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { Controller, Get } from '@nestjs/common'
|
import { Controller, Get } from '@nestjs/common'
|
||||||
import { ApiTags, ApiOperation } from '@nestjs/swagger'
|
import { ApiTags, ApiOperation } from '@nestjs/swagger'
|
||||||
|
|
||||||
@ApiTags('Health')
|
@ApiTags('Health')
|
||||||
@Controller('health')
|
@Controller('health')
|
||||||
export class HealthController {
|
export class HealthController {
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: '健康检查' })
|
@ApiOperation({ summary: '健康检查' })
|
||||||
check() {
|
check() {
|
||||||
return {
|
return {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
service: 'authorization-service',
|
service: 'authorization-service',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,21 +26,21 @@ export class InternalAuthorizationController {
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
accountSequence: { type: 'number', nullable: true },
|
accountSequence: { type: 'string', nullable: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
async findNearestCommunity(
|
async findNearestCommunity(
|
||||||
@Query('accountSequence') accountSequence: string,
|
@Query('accountSequence') accountSequence: string,
|
||||||
): Promise<{ accountSequence: number | null }> {
|
): Promise<{ accountSequence: string | null }> {
|
||||||
this.logger.debug(`[INTERNAL] findNearestCommunity: accountSequence=${accountSequence}`)
|
this.logger.debug(`[INTERNAL] findNearestCommunity: accountSequence=${accountSequence}`)
|
||||||
|
|
||||||
const result = await this.applicationService.findNearestAuthorizedCommunity(
|
const result = await this.applicationService.findNearestAuthorizedCommunity(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountSequence: result ? Number(result) : null,
|
accountSequence: result,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,25 +58,25 @@ export class InternalAuthorizationController {
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
accountSequence: { type: 'number', nullable: true },
|
accountSequence: { type: 'string', nullable: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
async findNearestProvince(
|
async findNearestProvince(
|
||||||
@Query('accountSequence') accountSequence: string,
|
@Query('accountSequence') accountSequence: string,
|
||||||
@Query('provinceCode') provinceCode: string,
|
@Query('provinceCode') provinceCode: string,
|
||||||
): Promise<{ accountSequence: number | null }> {
|
): Promise<{ accountSequence: string | null }> {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[INTERNAL] findNearestProvince: accountSequence=${accountSequence}, provinceCode=${provinceCode}`,
|
`[INTERNAL] findNearestProvince: accountSequence=${accountSequence}, provinceCode=${provinceCode}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await this.applicationService.findNearestAuthorizedProvince(
|
const result = await this.applicationService.findNearestAuthorizedProvince(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
provinceCode,
|
provinceCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountSequence: result ? Number(result) : null,
|
accountSequence: result ? result : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,25 +94,25 @@ export class InternalAuthorizationController {
|
||||||
schema: {
|
schema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
accountSequence: { type: 'number', nullable: true },
|
accountSequence: { type: 'string', nullable: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
async findNearestCity(
|
async findNearestCity(
|
||||||
@Query('accountSequence') accountSequence: string,
|
@Query('accountSequence') accountSequence: string,
|
||||||
@Query('cityCode') cityCode: string,
|
@Query('cityCode') cityCode: string,
|
||||||
): Promise<{ accountSequence: number | null }> {
|
): Promise<{ accountSequence: string | null }> {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[INTERNAL] findNearestCity: accountSequence=${accountSequence}, cityCode=${cityCode}`,
|
`[INTERNAL] findNearestCity: accountSequence=${accountSequence}, cityCode=${cityCode}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await this.applicationService.findNearestAuthorizedCity(
|
const result = await this.applicationService.findNearestAuthorizedCity(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
cityCode,
|
cityCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accountSequence: result ? Number(result) : null,
|
accountSequence: result ? result : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,8 +136,8 @@ export class InternalAuthorizationController {
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
accountSequence: { type: 'number', description: '接收者账号' },
|
accountSequence: { type: 'string', description: '接收者账号' },
|
||||||
treeCount: { type: 'number', description: '分配棵数' },
|
treeCount: { type: 'string', description: '分配棵数' },
|
||||||
reason: { type: 'string', description: '分配原因' },
|
reason: { type: 'string', description: '分配原因' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -150,7 +150,7 @@ export class InternalAuthorizationController {
|
||||||
@Query('treeCount') treeCount: string,
|
@Query('treeCount') treeCount: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
distributions: Array<{
|
distributions: Array<{
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
treeCount: number
|
treeCount: number
|
||||||
reason: string
|
reason: string
|
||||||
}>
|
}>
|
||||||
|
|
@ -160,7 +160,7 @@ export class InternalAuthorizationController {
|
||||||
)
|
)
|
||||||
|
|
||||||
return this.applicationService.getCommunityRewardDistribution(
|
return this.applicationService.getCommunityRewardDistribution(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
Number(treeCount),
|
Number(treeCount),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +179,7 @@ export class InternalAuthorizationController {
|
||||||
@Query('treeCount') treeCount: string,
|
@Query('treeCount') treeCount: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
distributions: Array<{
|
distributions: Array<{
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
treeCount: number
|
treeCount: number
|
||||||
reason: string
|
reason: string
|
||||||
}>
|
}>
|
||||||
|
|
@ -189,7 +189,7 @@ export class InternalAuthorizationController {
|
||||||
)
|
)
|
||||||
|
|
||||||
return this.applicationService.getProvinceTeamRewardDistribution(
|
return this.applicationService.getProvinceTeamRewardDistribution(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
provinceCode,
|
provinceCode,
|
||||||
Number(treeCount),
|
Number(treeCount),
|
||||||
)
|
)
|
||||||
|
|
@ -207,7 +207,7 @@ export class InternalAuthorizationController {
|
||||||
@Query('treeCount') treeCount: string,
|
@Query('treeCount') treeCount: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
distributions: Array<{
|
distributions: Array<{
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
treeCount: number
|
treeCount: number
|
||||||
reason: string
|
reason: string
|
||||||
isSystemAccount: boolean
|
isSystemAccount: boolean
|
||||||
|
|
@ -237,7 +237,7 @@ export class InternalAuthorizationController {
|
||||||
@Query('treeCount') treeCount: string,
|
@Query('treeCount') treeCount: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
distributions: Array<{
|
distributions: Array<{
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
treeCount: number
|
treeCount: number
|
||||||
reason: string
|
reason: string
|
||||||
}>
|
}>
|
||||||
|
|
@ -247,7 +247,7 @@ export class InternalAuthorizationController {
|
||||||
)
|
)
|
||||||
|
|
||||||
return this.applicationService.getCityTeamRewardDistribution(
|
return this.applicationService.getCityTeamRewardDistribution(
|
||||||
Number(accountSequence),
|
accountSequence,
|
||||||
cityCode,
|
cityCode,
|
||||||
Number(treeCount),
|
Number(treeCount),
|
||||||
)
|
)
|
||||||
|
|
@ -265,7 +265,7 @@ export class InternalAuthorizationController {
|
||||||
@Query('treeCount') treeCount: string,
|
@Query('treeCount') treeCount: string,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
distributions: Array<{
|
distributions: Array<{
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
treeCount: number
|
treeCount: number
|
||||||
reason: string
|
reason: string
|
||||||
isSystemAccount: boolean
|
isSystemAccount: boolean
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||||
import { ApiProperty } from '@nestjs/swagger'
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class ApplyAuthCityDto {
|
export class ApplyAuthCityDto {
|
||||||
@ApiProperty({ description: '城市代码', example: '430100' })
|
@ApiProperty({ description: '城市代码', example: '430100' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '城市代码不能为空' })
|
@IsNotEmpty({ message: '城市代码不能为空' })
|
||||||
@MaxLength(20, { message: '城市代码最大20字符' })
|
@MaxLength(20, { message: '城市代码最大20字符' })
|
||||||
cityCode: string
|
cityCode: string
|
||||||
|
|
||||||
@ApiProperty({ description: '城市名称', example: '长沙市' })
|
@ApiProperty({ description: '城市名称', example: '长沙市' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '城市名称不能为空' })
|
@IsNotEmpty({ message: '城市名称不能为空' })
|
||||||
@MaxLength(50, { message: '城市名称最大50字符' })
|
@MaxLength(50, { message: '城市名称最大50字符' })
|
||||||
cityName: string
|
cityName: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||||
import { ApiProperty } from '@nestjs/swagger'
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class ApplyAuthProvinceDto {
|
export class ApplyAuthProvinceDto {
|
||||||
@ApiProperty({ description: '省份代码', example: '430000' })
|
@ApiProperty({ description: '省份代码', example: '430000' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '省份代码不能为空' })
|
@IsNotEmpty({ message: '省份代码不能为空' })
|
||||||
@MaxLength(20, { message: '省份代码最大20字符' })
|
@MaxLength(20, { message: '省份代码最大20字符' })
|
||||||
provinceCode: string
|
provinceCode: string
|
||||||
|
|
||||||
@ApiProperty({ description: '省份名称', example: '湖南省' })
|
@ApiProperty({ description: '省份名称', example: '湖南省' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '省份名称不能为空' })
|
@IsNotEmpty({ message: '省份名称不能为空' })
|
||||||
@MaxLength(50, { message: '省份名称最大50字符' })
|
@MaxLength(50, { message: '省份名称最大50字符' })
|
||||||
provinceName: string
|
provinceName: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||||
import { ApiProperty } from '@nestjs/swagger'
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class ApplyCommunityAuthDto {
|
export class ApplyCommunityAuthDto {
|
||||||
@ApiProperty({ description: '社区名称', example: '量子社区' })
|
@ApiProperty({ description: '社区名称', example: '量子社区' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '社区名称不能为空' })
|
@IsNotEmpty({ message: '社区名称不能为空' })
|
||||||
@MaxLength(50, { message: '社区名称最大50字符' })
|
@MaxLength(50, { message: '社区名称最大50字符' })
|
||||||
communityName: string
|
communityName: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export class GrantAuthCityCompanyDto {
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '城市代码', example: '430100' })
|
@ApiProperty({ description: '城市代码', example: '430100' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export class GrantAuthProvinceCompanyDto {
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '省份代码', example: '430000' })
|
@ApiProperty({ description: '省份代码', example: '430000' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class GrantCityCompanyDto {
|
export class GrantCityCompanyDto {
|
||||||
@ApiProperty({ description: '用户ID' })
|
@ApiProperty({ description: '用户ID' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '用户ID不能为空' })
|
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '城市代码', example: '430100' })
|
@ApiProperty({ description: '城市代码', example: '430100' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '城市代码不能为空' })
|
@IsNotEmpty({ message: '城市代码不能为空' })
|
||||||
@MaxLength(20, { message: '城市代码最大20字符' })
|
@MaxLength(20, { message: '城市代码最大20字符' })
|
||||||
cityCode: string
|
cityCode: string
|
||||||
|
|
||||||
@ApiProperty({ description: '城市名称', example: '长沙市' })
|
@ApiProperty({ description: '城市名称', example: '长沙市' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '城市名称不能为空' })
|
@IsNotEmpty({ message: '城市名称不能为空' })
|
||||||
@MaxLength(50, { message: '城市名称最大50字符' })
|
@MaxLength(50, { message: '城市名称最大50字符' })
|
||||||
cityName: string
|
cityName: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
skipAssessment?: boolean
|
skipAssessment?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export class GrantCommunityDto {
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '社区名称', example: '深圳社区' })
|
@ApiProperty({ description: '社区名称', example: '深圳社区' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { IsString, IsNotEmpty, IsOptional, MaxLength, Matches } from 'class-validator'
|
import { IsString, IsNotEmpty, IsOptional, MaxLength, Matches } from 'class-validator'
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class GrantMonthlyBypassDto {
|
export class GrantMonthlyBypassDto {
|
||||||
@ApiProperty({ description: '豁免月份', example: '2024-01' })
|
@ApiProperty({ description: '豁免月份', example: '2024-01' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '豁免月份不能为空' })
|
@IsNotEmpty({ message: '豁免月份不能为空' })
|
||||||
@Matches(/^\d{4}-\d{2}$/, { message: '月份格式应为YYYY-MM' })
|
@Matches(/^\d{4}-\d{2}$/, { message: '月份格式应为YYYY-MM' })
|
||||||
month: string
|
month: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '豁免原因', example: '特殊情况' })
|
@ApiPropertyOptional({ description: '豁免原因', example: '特殊情况' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MaxLength(200, { message: '豁免原因最大200字符' })
|
@MaxLength(200, { message: '豁免原因最大200字符' })
|
||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class GrantProvinceCompanyDto {
|
export class GrantProvinceCompanyDto {
|
||||||
@ApiProperty({ description: '用户ID' })
|
@ApiProperty({ description: '用户ID' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '用户ID不能为空' })
|
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '省份代码', example: '430000' })
|
@ApiProperty({ description: '省份代码', example: '430000' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '省份代码不能为空' })
|
@IsNotEmpty({ message: '省份代码不能为空' })
|
||||||
@MaxLength(20, { message: '省份代码最大20字符' })
|
@MaxLength(20, { message: '省份代码最大20字符' })
|
||||||
provinceCode: string
|
provinceCode: string
|
||||||
|
|
||||||
@ApiProperty({ description: '省份名称', example: '湖南省' })
|
@ApiProperty({ description: '省份名称', example: '湖南省' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '省份名称不能为空' })
|
@IsNotEmpty({ message: '省份名称不能为空' })
|
||||||
@MaxLength(50, { message: '省份名称最大50字符' })
|
@MaxLength(50, { message: '省份名称最大50字符' })
|
||||||
provinceName: string
|
provinceName: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
skipAssessment?: boolean
|
skipAssessment?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||||
import { ApiProperty } from '@nestjs/swagger'
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
|
|
||||||
export class RevokeAuthorizationDto {
|
export class RevokeAuthorizationDto {
|
||||||
@ApiProperty({ description: '撤销原因', example: '违规操作' })
|
@ApiProperty({ description: '撤销原因', example: '违规操作' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty({ message: '撤销原因不能为空' })
|
@IsNotEmpty({ message: '撤销原因不能为空' })
|
||||||
@MaxLength(200, { message: '撤销原因最大200字符' })
|
@MaxLength(200, { message: '撤销原因最大200字符' })
|
||||||
reason: string
|
reason: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,122 @@
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||||
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
||||||
|
|
||||||
export class AuthorizationResponse {
|
export class AuthorizationResponse {
|
||||||
@ApiProperty({ description: '授权ID' })
|
@ApiProperty({ description: '授权ID' })
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '用户ID' })
|
@ApiProperty({ description: '用户ID' })
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '角色类型', enum: RoleType })
|
@ApiProperty({ description: '角色类型', enum: RoleType })
|
||||||
roleType: RoleType
|
roleType: RoleType
|
||||||
|
|
||||||
@ApiProperty({ description: '区域代码' })
|
@ApiProperty({ description: '区域代码' })
|
||||||
regionCode: string
|
regionCode: string
|
||||||
|
|
||||||
@ApiProperty({ description: '区域名称' })
|
@ApiProperty({ description: '区域名称' })
|
||||||
regionName: string
|
regionName: string
|
||||||
|
|
||||||
@ApiProperty({ description: '授权状态', enum: AuthorizationStatus })
|
@ApiProperty({ description: '授权状态', enum: AuthorizationStatus })
|
||||||
status: AuthorizationStatus
|
status: AuthorizationStatus
|
||||||
|
|
||||||
@ApiProperty({ description: '显示标题' })
|
@ApiProperty({ description: '显示标题' })
|
||||||
displayTitle: string
|
displayTitle: string
|
||||||
|
|
||||||
@ApiProperty({ description: '权益是否激活' })
|
@ApiProperty({ description: '权益是否激活' })
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
|
|
||||||
@ApiProperty({ description: '当前考核月份索引' })
|
@ApiProperty({ description: '当前考核月份索引' })
|
||||||
currentMonthIndex: number
|
currentMonthIndex: number
|
||||||
|
|
||||||
@ApiProperty({ description: '本地占比要求' })
|
@ApiProperty({ description: '本地占比要求' })
|
||||||
requireLocalPercentage: number
|
requireLocalPercentage: number
|
||||||
|
|
||||||
@ApiProperty({ description: '是否豁免占比考核' })
|
@ApiProperty({ description: '是否豁免占比考核' })
|
||||||
exemptFromPercentageCheck: boolean
|
exemptFromPercentageCheck: boolean
|
||||||
|
|
||||||
@ApiProperty({ description: '初始考核目标(社区10,市100,省500)' })
|
@ApiProperty({ description: '初始考核目标(社区10,市100,省500)' })
|
||||||
initialTargetTreeCount: number
|
initialTargetTreeCount: number
|
||||||
|
|
||||||
@ApiProperty({ description: '当前团队认种数量' })
|
@ApiProperty({ description: '当前团队认种数量' })
|
||||||
currentTreeCount: number
|
currentTreeCount: number
|
||||||
|
|
||||||
@ApiProperty({ description: '月度考核目标' })
|
@ApiProperty({ description: '月度考核目标' })
|
||||||
monthlyTargetTreeCount: number
|
monthlyTargetTreeCount: number
|
||||||
|
|
||||||
@ApiProperty({ description: '创建时间' })
|
@ApiProperty({ description: '创建时间' })
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
@ApiProperty({ description: '更新时间' })
|
@ApiProperty({ description: '更新时间' })
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApplyAuthorizationResponse {
|
export class ApplyAuthorizationResponse {
|
||||||
@ApiProperty({ description: '授权ID' })
|
@ApiProperty({ description: '授权ID' })
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '授权状态' })
|
@ApiProperty({ description: '授权状态' })
|
||||||
status: string
|
status: string
|
||||||
|
|
||||||
@ApiProperty({ description: '权益是否激活' })
|
@ApiProperty({ description: '权益是否激活' })
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '显示标题' })
|
@ApiPropertyOptional({ description: '显示标题' })
|
||||||
displayTitle?: string
|
displayTitle?: string
|
||||||
|
|
||||||
@ApiProperty({ description: '消息提示' })
|
@ApiProperty({ description: '消息提示' })
|
||||||
message: string
|
message: string
|
||||||
|
|
||||||
@ApiProperty({ description: '当前认种数量' })
|
@ApiProperty({ description: '当前认种数量' })
|
||||||
currentTreeCount: number
|
currentTreeCount: number
|
||||||
|
|
||||||
@ApiProperty({ description: '所需认种数量' })
|
@ApiProperty({ description: '所需认种数量' })
|
||||||
requiredTreeCount: number
|
requiredTreeCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StickmanRankingResponse {
|
export class StickmanRankingResponse {
|
||||||
@ApiProperty({ description: '用户ID' })
|
@ApiProperty({ description: '用户ID' })
|
||||||
userId: string
|
userId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '授权ID' })
|
@ApiProperty({ description: '授权ID' })
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '角色类型', enum: RoleType })
|
@ApiProperty({ description: '角色类型', enum: RoleType })
|
||||||
roleType: RoleType
|
roleType: RoleType
|
||||||
|
|
||||||
@ApiProperty({ description: '区域代码' })
|
@ApiProperty({ description: '区域代码' })
|
||||||
regionCode: string
|
regionCode: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '昵称' })
|
@ApiPropertyOptional({ description: '昵称' })
|
||||||
nickname?: string
|
nickname?: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '头像URL' })
|
@ApiPropertyOptional({ description: '头像URL' })
|
||||||
avatarUrl?: string
|
avatarUrl?: string
|
||||||
|
|
||||||
@ApiProperty({ description: '排名' })
|
@ApiProperty({ description: '排名' })
|
||||||
ranking: number
|
ranking: number
|
||||||
|
|
||||||
@ApiProperty({ description: '是否第一名' })
|
@ApiProperty({ description: '是否第一名' })
|
||||||
isFirstPlace: boolean
|
isFirstPlace: boolean
|
||||||
|
|
||||||
@ApiProperty({ description: '累计完成数量' })
|
@ApiProperty({ description: '累计完成数量' })
|
||||||
cumulativeCompleted: number
|
cumulativeCompleted: number
|
||||||
|
|
||||||
@ApiProperty({ description: '累计目标数量' })
|
@ApiProperty({ description: '累计目标数量' })
|
||||||
cumulativeTarget: number
|
cumulativeTarget: number
|
||||||
|
|
||||||
@ApiProperty({ description: '最终目标数量' })
|
@ApiProperty({ description: '最终目标数量' })
|
||||||
finalTarget: number
|
finalTarget: number
|
||||||
|
|
||||||
@ApiProperty({ description: '进度百分比' })
|
@ApiProperty({ description: '进度百分比' })
|
||||||
progressPercentage: number
|
progressPercentage: number
|
||||||
|
|
||||||
@ApiProperty({ description: '超越比例' })
|
@ApiProperty({ description: '超越比例' })
|
||||||
exceedRatio: number
|
exceedRatio: number
|
||||||
|
|
||||||
@ApiProperty({ description: '本月USDT收益' })
|
@ApiProperty({ description: '本月USDT收益' })
|
||||||
monthlyRewardUsdt: number
|
monthlyRewardUsdt: number
|
||||||
|
|
||||||
@ApiProperty({ description: '本月RWAD收益' })
|
@ApiProperty({ description: '本月RWAD收益' })
|
||||||
monthlyRewardRwad: number
|
monthlyRewardRwad: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export class CommunityInfo {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ description: '账户序列号' })
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
|
|
||||||
@ApiProperty({ description: '社区名称' })
|
@ApiProperty({ description: '社区名称' })
|
||||||
communityName: string
|
communityName: string
|
||||||
|
|
@ -45,7 +45,7 @@ export class CommunityHierarchyResponse {
|
||||||
*/
|
*/
|
||||||
export const HEADQUARTERS_COMMUNITY: CommunityInfo = {
|
export const HEADQUARTERS_COMMUNITY: CommunityInfo = {
|
||||||
authorizationId: 'headquarters',
|
authorizationId: 'headquarters',
|
||||||
accountSequence: 0,
|
accountSequence: '',
|
||||||
communityName: '总部社区',
|
communityName: '总部社区',
|
||||||
userId: undefined,
|
userId: undefined,
|
||||||
isHeadquarters: true,
|
isHeadquarters: true,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
export class ApplyAuthCityCompanyCommand {
|
export class ApplyAuthCityCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly cityCode: string,
|
public readonly cityCode: string,
|
||||||
public readonly cityName: string,
|
public readonly cityName: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplyAuthCityCompanyResult {
|
export interface ApplyAuthCityCompanyResult {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
status: string
|
status: string
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
displayTitle: string
|
displayTitle: string
|
||||||
message: string
|
message: string
|
||||||
currentTreeCount: number
|
currentTreeCount: number
|
||||||
requiredTreeCount: number
|
requiredTreeCount: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
export class ApplyAuthProvinceCompanyCommand {
|
export class ApplyAuthProvinceCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly provinceCode: string,
|
public readonly provinceCode: string,
|
||||||
public readonly provinceName: string,
|
public readonly provinceName: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplyAuthProvinceCompanyResult {
|
export interface ApplyAuthProvinceCompanyResult {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
status: string
|
status: string
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
displayTitle: string
|
displayTitle: string
|
||||||
message: string
|
message: string
|
||||||
currentTreeCount: number
|
currentTreeCount: number
|
||||||
requiredTreeCount: number
|
requiredTreeCount: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
export class ApplyCommunityAuthCommand {
|
export class ApplyCommunityAuthCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly communityName: string,
|
public readonly communityName: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplyCommunityAuthResult {
|
export interface ApplyCommunityAuthResult {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
status: string
|
status: string
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
message: string
|
message: string
|
||||||
currentTreeCount: number
|
currentTreeCount: number
|
||||||
requiredTreeCount: number
|
requiredTreeCount: number
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export class ExemptLocalPercentageCheckCommand {
|
export class ExemptLocalPercentageCheckCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly authorizationId: string,
|
public readonly authorizationId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export class GrantAuthCityCompanyCommand {
|
export class GrantAuthCityCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly cityCode: string,
|
public readonly cityCode: string,
|
||||||
public readonly cityName: string,
|
public readonly cityName: string,
|
||||||
public readonly adminId: string,
|
public readonly adminId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly skipAssessment: boolean = false,
|
public readonly skipAssessment: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export class GrantAuthProvinceCompanyCommand {
|
export class GrantAuthProvinceCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly provinceCode: string,
|
public readonly provinceCode: string,
|
||||||
public readonly provinceName: string,
|
public readonly provinceName: string,
|
||||||
public readonly adminId: string,
|
public readonly adminId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly skipAssessment: boolean = false,
|
public readonly skipAssessment: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export class GrantCityCompanyCommand {
|
export class GrantCityCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly cityCode: string,
|
public readonly cityCode: string,
|
||||||
public readonly cityName: string,
|
public readonly cityName: string,
|
||||||
public readonly adminId: string,
|
public readonly adminId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly skipAssessment: boolean = false,
|
public readonly skipAssessment: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
export class GrantCommunityCommand {
|
export class GrantCommunityCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly communityName: string,
|
public readonly communityName: string,
|
||||||
public readonly adminId: string,
|
public readonly adminId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly skipAssessment: boolean = false,
|
public readonly skipAssessment: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
export class GrantMonthlyBypassCommand {
|
export class GrantMonthlyBypassCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly authorizationId: string,
|
public readonly authorizationId: string,
|
||||||
public readonly month: string,
|
public readonly month: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly reason?: string,
|
public readonly reason?: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export class GrantProvinceCompanyCommand {
|
export class GrantProvinceCompanyCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly provinceCode: string,
|
public readonly provinceCode: string,
|
||||||
public readonly provinceName: string,
|
public readonly provinceName: string,
|
||||||
public readonly adminId: string,
|
public readonly adminId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly skipAssessment: boolean = false,
|
public readonly skipAssessment: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export class RevokeAuthorizationCommand {
|
export class RevokeAuthorizationCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly authorizationId: string,
|
public readonly authorizationId: string,
|
||||||
public readonly adminAccountSequence: number,
|
public readonly adminAccountSequence: string,
|
||||||
public readonly reason: string,
|
public readonly reason: string,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,60 @@
|
||||||
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
||||||
|
|
||||||
export interface AuthorizationDTO {
|
export interface AuthorizationDTO {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
userId: string
|
userId: string
|
||||||
roleType: RoleType
|
roleType: RoleType
|
||||||
regionCode: string
|
regionCode: string
|
||||||
regionName: string
|
regionName: string
|
||||||
status: AuthorizationStatus
|
status: AuthorizationStatus
|
||||||
displayTitle: string
|
displayTitle: string
|
||||||
benefitActive: boolean
|
benefitActive: boolean
|
||||||
currentMonthIndex: number
|
currentMonthIndex: number
|
||||||
requireLocalPercentage: number
|
requireLocalPercentage: number
|
||||||
exemptFromPercentageCheck: boolean
|
exemptFromPercentageCheck: boolean
|
||||||
// 考核进度字段
|
// 考核进度字段
|
||||||
initialTargetTreeCount: number // 初始考核目标(社区10,市100,省500)
|
initialTargetTreeCount: number // 初始考核目标(社区10,市100,省500)
|
||||||
currentTreeCount: number // 当前团队认种数量
|
currentTreeCount: number // 当前团队认种数量
|
||||||
monthlyTargetTreeCount: number // 月度考核目标(社区固定10)
|
monthlyTargetTreeCount: number // 月度考核目标(社区固定10)
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StickmanRankingDTO {
|
export interface StickmanRankingDTO {
|
||||||
userId: string
|
userId: string
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
roleType: RoleType
|
roleType: RoleType
|
||||||
regionCode: string
|
regionCode: string
|
||||||
nickname?: string
|
nickname?: string
|
||||||
avatarUrl?: string
|
avatarUrl?: string
|
||||||
ranking: number
|
ranking: number
|
||||||
isFirstPlace: boolean
|
isFirstPlace: boolean
|
||||||
cumulativeCompleted: number
|
cumulativeCompleted: number
|
||||||
cumulativeTarget: number
|
cumulativeTarget: number
|
||||||
finalTarget: number
|
finalTarget: number
|
||||||
progressPercentage: number
|
progressPercentage: number
|
||||||
exceedRatio: number
|
exceedRatio: number
|
||||||
monthlyRewardUsdt: number
|
monthlyRewardUsdt: number
|
||||||
monthlyRewardRwad: number
|
monthlyRewardRwad: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonthlyAssessmentDTO {
|
export interface MonthlyAssessmentDTO {
|
||||||
assessmentId: string
|
assessmentId: string
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
userId: string
|
userId: string
|
||||||
roleType: RoleType
|
roleType: RoleType
|
||||||
regionCode: string
|
regionCode: string
|
||||||
assessmentMonth: string
|
assessmentMonth: string
|
||||||
monthIndex: number
|
monthIndex: number
|
||||||
monthlyTarget: number
|
monthlyTarget: number
|
||||||
cumulativeTarget: number
|
cumulativeTarget: number
|
||||||
monthlyCompleted: number
|
monthlyCompleted: number
|
||||||
cumulativeCompleted: number
|
cumulativeCompleted: number
|
||||||
localPercentage: number
|
localPercentage: number
|
||||||
localPercentagePass: boolean
|
localPercentagePass: boolean
|
||||||
exceedRatio: number
|
exceedRatio: number
|
||||||
result: string
|
result: string
|
||||||
rankingInRegion: number | null
|
rankingInRegion: number | null
|
||||||
isFirstPlace: boolean
|
isFirstPlace: boolean
|
||||||
isBypassed: boolean
|
isBypassed: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
export interface CommunityInfoDTO {
|
export interface CommunityInfoDTO {
|
||||||
authorizationId: string
|
authorizationId: string
|
||||||
accountSequence: number
|
accountSequence: string
|
||||||
communityName: string
|
communityName: string
|
||||||
userId?: string
|
userId?: string
|
||||||
isHeadquarters: boolean
|
isHeadquarters: boolean
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,193 +1,193 @@
|
||||||
import { AuthorizationRole } from './authorization-role.aggregate'
|
import { AuthorizationRole } from './authorization-role.aggregate'
|
||||||
import { UserId, AdminUserId } from '@/domain/value-objects'
|
import { UserId, AdminUserId } from '@/domain/value-objects'
|
||||||
import { RoleType, AuthorizationStatus, MonthlyTargetType } from '@/domain/enums'
|
import { RoleType, AuthorizationStatus, MonthlyTargetType } from '@/domain/enums'
|
||||||
import { DomainError } from '@/shared/exceptions'
|
import { DomainError } from '@/shared/exceptions'
|
||||||
|
|
||||||
describe('AuthorizationRole Aggregate', () => {
|
describe('AuthorizationRole Aggregate', () => {
|
||||||
describe('createCommunityAuth', () => {
|
describe('createCommunityAuth', () => {
|
||||||
it('should create community authorization', () => {
|
it('should create community authorization', () => {
|
||||||
const auth = AuthorizationRole.createCommunityAuth({
|
const auth = AuthorizationRole.createCommunityAuth({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
communityName: '量子社区',
|
communityName: '量子社区',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(auth.roleType).toBe(RoleType.COMMUNITY)
|
expect(auth.roleType).toBe(RoleType.COMMUNITY)
|
||||||
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
||||||
expect(auth.displayTitle).toBe('量子社区')
|
expect(auth.displayTitle).toBe('量子社区')
|
||||||
expect(auth.benefitActive).toBe(false)
|
expect(auth.benefitActive).toBe(false)
|
||||||
expect(auth.getInitialTarget()).toBe(10)
|
expect(auth.getInitialTarget()).toBe(10)
|
||||||
expect(auth.domainEvents.length).toBe(1)
|
expect(auth.domainEvents.length).toBe(1)
|
||||||
expect(auth.domainEvents[0].eventType).toBe('authorization.community.requested')
|
expect(auth.domainEvents[0].eventType).toBe('authorization.community.requested')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('createAuthProvinceCompany', () => {
|
describe('createAuthProvinceCompany', () => {
|
||||||
it('should create auth province company authorization', () => {
|
it('should create auth province company authorization', () => {
|
||||||
const auth = AuthorizationRole.createAuthProvinceCompany({
|
const auth = AuthorizationRole.createAuthProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(auth.roleType).toBe(RoleType.AUTH_PROVINCE_COMPANY)
|
expect(auth.roleType).toBe(RoleType.AUTH_PROVINCE_COMPANY)
|
||||||
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
||||||
expect(auth.displayTitle).toBe('授权湖南省')
|
expect(auth.displayTitle).toBe('授权湖南省')
|
||||||
expect(auth.benefitActive).toBe(false)
|
expect(auth.benefitActive).toBe(false)
|
||||||
expect(auth.getInitialTarget()).toBe(500)
|
expect(auth.getInitialTarget()).toBe(500)
|
||||||
expect(auth.requireLocalPercentage).toBe(5.0)
|
expect(auth.requireLocalPercentage).toBe(5.0)
|
||||||
expect(auth.needsLadderAssessment()).toBe(true)
|
expect(auth.needsLadderAssessment()).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('createAuthCityCompany', () => {
|
describe('createAuthCityCompany', () => {
|
||||||
it('should create auth city company authorization', () => {
|
it('should create auth city company authorization', () => {
|
||||||
const auth = AuthorizationRole.createAuthCityCompany({
|
const auth = AuthorizationRole.createAuthCityCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
cityCode: '430100',
|
cityCode: '430100',
|
||||||
cityName: '长沙市',
|
cityName: '长沙市',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(auth.roleType).toBe(RoleType.AUTH_CITY_COMPANY)
|
expect(auth.roleType).toBe(RoleType.AUTH_CITY_COMPANY)
|
||||||
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
expect(auth.status).toBe(AuthorizationStatus.PENDING)
|
||||||
expect(auth.displayTitle).toBe('授权长沙市')
|
expect(auth.displayTitle).toBe('授权长沙市')
|
||||||
expect(auth.benefitActive).toBe(false)
|
expect(auth.benefitActive).toBe(false)
|
||||||
expect(auth.getInitialTarget()).toBe(100)
|
expect(auth.getInitialTarget()).toBe(100)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('createProvinceCompany', () => {
|
describe('createProvinceCompany', () => {
|
||||||
it('should create official province company with active benefits', () => {
|
it('should create official province company with active benefits', () => {
|
||||||
const adminId = AdminUserId.create('admin-1', BigInt(101))
|
const adminId = AdminUserId.create('admin-1', '101')
|
||||||
const auth = AuthorizationRole.createProvinceCompany({
|
const auth = AuthorizationRole.createProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
adminId,
|
adminId,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(auth.roleType).toBe(RoleType.PROVINCE_COMPANY)
|
expect(auth.roleType).toBe(RoleType.PROVINCE_COMPANY)
|
||||||
expect(auth.status).toBe(AuthorizationStatus.AUTHORIZED)
|
expect(auth.status).toBe(AuthorizationStatus.AUTHORIZED)
|
||||||
expect(auth.displayTitle).toBe('湖南省')
|
expect(auth.displayTitle).toBe('湖南省')
|
||||||
expect(auth.benefitActive).toBe(true)
|
expect(auth.benefitActive).toBe(true)
|
||||||
expect(auth.getInitialTarget()).toBe(0) // No initial target for official company
|
expect(auth.getInitialTarget()).toBe(0) // No initial target for official company
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('activateBenefit', () => {
|
describe('activateBenefit', () => {
|
||||||
it('should activate benefit and emit event', () => {
|
it('should activate benefit and emit event', () => {
|
||||||
const auth = AuthorizationRole.createCommunityAuth({
|
const auth = AuthorizationRole.createCommunityAuth({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
communityName: '量子社区',
|
communityName: '量子社区',
|
||||||
})
|
})
|
||||||
auth.clearDomainEvents()
|
auth.clearDomainEvents()
|
||||||
|
|
||||||
auth.activateBenefit()
|
auth.activateBenefit()
|
||||||
|
|
||||||
expect(auth.benefitActive).toBe(true)
|
expect(auth.benefitActive).toBe(true)
|
||||||
expect(auth.status).toBe(AuthorizationStatus.AUTHORIZED)
|
expect(auth.status).toBe(AuthorizationStatus.AUTHORIZED)
|
||||||
expect(auth.currentMonthIndex).toBe(1)
|
expect(auth.currentMonthIndex).toBe(1)
|
||||||
expect(auth.domainEvents.length).toBe(1)
|
expect(auth.domainEvents.length).toBe(1)
|
||||||
expect(auth.domainEvents[0].eventType).toBe('authorization.benefit.activated')
|
expect(auth.domainEvents[0].eventType).toBe('authorization.benefit.activated')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error if already active', () => {
|
it('should throw error if already active', () => {
|
||||||
const auth = AuthorizationRole.createProvinceCompany({
|
const auth = AuthorizationRole.createProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
adminId: AdminUserId.create('admin-1', '101'),
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(() => auth.activateBenefit()).toThrow(DomainError)
|
expect(() => auth.activateBenefit()).toThrow(DomainError)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('deactivateBenefit', () => {
|
describe('deactivateBenefit', () => {
|
||||||
it('should deactivate benefit and reset month index', () => {
|
it('should deactivate benefit and reset month index', () => {
|
||||||
const auth = AuthorizationRole.createProvinceCompany({
|
const auth = AuthorizationRole.createProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
adminId: AdminUserId.create('admin-1', '101'),
|
||||||
})
|
})
|
||||||
auth.clearDomainEvents()
|
auth.clearDomainEvents()
|
||||||
|
|
||||||
auth.deactivateBenefit('考核不达标')
|
auth.deactivateBenefit('考核不达标')
|
||||||
|
|
||||||
expect(auth.benefitActive).toBe(false)
|
expect(auth.benefitActive).toBe(false)
|
||||||
expect(auth.currentMonthIndex).toBe(0)
|
expect(auth.currentMonthIndex).toBe(0)
|
||||||
expect(auth.domainEvents.length).toBe(1)
|
expect(auth.domainEvents.length).toBe(1)
|
||||||
expect(auth.domainEvents[0].eventType).toBe('authorization.benefit.deactivated')
|
expect(auth.domainEvents[0].eventType).toBe('authorization.benefit.deactivated')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('revoke', () => {
|
describe('revoke', () => {
|
||||||
it('should revoke authorization', () => {
|
it('should revoke authorization', () => {
|
||||||
const auth = AuthorizationRole.createProvinceCompany({
|
const auth = AuthorizationRole.createProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
adminId: AdminUserId.create('admin-1', '101'),
|
||||||
})
|
})
|
||||||
auth.clearDomainEvents()
|
auth.clearDomainEvents()
|
||||||
|
|
||||||
auth.revoke(AdminUserId.create('admin-2', BigInt(102)), '违规操作')
|
auth.revoke(AdminUserId.create('admin-2', '102'), '违规操作')
|
||||||
|
|
||||||
expect(auth.status).toBe(AuthorizationStatus.REVOKED)
|
expect(auth.status).toBe(AuthorizationStatus.REVOKED)
|
||||||
expect(auth.benefitActive).toBe(false)
|
expect(auth.benefitActive).toBe(false)
|
||||||
expect(auth.revokeReason).toBe('违规操作')
|
expect(auth.revokeReason).toBe('违规操作')
|
||||||
expect(auth.domainEvents.length).toBe(1)
|
expect(auth.domainEvents.length).toBe(1)
|
||||||
expect(auth.domainEvents[0].eventType).toBe('authorization.role.revoked')
|
expect(auth.domainEvents[0].eventType).toBe('authorization.role.revoked')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error if already revoked', () => {
|
it('should throw error if already revoked', () => {
|
||||||
const auth = AuthorizationRole.createProvinceCompany({
|
const auth = AuthorizationRole.createProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
adminId: AdminUserId.create('admin-1', '101'),
|
||||||
})
|
})
|
||||||
auth.revoke(AdminUserId.create('admin-2', BigInt(102)), '违规操作')
|
auth.revoke(AdminUserId.create('admin-2', '102'), '违规操作')
|
||||||
|
|
||||||
expect(() => auth.revoke(AdminUserId.create('admin-3', BigInt(103)), '再次撤销')).toThrow(
|
expect(() => auth.revoke(AdminUserId.create('admin-3', '103'), '再次撤销')).toThrow(
|
||||||
DomainError,
|
DomainError,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('exemptLocalPercentageCheck', () => {
|
describe('exemptLocalPercentageCheck', () => {
|
||||||
it('should exempt from percentage check', () => {
|
it('should exempt from percentage check', () => {
|
||||||
const auth = AuthorizationRole.createAuthProvinceCompany({
|
const auth = AuthorizationRole.createAuthProvinceCompany({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
provinceCode: '430000',
|
provinceCode: '430000',
|
||||||
provinceName: '湖南省',
|
provinceName: '湖南省',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(auth.exemptFromPercentageCheck).toBe(false)
|
expect(auth.exemptFromPercentageCheck).toBe(false)
|
||||||
expect(auth.needsLocalPercentageCheck()).toBe(true)
|
expect(auth.needsLocalPercentageCheck()).toBe(true)
|
||||||
|
|
||||||
auth.exemptLocalPercentageCheck(AdminUserId.create('admin-1', BigInt(101)))
|
auth.exemptLocalPercentageCheck(AdminUserId.create('admin-1', '101'))
|
||||||
|
|
||||||
expect(auth.exemptFromPercentageCheck).toBe(true)
|
expect(auth.exemptFromPercentageCheck).toBe(true)
|
||||||
expect(auth.needsLocalPercentageCheck()).toBe(false)
|
expect(auth.needsLocalPercentageCheck()).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('incrementMonthIndex', () => {
|
describe('incrementMonthIndex', () => {
|
||||||
it('should increment month index', () => {
|
it('should increment month index', () => {
|
||||||
const auth = AuthorizationRole.createCommunityAuth({
|
const auth = AuthorizationRole.createCommunityAuth({
|
||||||
userId: UserId.create('user-1', BigInt(1)),
|
userId: UserId.create('user-1', '1'),
|
||||||
communityName: '量子社区',
|
communityName: '量子社区',
|
||||||
})
|
})
|
||||||
auth.activateBenefit()
|
auth.activateBenefit()
|
||||||
|
|
||||||
expect(auth.currentMonthIndex).toBe(1)
|
expect(auth.currentMonthIndex).toBe(1)
|
||||||
|
|
||||||
auth.incrementMonthIndex()
|
auth.incrementMonthIndex()
|
||||||
expect(auth.currentMonthIndex).toBe(2)
|
expect(auth.currentMonthIndex).toBe(2)
|
||||||
|
|
||||||
auth.incrementMonthIndex()
|
auth.incrementMonthIndex()
|
||||||
expect(auth.currentMonthIndex).toBe(3)
|
expect(auth.currentMonthIndex).toBe(3)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,86 @@
|
||||||
import { AuthorizationRole } from '@/domain/aggregates'
|
import { AuthorizationRole } from '@/domain/aggregates'
|
||||||
import { AuthorizationId, UserId, RegionCode } from '@/domain/value-objects'
|
import { AuthorizationId, UserId, RegionCode } from '@/domain/value-objects'
|
||||||
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
import { RoleType, AuthorizationStatus } from '@/domain/enums'
|
||||||
|
|
||||||
export const AUTHORIZATION_ROLE_REPOSITORY = Symbol('IAuthorizationRoleRepository')
|
export const AUTHORIZATION_ROLE_REPOSITORY = Symbol('IAuthorizationRoleRepository')
|
||||||
|
|
||||||
export interface IAuthorizationRoleRepository {
|
export interface IAuthorizationRoleRepository {
|
||||||
save(authorization: AuthorizationRole): Promise<void>
|
save(authorization: AuthorizationRole): Promise<void>
|
||||||
findById(authorizationId: AuthorizationId): Promise<AuthorizationRole | null>
|
findById(authorizationId: AuthorizationId): Promise<AuthorizationRole | null>
|
||||||
findByUserIdAndRoleType(userId: UserId, roleType: RoleType): Promise<AuthorizationRole | null>
|
findByUserIdAndRoleType(userId: UserId, roleType: RoleType): Promise<AuthorizationRole | null>
|
||||||
findByAccountSequenceAndRoleType(accountSequence: bigint, roleType: RoleType): Promise<AuthorizationRole | null>
|
findByAccountSequenceAndRoleType(accountSequence: string, roleType: RoleType): Promise<AuthorizationRole | null>
|
||||||
findByUserIdRoleTypeAndRegion(
|
findByUserIdRoleTypeAndRegion(
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
roleType: RoleType,
|
roleType: RoleType,
|
||||||
regionCode: RegionCode,
|
regionCode: RegionCode,
|
||||||
): Promise<AuthorizationRole | null>
|
): Promise<AuthorizationRole | null>
|
||||||
findByUserId(userId: UserId): Promise<AuthorizationRole[]>
|
findByUserId(userId: UserId): Promise<AuthorizationRole[]>
|
||||||
findByAccountSequence(accountSequence: bigint): Promise<AuthorizationRole[]>
|
findByAccountSequence(accountSequence: string): Promise<AuthorizationRole[]>
|
||||||
findActiveByRoleTypeAndRegion(
|
findActiveByRoleTypeAndRegion(
|
||||||
roleType: RoleType,
|
roleType: RoleType,
|
||||||
regionCode: RegionCode,
|
regionCode: RegionCode,
|
||||||
): Promise<AuthorizationRole[]>
|
): Promise<AuthorizationRole[]>
|
||||||
findAllActive(roleType?: RoleType): Promise<AuthorizationRole[]>
|
findAllActive(roleType?: RoleType): Promise<AuthorizationRole[]>
|
||||||
findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]>
|
findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]>
|
||||||
findByStatus(status: AuthorizationStatus): Promise<AuthorizationRole[]>
|
findByStatus(status: AuthorizationStatus): Promise<AuthorizationRole[]>
|
||||||
delete(authorizationId: AuthorizationId): Promise<void>
|
delete(authorizationId: AuthorizationId): Promise<void>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有活跃社区授权的用户
|
* 批量查询指定 accountSequence 列表中具有活跃社区授权的用户
|
||||||
*/
|
*/
|
||||||
findActiveCommunityByAccountSequences(accountSequences: bigint[]): Promise<AuthorizationRole[]>
|
findActiveCommunityByAccountSequences(accountSequences: string[]): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有活跃省公司授权(且匹配省份代码)的用户
|
* 批量查询指定 accountSequence 列表中具有活跃省公司授权(且匹配省份代码)的用户
|
||||||
*/
|
*/
|
||||||
findActiveProvinceByAccountSequencesAndRegion(
|
findActiveProvinceByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
provinceCode: string,
|
provinceCode: string,
|
||||||
): Promise<AuthorizationRole[]>
|
): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有活跃市公司授权(且匹配城市代码)的用户
|
* 批量查询指定 accountSequence 列表中具有活跃市公司授权(且匹配城市代码)的用户
|
||||||
*/
|
*/
|
||||||
findActiveCityByAccountSequencesAndRegion(
|
findActiveCityByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
cityCode: string,
|
cityCode: string,
|
||||||
): Promise<AuthorizationRole[]>
|
): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有社区授权的用户(包括 benefitActive=false)
|
* 批量查询指定 accountSequence 列表中具有社区授权的用户(包括 benefitActive=false)
|
||||||
* 用于社区权益分配计算
|
* 用于社区权益分配计算
|
||||||
*/
|
*/
|
||||||
findCommunityByAccountSequences(accountSequences: bigint[]): Promise<AuthorizationRole[]>
|
findCommunityByAccountSequences(accountSequences: string[]): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有授权省公司授权的用户(包括 benefitActive=false)
|
* 批量查询指定 accountSequence 列表中具有授权省公司授权的用户(包括 benefitActive=false)
|
||||||
* 用于省团队权益分配计算
|
* 用于省团队权益分配计算
|
||||||
* @deprecated 使用 findAuthProvinceByAccountSequences 替代,省团队收益不再要求省份匹配
|
* @deprecated 使用 findAuthProvinceByAccountSequences 替代,省团队收益不再要求省份匹配
|
||||||
*/
|
*/
|
||||||
findAuthProvinceByAccountSequencesAndRegion(
|
findAuthProvinceByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
provinceCode: string,
|
provinceCode: string,
|
||||||
): Promise<AuthorizationRole[]>
|
): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有授权市公司授权的用户(包括 benefitActive=false)
|
* 批量查询指定 accountSequence 列表中具有授权市公司授权的用户(包括 benefitActive=false)
|
||||||
* 用于市团队权益分配计算
|
* 用于市团队权益分配计算
|
||||||
* @deprecated 使用 findAuthCityByAccountSequences 替代,市团队收益不再要求城市匹配
|
* @deprecated 使用 findAuthCityByAccountSequences 替代,市团队收益不再要求城市匹配
|
||||||
*/
|
*/
|
||||||
findAuthCityByAccountSequencesAndRegion(
|
findAuthCityByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
cityCode: string,
|
cityCode: string,
|
||||||
): Promise<AuthorizationRole[]>
|
): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有授权省公司(省团队)授权的用户(包括 benefitActive=false)
|
* 批量查询指定 accountSequence 列表中具有授权省公司(省团队)授权的用户(包括 benefitActive=false)
|
||||||
* 用于省团队权益分配计算,不要求省份匹配
|
* 用于省团队权益分配计算,不要求省份匹配
|
||||||
*/
|
*/
|
||||||
findAuthProvinceByAccountSequences(accountSequences: bigint[]): Promise<AuthorizationRole[]>
|
findAuthProvinceByAccountSequences(accountSequences: string[]): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 批量查询指定 accountSequence 列表中具有授权市公司(市团队)授权的用户(包括 benefitActive=false)
|
* 批量查询指定 accountSequence 列表中具有授权市公司(市团队)授权的用户(包括 benefitActive=false)
|
||||||
* 用于市团队权益分配计算,不要求城市匹配
|
* 用于市团队权益分配计算,不要求城市匹配
|
||||||
*/
|
*/
|
||||||
findAuthCityByAccountSequences(accountSequences: bigint[]): Promise<AuthorizationRole[]>
|
findAuthCityByAccountSequences(accountSequences: string[]): Promise<AuthorizationRole[]>
|
||||||
/**
|
/**
|
||||||
* 查找指定省份的正式省公司授权(用于省区域权益分配)
|
* 查找指定省份的正式省公司授权(用于省区域权益分配)
|
||||||
*/
|
*/
|
||||||
findProvinceCompanyByRegion(provinceCode: string): Promise<AuthorizationRole | null>
|
findProvinceCompanyByRegion(provinceCode: string): Promise<AuthorizationRole | null>
|
||||||
/**
|
/**
|
||||||
* 查找指定城市的正式市公司授权(用于市区域权益分配)
|
* 查找指定城市的正式市公司授权(用于市区域权益分配)
|
||||||
*/
|
*/
|
||||||
findCityCompanyByRegion(cityCode: string): Promise<AuthorizationRole | null>
|
findCityCompanyByRegion(cityCode: string): Promise<AuthorizationRole | null>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { IMonthlyAssessmentRepository, IAuthorizationRoleRepository } from '@/do
|
||||||
|
|
||||||
export interface TeamStatistics {
|
export interface TeamStatistics {
|
||||||
userId: string
|
userId: string
|
||||||
accountSequence: bigint
|
accountSequence: string
|
||||||
totalTeamPlantingCount: number
|
totalTeamPlantingCount: number
|
||||||
selfPlantingCount: number
|
selfPlantingCount: number
|
||||||
/** 下级团队认种数(不包括自己)= totalTeamPlantingCount - selfPlantingCount */
|
/** 下级团队认种数(不包括自己)= totalTeamPlantingCount - selfPlantingCount */
|
||||||
|
|
@ -17,7 +17,7 @@ export interface TeamStatistics {
|
||||||
|
|
||||||
export interface ITeamStatisticsRepository {
|
export interface ITeamStatisticsRepository {
|
||||||
findByUserId(userId: string): Promise<TeamStatistics | null>
|
findByUserId(userId: string): Promise<TeamStatistics | null>
|
||||||
findByAccountSequence(accountSequence: bigint): Promise<TeamStatistics | null>
|
findByAccountSequence(accountSequence: string): Promise<TeamStatistics | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssessmentCalculatorService {
|
export class AssessmentCalculatorService {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { DomainError } from '@/shared/exceptions'
|
||||||
export class UserId {
|
export class UserId {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly value: string,
|
public readonly value: string,
|
||||||
public readonly accountSequence: bigint,
|
public readonly accountSequence: string,
|
||||||
) {
|
) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new DomainError('用户ID不能为空')
|
throw new DomainError('用户ID不能为空')
|
||||||
|
|
@ -13,8 +13,8 @@ export class UserId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string, accountSequence: number | bigint): UserId {
|
static create(value: string, accountSequence: string): UserId {
|
||||||
return new UserId(value, BigInt(accountSequence))
|
return new UserId(value, accountSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: UserId): boolean {
|
equals(other: UserId): boolean {
|
||||||
|
|
@ -29,7 +29,7 @@ export class UserId {
|
||||||
export class AdminUserId {
|
export class AdminUserId {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly value: string,
|
public readonly value: string,
|
||||||
public readonly accountSequence: bigint,
|
public readonly accountSequence: string,
|
||||||
) {
|
) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new DomainError('管理员ID不能为空')
|
throw new DomainError('管理员ID不能为空')
|
||||||
|
|
@ -39,8 +39,8 @@ export class AdminUserId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(value: string, accountSequence: number | bigint): AdminUserId {
|
static create(value: string, accountSequence: string): AdminUserId {
|
||||||
return new AdminUserId(value, BigInt(accountSequence))
|
return new AdminUserId(value, accountSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: AdminUserId): boolean {
|
equals(other: AdminUserId): boolean {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ interface ReferralTeamStatsResponse {
|
||||||
* 推荐链数据接口
|
* 推荐链数据接口
|
||||||
*/
|
*/
|
||||||
interface ReferralChainResponse {
|
interface ReferralChainResponse {
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
ancestorPath: string[];
|
ancestorPath: string[];
|
||||||
referrerId: string | null;
|
referrerId: string | null;
|
||||||
|
|
@ -31,8 +31,8 @@ interface ReferralChainResponse {
|
||||||
* 团队成员数据接口
|
* 团队成员数据接口
|
||||||
*/
|
*/
|
||||||
interface TeamMembersResponse {
|
interface TeamMembersResponse {
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
teamMembers: number[];
|
teamMembers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,7 +41,7 @@ interface TeamMembersResponse {
|
||||||
class TeamStatisticsAdapter implements TeamStatistics {
|
class TeamStatisticsAdapter implements TeamStatistics {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: bigint,
|
public readonly accountSequence: string,
|
||||||
public readonly totalTeamPlantingCount: number,
|
public readonly totalTeamPlantingCount: number,
|
||||||
public readonly selfPlantingCount: number,
|
public readonly selfPlantingCount: number,
|
||||||
private readonly provinceCityDistribution: Record<string, Record<string, number>> | null,
|
private readonly provinceCityDistribution: Record<string, Record<string, number>> | null,
|
||||||
|
|
@ -110,7 +110,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
async findByUserId(userId: string): Promise<TeamStatistics | null> {
|
async findByUserId(userId: string): Promise<TeamStatistics | null> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||||
return this.createEmptyStats(userId, BigInt(0));
|
return this.createEmptyStats(userId, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -122,7 +122,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
|
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
this.logger.debug(`[HTTP] No stats found for userId: ${userId}`);
|
this.logger.debug(`[HTTP] No stats found for userId: ${userId}`);
|
||||||
return this.createEmptyStats(userId, BigInt(0));
|
return this.createEmptyStats(userId, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
@ -130,7 +130,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
|
|
||||||
return new TeamStatisticsAdapter(
|
return new TeamStatisticsAdapter(
|
||||||
data.userId,
|
data.userId,
|
||||||
BigInt(data.accountSequence || 0),
|
data.accountSequence || '0',
|
||||||
data.totalTeamPlantingCount,
|
data.totalTeamPlantingCount,
|
||||||
data.selfPlantingCount || 0,
|
data.selfPlantingCount || 0,
|
||||||
data.provinceCityDistribution,
|
data.provinceCityDistribution,
|
||||||
|
|
@ -138,14 +138,14 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`[HTTP] Failed to get stats for userId ${userId}:`, error);
|
this.logger.error(`[HTTP] Failed to get stats for userId ${userId}:`, error);
|
||||||
// 返回空数据而不是抛出错误,避免影响主流程
|
// 返回空数据而不是抛出错误,避免影响主流程
|
||||||
return this.createEmptyStats(userId, BigInt(0));
|
return this.createEmptyStats(userId, '0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 accountSequence 查询团队统计
|
* 根据 accountSequence 查询团队统计
|
||||||
*/
|
*/
|
||||||
async findByAccountSequence(accountSequence: bigint): Promise<TeamStatistics | null> {
|
async findByAccountSequence(accountSequence: string): Promise<TeamStatistics | null> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||||
return this.createEmptyStats('', accountSequence);
|
return this.createEmptyStats('', accountSequence);
|
||||||
|
|
@ -168,7 +168,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
|
|
||||||
return new TeamStatisticsAdapter(
|
return new TeamStatisticsAdapter(
|
||||||
data.userId,
|
data.userId,
|
||||||
BigInt(data.accountSequence || accountSequence.toString()),
|
data.accountSequence || accountSequence,
|
||||||
data.totalTeamPlantingCount,
|
data.totalTeamPlantingCount,
|
||||||
data.selfPlantingCount || 0,
|
data.selfPlantingCount || 0,
|
||||||
data.provinceCityDistribution,
|
data.provinceCityDistribution,
|
||||||
|
|
@ -183,7 +183,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
/**
|
/**
|
||||||
* 创建空的统计数据
|
* 创建空的统计数据
|
||||||
*/
|
*/
|
||||||
private createEmptyStats(userId: string, accountSequence: bigint): TeamStatistics {
|
private createEmptyStats(userId: string, accountSequence: string): TeamStatistics {
|
||||||
return new TeamStatisticsAdapter(userId, accountSequence, 0, 0, null);
|
return new TeamStatisticsAdapter(userId, accountSequence, 0, 0, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +191,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
* 获取用户的祖先链(推荐链)
|
* 获取用户的祖先链(推荐链)
|
||||||
* 返回从直接推荐人到根节点的 accountSequence 列表
|
* 返回从直接推荐人到根节点的 accountSequence 列表
|
||||||
*/
|
*/
|
||||||
async getReferralChain(accountSequence: bigint): Promise<number[]> {
|
async getReferralChain(accountSequence: string): Promise<string[]> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -208,9 +208,8 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ancestorPath 存储的是 userId (bigint string),我们需要映射到 accountSequence
|
// ancestorPath 存储的是 accountSequence string
|
||||||
// 由于 referral-service 中 userId = BigInt(accountSequence),可以直接转换
|
return response.data.ancestorPath;
|
||||||
return response.data.ancestorPath.map((id) => Number(id));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`[HTTP] Failed to get referral chain for accountSequence ${accountSequence}:`, error);
|
this.logger.error(`[HTTP] Failed to get referral chain for accountSequence ${accountSequence}:`, error);
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -220,7 +219,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
||||||
/**
|
/**
|
||||||
* 获取用户的团队成员 accountSequence 列表
|
* 获取用户的团队成员 accountSequence 列表
|
||||||
*/
|
*/
|
||||||
async getTeamMembers(accountSequence: bigint): Promise<number[]> {
|
async getTeamMembers(accountSequence: string): Promise<string[]> {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
): Promise<AuthorizationRole | null> {
|
): Promise<AuthorizationRole | null> {
|
||||||
const record = await this.prisma.authorizationRole.findFirst({
|
const record = await this.prisma.authorizationRole.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: BigInt(userId.value),
|
userId: userId.value,
|
||||||
roleType: roleType,
|
roleType: roleType,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -90,7 +90,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
): Promise<AuthorizationRole | null> {
|
): Promise<AuthorizationRole | null> {
|
||||||
const record = await this.prisma.authorizationRole.findFirst({
|
const record = await this.prisma.authorizationRole.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId: BigInt(userId.value),
|
userId: userId.value,
|
||||||
roleType: roleType,
|
roleType: roleType,
|
||||||
regionCode: regionCode.value,
|
regionCode: regionCode.value,
|
||||||
},
|
},
|
||||||
|
|
@ -99,7 +99,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByAccountSequenceAndRoleType(
|
async findByAccountSequenceAndRoleType(
|
||||||
accountSequence: bigint,
|
accountSequence: string,
|
||||||
roleType: RoleType,
|
roleType: RoleType,
|
||||||
): Promise<AuthorizationRole | null> {
|
): Promise<AuthorizationRole | null> {
|
||||||
const record = await this.prisma.authorizationRole.findFirst({
|
const record = await this.prisma.authorizationRole.findFirst({
|
||||||
|
|
@ -113,13 +113,13 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
|
|
||||||
async findByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
async findByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
||||||
const records = await this.prisma.authorizationRole.findMany({
|
const records = await this.prisma.authorizationRole.findMany({
|
||||||
where: { userId: BigInt(userId.value) },
|
where: { userId: userId.value },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
})
|
})
|
||||||
return records.map((record) => this.toDomain(record))
|
return records.map((record) => this.toDomain(record))
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByAccountSequence(accountSequence: bigint): Promise<AuthorizationRole[]> {
|
async findByAccountSequence(accountSequence: string): Promise<AuthorizationRole[]> {
|
||||||
const records = await this.prisma.authorizationRole.findMany({
|
const records = await this.prisma.authorizationRole.findMany({
|
||||||
where: { accountSequence: accountSequence },
|
where: { accountSequence: accountSequence },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
|
|
@ -156,7 +156,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
async findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
async findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
||||||
const records = await this.prisma.authorizationRole.findMany({
|
const records = await this.prisma.authorizationRole.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: BigInt(userId.value),
|
userId: userId.value,
|
||||||
status: AuthorizationStatus.PENDING,
|
status: AuthorizationStatus.PENDING,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -177,7 +177,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findActiveCommunityByAccountSequences(
|
async findActiveCommunityByAccountSequences(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -197,7 +197,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findActiveProvinceByAccountSequencesAndRegion(
|
async findActiveProvinceByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
provinceCode: string,
|
provinceCode: string,
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
|
|
@ -218,7 +218,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findActiveCityByAccountSequencesAndRegion(
|
async findActiveCityByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
cityCode: string,
|
cityCode: string,
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
|
|
@ -239,7 +239,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCommunityByAccountSequences(
|
async findCommunityByAccountSequences(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -258,7 +258,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAuthProvinceByAccountSequencesAndRegion(
|
async findAuthProvinceByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
provinceCode: string,
|
provinceCode: string,
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
|
|
@ -279,7 +279,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAuthCityByAccountSequencesAndRegion(
|
async findAuthCityByAccountSequencesAndRegion(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
cityCode: string,
|
cityCode: string,
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
|
|
@ -300,7 +300,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAuthProvinceByAccountSequences(
|
async findAuthProvinceByAccountSequences(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -320,7 +320,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAuthCityByAccountSequences(
|
async findAuthCityByAccountSequences(
|
||||||
accountSequences: bigint[],
|
accountSequences: string[],
|
||||||
): Promise<AuthorizationRole[]> {
|
): Promise<AuthorizationRole[]> {
|
||||||
if (accountSequences.length === 0) {
|
if (accountSequences.length === 0) {
|
||||||
return []
|
return []
|
||||||
|
|
@ -364,16 +364,16 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
||||||
private toDomain(record: any): AuthorizationRole {
|
private toDomain(record: any): AuthorizationRole {
|
||||||
const props: AuthorizationRoleProps = {
|
const props: AuthorizationRoleProps = {
|
||||||
authorizationId: AuthorizationId.create(record.id),
|
authorizationId: AuthorizationId.create(record.id),
|
||||||
userId: UserId.create(record.userId.toString(), record.accountSequence),
|
userId: UserId.create(record.userId, record.accountSequence),
|
||||||
roleType: record.roleType as RoleType,
|
roleType: record.roleType as RoleType,
|
||||||
regionCode: RegionCode.create(record.regionCode),
|
regionCode: RegionCode.create(record.regionCode),
|
||||||
regionName: record.regionName,
|
regionName: record.regionName,
|
||||||
status: record.status as AuthorizationStatus,
|
status: record.status as AuthorizationStatus,
|
||||||
displayTitle: record.displayTitle,
|
displayTitle: record.displayTitle,
|
||||||
authorizedAt: record.authorizedAt,
|
authorizedAt: record.authorizedAt,
|
||||||
authorizedBy: record.authorizedBy ? AdminUserId.create(record.authorizedBy.toString(), record.authorizedBy) : null,
|
authorizedBy: record.authorizedBy ? AdminUserId.create(record.authorizedBy, record.authorizedBy) : null,
|
||||||
revokedAt: record.revokedAt,
|
revokedAt: record.revokedAt,
|
||||||
revokedBy: record.revokedBy ? AdminUserId.create(record.revokedBy.toString(), record.revokedBy) : null,
|
revokedBy: record.revokedBy ? AdminUserId.create(record.revokedBy, record.revokedBy) : null,
|
||||||
revokeReason: record.revokeReason,
|
revokeReason: record.revokeReason,
|
||||||
assessmentConfig: new AssessmentConfig(
|
assessmentConfig: new AssessmentConfig(
|
||||||
record.initialTargetTreeCount,
|
record.initialTargetTreeCount,
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ export class MonthlyAssessmentRepositoryImpl implements IMonthlyAssessmentReposi
|
||||||
async findByUserAndMonth(userId: UserId, month: Month): Promise<MonthlyAssessment[]> {
|
async findByUserAndMonth(userId: UserId, month: Month): Promise<MonthlyAssessment[]> {
|
||||||
const records = await this.prisma.monthlyAssessment.findMany({
|
const records = await this.prisma.monthlyAssessment.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: BigInt(userId.value),
|
userId: userId.value,
|
||||||
assessmentMonth: month.value,
|
assessmentMonth: month.value,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -214,7 +214,7 @@ export class MonthlyAssessmentRepositoryImpl implements IMonthlyAssessmentReposi
|
||||||
const props: MonthlyAssessmentProps = {
|
const props: MonthlyAssessmentProps = {
|
||||||
assessmentId: AssessmentId.create(record.id),
|
assessmentId: AssessmentId.create(record.id),
|
||||||
authorizationId: AuthorizationId.create(record.authorizationId),
|
authorizationId: AuthorizationId.create(record.authorizationId),
|
||||||
userId: UserId.create(record.userId.toString(), record.accountSequence),
|
userId: UserId.create(record.userId, record.accountSequence),
|
||||||
roleType: record.roleType as RoleType,
|
roleType: record.roleType as RoleType,
|
||||||
regionCode: RegionCode.create(record.regionCode),
|
regionCode: RegionCode.create(record.regionCode),
|
||||||
assessmentMonth: Month.create(record.assessmentMonth),
|
assessmentMonth: Month.create(record.assessmentMonth),
|
||||||
|
|
@ -233,7 +233,7 @@ export class MonthlyAssessmentRepositoryImpl implements IMonthlyAssessmentReposi
|
||||||
rankingInRegion: record.rankingInRegion,
|
rankingInRegion: record.rankingInRegion,
|
||||||
isFirstPlace: record.isFirstPlace,
|
isFirstPlace: record.isFirstPlace,
|
||||||
isBypassed: record.isBypassed,
|
isBypassed: record.isBypassed,
|
||||||
bypassedBy: record.bypassedBy ? AdminUserId.create(record.bypassedBy.toString(), record.bypassedBy) : null,
|
bypassedBy: record.bypassedBy ? AdminUserId.create(record.bypassedBy, record.bypassedBy) : null,
|
||||||
bypassedAt: record.bypassedAt,
|
bypassedAt: record.bypassedAt,
|
||||||
assessedAt: record.assessedAt,
|
assessedAt: record.assessedAt,
|
||||||
createdAt: record.createdAt,
|
createdAt: record.createdAt,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
||||||
|
|
||||||
export interface CurrentUserData {
|
export interface CurrentUserData {
|
||||||
userId: string
|
userId: string
|
||||||
accountSequence?: number
|
accountSequence?: string
|
||||||
walletAddress?: string
|
walletAddress?: string
|
||||||
roles?: string[]
|
roles?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CurrentUser = createParamDecorator(
|
export const CurrentUser = createParamDecorator(
|
||||||
(data: unknown, ctx: ExecutionContext): CurrentUserData => {
|
(data: unknown, ctx: ExecutionContext): CurrentUserData => {
|
||||||
const request = ctx.switchToHttp().getRequest()
|
const request = ctx.switchToHttp().getRequest()
|
||||||
return request.user
|
return request.user
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './current-user.decorator'
|
export * from './current-user.decorator'
|
||||||
export * from './public.decorator'
|
export * from './public.decorator'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { SetMetadata } from '@nestjs/common'
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
|
||||||
export const IS_PUBLIC_KEY = 'isPublic'
|
export const IS_PUBLIC_KEY = 'isPublic'
|
||||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true)
|
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import { HttpException, HttpStatus } from '@nestjs/common'
|
import { HttpException, HttpStatus } from '@nestjs/common'
|
||||||
|
|
||||||
export class ApplicationException extends HttpException {
|
export class ApplicationException extends HttpException {
|
||||||
constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) {
|
constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) {
|
||||||
super(message, status)
|
super(message, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApplicationError extends ApplicationException {
|
export class ApplicationError extends ApplicationException {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message, HttpStatus.BAD_REQUEST)
|
super(message, HttpStatus.BAD_REQUEST)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotFoundError extends ApplicationException {
|
export class NotFoundError extends ApplicationException {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message, HttpStatus.NOT_FOUND)
|
super(message, HttpStatus.NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnauthorizedError extends ApplicationException {
|
export class UnauthorizedError extends ApplicationException {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message, HttpStatus.UNAUTHORIZED)
|
super(message, HttpStatus.UNAUTHORIZED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForbiddenError extends ApplicationException {
|
export class ForbiddenError extends ApplicationException {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message, HttpStatus.FORBIDDEN)
|
super(message, HttpStatus.FORBIDDEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
export class DomainException extends Error {
|
export class DomainException extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'DomainException'
|
this.name = 'DomainException'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DomainError extends DomainException {
|
export class DomainError extends DomainException {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'DomainError'
|
this.name = 'DomainError'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './domain.exception'
|
export * from './domain.exception'
|
||||||
export * from './application.exception'
|
export * from './application.exception'
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,63 @@
|
||||||
import {
|
import {
|
||||||
ExceptionFilter,
|
ExceptionFilter,
|
||||||
Catch,
|
Catch,
|
||||||
ArgumentsHost,
|
ArgumentsHost,
|
||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
Logger,
|
Logger,
|
||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { DomainException } from '@/shared/exceptions'
|
import { DomainException } from '@/shared/exceptions'
|
||||||
|
|
||||||
@Catch()
|
@Catch()
|
||||||
export class GlobalExceptionFilter implements ExceptionFilter {
|
export class GlobalExceptionFilter implements ExceptionFilter {
|
||||||
private readonly logger = new Logger(GlobalExceptionFilter.name)
|
private readonly logger = new Logger(GlobalExceptionFilter.name)
|
||||||
|
|
||||||
catch(exception: unknown, host: ArgumentsHost) {
|
catch(exception: unknown, host: ArgumentsHost) {
|
||||||
const ctx = host.switchToHttp()
|
const ctx = host.switchToHttp()
|
||||||
const response = ctx.getResponse<Response>()
|
const response = ctx.getResponse<Response>()
|
||||||
const request = ctx.getRequest<Request>()
|
const request = ctx.getRequest<Request>()
|
||||||
|
|
||||||
let status: number
|
let status: number
|
||||||
let message: string
|
let message: string
|
||||||
let error: string
|
let error: string
|
||||||
|
|
||||||
if (exception instanceof HttpException) {
|
if (exception instanceof HttpException) {
|
||||||
status = exception.getStatus()
|
status = exception.getStatus()
|
||||||
const exceptionResponse = exception.getResponse()
|
const exceptionResponse = exception.getResponse()
|
||||||
message =
|
message =
|
||||||
typeof exceptionResponse === 'string'
|
typeof exceptionResponse === 'string'
|
||||||
? exceptionResponse
|
? exceptionResponse
|
||||||
: (exceptionResponse as any).message || exception.message
|
: (exceptionResponse as any).message || exception.message
|
||||||
error = exception.name
|
error = exception.name
|
||||||
} else if (exception instanceof DomainException) {
|
} else if (exception instanceof DomainException) {
|
||||||
status = HttpStatus.BAD_REQUEST
|
status = HttpStatus.BAD_REQUEST
|
||||||
message = exception.message
|
message = exception.message
|
||||||
error = 'DomainError'
|
error = 'DomainError'
|
||||||
} else if (exception instanceof Error) {
|
} else if (exception instanceof Error) {
|
||||||
status = HttpStatus.INTERNAL_SERVER_ERROR
|
status = HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
message = 'Internal server error'
|
message = 'Internal server error'
|
||||||
error = exception.name
|
error = exception.name
|
||||||
|
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Unhandled exception: ${exception.message}`,
|
`Unhandled exception: ${exception.message}`,
|
||||||
exception.stack,
|
exception.stack,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
status = HttpStatus.INTERNAL_SERVER_ERROR
|
status = HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
message = 'Internal server error'
|
message = 'Internal server error'
|
||||||
error = 'UnknownError'
|
error = 'UnknownError'
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseBody = {
|
const responseBody = {
|
||||||
statusCode: status,
|
statusCode: status,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
path: request.url,
|
path: request.url,
|
||||||
method: request.method,
|
method: request.method,
|
||||||
error,
|
error,
|
||||||
message: Array.isArray(message) ? message : [message],
|
message: Array.isArray(message) ? message : [message],
|
||||||
}
|
}
|
||||||
|
|
||||||
response.status(status).json(responseBody)
|
response.status(status).json(responseBody)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './global-exception.filter'
|
export * from './global-exception.filter'
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './jwt-auth.guard'
|
export * from './jwt-auth.guard'
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'
|
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common'
|
||||||
import { AuthGuard } from '@nestjs/passport'
|
import { AuthGuard } from '@nestjs/passport'
|
||||||
import { Reflector } from '@nestjs/core'
|
import { Reflector } from '@nestjs/core'
|
||||||
import { IS_PUBLIC_KEY } from '@/shared/decorators'
|
import { IS_PUBLIC_KEY } from '@/shared/decorators'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||||
constructor(private reflector: Reflector) {
|
constructor(private reflector: Reflector) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
canActivate(context: ExecutionContext) {
|
canActivate(context: ExecutionContext) {
|
||||||
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||||
context.getHandler(),
|
context.getHandler(),
|
||||||
context.getClass(),
|
context.getClass(),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.canActivate(context)
|
return super.canActivate(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRequest(err: any, user: any, info: any) {
|
handleRequest(err: any, user: any, info: any) {
|
||||||
if (err || !user) {
|
if (err || !user) {
|
||||||
throw err || new UnauthorizedException('未授权访问')
|
throw err || new UnauthorizedException('未授权访问')
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './transform.interceptor'
|
export * from './transform.interceptor'
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
NestInterceptor,
|
NestInterceptor,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
CallHandler,
|
CallHandler,
|
||||||
} from '@nestjs/common'
|
} from '@nestjs/common'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { map } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
|
|
||||||
export interface ApiResponse<T> {
|
export interface ApiResponse<T> {
|
||||||
success: boolean
|
success: boolean
|
||||||
data: T
|
data: T
|
||||||
timestamp: string
|
timestamp: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
|
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
|
||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
map((data) => ({
|
map((data) => ({
|
||||||
success: true,
|
success: true,
|
||||||
data,
|
data,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export * from './jwt.strategy'
|
export * from './jwt.strategy'
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,41 @@
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { PassportStrategy } from '@nestjs/passport'
|
import { PassportStrategy } from '@nestjs/passport'
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt'
|
import { ExtractJwt, Strategy } from 'passport-jwt'
|
||||||
import { ConfigService } from '@nestjs/config'
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
|
||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
// Identity-service uses 'userId' field
|
// Identity-service uses 'userId' field
|
||||||
userId: string
|
userId: string
|
||||||
accountSequence?: number
|
accountSequence?: string
|
||||||
deviceId?: string
|
deviceId?: string
|
||||||
type?: string
|
type?: string
|
||||||
// Legacy support for 'sub' field
|
// Legacy support for 'sub' field
|
||||||
sub?: string
|
sub?: string
|
||||||
walletAddress?: string
|
walletAddress?: string
|
||||||
roles?: string[]
|
roles?: string[]
|
||||||
iat?: number
|
iat?: number
|
||||||
exp?: number
|
exp?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
constructor(private configService: ConfigService) {
|
constructor(private configService: ConfigService) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
ignoreExpiration: false,
|
ignoreExpiration: false,
|
||||||
secretOrKey: configService.get<string>('JWT_SECRET'),
|
secretOrKey: configService.get<string>('JWT_SECRET'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: JwtPayload) {
|
async validate(payload: JwtPayload) {
|
||||||
// Support both 'userId' (from identity-service) and 'sub' (legacy)
|
// Support both 'userId' (from identity-service) and 'sub' (legacy)
|
||||||
const userId = payload.userId || payload.sub
|
const userId = payload.userId || payload.sub
|
||||||
return {
|
return {
|
||||||
userId,
|
userId,
|
||||||
accountSequence: payload.accountSequence,
|
accountSequence: payload.accountSequence,
|
||||||
deviceId: payload.deviceId,
|
deviceId: payload.deviceId,
|
||||||
walletAddress: payload.walletAddress,
|
walletAddress: payload.walletAddress,
|
||||||
roles: payload.roles,
|
roles: payload.roles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ model BackupShare {
|
||||||
|
|
||||||
// 用户标识 (来自 identity-service)
|
// 用户标识 (来自 identity-service)
|
||||||
userId BigInt @unique @map("user_id")
|
userId BigInt @unique @map("user_id")
|
||||||
accountSequence BigInt @unique @map("account_sequence")
|
accountSequence String @unique @map("account_sequence") // 格式: D + YYMMDD + 5位序号
|
||||||
|
|
||||||
// MPC 密钥信息
|
// MPC 密钥信息
|
||||||
publicKey String @unique @map("public_key") @db.VarChar(130)
|
publicKey String @unique @map("public_key") @db.VarChar(130)
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ export class StoreShareDto {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsNumber()
|
@IsString()
|
||||||
@Min(1)
|
accountSequence: string;
|
||||||
accountSequence: number;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export class StoreBackupShareCommand {
|
export class StoreBackupShareCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userId: string,
|
public readonly userId: string,
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string,
|
||||||
public readonly publicKey: string,
|
public readonly publicKey: string,
|
||||||
public readonly encryptedShareData: string,
|
public readonly encryptedShareData: string,
|
||||||
public readonly sourceService: string,
|
public readonly sourceService: string,
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export class StoreBackupShareHandler {
|
||||||
// Create domain entity
|
// Create domain entity
|
||||||
const share = BackupShare.create({
|
const share = BackupShare.create({
|
||||||
userId,
|
userId,
|
||||||
accountSequence: BigInt(command.accountSequence),
|
accountSequence: command.accountSequence,
|
||||||
publicKey: command.publicKey,
|
publicKey: command.publicKey,
|
||||||
encryptedShareData: encrypted,
|
encryptedShareData: encrypted,
|
||||||
encryptionKeyId: keyId,
|
encryptionKeyId: keyId,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export enum BackupShareStatus {
|
||||||
export interface BackupShareProps {
|
export interface BackupShareProps {
|
||||||
shareId: bigint | null;
|
shareId: bigint | null;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: bigint;
|
accountSequence: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
partyIndex: number;
|
partyIndex: number;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
|
|
@ -27,7 +27,7 @@ export interface BackupShareProps {
|
||||||
export class BackupShare {
|
export class BackupShare {
|
||||||
private _shareId: bigint | null;
|
private _shareId: bigint | null;
|
||||||
private readonly _userId: bigint;
|
private readonly _userId: bigint;
|
||||||
private readonly _accountSequence: bigint;
|
private readonly _accountSequence: string;
|
||||||
private readonly _publicKey: string;
|
private readonly _publicKey: string;
|
||||||
private readonly _partyIndex: number;
|
private readonly _partyIndex: number;
|
||||||
private readonly _threshold: number;
|
private readonly _threshold: number;
|
||||||
|
|
@ -61,7 +61,7 @@ export class BackupShare {
|
||||||
|
|
||||||
static create(params: {
|
static create(params: {
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: bigint;
|
accountSequence: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
encryptedShareData: string;
|
encryptedShareData: string;
|
||||||
encryptionKeyId: string;
|
encryptionKeyId: string;
|
||||||
|
|
@ -131,7 +131,7 @@ export class BackupShare {
|
||||||
get userId(): bigint {
|
get userId(): bigint {
|
||||||
return this._userId;
|
return this._userId;
|
||||||
}
|
}
|
||||||
get accountSequence(): bigint {
|
get accountSequence(): string {
|
||||||
return this._accountSequence;
|
return this._accountSequence;
|
||||||
}
|
}
|
||||||
get publicKey(): string {
|
get publicKey(): string {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,6 @@ export interface BackupShareRepository {
|
||||||
userId: bigint,
|
userId: bigint,
|
||||||
publicKey: string,
|
publicKey: string,
|
||||||
): Promise<BackupShare | null>;
|
): Promise<BackupShare | null>;
|
||||||
findByAccountSequence(accountSequence: bigint): Promise<BackupShare | null>;
|
findByAccountSequence(accountSequence: string): Promise<BackupShare | null>;
|
||||||
delete(shareId: bigint): Promise<void>;
|
delete(shareId: bigint): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export class BackupShareRepositoryImpl implements BackupShareRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByAccountSequence(
|
async findByAccountSequence(
|
||||||
accountSequence: bigint,
|
accountSequence: string,
|
||||||
): Promise<BackupShare | null> {
|
): Promise<BackupShare | null> {
|
||||||
const record = await this.prisma.backupShare.findUnique({
|
const record = await this.prisma.backupShare.findUnique({
|
||||||
where: { accountSequence },
|
where: { accountSequence },
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ datasource db {
|
||||||
// 存储需要监听充值的地址(用户地址和系统账户地址)
|
// 存储需要监听充值的地址(用户地址和系统账户地址)
|
||||||
// ============================================
|
// ============================================
|
||||||
model MonitoredAddress {
|
model MonitoredAddress {
|
||||||
id BigInt @id @default(autoincrement()) @map("address_id")
|
id BigInt @id @default(autoincrement()) @map("address_id")
|
||||||
|
|
||||||
chainType String @map("chain_type") @db.VarChar(20) // KAVA, BSC
|
chainType String @map("chain_type") @db.VarChar(20) // KAVA, BSC
|
||||||
address String @db.VarChar(42) // 0x地址
|
address String @db.VarChar(42) // 0x地址
|
||||||
|
|
@ -24,7 +24,7 @@ model MonitoredAddress {
|
||||||
addressType String @default("USER") @map("address_type") @db.VarChar(20)
|
addressType String @default("USER") @map("address_type") @db.VarChar(20)
|
||||||
|
|
||||||
// 用户地址关联 (addressType = USER 时使用)
|
// 用户地址关联 (addressType = USER 时使用)
|
||||||
accountSequence BigInt? @map("account_sequence") // 跨服务关联标识
|
accountSequence String? @map("account_sequence") @db.VarChar(20) // 跨服务关联标识 (格式: D + YYMMDD + 5位序号)
|
||||||
userId BigInt? @map("user_id") // 保留兼容
|
userId BigInt? @map("user_id") // 保留兼容
|
||||||
|
|
||||||
// 系统账户关联 (addressType = SYSTEM 时使用)
|
// 系统账户关联 (addressType = SYSTEM 时使用)
|
||||||
|
|
@ -74,11 +74,11 @@ model DepositTransaction {
|
||||||
status String @default("DETECTED") @db.VarChar(20) // DETECTED, CONFIRMING, CONFIRMED, NOTIFIED
|
status String @default("DETECTED") @db.VarChar(20) // DETECTED, CONFIRMING, CONFIRMED, NOTIFIED
|
||||||
|
|
||||||
// 关联 - 使用 accountSequence 作为跨服务主键
|
// 关联 - 使用 accountSequence 作为跨服务主键
|
||||||
addressId BigInt @map("address_id")
|
addressId BigInt @map("address_id")
|
||||||
addressType String @default("USER") @map("address_type") @db.VarChar(20) // USER 或 SYSTEM
|
addressType String @default("USER") @map("address_type") @db.VarChar(20) // USER 或 SYSTEM
|
||||||
|
|
||||||
// 用户地址关联
|
// 用户地址关联
|
||||||
accountSequence BigInt? @map("account_sequence") // 跨服务关联标识
|
accountSequence String? @map("account_sequence") @db.VarChar(20) // 跨服务关联标识 (格式: D + YYMMDD + 5位序号)
|
||||||
userId BigInt? @map("user_id") // 保留兼容
|
userId BigInt? @map("user_id") // 保留兼容
|
||||||
|
|
||||||
// 系统账户关联(当 addressType = SYSTEM 时)
|
// 系统账户关联(当 addressType = SYSTEM 时)
|
||||||
|
|
@ -174,26 +174,26 @@ model TransactionRequest {
|
||||||
// 与账户序列号关联,用于账户恢复验证
|
// 与账户序列号关联,用于账户恢复验证
|
||||||
// ============================================
|
// ============================================
|
||||||
model RecoveryMnemonic {
|
model RecoveryMnemonic {
|
||||||
id BigInt @id @default(autoincrement())
|
id BigInt @id @default(autoincrement())
|
||||||
accountSequence Int @map("account_sequence") // 8位账户序列号
|
accountSequence String @map("account_sequence") @db.VarChar(20) // 账户序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
publicKey String @map("public_key") @db.VarChar(130) // 关联的钱包公钥
|
publicKey String @map("public_key") @db.VarChar(130) // 关联的钱包公钥
|
||||||
|
|
||||||
// 助记词存储 (加密)
|
// 助记词存储 (加密)
|
||||||
encryptedMnemonic String @map("encrypted_mnemonic") @db.Text // AES加密的助记词
|
encryptedMnemonic String @map("encrypted_mnemonic") @db.Text // AES加密的助记词
|
||||||
mnemonicHash String @map("mnemonic_hash") @db.VarChar(64) // SHA256哈希(用于验证)
|
mnemonicHash String @map("mnemonic_hash") @db.VarChar(64) // SHA256哈希(用于验证)
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
status String @default("ACTIVE") @db.VarChar(20) // ACTIVE, REVOKED, REPLACED
|
status String @default("ACTIVE") @db.VarChar(20) // ACTIVE, REVOKED, REPLACED
|
||||||
isBackedUp Boolean @default(false) @map("is_backed_up") // 用户是否已备份
|
isBackedUp Boolean @default(false) @map("is_backed_up") // 用户是否已备份
|
||||||
|
|
||||||
// 挂失/更换相关
|
// 挂失/更换相关
|
||||||
revokedAt DateTime? @map("revoked_at")
|
revokedAt DateTime? @map("revoked_at")
|
||||||
revokedReason String? @map("revoked_reason") @db.VarChar(200)
|
revokedReason String? @map("revoked_reason") @db.VarChar(200)
|
||||||
replacedById BigInt? @map("replaced_by_id") // 被哪个新助记词替代
|
replacedById BigInt? @map("replaced_by_id") // 被哪个新助记词替代
|
||||||
|
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
@@unique([accountSequence, status], name: "uk_account_active_mnemonic") // 一个账户只有一个ACTIVE助记词
|
@@unique([accountSequence, status], name: "uk_account_active_mnemonic") // 一个账户只有一个ACTIVE助记词
|
||||||
@@index([accountSequence], name: "idx_recovery_account")
|
@@index([accountSequence], name: "idx_recovery_account")
|
||||||
@@index([publicKey], name: "idx_recovery_public_key")
|
@@index([publicKey], name: "idx_recovery_public_key")
|
||||||
@@index([status], name: "idx_recovery_status")
|
@@index([status], name: "idx_recovery_status")
|
||||||
|
|
@ -217,15 +217,15 @@ model OutboxEvent {
|
||||||
status String @default("PENDING") @db.VarChar(20)
|
status String @default("PENDING") @db.VarChar(20)
|
||||||
|
|
||||||
// 重试信息
|
// 重试信息
|
||||||
retryCount Int @default(0) @map("retry_count")
|
retryCount Int @default(0) @map("retry_count")
|
||||||
maxRetries Int @default(10) @map("max_retries")
|
maxRetries Int @default(10) @map("max_retries")
|
||||||
lastError String? @map("last_error") @db.Text
|
lastError String? @map("last_error") @db.Text
|
||||||
nextRetryAt DateTime? @map("next_retry_at")
|
nextRetryAt DateTime? @map("next_retry_at")
|
||||||
|
|
||||||
// 时间戳
|
// 时间戳
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
sentAt DateTime? @map("sent_at")
|
sentAt DateTime? @map("sent_at")
|
||||||
ackedAt DateTime? @map("acked_at")
|
ackedAt DateTime? @map("acked_at")
|
||||||
|
|
||||||
@@index([status, nextRetryAt], name: "idx_outbox_pending")
|
@@index([status, nextRetryAt], name: "idx_outbox_pending")
|
||||||
@@index([aggregateType, aggregateId], name: "idx_outbox_aggregate")
|
@@index([aggregateType, aggregateId], name: "idx_outbox_aggregate")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { IsString, IsNumberString, IsInt } from 'class-validator';
|
import { IsString, IsNumberString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class DeriveAddressDto {
|
export class DeriveAddressDto {
|
||||||
|
|
@ -6,9 +6,9 @@ export class DeriveAddressDto {
|
||||||
@IsNumberString()
|
@IsNumberString()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号 (8位数字)', example: 10000001 })
|
@ApiProperty({ description: '账户序列号 (格式: D + YYMMDD + 5位序号)', example: 'D2512110008' })
|
||||||
@IsInt()
|
@IsString()
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: '压缩公钥 (33 bytes, 0x02/0x03 开头)',
|
description: '压缩公钥 (33 bytes, 0x02/0x03 开头)',
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { IsInt } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class MarkMnemonicBackupDto {
|
export class MarkMnemonicBackupDto {
|
||||||
@ApiProperty({ description: '账户序列号 (8位数字)', example: 10000001 })
|
@ApiProperty({ description: '账户序列号 (格式: D + YYMMDD + 5位序号)', example: 'D2512110008' })
|
||||||
@IsInt()
|
@IsString()
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { IsString, IsInt } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class VerifyMnemonicHashDto {
|
export class VerifyMnemonicHashDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: '账户序列号 (8位数字)',
|
description: '账户序列号 (格式: D + YYMMDD + 5位序号)',
|
||||||
example: 10000001,
|
example: 'D2512110008',
|
||||||
})
|
})
|
||||||
@IsInt()
|
@IsString()
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: '助记词 (12个单词,空格分隔)',
|
description: '助记词 (12个单词,空格分隔)',
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export class MpcKeygenCompletedHandler implements OnModuleInit {
|
||||||
|
|
||||||
const result = await this.addressDerivationService.deriveAndRegister({
|
const result = await this.addressDerivationService.deriveAndRegister({
|
||||||
userId: BigInt(userId),
|
userId: BigInt(userId),
|
||||||
accountSequence: Number(accountSequence),
|
accountSequence: accountSequence,
|
||||||
publicKey,
|
publicKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ import { ChainTypeEnum } from '@/domain/enums';
|
||||||
|
|
||||||
export interface DeriveAddressParams {
|
export interface DeriveAddressParams {
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeriveAddressResult {
|
export interface DeriveAddressResult {
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
addresses: DerivedAddress[];
|
addresses: DerivedAddress[];
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +146,7 @@ export class AddressDerivationService {
|
||||||
*/
|
*/
|
||||||
private async registerEvmAddressForMonitoring(
|
private async registerEvmAddressForMonitoring(
|
||||||
userId: bigint,
|
userId: bigint,
|
||||||
accountSequence: number,
|
accountSequence: string,
|
||||||
derived: DerivedAddress,
|
derived: DerivedAddress,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const chainType = ChainType.fromEnum(derived.chainType);
|
const chainType = ChainType.fromEnum(derived.chainType);
|
||||||
|
|
@ -159,7 +159,7 @@ export class AddressDerivationService {
|
||||||
const monitored = MonitoredAddress.create({
|
const monitored = MonitoredAddress.create({
|
||||||
chainType,
|
chainType,
|
||||||
address,
|
address,
|
||||||
accountSequence: BigInt(accountSequence),
|
accountSequence,
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export class DepositRepairService {
|
||||||
id: d.id?.toString() ?? '',
|
id: d.id?.toString() ?? '',
|
||||||
txHash: d.txHash.toString(),
|
txHash: d.txHash.toString(),
|
||||||
userId: d.userId.toString(),
|
userId: d.userId.toString(),
|
||||||
accountSequence: d.accountSequence.toString(),
|
accountSequence: d.accountSequence,
|
||||||
amount: d.amount.toFixed(6),
|
amount: d.amount.toFixed(6),
|
||||||
confirmedAt: d.createdAt?.toISOString() ?? '',
|
confirmedAt: d.createdAt?.toISOString() ?? '',
|
||||||
})),
|
})),
|
||||||
|
|
@ -99,7 +99,7 @@ export class DepositRepairService {
|
||||||
amount: deposit.amount.raw.toString(),
|
amount: deposit.amount.raw.toString(),
|
||||||
amountFormatted: deposit.amount.toFixed(8),
|
amountFormatted: deposit.amount.toFixed(8),
|
||||||
confirmations: deposit.confirmations,
|
confirmations: deposit.confirmations,
|
||||||
accountSequence: deposit.accountSequence.toString(),
|
accountSequence: deposit.accountSequence,
|
||||||
userId: deposit.userId.toString(),
|
userId: deposit.userId.toString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.servic
|
||||||
import { RecoveryMnemonicAdapter } from '@/infrastructure/blockchain/recovery-mnemonic.adapter';
|
import { RecoveryMnemonicAdapter } from '@/infrastructure/blockchain/recovery-mnemonic.adapter';
|
||||||
|
|
||||||
export interface VerifyMnemonicByAccountParams {
|
export interface VerifyMnemonicByAccountParams {
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
mnemonic: string;
|
mnemonic: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ export class MnemonicVerificationService {
|
||||||
* 保存助记词记录(创建账户时调用)
|
* 保存助记词记录(创建账户时调用)
|
||||||
*/
|
*/
|
||||||
async saveRecoveryMnemonic(params: {
|
async saveRecoveryMnemonic(params: {
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
encryptedMnemonic: string;
|
encryptedMnemonic: string;
|
||||||
mnemonicHash: string;
|
mnemonicHash: string;
|
||||||
|
|
@ -88,7 +88,7 @@ export class MnemonicVerificationService {
|
||||||
/**
|
/**
|
||||||
* 标记助记词已备份
|
* 标记助记词已备份
|
||||||
*/
|
*/
|
||||||
async markAsBackedUp(accountSequence: number): Promise<void> {
|
async markAsBackedUp(accountSequence: string): Promise<void> {
|
||||||
await this.prisma.recoveryMnemonic.updateMany({
|
await this.prisma.recoveryMnemonic.updateMany({
|
||||||
where: {
|
where: {
|
||||||
accountSequence,
|
accountSequence,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export interface DepositTransactionProps {
|
||||||
confirmations: number;
|
confirmations: number;
|
||||||
status: DepositStatus;
|
status: DepositStatus;
|
||||||
addressId: bigint;
|
addressId: bigint;
|
||||||
accountSequence: bigint; // 跨服务关联标识
|
accountSequence: string; // 跨服务关联标识 (格式: D + YYMMDD + 5位序号)
|
||||||
userId: bigint; // 保留兼容
|
userId: bigint; // 保留兼容
|
||||||
notifiedAt?: Date;
|
notifiedAt?: Date;
|
||||||
notifyAttempts: number;
|
notifyAttempts: number;
|
||||||
|
|
@ -74,7 +74,7 @@ export class DepositTransaction extends AggregateRoot<bigint> {
|
||||||
get addressId(): bigint {
|
get addressId(): bigint {
|
||||||
return this.props.addressId;
|
return this.props.addressId;
|
||||||
}
|
}
|
||||||
get accountSequence(): bigint {
|
get accountSequence(): string {
|
||||||
return this.props.accountSequence;
|
return this.props.accountSequence;
|
||||||
}
|
}
|
||||||
get userId(): bigint {
|
get userId(): bigint {
|
||||||
|
|
@ -117,7 +117,7 @@ export class DepositTransaction extends AggregateRoot<bigint> {
|
||||||
blockTimestamp: Date;
|
blockTimestamp: Date;
|
||||||
logIndex: number;
|
logIndex: number;
|
||||||
addressId: bigint;
|
addressId: bigint;
|
||||||
accountSequence: bigint;
|
accountSequence: string;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
}): DepositTransaction {
|
}): DepositTransaction {
|
||||||
const deposit = new DepositTransaction({
|
const deposit = new DepositTransaction({
|
||||||
|
|
@ -139,7 +139,7 @@ export class DepositTransaction extends AggregateRoot<bigint> {
|
||||||
amountFormatted: params.amount.toFixed(8),
|
amountFormatted: params.amount.toFixed(8),
|
||||||
blockNumber: params.blockNumber.toString(),
|
blockNumber: params.blockNumber.toString(),
|
||||||
blockTimestamp: params.blockTimestamp.toISOString(),
|
blockTimestamp: params.blockTimestamp.toISOString(),
|
||||||
accountSequence: params.accountSequence.toString(),
|
accountSequence: params.accountSequence,
|
||||||
userId: params.userId.toString(),
|
userId: params.userId.toString(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -188,7 +188,7 @@ export class DepositTransaction extends AggregateRoot<bigint> {
|
||||||
amount: this.props.amount.raw.toString(),
|
amount: this.props.amount.raw.toString(),
|
||||||
amountFormatted: this.props.amount.toFixed(8),
|
amountFormatted: this.props.amount.toFixed(8),
|
||||||
confirmations: this.props.confirmations,
|
confirmations: this.props.confirmations,
|
||||||
accountSequence: this.props.accountSequence.toString(),
|
accountSequence: this.props.accountSequence,
|
||||||
userId: this.props.userId.toString(),
|
userId: this.props.userId.toString(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export interface MonitoredAddressProps {
|
||||||
id?: bigint;
|
id?: bigint;
|
||||||
chainType: ChainType;
|
chainType: ChainType;
|
||||||
address: EvmAddress;
|
address: EvmAddress;
|
||||||
accountSequence: bigint; // 跨服务关联标识 (全局唯一业务ID)
|
accountSequence: string; // 跨服务关联标识 (格式: D + YYMMDD + 5位序号)
|
||||||
userId: bigint; // 保留兼容
|
userId: bigint; // 保留兼容
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
|
|
@ -30,7 +30,7 @@ export class MonitoredAddress extends AggregateRoot<bigint> {
|
||||||
get address(): EvmAddress {
|
get address(): EvmAddress {
|
||||||
return this.props.address;
|
return this.props.address;
|
||||||
}
|
}
|
||||||
get accountSequence(): bigint {
|
get accountSequence(): string {
|
||||||
return this.props.accountSequence;
|
return this.props.accountSequence;
|
||||||
}
|
}
|
||||||
get userId(): bigint {
|
get userId(): bigint {
|
||||||
|
|
@ -52,7 +52,7 @@ export class MonitoredAddress extends AggregateRoot<bigint> {
|
||||||
static create(params: {
|
static create(params: {
|
||||||
chainType: ChainType;
|
chainType: ChainType;
|
||||||
address: EvmAddress;
|
address: EvmAddress;
|
||||||
accountSequence: bigint;
|
accountSequence: string;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
}): MonitoredAddress {
|
}): MonitoredAddress {
|
||||||
return new MonitoredAddress({
|
return new MonitoredAddress({
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { DomainEvent } from './domain-event.base';
|
||||||
|
|
||||||
export interface WalletAddressCreatedPayload {
|
export interface WalletAddressCreatedPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number; // 8位账户序列号
|
accountSequence: string; // 账户序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
addresses: {
|
addresses: {
|
||||||
chainType: string;
|
chainType: string;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export interface KeygenCompletedPayload {
|
||||||
threshold: string;
|
threshold: string;
|
||||||
extraPayload?: {
|
extraPayload?: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number; // 8位账户序列号,用于关联恢复助记词
|
accountSequence: string; // 账户序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
username: string;
|
username: string;
|
||||||
delegateShare?: {
|
delegateShare?: {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
interface JwtPayload {
|
interface JwtPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
type: 'access' | 'refresh';
|
type: 'access' | 'refresh';
|
||||||
iat: number;
|
iat: number;
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ datasource db {
|
||||||
|
|
||||||
model UserAccount {
|
model UserAccount {
|
||||||
userId BigInt @id @default(autoincrement()) @map("user_id")
|
userId BigInt @id @default(autoincrement()) @map("user_id")
|
||||||
accountSequence BigInt @unique @map("account_sequence")
|
accountSequence String @unique @map("account_sequence") @db.VarChar(12) // 格式: D + YYMMDD + 5位序号
|
||||||
|
|
||||||
phoneNumber String? @unique @map("phone_number") @db.VarChar(20)
|
phoneNumber String? @unique @map("phone_number") @db.VarChar(20)
|
||||||
nickname String @db.VarChar(100)
|
nickname String @db.VarChar(100)
|
||||||
avatarUrl String? @map("avatar_url") @db.Text
|
avatarUrl String? @map("avatar_url") @db.Text
|
||||||
|
|
||||||
inviterSequence BigInt? @map("inviter_sequence")
|
inviterSequence String? @map("inviter_sequence") @db.VarChar(12) // 推荐人序列号
|
||||||
referralCode String @unique @map("referral_code") @db.VarChar(10)
|
referralCode String @unique @map("referral_code") @db.VarChar(10)
|
||||||
|
|
||||||
kycStatus String @default("NOT_VERIFIED") @map("kyc_status") @db.VarChar(20)
|
kycStatus String @default("NOT_VERIFIED") @map("kyc_status") @db.VarChar(20)
|
||||||
|
|
@ -102,10 +102,11 @@ model WalletAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
model AccountSequenceGenerator {
|
model AccountSequenceGenerator {
|
||||||
id Int @id @default(1)
|
id Int @id @default(autoincrement())
|
||||||
currentSequence BigInt @default(0) @map("current_sequence")
|
dateKey String @unique @map("date_key") @db.VarChar(6) // 格式: YYMMDD
|
||||||
|
currentSequence Int @default(0) @map("current_sequence") // 当日序号 (0-99999)
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@map("account_sequence_generator")
|
@@map("account_sequence_generator")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,91 @@
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// 系统账户定义
|
// 系统账户定义
|
||||||
// ============================================
|
// 系统账户使用特殊序列号格式: S + 00000 + 序号 (S0000000001 ~ S0000000004)
|
||||||
const SYSTEM_ACCOUNTS = [
|
// ============================================
|
||||||
{
|
const SYSTEM_ACCOUNTS = [
|
||||||
userId: BigInt(1),
|
{
|
||||||
accountSequence: BigInt(1),
|
userId: BigInt(1),
|
||||||
nickname: '总部社区',
|
accountSequence: 'S0000000001', // 总部社区
|
||||||
referralCode: 'HQ000001',
|
nickname: '总部社区',
|
||||||
provinceCode: '000000',
|
referralCode: 'HQ000001',
|
||||||
cityCode: '000000',
|
status: 'SYSTEM',
|
||||||
status: 'SYSTEM',
|
},
|
||||||
},
|
{
|
||||||
{
|
userId: BigInt(2),
|
||||||
userId: BigInt(2),
|
accountSequence: 'S0000000002', // 成本费账户
|
||||||
accountSequence: BigInt(2),
|
nickname: '成本费账户',
|
||||||
nickname: '成本费账户',
|
referralCode: 'COST0002',
|
||||||
referralCode: 'COST0002',
|
status: 'SYSTEM',
|
||||||
provinceCode: '000000',
|
},
|
||||||
cityCode: '000000',
|
{
|
||||||
status: 'SYSTEM',
|
userId: BigInt(3),
|
||||||
},
|
accountSequence: 'S0000000003', // 运营费账户
|
||||||
{
|
nickname: '运营费账户',
|
||||||
userId: BigInt(3),
|
referralCode: 'OPER0003',
|
||||||
accountSequence: BigInt(3),
|
status: 'SYSTEM',
|
||||||
nickname: '运营费账户',
|
},
|
||||||
referralCode: 'OPER0003',
|
{
|
||||||
provinceCode: '000000',
|
userId: BigInt(4),
|
||||||
cityCode: '000000',
|
accountSequence: 'S0000000004', // RWAD底池账户
|
||||||
status: 'SYSTEM',
|
nickname: 'RWAD底池账户',
|
||||||
},
|
referralCode: 'POOL0004',
|
||||||
{
|
status: 'SYSTEM',
|
||||||
userId: BigInt(4),
|
},
|
||||||
accountSequence: BigInt(4),
|
];
|
||||||
nickname: 'RWAD底池账户',
|
|
||||||
referralCode: 'POOL0004',
|
async function main() {
|
||||||
provinceCode: '000000',
|
console.log('Seeding database...');
|
||||||
cityCode: '000000',
|
|
||||||
status: 'SYSTEM',
|
// 清理现有数据
|
||||||
},
|
await prisma.deadLetterEvent.deleteMany();
|
||||||
];
|
await prisma.smsCode.deleteMany();
|
||||||
|
await prisma.userEvent.deleteMany();
|
||||||
async function main() {
|
await prisma.deviceToken.deleteMany();
|
||||||
console.log('Seeding database...');
|
await prisma.walletAddress.deleteMany();
|
||||||
|
await prisma.userDevice.deleteMany();
|
||||||
// 清理现有数据
|
await prisma.userAccount.deleteMany();
|
||||||
await prisma.deadLetterEvent.deleteMany();
|
|
||||||
await prisma.smsCode.deleteMany();
|
// 初始化账户序列号生成器 (新格式: D + YYMMDD + 5位序号)
|
||||||
await prisma.userEvent.deleteMany();
|
await prisma.accountSequenceGenerator.deleteMany();
|
||||||
await prisma.deviceToken.deleteMany();
|
const today = new Date();
|
||||||
await prisma.walletAddress.deleteMany();
|
const year = String(today.getFullYear()).slice(-2);
|
||||||
await prisma.userDevice.deleteMany();
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||||
await prisma.userAccount.deleteMany();
|
const day = String(today.getDate()).padStart(2, '0');
|
||||||
|
const dateKey = `${year}${month}${day}`;
|
||||||
// 初始化账户序列号生成器 (从100000开始,系统账户使用1-99)
|
|
||||||
await prisma.accountSequenceGenerator.deleteMany();
|
await prisma.accountSequenceGenerator.create({
|
||||||
await prisma.accountSequenceGenerator.create({
|
data: {
|
||||||
data: {
|
id: 1,
|
||||||
id: 1,
|
dateKey: dateKey,
|
||||||
currentSequence: BigInt(100000), // 普通用户从100000开始
|
currentSequence: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建系统账户
|
// 创建系统账户
|
||||||
console.log('Creating system accounts...');
|
console.log('Creating system accounts...');
|
||||||
for (const account of SYSTEM_ACCOUNTS) {
|
for (const account of SYSTEM_ACCOUNTS) {
|
||||||
await prisma.userAccount.upsert({
|
await prisma.userAccount.upsert({
|
||||||
where: { userId: account.userId },
|
where: { userId: account.userId },
|
||||||
update: account,
|
update: account,
|
||||||
create: account,
|
create: account,
|
||||||
});
|
});
|
||||||
console.log(` - Created system account: ${account.nickname} (userId=${account.userId})`);
|
console.log(` - Created system account: ${account.nickname} (accountSequence=${account.accountSequence})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Database seeded successfully!');
|
console.log('Database seeded successfully!');
|
||||||
console.log('- Initialized account sequence generator starting at 100000');
|
console.log(`- Initialized account sequence generator for date ${dateKey}`);
|
||||||
console.log(`- Created ${SYSTEM_ACCOUNTS.length} system accounts (userId 1-4)`);
|
console.log(`- Created ${SYSTEM_ACCOUNTS.length} system accounts (S0000000001-S0000000004)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
})
|
})
|
||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,8 @@ export class RemoveDeviceDto {
|
||||||
|
|
||||||
// Response DTOs
|
// Response DTOs
|
||||||
export class AutoCreateAccountResponseDto {
|
export class AutoCreateAccountResponseDto {
|
||||||
@ApiProperty({ example: 100001, description: '用户序列号 (唯一标识)' })
|
@ApiProperty({ example: 'D2512110001', description: '用户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
userSerialNum: number;
|
userSerialNum: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'ABC123', description: '推荐码' })
|
@ApiProperty({ example: 'ABC123', description: '推荐码' })
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
|
|
@ -135,8 +135,8 @@ export class RecoverAccountResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
nickname: string;
|
nickname: string;
|
||||||
|
|
@ -188,8 +188,8 @@ export class LoginResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
|
@ -216,8 +216,8 @@ export class MeResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '账户序列号' })
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
|
|
@ -234,8 +234,8 @@ export class MeResponseDto {
|
||||||
@ApiProperty({ description: '完整推荐链接' })
|
@ApiProperty({ description: '完整推荐链接' })
|
||||||
referralLink: string;
|
referralLink: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '推荐人序列号', nullable: true })
|
@ApiProperty({ example: 'D2512110001', description: '推荐人序列号', nullable: true })
|
||||||
inviterSequence: number | null;
|
inviterSequence: string | null;
|
||||||
|
|
||||||
@ApiProperty({ description: '钱包地址列表' })
|
@ApiProperty({ description: '钱包地址列表' })
|
||||||
walletAddresses: Array<{ chainType: string; address: string }>;
|
walletAddresses: Array<{ chainType: string; address: string }>;
|
||||||
|
|
@ -259,7 +259,7 @@ export class ReferralValidationResponseDto {
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '邀请人信息' })
|
@ApiPropertyOptional({ description: '邀请人信息' })
|
||||||
inviterInfo?: {
|
inviterInfo?: {
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
};
|
};
|
||||||
|
|
@ -292,8 +292,8 @@ export class ReferralLinkResponseDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InviteRecordDto {
|
export class InviteRecordDto {
|
||||||
@ApiProperty()
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
nickname: string;
|
nickname: string;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { IsString, IsOptional, IsNotEmpty, IsNumber } from 'class-validator';
|
import { IsString, IsOptional, IsNotEmpty, Matches } from 'class-validator';
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class RecoverByMnemonicDto {
|
export class RecoverByMnemonicDto {
|
||||||
@ApiProperty({ example: 10001 })
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
accountSequence: number;
|
@Matches(/^D\d{11}$/, { message: '账户序列号格式错误,应为 D + 年月日(6位) + 序号(5位)' })
|
||||||
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({ example: 'abandon ability able about above absent absorb abstract absurd abuse access accident' })
|
@ApiProperty({ example: 'abandon ability able about above absent absorb abstract absurd abuse access accident' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { IsString, IsOptional, IsNotEmpty, IsNumber, Matches } from 'class-validator';
|
import { IsString, IsOptional, IsNotEmpty, Matches } from 'class-validator';
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class RecoverByPhoneDto {
|
export class RecoverByPhoneDto {
|
||||||
@ApiProperty({ example: 10001 })
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
@IsNumber()
|
@IsString()
|
||||||
accountSequence: number;
|
@Matches(/^D\d{11}$/, { message: '账户序列号格式错误,应为 D + 年月日(6位) + 序号(5位)' })
|
||||||
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '13800138000' })
|
@ApiProperty({ example: '13800138000' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ export class UserProfileDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ example: 'D2512110001', description: '账户序列号 (格式: D + YYMMDD + 5位序号)' })
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ nullable: true })
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export class AutoCreateAccountCommand {
|
||||||
|
|
||||||
export class RecoverByMnemonicCommand {
|
export class RecoverByMnemonicCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string, // 格式: D + YYMMDD + 5位序号
|
||||||
public readonly mnemonic: string,
|
public readonly mnemonic: string,
|
||||||
public readonly newDeviceId: string,
|
public readonly newDeviceId: string,
|
||||||
public readonly deviceName?: string,
|
public readonly deviceName?: string,
|
||||||
|
|
@ -27,7 +27,7 @@ export class RecoverByMnemonicCommand {
|
||||||
|
|
||||||
export class RecoverByPhoneCommand {
|
export class RecoverByPhoneCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string, // 格式: D + YYMMDD + 5位序号
|
||||||
public readonly phoneNumber: string,
|
public readonly phoneNumber: string,
|
||||||
public readonly smsCode: string,
|
public readonly smsCode: string,
|
||||||
public readonly newDeviceId: string,
|
public readonly newDeviceId: string,
|
||||||
|
|
@ -150,7 +150,7 @@ export class GenerateReferralLinkCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GetWalletStatusQuery {
|
export class GetWalletStatusQuery {
|
||||||
constructor(public readonly userSerialNum: number) {}
|
constructor(public readonly userSerialNum: string) {} // 格式: D + YYMMDD + 5位序号
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MarkMnemonicBackedUpCommand {
|
export class MarkMnemonicBackedUpCommand {
|
||||||
|
|
@ -173,7 +173,7 @@ export interface WalletStatusResult {
|
||||||
errorMessage?: string; // 失败原因 (failed 状态时返回)
|
errorMessage?: string; // 失败原因 (failed 状态时返回)
|
||||||
}
|
}
|
||||||
export interface AutoCreateAccountResult {
|
export interface AutoCreateAccountResult {
|
||||||
userSerialNum: number; // 用户序列号
|
userSerialNum: string; // 用户序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
referralCode: string; // 推荐码
|
referralCode: string; // 推荐码
|
||||||
username: string; // 随机用户名
|
username: string; // 随机用户名
|
||||||
avatarSvg: string; // 随机SVG头像
|
avatarSvg: string; // 随机SVG头像
|
||||||
|
|
@ -183,7 +183,7 @@ export interface AutoCreateAccountResult {
|
||||||
|
|
||||||
export interface RecoverAccountResult {
|
export interface RecoverAccountResult {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
|
|
@ -193,14 +193,14 @@ export interface RecoverAccountResult {
|
||||||
|
|
||||||
export interface AutoLoginResult {
|
export interface AutoLoginResult {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RegisterResult {
|
export interface RegisterResult {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
|
|
@ -208,14 +208,14 @@ export interface RegisterResult {
|
||||||
|
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfileDTO {
|
export interface UserProfileDTO {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
|
|
@ -238,7 +238,7 @@ export interface DeviceDTO {
|
||||||
|
|
||||||
export interface UserBriefDTO {
|
export interface UserBriefDTO {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +247,7 @@ export interface ReferralCodeValidationResult {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
referralCode?: string;
|
referralCode?: string;
|
||||||
inviterInfo?: {
|
inviterInfo?: {
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
};
|
};
|
||||||
|
|
@ -273,7 +273,7 @@ export interface ReferralStatsResult {
|
||||||
thisWeekInvites: number; // 本周邀请
|
thisWeekInvites: number; // 本周邀请
|
||||||
thisMonthInvites: number; // 本月邀请
|
thisMonthInvites: number; // 本月邀请
|
||||||
recentInvites: Array<{ // 最近邀请记录
|
recentInvites: Array<{ // 最近邀请记录
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
|
|
@ -283,13 +283,13 @@ export interface ReferralStatsResult {
|
||||||
|
|
||||||
export interface MeResult {
|
export interface MeResult {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
referralLink: string; // 完整推荐链接
|
referralLink: string; // 完整推荐链接
|
||||||
inviterSequence: number | null; // 推荐人序列号
|
inviterSequence: string | null; // 推荐人序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
walletAddresses: Array<{ chainType: string; address: string }>;
|
walletAddresses: Array<{ chainType: string; address: string }>;
|
||||||
kycStatus: string;
|
kycStatus: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export class RecoverByMnemonicCommand {
|
export class RecoverByMnemonicCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string, // 格式: D + YYMMDD + 5位序号
|
||||||
public readonly mnemonic: string,
|
public readonly mnemonic: string,
|
||||||
public readonly newDeviceId: string,
|
public readonly newDeviceId: string,
|
||||||
public readonly deviceName?: string,
|
public readonly deviceName?: string,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export class RecoverByPhoneCommand {
|
export class RecoverByPhoneCommand {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly accountSequence: number,
|
public readonly accountSequence: string, // 格式: D + YYMMDD + 5位序号
|
||||||
public readonly phoneNumber: string,
|
public readonly phoneNumber: string,
|
||||||
public readonly smsCode: string,
|
public readonly smsCode: string,
|
||||||
public readonly newDeviceId: string,
|
public readonly newDeviceId: string,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
||||||
|
|
||||||
export interface TokenPayload {
|
export interface TokenPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
type: 'access' | 'refresh';
|
type: 'access' | 'refresh';
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export class TokenService {
|
||||||
|
|
||||||
async generateTokenPair(payload: {
|
async generateTokenPair(payload: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
}): Promise<{ accessToken: string; refreshToken: string }> {
|
}): Promise<{ accessToken: string; refreshToken: string }> {
|
||||||
const accessToken = this.jwtService.sign(
|
const accessToken = this.jwtService.sign(
|
||||||
|
|
@ -51,7 +51,7 @@ export class TokenService {
|
||||||
|
|
||||||
async verifyRefreshToken(token: string): Promise<{
|
async verifyRefreshToken(token: string): Promise<{
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -147,9 +147,9 @@ export class UserAccount {
|
||||||
}
|
}
|
||||||
|
|
||||||
static reconstruct(params: {
|
static reconstruct(params: {
|
||||||
userId: string; accountSequence: number; devices: DeviceInfo[];
|
userId: string; accountSequence: string; devices: DeviceInfo[];
|
||||||
phoneNumber: string | null; nickname: string; avatarUrl: string | null;
|
phoneNumber: string | null; nickname: string; avatarUrl: string | null;
|
||||||
inviterSequence: number | null; referralCode: string;
|
inviterSequence: string | null; referralCode: string;
|
||||||
walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null;
|
walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null;
|
||||||
kycStatus: KYCStatus; status: AccountStatus;
|
kycStatus: KYCStatus; status: AccountStatus;
|
||||||
registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date;
|
registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date;
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ export class UserAccountAutoCreatedEvent extends DomainEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly payload: {
|
public readonly payload: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
inviterSequence: number | null;
|
inviterSequence: string | null; // 格式: D + YYMMDD + 5位序号
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
@ -32,10 +32,10 @@ export class UserAccountCreatedEvent extends DomainEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly payload: {
|
public readonly payload: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
initialDeviceId: string;
|
initialDeviceId: string;
|
||||||
inviterSequence: number | null;
|
inviterSequence: string | null; // 格式: D + YYMMDD + 5位序号
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
@ -51,7 +51,7 @@ export class DeviceAddedEvent extends DomainEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly payload: {
|
public readonly payload: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
deviceName: string;
|
deviceName: string;
|
||||||
},
|
},
|
||||||
|
|
@ -177,7 +177,7 @@ export class MpcKeygenRequestedEvent extends DomainEvent {
|
||||||
public readonly payload: {
|
public readonly payload: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
username: string;
|
username: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
totalParties: number;
|
totalParties: number;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,58 @@
|
||||||
import { DomainError } from '@/shared/exceptions/domain.exception';
|
import { DomainError } from '@/shared/exceptions/domain.exception';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户序列号值对象
|
||||||
|
* 格式: D + 年(2位) + 月(2位) + 日(2位) + 5位序号
|
||||||
|
* 示例: D2512110008 -> 2025年12月11日的第8个注册用户
|
||||||
|
*/
|
||||||
export class AccountSequence {
|
export class AccountSequence {
|
||||||
constructor(public readonly value: number) {
|
private static readonly PATTERN = /^D\d{11}$/;
|
||||||
if (value <= 0) throw new DomainError('账户序列号必须大于0');
|
|
||||||
|
constructor(public readonly value: string) {
|
||||||
|
if (!AccountSequence.PATTERN.test(value)) {
|
||||||
|
throw new DomainError(`账户序列号格式无效: ${value},应为 D + 年月日(6位) + 序号(5位)`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(value: number): AccountSequence {
|
static create(value: string): AccountSequence {
|
||||||
return new AccountSequence(value);
|
return new AccountSequence(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static next(current: AccountSequence): AccountSequence {
|
/**
|
||||||
return new AccountSequence(current.value + 1);
|
* 根据日期和当日序号生成新的账户序列号
|
||||||
|
* @param date 日期
|
||||||
|
* @param dailySequence 当日序号 (0-99999)
|
||||||
|
*/
|
||||||
|
static generate(date: Date, dailySequence: number): AccountSequence {
|
||||||
|
if (dailySequence < 0 || dailySequence > 99999) {
|
||||||
|
throw new DomainError(`当日序号超出范围: ${dailySequence},应为 0-99999`);
|
||||||
|
}
|
||||||
|
const year = String(date.getFullYear()).slice(-2);
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const seq = String(dailySequence).padStart(5, '0');
|
||||||
|
return new AccountSequence(`D${year}${month}${day}${seq}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从序列号中提取日期字符串 (YYMMDD)
|
||||||
|
*/
|
||||||
|
get dateString(): string {
|
||||||
|
return this.value.slice(1, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从序列号中提取当日序号
|
||||||
|
*/
|
||||||
|
get dailySequence(): number {
|
||||||
|
return parseInt(this.value.slice(7), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: AccountSequence): boolean {
|
equals(other: AccountSequence): boolean {
|
||||||
return this.value === other.value;
|
return this.value === other.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,284 +1,269 @@
|
||||||
import { DomainError } from '@/shared/exceptions/domain.exception';
|
import { DomainError } from '@/shared/exceptions/domain.exception';
|
||||||
import { createHash, createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
|
import { createHash, createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
|
||||||
import * as bip39 from '@scure/bip39';
|
import * as bip39 from '@scure/bip39';
|
||||||
import { wordlist } from '@scure/bip39/wordlists/english';
|
import { wordlist } from '@scure/bip39/wordlists/english';
|
||||||
|
|
||||||
// ============ UserId ============
|
// ============ UserId ============
|
||||||
export class UserId {
|
export class UserId {
|
||||||
constructor(public readonly value: bigint) {
|
constructor(public readonly value: bigint) {
|
||||||
// 允许 0 作为临时值(表示未持久化的新账户)
|
// 允许 0 作为临时值(表示未持久化的新账户)
|
||||||
if (value === null || value === undefined) {
|
if (value === null || value === undefined) {
|
||||||
throw new DomainError('UserId不能为空');
|
throw new DomainError('UserId不能为空');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(value: bigint | string | number): UserId {
|
static create(value: bigint | string | number): UserId {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return new UserId(BigInt(value));
|
return new UserId(BigInt(value));
|
||||||
}
|
}
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'number') {
|
||||||
return new UserId(BigInt(value));
|
return new UserId(BigInt(value));
|
||||||
}
|
}
|
||||||
return new UserId(value);
|
return new UserId(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: UserId): boolean {
|
equals(other: UserId): boolean {
|
||||||
return this.value === other.value;
|
return this.value === other.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
return this.value.toString();
|
return this.value.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ AccountSequence ============
|
// ============ AccountSequence ============
|
||||||
export class AccountSequence {
|
// 导出新格式的账户序列号 (D + YYMMDD + 5位序号)
|
||||||
constructor(public readonly value: number) {
|
export { AccountSequence } from './account-sequence.vo';
|
||||||
if (value <= 0) throw new DomainError('账户序列号必须大于0');
|
|
||||||
}
|
// ============ PhoneNumber ============
|
||||||
|
export class PhoneNumber {
|
||||||
static create(value: number): AccountSequence {
|
constructor(public readonly value: string) {
|
||||||
return new AccountSequence(value);
|
if (!/^1[3-9]\d{9}$/.test(value)) {
|
||||||
}
|
throw new DomainError('手机号格式错误');
|
||||||
|
}
|
||||||
static next(current: AccountSequence): AccountSequence {
|
}
|
||||||
return new AccountSequence(current.value + 1);
|
|
||||||
}
|
static create(value: string): PhoneNumber {
|
||||||
|
return new PhoneNumber(value);
|
||||||
equals(other: AccountSequence): boolean {
|
}
|
||||||
return this.value === other.value;
|
|
||||||
}
|
equals(other: PhoneNumber): boolean {
|
||||||
}
|
return this.value === other.value;
|
||||||
|
}
|
||||||
// ============ PhoneNumber ============
|
|
||||||
export class PhoneNumber {
|
masked(): string {
|
||||||
constructor(public readonly value: string) {
|
return this.value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||||||
if (!/^1[3-9]\d{9}$/.test(value)) {
|
}
|
||||||
throw new DomainError('手机号格式错误');
|
}
|
||||||
}
|
|
||||||
}
|
// ============ ReferralCode ============
|
||||||
|
export class ReferralCode {
|
||||||
static create(value: string): PhoneNumber {
|
constructor(public readonly value: string) {
|
||||||
return new PhoneNumber(value);
|
if (!/^[A-Z0-9]{6}$/.test(value)) {
|
||||||
}
|
throw new DomainError('推荐码格式错误');
|
||||||
|
}
|
||||||
equals(other: PhoneNumber): boolean {
|
}
|
||||||
return this.value === other.value;
|
|
||||||
}
|
static generate(): ReferralCode {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
masked(): string {
|
let code = '';
|
||||||
return this.value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
for (let i = 0; i < 6; i++) {
|
||||||
}
|
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
}
|
}
|
||||||
|
return new ReferralCode(code);
|
||||||
// ============ ReferralCode ============
|
}
|
||||||
export class ReferralCode {
|
|
||||||
constructor(public readonly value: string) {
|
static create(value: string): ReferralCode {
|
||||||
if (!/^[A-Z0-9]{6}$/.test(value)) {
|
return new ReferralCode(value.toUpperCase());
|
||||||
throw new DomainError('推荐码格式错误');
|
}
|
||||||
}
|
|
||||||
}
|
equals(other: ReferralCode): boolean {
|
||||||
|
return this.value === other.value;
|
||||||
static generate(): ReferralCode {
|
}
|
||||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
}
|
||||||
let code = '';
|
|
||||||
for (let i = 0; i < 6; i++) {
|
// ============ Mnemonic ============
|
||||||
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
export class Mnemonic {
|
||||||
}
|
constructor(public readonly value: string) {
|
||||||
return new ReferralCode(code);
|
if (!bip39.validateMnemonic(value, wordlist)) {
|
||||||
}
|
throw new DomainError('助记词格式错误');
|
||||||
|
}
|
||||||
static create(value: string): ReferralCode {
|
}
|
||||||
return new ReferralCode(value.toUpperCase());
|
|
||||||
}
|
static generate(): Mnemonic {
|
||||||
|
const mnemonic = bip39.generateMnemonic(wordlist, 128);
|
||||||
equals(other: ReferralCode): boolean {
|
return new Mnemonic(mnemonic);
|
||||||
return this.value === other.value;
|
}
|
||||||
}
|
|
||||||
}
|
static create(value: string): Mnemonic {
|
||||||
|
return new Mnemonic(value);
|
||||||
// ============ Mnemonic ============
|
}
|
||||||
export class Mnemonic {
|
|
||||||
constructor(public readonly value: string) {
|
toSeed(): Uint8Array {
|
||||||
if (!bip39.validateMnemonic(value, wordlist)) {
|
return bip39.mnemonicToSeedSync(this.value);
|
||||||
throw new DomainError('助记词格式错误');
|
}
|
||||||
}
|
|
||||||
}
|
getWords(): string[] {
|
||||||
|
return this.value.split(' ');
|
||||||
static generate(): Mnemonic {
|
}
|
||||||
const mnemonic = bip39.generateMnemonic(wordlist, 128);
|
|
||||||
return new Mnemonic(mnemonic);
|
equals(other: Mnemonic): boolean {
|
||||||
}
|
return this.value === other.value;
|
||||||
|
}
|
||||||
static create(value: string): Mnemonic {
|
}
|
||||||
return new Mnemonic(value);
|
|
||||||
}
|
// ============ DeviceInfo ============
|
||||||
|
// deviceInfo: 完整的设备信息 JSON,100% 保持前端传递的原样
|
||||||
toSeed(): Uint8Array {
|
export class DeviceInfo {
|
||||||
return bip39.mnemonicToSeedSync(this.value);
|
private _lastActiveAt: Date;
|
||||||
}
|
private _deviceInfo: Record<string, unknown>;
|
||||||
|
|
||||||
getWords(): string[] {
|
constructor(
|
||||||
return this.value.split(' ');
|
public readonly deviceId: string,
|
||||||
}
|
public readonly deviceName: string,
|
||||||
|
public readonly addedAt: Date,
|
||||||
equals(other: Mnemonic): boolean {
|
lastActiveAt: Date,
|
||||||
return this.value === other.value;
|
deviceInfo?: Record<string, unknown>,
|
||||||
}
|
) {
|
||||||
}
|
this._lastActiveAt = lastActiveAt;
|
||||||
|
this._deviceInfo = deviceInfo || {};
|
||||||
// ============ DeviceInfo ============
|
}
|
||||||
// deviceInfo: 完整的设备信息 JSON,100% 保持前端传递的原样
|
|
||||||
export class DeviceInfo {
|
get lastActiveAt(): Date {
|
||||||
private _lastActiveAt: Date;
|
return this._lastActiveAt;
|
||||||
private _deviceInfo: Record<string, unknown>;
|
}
|
||||||
|
|
||||||
constructor(
|
// 100% 保持原样的完整设备信息 JSON
|
||||||
public readonly deviceId: string,
|
get deviceInfo(): Record<string, unknown> {
|
||||||
public readonly deviceName: string,
|
return this._deviceInfo;
|
||||||
public readonly addedAt: Date,
|
}
|
||||||
lastActiveAt: Date,
|
|
||||||
deviceInfo?: Record<string, unknown>,
|
// 便捷访问器
|
||||||
) {
|
get platform(): string | undefined {
|
||||||
this._lastActiveAt = lastActiveAt;
|
return this._deviceInfo.platform as string | undefined;
|
||||||
this._deviceInfo = deviceInfo || {};
|
}
|
||||||
}
|
|
||||||
|
get deviceModel(): string | undefined {
|
||||||
get lastActiveAt(): Date {
|
return (this._deviceInfo.model || this._deviceInfo.deviceModel) as string | undefined;
|
||||||
return this._lastActiveAt;
|
}
|
||||||
}
|
|
||||||
|
get osVersion(): string | undefined {
|
||||||
// 100% 保持原样的完整设备信息 JSON
|
return this._deviceInfo.osVersion as string | undefined;
|
||||||
get deviceInfo(): Record<string, unknown> {
|
}
|
||||||
return this._deviceInfo;
|
|
||||||
}
|
get appVersion(): string | undefined {
|
||||||
|
return this._deviceInfo.appVersion as string | undefined;
|
||||||
// 便捷访问器
|
}
|
||||||
get platform(): string | undefined {
|
|
||||||
return this._deviceInfo.platform as string | undefined;
|
updateActivity(): void {
|
||||||
}
|
this._lastActiveAt = new Date();
|
||||||
|
}
|
||||||
get deviceModel(): string | undefined {
|
|
||||||
return (this._deviceInfo.model || this._deviceInfo.deviceModel) as string | undefined;
|
updateDeviceInfo(info: Record<string, unknown>): void {
|
||||||
}
|
this._deviceInfo = { ...this._deviceInfo, ...info };
|
||||||
|
}
|
||||||
get osVersion(): string | undefined {
|
}
|
||||||
return this._deviceInfo.osVersion as string | undefined;
|
|
||||||
}
|
// ============ ChainType ============
|
||||||
|
export enum ChainType {
|
||||||
get appVersion(): string | undefined {
|
KAVA = 'KAVA',
|
||||||
return this._deviceInfo.appVersion as string | undefined;
|
DST = 'DST',
|
||||||
}
|
BSC = 'BSC',
|
||||||
|
}
|
||||||
updateActivity(): void {
|
|
||||||
this._lastActiveAt = new Date();
|
export const CHAIN_CONFIG = {
|
||||||
}
|
[ChainType.KAVA]: { prefix: 'kava', derivationPath: "m/44'/459'/0'/0/0" },
|
||||||
|
[ChainType.DST]: { prefix: 'dst', derivationPath: "m/44'/118'/0'/0/0" },
|
||||||
updateDeviceInfo(info: Record<string, unknown>): void {
|
[ChainType.BSC]: { prefix: '0x', derivationPath: "m/44'/60'/0'/0/0" },
|
||||||
this._deviceInfo = { ...this._deviceInfo, ...info };
|
};
|
||||||
}
|
|
||||||
}
|
// ============ KYCInfo ============
|
||||||
|
export class KYCInfo {
|
||||||
// ============ ChainType ============
|
constructor(
|
||||||
export enum ChainType {
|
public readonly realName: string,
|
||||||
KAVA = 'KAVA',
|
public readonly idCardNumber: string,
|
||||||
DST = 'DST',
|
public readonly idCardFrontUrl: string,
|
||||||
BSC = 'BSC',
|
public readonly idCardBackUrl: string,
|
||||||
}
|
) {
|
||||||
|
if (!realName || realName.length < 2) {
|
||||||
export const CHAIN_CONFIG = {
|
throw new DomainError('真实姓名不合法');
|
||||||
[ChainType.KAVA]: { prefix: 'kava', derivationPath: "m/44'/459'/0'/0/0" },
|
}
|
||||||
[ChainType.DST]: { prefix: 'dst', derivationPath: "m/44'/118'/0'/0/0" },
|
if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCardNumber)) {
|
||||||
[ChainType.BSC]: { prefix: '0x', derivationPath: "m/44'/60'/0'/0/0" },
|
throw new DomainError('身份证号格式错误');
|
||||||
};
|
}
|
||||||
|
}
|
||||||
// ============ KYCInfo ============
|
|
||||||
export class KYCInfo {
|
static create(params: { realName: string; idCardNumber: string; idCardFrontUrl: string; idCardBackUrl: string }): KYCInfo {
|
||||||
constructor(
|
return new KYCInfo(params.realName, params.idCardNumber, params.idCardFrontUrl, params.idCardBackUrl);
|
||||||
public readonly realName: string,
|
}
|
||||||
public readonly idCardNumber: string,
|
|
||||||
public readonly idCardFrontUrl: string,
|
maskedIdCardNumber(): string {
|
||||||
public readonly idCardBackUrl: string,
|
return this.idCardNumber.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
|
||||||
) {
|
}
|
||||||
if (!realName || realName.length < 2) {
|
}
|
||||||
throw new DomainError('真实姓名不合法');
|
|
||||||
}
|
// ============ Enums ============
|
||||||
if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCardNumber)) {
|
export enum KYCStatus {
|
||||||
throw new DomainError('身份证号格式错误');
|
NOT_VERIFIED = 'NOT_VERIFIED',
|
||||||
}
|
PENDING = 'PENDING',
|
||||||
}
|
VERIFIED = 'VERIFIED',
|
||||||
|
REJECTED = 'REJECTED',
|
||||||
static create(params: { realName: string; idCardNumber: string; idCardFrontUrl: string; idCardBackUrl: string }): KYCInfo {
|
}
|
||||||
return new KYCInfo(params.realName, params.idCardNumber, params.idCardFrontUrl, params.idCardBackUrl);
|
|
||||||
}
|
export enum AccountStatus {
|
||||||
|
ACTIVE = 'ACTIVE',
|
||||||
maskedIdCardNumber(): string {
|
FROZEN = 'FROZEN',
|
||||||
return this.idCardNumber.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2');
|
DEACTIVATED = 'DEACTIVATED',
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
export enum AddressStatus {
|
||||||
// ============ Enums ============
|
ACTIVE = 'ACTIVE',
|
||||||
export enum KYCStatus {
|
DISABLED = 'DISABLED',
|
||||||
NOT_VERIFIED = 'NOT_VERIFIED',
|
}
|
||||||
PENDING = 'PENDING',
|
|
||||||
VERIFIED = 'VERIFIED',
|
// ============ AddressId ============
|
||||||
REJECTED = 'REJECTED',
|
export class AddressId {
|
||||||
}
|
constructor(public readonly value: string) {}
|
||||||
|
|
||||||
export enum AccountStatus {
|
static generate(): AddressId {
|
||||||
ACTIVE = 'ACTIVE',
|
return new AddressId(crypto.randomUUID());
|
||||||
FROZEN = 'FROZEN',
|
}
|
||||||
DEACTIVATED = 'DEACTIVATED',
|
|
||||||
}
|
static create(value: string): AddressId {
|
||||||
|
return new AddressId(value);
|
||||||
export enum AddressStatus {
|
}
|
||||||
ACTIVE = 'ACTIVE',
|
}
|
||||||
DISABLED = 'DISABLED',
|
|
||||||
}
|
// ============ MnemonicEncryption ============
|
||||||
|
export class MnemonicEncryption {
|
||||||
// ============ AddressId ============
|
static encrypt(mnemonic: string, key: string): string {
|
||||||
export class AddressId {
|
const derivedKey = this.deriveKey(key);
|
||||||
constructor(public readonly value: string) {}
|
const iv = randomBytes(16);
|
||||||
|
const cipher = createCipheriv('aes-256-gcm', derivedKey, iv);
|
||||||
static generate(): AddressId {
|
|
||||||
return new AddressId(crypto.randomUUID());
|
let encrypted = cipher.update(mnemonic, 'utf8', 'hex');
|
||||||
}
|
encrypted += cipher.final('hex');
|
||||||
|
const authTag = cipher.getAuthTag();
|
||||||
static create(value: string): AddressId {
|
|
||||||
return new AddressId(value);
|
return JSON.stringify({
|
||||||
}
|
encrypted,
|
||||||
}
|
authTag: authTag.toString('hex'),
|
||||||
|
iv: iv.toString('hex'),
|
||||||
// ============ MnemonicEncryption ============
|
});
|
||||||
export class MnemonicEncryption {
|
}
|
||||||
static encrypt(mnemonic: string, key: string): string {
|
|
||||||
const derivedKey = this.deriveKey(key);
|
static decrypt(encryptedData: string, key: string): string {
|
||||||
const iv = randomBytes(16);
|
const { encrypted, authTag, iv } = JSON.parse(encryptedData);
|
||||||
const cipher = createCipheriv('aes-256-gcm', derivedKey, iv);
|
const derivedKey = this.deriveKey(key);
|
||||||
|
const decipher = createDecipheriv('aes-256-gcm', derivedKey, Buffer.from(iv, 'hex'));
|
||||||
let encrypted = cipher.update(mnemonic, 'utf8', 'hex');
|
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
|
||||||
encrypted += cipher.final('hex');
|
|
||||||
const authTag = cipher.getAuthTag();
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
return JSON.stringify({
|
return decrypted;
|
||||||
encrypted,
|
}
|
||||||
authTag: authTag.toString('hex'),
|
|
||||||
iv: iv.toString('hex'),
|
private static deriveKey(password: string): Buffer {
|
||||||
});
|
return scryptSync(password, 'rwa-wallet-salt', 32);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
static decrypt(encryptedData: string, key: string): string {
|
|
||||||
const { encrypted, authTag, iv } = JSON.parse(encryptedData);
|
|
||||||
const derivedKey = this.deriveKey(key);
|
|
||||||
const decipher = createDecipheriv('aes-256-gcm', derivedKey, Buffer.from(iv, 'hex'));
|
|
||||||
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
|
|
||||||
|
|
||||||
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
||||||
decrypted += decipher.final('utf8');
|
|
||||||
return decrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static deriveKey(password: string): Buffer {
|
|
||||||
return scryptSync(password, 'rwa-wallet-salt', 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export interface VerifyMnemonicResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VerifyMnemonicByAccountParams {
|
export interface VerifyMnemonicByAccountParams {
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
mnemonic: string;
|
mnemonic: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +144,7 @@ export class BlockchainClientService {
|
||||||
/**
|
/**
|
||||||
* 标记助记词已备份
|
* 标记助记词已备份
|
||||||
*/
|
*/
|
||||||
async markMnemonicBackedUp(accountSequence: number): Promise<void> {
|
async markMnemonicBackedUp(accountSequence: string): Promise<void> {
|
||||||
this.logger.log(`Marking mnemonic as backed up for account ${accountSequence}`);
|
this.logger.log(`Marking mnemonic as backed up for account ${accountSequence}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export interface KeygenCompletedPayload {
|
||||||
threshold: string;
|
threshold: string;
|
||||||
extraPayload?: {
|
extraPayload?: {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number; // 8位账户序列号
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
username: string;
|
username: string;
|
||||||
delegateShare?: {
|
delegateShare?: {
|
||||||
partyId: string;
|
partyId: string;
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,56 @@
|
||||||
// Prisma Entity Types - 用于Mapper转换
|
// Prisma Entity Types - 用于Mapper转换
|
||||||
export interface UserAccountEntity {
|
export interface UserAccountEntity {
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: bigint;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
phoneNumber: string | null;
|
phoneNumber: string | null;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
avatarUrl: string | null;
|
avatarUrl: string | null;
|
||||||
inviterSequence: bigint | null;
|
inviterSequence: string | null; // 格式: D + YYMMDD + 5位序号
|
||||||
referralCode: string;
|
referralCode: string;
|
||||||
kycStatus: string;
|
kycStatus: string;
|
||||||
realName: string | null;
|
realName: string | null;
|
||||||
idCardNumber: string | null;
|
idCardNumber: string | null;
|
||||||
idCardFrontUrl: string | null;
|
idCardFrontUrl: string | null;
|
||||||
idCardBackUrl: string | null;
|
idCardBackUrl: string | null;
|
||||||
kycVerifiedAt: Date | null;
|
kycVerifiedAt: Date | null;
|
||||||
status: string;
|
status: string;
|
||||||
registeredAt: Date;
|
registeredAt: Date;
|
||||||
lastLoginAt: Date | null;
|
lastLoginAt: Date | null;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
devices?: UserDeviceEntity[];
|
devices?: UserDeviceEntity[];
|
||||||
walletAddresses?: WalletAddressEntity[];
|
walletAddresses?: WalletAddressEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDeviceEntity {
|
export interface UserDeviceEntity {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
deviceName: string | null;
|
deviceName: string | null;
|
||||||
deviceInfo: Record<string, unknown> | null; // 完整的设备信息 JSON
|
deviceInfo: Record<string, unknown> | null; // 完整的设备信息 JSON
|
||||||
// Hardware Info (冗余字段,便于查询)
|
// Hardware Info (冗余字段,便于查询)
|
||||||
platform: string | null;
|
platform: string | null;
|
||||||
deviceModel: string | null;
|
deviceModel: string | null;
|
||||||
osVersion: string | null;
|
osVersion: string | null;
|
||||||
appVersion: string | null;
|
appVersion: string | null;
|
||||||
screenWidth: number | null;
|
screenWidth: number | null;
|
||||||
screenHeight: number | null;
|
screenHeight: number | null;
|
||||||
locale: string | null;
|
locale: string | null;
|
||||||
timezone: string | null;
|
timezone: string | null;
|
||||||
// Timestamps
|
// Timestamps
|
||||||
addedAt: Date;
|
addedAt: Date;
|
||||||
lastActiveAt: Date;
|
lastActiveAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WalletAddressEntity {
|
export interface WalletAddressEntity {
|
||||||
addressId: bigint;
|
addressId: bigint;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
chainType: string;
|
chainType: string;
|
||||||
address: string;
|
address: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
addressDigest: string;
|
addressDigest: string;
|
||||||
mpcSignatureR: string;
|
mpcSignatureR: string;
|
||||||
mpcSignatureS: string;
|
mpcSignatureS: string;
|
||||||
mpcSignatureV: number;
|
mpcSignatureV: number;
|
||||||
status: string;
|
status: string;
|
||||||
boundAt: Date;
|
boundAt: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,64 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate';
|
||||||
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
|
import { WalletAddress } from '@/domain/entities/wallet-address.entity';
|
||||||
import { DeviceInfo, KYCInfo, KYCStatus, AccountStatus, ChainType, AddressStatus } from '@/domain/value-objects';
|
import { DeviceInfo, KYCInfo, KYCStatus, AccountStatus, ChainType, AddressStatus } from '@/domain/value-objects';
|
||||||
import { UserAccountEntity } from '../entities/user-account.entity';
|
import { UserAccountEntity } from '../entities/user-account.entity';
|
||||||
import { toMpcSignatureString } from '../entities/wallet-address.entity';
|
import { toMpcSignatureString } from '../entities/wallet-address.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserAccountMapper {
|
export class UserAccountMapper {
|
||||||
toDomain(entity: UserAccountEntity): UserAccount {
|
toDomain(entity: UserAccountEntity): UserAccount {
|
||||||
const devices = (entity.devices || []).map((d) => {
|
const devices = (entity.devices || []).map((d) => {
|
||||||
// 直接使用完整的 deviceInfo JSON,100% 保持原样
|
// 直接使用完整的 deviceInfo JSON,100% 保持原样
|
||||||
return new DeviceInfo(
|
return new DeviceInfo(
|
||||||
d.deviceId,
|
d.deviceId,
|
||||||
d.deviceName || '未命名设备',
|
d.deviceName || '未命名设备',
|
||||||
d.addedAt,
|
d.addedAt,
|
||||||
d.lastActiveAt,
|
d.lastActiveAt,
|
||||||
d.deviceInfo || undefined,
|
d.deviceInfo || undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const wallets = (entity.walletAddresses || []).map((w) =>
|
const wallets = (entity.walletAddresses || []).map((w) =>
|
||||||
WalletAddress.reconstruct({
|
WalletAddress.reconstruct({
|
||||||
addressId: w.addressId.toString(),
|
addressId: w.addressId.toString(),
|
||||||
userId: w.userId.toString(),
|
userId: w.userId.toString(),
|
||||||
chainType: w.chainType as ChainType,
|
chainType: w.chainType as ChainType,
|
||||||
address: w.address,
|
address: w.address,
|
||||||
publicKey: w.publicKey,
|
publicKey: w.publicKey,
|
||||||
addressDigest: w.addressDigest,
|
addressDigest: w.addressDigest,
|
||||||
mpcSignature: toMpcSignatureString(w), // 64 bytes hex (r + s)
|
mpcSignature: toMpcSignatureString(w), // 64 bytes hex (r + s)
|
||||||
status: w.status as AddressStatus,
|
status: w.status as AddressStatus,
|
||||||
boundAt: w.boundAt,
|
boundAt: w.boundAt,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const kycInfo =
|
const kycInfo =
|
||||||
entity.realName && entity.idCardNumber
|
entity.realName && entity.idCardNumber
|
||||||
? KYCInfo.create({
|
? KYCInfo.create({
|
||||||
realName: entity.realName,
|
realName: entity.realName,
|
||||||
idCardNumber: entity.idCardNumber,
|
idCardNumber: entity.idCardNumber,
|
||||||
idCardFrontUrl: entity.idCardFrontUrl || '',
|
idCardFrontUrl: entity.idCardFrontUrl || '',
|
||||||
idCardBackUrl: entity.idCardBackUrl || '',
|
idCardBackUrl: entity.idCardBackUrl || '',
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return UserAccount.reconstruct({
|
return UserAccount.reconstruct({
|
||||||
userId: entity.userId.toString(),
|
userId: entity.userId.toString(),
|
||||||
accountSequence: Number(entity.accountSequence),
|
accountSequence: entity.accountSequence, // 现在是字符串类型
|
||||||
devices,
|
devices,
|
||||||
phoneNumber: entity.phoneNumber,
|
phoneNumber: entity.phoneNumber,
|
||||||
nickname: entity.nickname,
|
nickname: entity.nickname,
|
||||||
avatarUrl: entity.avatarUrl,
|
avatarUrl: entity.avatarUrl,
|
||||||
inviterSequence: entity.inviterSequence ? Number(entity.inviterSequence) : null,
|
inviterSequence: entity.inviterSequence, // 现在是字符串类型
|
||||||
referralCode: entity.referralCode,
|
referralCode: entity.referralCode,
|
||||||
walletAddresses: wallets,
|
walletAddresses: wallets,
|
||||||
kycInfo,
|
kycInfo,
|
||||||
kycStatus: entity.kycStatus as KYCStatus,
|
kycStatus: entity.kycStatus as KYCStatus,
|
||||||
status: entity.status as AccountStatus,
|
status: entity.status as AccountStatus,
|
||||||
registeredAt: entity.registeredAt,
|
registeredAt: entity.registeredAt,
|
||||||
lastLoginAt: entity.lastLoginAt,
|
lastLoginAt: entity.lastLoginAt,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,11 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
// 新账户,让数据库自动生成userId
|
// 新账户,让数据库自动生成userId
|
||||||
const created = await tx.userAccount.create({
|
const created = await tx.userAccount.create({
|
||||||
data: {
|
data: {
|
||||||
accountSequence: BigInt(account.accountSequence.value),
|
accountSequence: account.accountSequence.value,
|
||||||
phoneNumber: account.phoneNumber?.value || null,
|
phoneNumber: account.phoneNumber?.value || null,
|
||||||
nickname: account.nickname,
|
nickname: account.nickname,
|
||||||
avatarUrl: account.avatarUrl,
|
avatarUrl: account.avatarUrl,
|
||||||
inviterSequence: account.inviterSequence ? BigInt(account.inviterSequence.value) : null,
|
inviterSequence: account.inviterSequence?.value || null,
|
||||||
referralCode: account.referralCode.value,
|
referralCode: account.referralCode.value,
|
||||||
kycStatus: account.kycStatus,
|
kycStatus: account.kycStatus,
|
||||||
realName: account.kycInfo?.realName || null,
|
realName: account.kycInfo?.realName || null,
|
||||||
|
|
@ -125,7 +125,7 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
|
|
||||||
async findByAccountSequence(sequence: AccountSequence): Promise<UserAccount | null> {
|
async findByAccountSequence(sequence: AccountSequence): Promise<UserAccount | null> {
|
||||||
const data = await this.prisma.userAccount.findUnique({
|
const data = await this.prisma.userAccount.findUnique({
|
||||||
where: { accountSequence: BigInt(sequence.value) },
|
where: { accountSequence: sequence.value },
|
||||||
include: { devices: true, walletAddresses: true },
|
include: { devices: true, walletAddresses: true },
|
||||||
});
|
});
|
||||||
return data ? this.toDomain(data) : null;
|
return data ? this.toDomain(data) : null;
|
||||||
|
|
@ -163,18 +163,38 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
|
|
||||||
async getMaxAccountSequence(): Promise<AccountSequence | null> {
|
async getMaxAccountSequence(): Promise<AccountSequence | null> {
|
||||||
const result = await this.prisma.userAccount.aggregate({ _max: { accountSequence: true } });
|
const result = await this.prisma.userAccount.aggregate({ _max: { accountSequence: true } });
|
||||||
return result._max.accountSequence ? AccountSequence.create(Number(result._max.accountSequence)) : null;
|
return result._max.accountSequence ? AccountSequence.create(result._max.accountSequence) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getNextAccountSequence(): Promise<AccountSequence> {
|
async getNextAccountSequence(): Promise<AccountSequence> {
|
||||||
|
const now = new Date();
|
||||||
|
const year = String(now.getFullYear()).slice(-2);
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const dateKey = `${year}${month}${day}`;
|
||||||
|
|
||||||
const result = await this.prisma.$transaction(async (tx) => {
|
const result = await this.prisma.$transaction(async (tx) => {
|
||||||
const updated = await tx.accountSequenceGenerator.update({
|
// 尝试更新当日记录,如果不存在则创建
|
||||||
where: { id: 1 },
|
const existing = await tx.accountSequenceGenerator.findUnique({
|
||||||
data: { currentSequence: { increment: 1 } },
|
where: { dateKey },
|
||||||
});
|
});
|
||||||
return updated.currentSequence;
|
|
||||||
|
if (existing) {
|
||||||
|
const updated = await tx.accountSequenceGenerator.update({
|
||||||
|
where: { dateKey },
|
||||||
|
data: { currentSequence: { increment: 1 } },
|
||||||
|
});
|
||||||
|
return updated.currentSequence;
|
||||||
|
} else {
|
||||||
|
// 当日第一个用户,创建新记录
|
||||||
|
const created = await tx.accountSequenceGenerator.create({
|
||||||
|
data: { dateKey, currentSequence: 0 },
|
||||||
|
});
|
||||||
|
return created.currentSequence;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return AccountSequence.create(Number(result));
|
|
||||||
|
return AccountSequence.generate(now, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findUsers(
|
async findUsers(
|
||||||
|
|
@ -247,12 +267,12 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
|
|
||||||
return UserAccount.reconstruct({
|
return UserAccount.reconstruct({
|
||||||
userId: data.userId.toString(),
|
userId: data.userId.toString(),
|
||||||
accountSequence: Number(data.accountSequence),
|
accountSequence: data.accountSequence,
|
||||||
devices,
|
devices,
|
||||||
phoneNumber: data.phoneNumber,
|
phoneNumber: data.phoneNumber,
|
||||||
nickname: data.nickname,
|
nickname: data.nickname,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
inviterSequence: data.inviterSequence ? Number(data.inviterSequence) : null,
|
inviterSequence: data.inviterSequence || null,
|
||||||
referralCode: data.referralCode,
|
referralCode: data.referralCode,
|
||||||
walletAddresses: wallets,
|
walletAddresses: wallets,
|
||||||
kycInfo,
|
kycInfo,
|
||||||
|
|
@ -268,7 +288,7 @@ export class UserAccountRepositoryImpl implements UserAccountRepository {
|
||||||
|
|
||||||
async findByInviterSequence(inviterSequence: AccountSequence): Promise<UserAccount[]> {
|
async findByInviterSequence(inviterSequence: AccountSequence): Promise<UserAccount[]> {
|
||||||
const data = await this.prisma.userAccount.findMany({
|
const data = await this.prisma.userAccount.findMany({
|
||||||
where: { inviterSequence: BigInt(inviterSequence.value) },
|
where: { inviterSequence: inviterSequence.value },
|
||||||
include: { devices: true, walletAddresses: true },
|
include: { devices: true, walletAddresses: true },
|
||||||
orderBy: { registeredAt: 'desc' },
|
orderBy: { registeredAt: 'desc' },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import { UnauthorizedException } from '@/shared/exceptions/domain.exception';
|
||||||
|
|
||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
type: 'access' | 'refresh';
|
type: 'access' | 'refresh';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CurrentUserData {
|
export interface CurrentUserData {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
export interface JwtPayload {
|
export interface JwtPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string; // 格式: D + YYMMDD + 5位序号
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
type: 'access' | 'refresh';
|
type: 'access' | 'refresh';
|
||||||
iat: number;
|
iat: number;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 生成用户名: 榴莲女皇x号
|
// 生成用户名: 榴莲女皇x号
|
||||||
export function generateUsername(accountSequence: number): string {
|
export function generateUsername(accountSequence: string): string {
|
||||||
return `榴莲女皇${accountSequence}号`;
|
return `榴莲女皇${accountSequence}号`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,9 +132,9 @@ export function generateRandomAvatarSvg(): string {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成用户身份
|
* 生成用户身份
|
||||||
* @param accountSequence 用户序列号
|
* @param accountSequence 用户序列号 (格式: D + YYMMDD + 5位序号)
|
||||||
*/
|
*/
|
||||||
export function generateIdentity(accountSequence: number): { username: string; avatarSvg: string } {
|
export function generateIdentity(accountSequence: string): { username: string; avatarSvg: string } {
|
||||||
return {
|
return {
|
||||||
username: generateUsername(accountSequence),
|
username: generateUsername(accountSequence),
|
||||||
avatarSvg: generateRandomAvatarSvg(),
|
avatarSvg: generateRandomAvatarSvg(),
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export class KeygenRequestedHandler implements OnModuleInit {
|
||||||
try {
|
try {
|
||||||
const deriveResult = await this.blockchainClient.deriveAddresses({
|
const deriveResult = await this.blockchainClient.deriveAddresses({
|
||||||
userId,
|
userId,
|
||||||
accountSequence, // 8位账户序列号,用于关联恢复助记词
|
accountSequence, // 账户序列号,格式: D + YYMMDD + 5位序号,如 D2512110008
|
||||||
publicKey: result.publicKey,
|
publicKey: result.publicKey,
|
||||||
});
|
});
|
||||||
derivedAddresses = deriveResult.addresses;
|
derivedAddresses = deriveResult.addresses;
|
||||||
|
|
@ -131,7 +131,7 @@ export class KeygenRequestedHandler implements OnModuleInit {
|
||||||
// Add extra payload for identity-service
|
// Add extra payload for identity-service
|
||||||
(completedEvent as any).extraPayload = {
|
(completedEvent as any).extraPayload = {
|
||||||
userId,
|
userId,
|
||||||
accountSequence, // 8位账户序列号,用于关联恢复助记词
|
accountSequence, // 账户序列号,格式: D + YYMMDD + 5位序号,如 D2512110008
|
||||||
username,
|
username,
|
||||||
delegateShare: result.delegateShare,
|
delegateShare: result.delegateShare,
|
||||||
derivedAddresses, // BSC, KAVA, DST addresses
|
derivedAddresses, // BSC, KAVA, DST addresses
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import * as jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
export interface StoreBackupShareParams {
|
export interface StoreBackupShareParams {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number;
|
accountSequence: string;
|
||||||
username: string;
|
username: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
partyId: string;
|
partyId: string;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
export interface DeriveAddressParams {
|
export interface DeriveAddressParams {
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number; // 8位账户序列号,用于关联恢复助记词
|
accountSequence: string; // 账户序列号,格式: D + YYMMDD + 5位序号,如 D2512110008
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const MPC_CONSUME_TOPICS = {
|
||||||
export interface KeygenRequestedPayload {
|
export interface KeygenRequestedPayload {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
accountSequence: number; // 8位账户序列号,用于关联恢复助记词
|
accountSequence: string; // 账户序列号,格式: D + YYMMDD + 5位序号,如 D2512110008
|
||||||
username: string;
|
username: string;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
totalParties: number;
|
totalParties: number;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import {
|
||||||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||||
|
|
||||||
interface AuthenticatedRequest {
|
interface AuthenticatedRequest {
|
||||||
user: { id: string; accountSequence: number };
|
user: { id: string; accountSequence: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiTags('认种订单')
|
@ApiTags('认种订单')
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue