fix(referral): 修复 getMyReferralInfo 使用 userId 而不是 accountSequence 的问题

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-14 05:05:03 -08:00
parent 1a97a9df54
commit 6ef8824ef0
6 changed files with 956 additions and 28 deletions

View File

@ -45,7 +45,147 @@
"Bash(do sed -i '/@ApiProperty.*账户序列号/,/accountSequence:/ s/@IsNumber()/@IsString()/' \"$file\")",
"Bash(done)",
"Bash(git diff:*)",
"Bash(npm install:*)"
"Bash(npm install:*)",
"Bash(docker-compose:*)",
"Bash(if [ -f \"c:/Users/dong/Desktop/rwadurian/backend/services/referral-service/Dockerfile\" ])",
"Bash(then cat \"c:/Users/dong/Desktop/rwadurian/backend/services/referral-service/Dockerfile\")",
"Bash(else echo \"FILE_NOT_EXISTS\")",
"Bash(fi)",
"Bash(docker logs:*)",
"Bash(git checkout:*)",
"Bash(curl:*)",
"Bash(docker builder prune:*)",
"Bash(docker images:*)",
"Bash(docker restart:*)",
"Bash(docker inspect:*)",
"Bash(node -e 'require(\"\"fs\"\").writeFileSync(\"\"user-registered.handler.ts\"\", `import { Injectable, Logger, OnModuleInit } from '\"''\"'@nestjs/common'\"''\"';\nimport { KafkaService } from '\"''\"'../../infrastructure'\"''\"';\nimport { ReferralService } from '\"''\"'../services'\"''\"';\nimport { CreateReferralRelationshipCommand } from '\"''\"'../commands'\"''\"';\n\n/**\n * identity-service 发布的账户创建事件结构\n */\ninterface UserAccountCreatedPayload {\n userId: string;\n accountSequence: string; // 格式: D + YYMMDD + 5位序号\n inviterSequence: string | null; // 格式: D + YYMMDD + 5位序号\n registeredAt: string;\n // UserAccountCreated 有 phoneNumber, UserAccountAutoCreated 没有\n phoneNumber?: string;\n initialDeviceId?: string;\n}\n\ninterface IdentityEvent {\n eventId: string;\n eventType: string;\n occurredAt: string;\n payload: UserAccountCreatedPayload;\n}\n\n/**\n * 用户注册事件处理器\n * 监听 identity-service 发出的用户创建事件\n * 支持两种创建方式:\n * - identity.UserAccountAutoCreated: 免密快捷创建\n * - identity.UserAccountCreated: 手机号密码创建\n */\n@Injectable()\nexport class UserRegisteredHandler implements OnModuleInit {\n private readonly logger = new Logger(UserRegisteredHandler.name);\n\n constructor(\n private readonly kafkaService: KafkaService,\n private readonly referralService: ReferralService,\n ) {}\n\n async onModuleInit() {\n await this.kafkaService.subscribe(\n '\"''\"'referral-service-user-account-created'\"''\"',\n ['\"''\"'identity.UserAccountAutoCreated'\"''\"', '\"''\"'identity.UserAccountCreated'\"''\"'],\n this.handleMessage.bind(this),\n );\n this.logger.log('\"''\"'Subscribed to identity.UserAccountAutoCreated and identity.UserAccountCreated events'\"''\"');\n }\n\n private async handleMessage(topic: string, message: Record<string, unknown>): Promise<void> {\n const event = message as unknown as IdentityEvent;\n\n // 验证事件类型\n if (event.eventType !== '\"''\"'UserAccountAutoCreated'\"''\"' && event.eventType !== '\"''\"'UserAccountCreated'\"''\"') {\n this.logger.debug(\\`Ignoring event type: \\${event.eventType}\\`);\n return;\n }\n\n const payload = event.payload;\n\n try {\n this.logger.log(\n \\`Processing \\${event.eventType} event: accountSequence=\\${payload.accountSequence}, inviterSequence=\\${payload.inviterSequence}\\`,\n );\n\n // 从 accountSequence 提取数值部分作为 userId\n // accountSequence 格式: D + YYMMDD + 5位序号 (例如: D25121200000)\n // 去掉 \"\"D\"\" 前缀后得到 11 位数字,作为全局唯一的 userId\n // 这样可以避免依赖 identity-service 的临时 userId (可能是 0)\n const userIdFromSequence = BigInt(payload.accountSequence.substring(1)); // 去掉 \"\"D\"\" 前缀\n\n const command = new CreateReferralRelationshipCommand(\n userIdFromSequence, // 使用从 accountSequence 提取的数值作为 userId\n payload.accountSequence, // 完整的 accountSequence 字符串\n null, // referrerCode - 不通过推荐码查找\n payload.inviterSequence, // 通过 accountSequence 查找推荐人\n );\n\n const result = await this.referralService.createReferralRelationship(command);\n this.logger.log(\n \\`Created referral relationship for accountSequence=\\${payload.accountSequence}, code: \\${result.referralCode}, inviter: \\${payload.inviterSequence ?? '\"''\"'none'\"''\"'}\\`,\n );\n } catch (error) {\n this.logger.error(\n \\`Failed to create referral relationship for accountSequence=\\${payload.accountSequence}:\\`,\n error,\n );\n }\n }\n}\n`)')",
"Bash(docker compose:*)",
"Bash(backend/services/referral-service/src/application/event-handlers/user-registered.handler.ts )",
"Bash(backend/services/identity-service/prisma/migrations/20241204000000_init/migration.sql )",
"Bash(backend/services/authorization-service/prisma/migrations/20241210000001_add_account_sequence/migration.sql )",
"Bash(backend/services/blockchain-service/prisma/migrations/20241208000000_add_system_accounts_and_recovery/migration.sql )",
"Bash(backend/services/referral-service/prisma/migrations/00000000000000_init/migration.sql )",
"Bash(backend/services/reward-service/prisma/migrations/20241210000001_add_account_sequence/migration.sql )",
"Bash(backend/services/backup-service/prisma/migrations/20241204000000_init/migration.sql )",
"Bash(backend/services/backup-service/prisma/schema.prisma )",
"Bash(backend/services/backup-service/docker-compose.yml )",
"Bash(backend/services/mpc-service/docker-compose.yml )",
"Bash(backend/services/mpc-service/docker-entrypoint.sh )",
"Bash(git commit -m \"$(cat <<''EOF''\nfix: 修复多个服务的 accountSequence 类型和推荐关系 bug\n\n1. referral-service: 修复 userId 从临时值 0 导致的 \"用户ID必须大于0\" 错误\n - 从 accountSequence 提取数值部分作为 userId (去掉 \"D\" 前缀)\n - 避免依赖 identity-service 发送的临时 userId\n\n2. 多服务 migration 修复: accountSequence/inviterSequence 类型从 BIGINT 改为 VARCHAR(12)\n - identity-service: account_sequence, inviter_sequence\n - authorization-service: account_sequence\n - blockchain-service: account_sequence\n - referral-service: account_sequence\n - reward-service: account_sequence\n - backup-service: account_sequence\n\n3. mpc-service 与 backup-service 集成:\n - mpc-service: 添加 BACKUP_SERVICE_URL, BACKUP_SERVICE_ENABLED, SERVICE_JWT_SECRET\n - backup-service: ALLOWED_SERVICES 添加 mpc-service\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix: 修复授权唯一性验证不检查地区的bug\n\n授权验证规则一条推荐线上同一类型授权只能有一个人不管地区是什么\n- 使用 findByUserIdAndRoleType 替代 findByUserIdRoleTypeAndRegion\n- 错误信息中显示已存在授权的地区名称\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(mobile): 修复序列号类型转换错误\n\naccountSequence 格式已从数字改为字符串 (D + YYMMDD + 5位序号)\n- profile_page.dart: `as int?` → `as String?`\n- mining_page.dart: `as int?` → `as String?`\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\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 = ''0x6c37675527cf0727fe6063780e2a7e22ba2b9d90'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(wallet-service): 修复 account_sequence 类型从 BIGINT 改为 VARCHAR(20)\n\n与其他服务保持一致accountSequence 格式为 D + YYMMDD + 5位序号\n- wallet_accounts.account_sequence\n- wallet_ledger_entries.account_sequence\n- deposit_orders.account_sequence\n- withdrawal_orders.account_sequence\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(reward-service): 修复与 wallet-service 的接口字段不匹配\n\n修复 allocateFunds 接口:\n- targetType: 使用 USER/SYSTEM 而不是 rightType\n- targetId: 使用 accountSequence 而不是 userId\n- allocationType: 新增字段,存储 rightType\n- hashpowerPercent: 新增字段,传递算力百分比\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(wallet-service): 修复 allocateToUserWallet 使用 accountSequence 查找钱包\n\n- targetId 现在是 accountSequence (如 D2512120001),不再是 userId\n- 移除无效的 BigInt(targetId) 转换\n- 从 wallet 对象获取 userId 用于流水记录和缓存失效\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(git reset:*)",
"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 = ''0x4485553966eef5de88e50c60cc21adf143ff1593'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(timeout 30 docker compose:*)",
"Bash(git pull:*)",
"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 = ''0x4485553966eef5de88e50c60cc21adf143ff1593'';\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 // 500,000 USDT = 500000 * 1e6 (6 decimals)\n const amount = BigInt(500000) * BigInt(1000000);\n \n console.log(''Transferring 500,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:*)",
"Bash(TOKEN1=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDAzIiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDEiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA0NTI0LCJleHAiOjE3NjU2MTE3MjR9.MqHdGvrSJ7wT2QjiL3l0ecg6HHQXzLhpAWxImj28pzs\")",
"Bash(TOKEN2=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA0IiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDIiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA0NTMyLCJleHAiOjE3NjU2MTE3MzJ9.BdM5DGsA27OCp6gypd6VPd08lRP9X0hwGSPA0nc3UAY\")",
"Bash(TOKEN1=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA1IiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDEiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA1MDM0LCJleHAiOjE3NjU2MTIyMzR9.9fyOKT2fXrfyWxPeEiSL7HUxHRHj4sL8y8jTWiswP2w\")",
"Bash(TOKEN2=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA2IiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDIiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA1MDQyLCJleHAiOjE3NjU2MTIyNDJ9.rtPlLrpaIuzqvNVXMKiN-zQ6AeuF_MCZ6f84cr3Nn8s\")",
"Bash(TOKEN1=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA1IiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDEiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA1MDM0LCJleHAiOjE3NjU2MTIyMzR9.9fyOKT2fXrfyWxPeEiSL7HUxHRHj4sL8y8jTWiswP2w\" TOKEN2=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA2IiwiZGV2aWNlSWQiOiJmbHV0dGVyLWRldmljZS0wMDIiLCJ0eXBlIjoiYWNjZXNzIiwiaWF0IjoxNzY1NjA1MDQyLCJleHAiOjE3NjU2MTIyNDJ9.rtPlLrpaIuzqvNVXMKiN-zQ6AeuF_MCZ6f84cr3Nn8s\")",
"Bash(echo \"=== 用户1钱包推荐人===\" curl -s \"http://localhost:3007/api/v1/wallet/my\" -H \"Authorization: Bearer $TOKEN1\")",
"Bash(echo \"\" echo \"=== 用户2钱包下单用户===\" curl -s \"http://localhost:3007/api/v1/wallet/my\" -H \"Authorization: Bearer $TOKEN2\")",
"Bash(__NEW_LINE__ echo \"=== 步骤1: 创建认种订单 ===\")",
"Bash(echo \"\" echo \"=== 尝试创建订单 ===\" curl -s -X POST \"http://localhost:3005/api/v1/planting/orders\" -H \"Authorization: Bearer $TOKEN2\" -H \"Content-Type: application/json\" -d '{\"\"\"\"treeCount\"\"\"\": 1}')",
"Bash(ORDER_NO=\"PLT1765607502964OZQD3K\")",
"Bash(__NEW_LINE__ echo \"=== 步骤2: 选择省市 ===\")",
"Bash(__NEW_LINE__ ORDER_NO=\"PLT1765607502964OZQD3K\")",
"Bash(__NEW_LINE__ echo \"=== 步骤3: 确认省市选择 ===\")",
"Bash(__NEW_LINE__ echo \"=== 步骤4: 支付订单 ===\")",
"Bash(__NEW_LINE__ echo \"=== 用户1钱包推荐人===\")",
"Bash(__NEW_LINE__ echo \"\")",
"Bash(echo \"=== 用户1钱包推荐人===\" curl -s \"http://localhost:3001/api/v1/wallet/my-wallet\" -H \"Authorization: Bearer $TOKEN1\")",
"Bash(echo \"\" echo \"\" echo \"=== 用户2钱包下单用户===\" curl -s \"http://localhost:3001/api/v1/wallet/my-wallet\" -H \"Authorization: Bearer $TOKEN2\")",
"Bash(echo \"=== 用户1钱包详情推荐人===\" curl -s \"http://localhost:3001/api/v1/wallet/my-wallet\" -H \"Authorization: Bearer $TOKEN1\")",
"Bash(python:*)",
"Bash(__NEW_LINE__ echo \"=== 用户1钱包 ===\")",
"Bash(__NEW_LINE__ echo \"=== 查询用户2的推荐关系 ===\")",
"Bash(__NEW_LINE__ echo \"=== 查询用户2的推荐信息 (GET /referral/me) ===\")",
"Bash(echo \"=== 测试前用户1钱包推荐人 D25121300005===\" curl -s \"http://localhost:3007/api/v1/wallet/my\" -H \"Authorization: Bearer $TOKEN1\")",
"Bash(echo echo '=== 测试前用户2钱包下单用户 D25121300006===' curl -s http://localhost:3007/api/v1/wallet/my -H 'Authorization: Bearer $TOKEN2')",
"Bash(__NEW_LINE__ echo \"=== 测试前用户1钱包推荐人 D25121300005===\")",
"Bash(ORDER_NO=\"PLT1765609358965I90B10\")",
"Bash(__NEW_LINE__ echo \"=== 创建新订单 ===\")",
"Bash(ORDER_NO=\"PLT1765609516070AVTBYV\")",
"Bash(__NEW_LINE__ echo \"=== 选择省市 ===\")",
"Bash(__NEW_LINE__ sleep 5)",
"Bash(__NEW_LINE__ echo \"=== 支付订单 ===\")",
"Bash(__NEW_LINE__ echo \"=== 1. 创建订单 ===\")",
"Bash(ORDER_NO=\"PLT17656097534706GUJ51\")",
"Bash(__NEW_LINE__ echo \"=== 2. 选择省市 ===\")",
"Bash(__NEW_LINE__ echo \"=== 4. 支付订单 ===\")",
"Bash(docker volume:*)",
"Bash(git commit -m \"$(cat <<''EOF''\nfix: 统一推荐码生成逻辑 - 由 identity-service 单点生成\n\n重要变更:\n- identity-service 生成用户推荐码,通过 Kafka 事件传递给 referral-service\n- referral-service 不再自己生成推荐码,直接使用事件中的推荐码\n- 修复两个服务推荐码不一致的问题\n\n涉及服务:\n- identity-service: 事件 payload 添加 referralCode 字段\n- referral-service: 接收并存储 identity-service 生成的推荐码\n- wallet-service: 添加区域账户动态创建接口\n- planting-service: 调用区域账户创建接口\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(taskkill:*)",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDAyIiwiZGV2aWNlSWQiOiJ1c2VyMy1kZXZpY2UtMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2NTYxNzA4NywiZXhwIjoxNzY1NjI0Mjg3fQ.afR9OWANGz_MbUJCIKO7CJZw12rXmMGsEtoGX8grRYY\")",
"Bash(ORDER_NO=\"PLT1765619473652JR0A9Q\")",
"Bash(__NEW_LINE__ curl -s -X POST \"http://localhost:3003/api/v1/planting/orders/$ORDER_NO/select-province-city\" -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" -d \"{\"\"provinceCode\"\": \"\"440000\"\", \"\"provinceName\"\": \"\"广东省\"\", \"\"cityCode\"\": \"\"440100\"\", \"\"cityName\"\": \"\"广州市\"\"}\")",
"Bash(__NEW_LINE__ curl -s -X POST \"http://localhost:3003/api/v1/planting/orders/$ORDER_NO/pay\" -H \"Authorization: Bearer $TOKEN\")",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDAyIiwiZGV2aWNlSWQiOiJ1c2VyMy1kZXZpY2UtMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2NTYxNzA4NywiZXhwIjoxNzY1NjI0Mjg3fQ.afR9OWANGz_MbUJCIKO7CJZw12rXmMGsEtoGX8grRYY\" ORDER_NO=\"PLT1765619473652JR0A9Q\")",
"Bash(echo \"=== 订单详情 ===\" curl -s \"http://localhost:3003/api/v1/planting/orders/$ORDER_NO\" -H \"Authorization: Bearer $TOKEN\")",
"Bash(__NEW_LINE__ curl -s \"http://localhost:3003/api/v1/planting/orders/$ORDER_NO\" -H \"Authorization: Bearer $TOKEN\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(wallet-service): 修复系统账户资金分配功能\n\n问题\n- 认种订单支付后,系统账户(成本费、运营费、总部社区、RWA底池)余额始终为0\n- reward-service 正确计算分配,但 wallet-service 未实际执行系统账户的资金转移\n\n根本原因\n1. allocateToSystemAccount() 方法只打印日志,未执行任何数据库操作(遗留的 TODO\n2. UserId 值对象不允许负数,而系统账户 user_id 为负数(-1 到 -4\n\n修复内容\n\n1. wallet-application.service.ts - allocateToSystemAccount()\n - 实现完整的系统账户资金分配逻辑\n - 通过 findByAccountSequence() 获取系统账户\n - 调用 addAvailableBalance() 直接增加可用余额\n - 创建 SYSTEM_ALLOCATION 类型的流水记录\n\n2. wallet-account.aggregate.ts\n - 新增 addAvailableBalance(amount: Money) 方法\n - 用于系统账户直接增加余额(无需待领取/过期机制)\n\n3. ledger-entry-type.enum.ts\n - 新增 SYSTEM_ALLOCATION 枚举值,用于系统账户分配流水\n\n4. user-id.vo.ts\n - 移除负数校验,允许系统账户使用负数 user_id\n - 系统账户约定:-1(总部社区)、-2(成本费)、-3(运营费)、-4(RWA底池)\n\n验证结果认种1棵树=2199 USDT\n- S0000000001 总部社区: 9 USDT ✓\n- S0000000002 成本费账户: 400 USDT ✓\n- S0000000003 运营费账户: 300 USDT ✓\n- S0000000004 RWA底池: 800 USDT ✓\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(npx prisma:*)",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDAyIiwiZGV2aWNlSWQiOiJ1c2VyMy1kZXZpY2UtMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2NTYxNzA4NywiZXhwIjoxNzY1NjI0Mjg3fQ.afR9OWANGz_MbUJCIKO7CJZw12rXmMGsEtoGX8grRYY\" curl -s -X POST \"http://localhost:3003/api/v1/planting/orders\" -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" -d '{\"\"\"\"treeCount\"\"\"\": 1}')",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA0IiwiZGV2aWNlSWQiOiJ1c2VyNC1kZXZpY2UtMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2NTYyMjYwMSwiZXhwIjoxNzY1NjI5ODAxfQ.ygY83ion6PutD7HCUSlVs7YLIlx44qrj-o6a-KVZ-Gw\" curl -s \"http://localhost:3000/api/v1/user/my-profile\" -H \"Authorization: Bearer $TOKEN\")",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA0IiwiZGV2aWNlSWQiOiJ1c2VyNC1kZXZpY2UtMDAxIiwidHlwZSI6ImFjY2VzcyIsImlhdCI6MTc2NTYyMjYwMSwiZXhwIjoxNzY1NjI5ODAxfQ.ygY83ion6PutD7HCUSlVs7YLIlx44qrj-o6a-KVZ-Gw\" curl -s -X POST \"http://localhost:3003/api/v1/planting/orders\" -H \"Authorization: Bearer $TOKEN\" -H \"Content-Type: application/json\" -d '{\"\"\"\"treeCount\"\"\"\": 1}')",
"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 = ''0x5e05fd75693be20f49b966b4a2faaab04dfd7f1d'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xfbf374e9edf45c5987a85d947af6017cc926ffed'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(docker volume ls:*)",
"Bash(docker volume rm:*)",
"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 = ''0xe738de852693dec8a1a42f84a8f0d68d25799f95'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x92329e8cbe08af056b47b3f527bb1c14ce996678'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(docker-compose build:*)",
"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 = ''0x0571d5eee54f31cbe5a58b6a7d36bdf5cd7accc6'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x9e54d8c94650672082b3ede7f1125a7c178ce5ee'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(wallet-service): 优化资金分配逻辑 - 区分直接到账和待领取\n\n- SHARE_RIGHT (分享权益): 写入 pending_rewards 表24小时待领取\n- PROVINCE_TEAM_RIGHT/PROVINCE_AREA_RIGHT/CITY_TEAM_RIGHT/CITY_AREA_RIGHT: 直接到账\n- COMMUNITY_RIGHT (社区权益): 进入总部社区账户 S0000000001直接到账\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\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 = ''0xa45ffba4681854649e11ea5a64cb63c8b460d281'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xf6b64113d287cc328cef810ab98eed2d8d4dffd9'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(USER6_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA1IiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyNi00NDQ0NCIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2MzcxMzIsImV4cCI6MTc2NTY0NDMzMn0.ZUiqW4YMLg9JjEEigdb7u2SdDHimWka_TR1UTn4RDRc\")",
"Bash(ORDER_NO=\"PLT1765637538749M1B2BF\")",
"Bash(__NEW_LINE__ echo \"=== Step 2: Select Province City ===\")",
"Bash(__NEW_LINE__ USER6_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxMzAwMDA1IiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyNi00NDQ0NCIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2MzcxMzIsImV4cCI6MTc2NTY0NDMzMn0.ZUiqW4YMLg9JjEEigdb7u2SdDHimWka_TR1UTn4RDRc\")",
"Bash(__NEW_LINE__ echo \"=== Step 3: Confirm Province City ===\")",
"Bash(npx prisma migrate dev:*)",
"Bash(DATABASE_URL=\"postgresql://rwa_user:rwa_dev_password@localhost:5432/rwa_authorization?schema=public\" npx prisma migrate dev:*)",
"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 = ''0x5bd21892a35209bd6e70c9ae1c02b39369f6f365'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x8b1110b9c0a3e8396ff29d7bd0f45f6ad8a17f92'';\n\nasync function transfer() {\n const provider = new ethers.JsonRpcProvider(KAVA_TESTNET_RPC);\n const wallet = new ethers.Wallet(privateKey, provider);\n \n const abi = [''function transfer(address to, uint256 amount) returns (bool)'', ''function balanceOf(address) view returns (uint256)''];\n const contract = new ethers.Contract(USDT_CONTRACT, abi, wallet);\n \n // 1,000,000 USDT = 1000000 * 1e6 (6 decimals)\n const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xede41fded07fc858ec4d906ae827585b3ad999c4'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xf6a6c91d5e812d12d861a201a74aed5171751fd0'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x527d384019648c8ec664be9df856c5ce3d1b07e7'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0xb0ff7bfd36fe9b92139d637280fb384c56a97f01'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(USER2_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIyIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxNDAwMDAxIiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyMi0yMjIyMiIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2NzYwNjQsImV4cCI6MTc2NTY4MzI2NH0.uNzIQkKfS2pNHEf8-Z5Wc4ufBM7_69RgHGrD6T_z2S0\")",
"Bash(ORDER_NO=\"PLT1765676984006U89FRB\")",
"Bash(USER4_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI0IiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxNDAwMDAzIiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyNC00NDQ0NCIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2NzYwODAsImV4cCI6MTc2NTY4MzI4MH0.5S41vGZaLR1KgYtEMUQuwaVVoCYBkgvATQg_j4wolw4\")",
"Bash(ORDER_NO=\"PLT176567709312222YUFD\")",
"Bash(USER1_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxNDAwMDAwIiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyMS0xMTExMSIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2NzYwNTQsImV4cCI6MTc2NTY4MzI1NH0.a8o6Qa5XEbalUB1rFOylNPIk08DM4r9e7YA0Ur4qDLQ\")",
"Bash(USER3_TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIzIiwiYWNjb3VudFNlcXVlbmNlIjoiRDI1MTIxNDAwMDAyIiwiZGV2aWNlSWQiOiJ0ZXN0LWRldmljZS11c2VyMy0zMzMzMyIsInR5cGUiOiJhY2Nlc3MiLCJpYXQiOjE3NjU2NzYwNzAsImV4cCI6MTc2NTY4MzI3MH0.4DGNTLiVc5Yx9PZYuoz7hvusXBxqEybHZbTypqfgayc\")",
"Bash(echo \"=== Step 1: Create Planting Order ===\" curl -s -X POST \"http://localhost:3003/api/v1/planting/orders\" -H \"Authorization: Bearer $USER3_TOKEN\" -H \"Content-Type: application/json\" -d '{\"\"\"\"treeCount\"\"\"\": 1}')",
"Bash(__NEW_LINE__ echo \"=== Step 1: Create Planting Order ===\")",
"Bash(ORDER_NO=\"PLT176568276703658GRMG\")",
"Bash(__NEW_LINE__ echo \"=== Step 4: Pay Order ===\")",
"Bash(__NEW_LINE__ echo \"=== User1 Step 1: Create Planting Order ===\")",
"Bash(ORDER_NO=\"PLT1765682864008NB0HH9\")",
"Bash(__NEW_LINE__ echo \"=== User1 Step 2: Select Province City ===\")",
"Bash(__NEW_LINE__ echo \"=== User1 Step 3: Confirm Province City ===\")",
"Bash(__NEW_LINE__ echo \"=== User1 Step 4: Pay Order ===\")",
"Bash(DATABASE_URL=\"postgresql://rwa_user:rwa_dev_password@localhost:5432/rwa_authorization?schema=public\" npx prisma migrate:*)",
"Bash(DATABASE_URL=\"postgresql://rwa_user:rwa_dev_password@localhost:5432/rwa_authorization?schema=public\" npx prisma migrate diff:*)",
"Bash(git commit -m \"$(cat <<''EOF''\nfix(identity): 优化默认昵称生成格式\n\n将新用户默认昵称从「用户D2512140001」改为「用户1」\n使用 accountSequence.dailySequence 提取当日序号并去除前导零。\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)\")",
"Bash(ls:*)",
"Bash(paste:*)",
"Bash(docker network:*)",
"Bash(git show:*)",
"Bash(docker image inspect:*)",
"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 = ''0x132883f6d80786109cf64004f6b5c4de99c1b3db'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(node -e \"\nconst { ethers } = require(''ethers'');\n\nconst KAVA_TESTNET_RPC = ''https://evm.testnet.kava.io'';\nconst privateKey = ''0xd42a6e6021ebd884f3f179d3793a32e97b9f1001db6ff44441ec455d748b9aa6'';\nconst USDT_CONTRACT = ''0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF'';\nconst TO_ADDRESS = ''0x14fcc4cad17f65ed4060ac6e89dd6dcbe8464b70'';\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 const amount = BigInt(1000000) * BigInt(1000000);\n \n console.log(''Transferring 1,000,000 USDT to'', TO_ADDRESS);\n const tx = await contract.transfer(TO_ADDRESS, amount, { gasLimit: 100000 });\n console.log(''TX Hash:'', tx.hash);\n await tx.wait();\n \n const newBalance = await contract.balanceOf(TO_ADDRESS);\n console.log(''New balance:'', Number(newBalance) / 1e6, ''USDT'');\n}\n\ntransfer().catch(e => console.error(''Error:'', e.message));\n\")",
"Bash(dir /s /b c:UsersdongDesktoprwadurianbackend*.env*)"
],
"deny": [],
"ask": []

38
backend/.env.windows Normal file
View File

@ -0,0 +1,38 @@
# =============================================================================
# RWA Backend - Windows Local Development Environment
# =============================================================================
# 用于 docker-compose.windows.yml
#
# 使用方法:
# docker-compose -f docker-compose.windows.yml --env-file .env.windows up -d
# =============================================================================
# =============================================================================
# Database Passwords
# =============================================================================
POSTGRES_PASSWORD=rwa_dev_password
MPC_POSTGRES_PASSWORD=mpc_dev_password
# =============================================================================
# JWT & Security
# =============================================================================
# JWT Secret (必须至少32字符)
JWT_SECRET=dev_jwt_secret_key_min_32_chars_long
# Service-to-service JWT Secret
SERVICE_JWT_SECRET=dev_service_jwt_secret_for_inter_service_auth
# =============================================================================
# MPC System
# =============================================================================
# MPC API Key (mpc-service 与 mpc-system 之间的认证)
MPC_API_KEY=dev_mpc_api_key_for_testing_only
# Master encryption key for key shares (64 hex chars = 256 bits)
CRYPTO_MASTER_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# =============================================================================
# Backup Service
# =============================================================================
# Backup encryption key (64 hex chars = 256 bits)
BACKUP_ENCRYPTION_KEY=fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210

View File

@ -0,0 +1,723 @@
# =============================================================================
# RWA Backend Services - Windows Local Development
# =============================================================================
# 用于在 Windows 上本地测试 auto-create-account 流程
#
# 包含服务:
# - PostgreSQL (各服务独立数据库)
# - Redis
# - Kafka + Zookeeper
# - mpc-system (Go TSS 后端)
# - identity-service
# - mpc-service
# - blockchain-service
# - backup-service
#
# 使用方法:
# docker-compose -f docker-compose.windows.yml --env-file .env.windows up -d
#
# =============================================================================
services:
# ============================================
# Infrastructure - Database & Cache
# ============================================
postgres:
image: postgres:15-alpine
container_name: rwa-postgres
environment:
POSTGRES_USER: rwa_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rwa_dev_password}
POSTGRES_MULTIPLE_DATABASES: rwa_identity,rwa_mpc,rwa_blockchain,rwa_backup,rwa_referral,rwa_wallet,rwa_planting,rwa_reward,rwa_leaderboard,rwa_reporting,rwa_authorization,rwa_admin,mpc_system
volumes:
- postgres-data:/var/lib/postgresql/data
- ./scripts/init-multiple-databases.sh:/docker-entrypoint-initdb.d/init-multiple-databases.sh:ro
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rwa_user"]
interval: 10s
timeout: 5s
retries: 5
networks:
- rwa-network
redis:
image: redis:7-alpine
container_name: rwa-redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- rwa-network
# ============================================
# Infrastructure - Kafka
# ============================================
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
container_name: rwa-zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
networks:
- rwa-network
kafka:
image: confluentinc/cp-kafka:7.5.0
container_name: rwa-kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
- "29092:29092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
healthcheck:
test: ["CMD", "kafka-topics", "--bootstrap-server", "localhost:9092", "--list"]
interval: 30s
timeout: 10s
retries: 5
networks:
- rwa-network
# ============================================
# MPC System (Go TSS Backend)
# ============================================
mpc-postgres:
image: postgres:15-alpine
container_name: mpc-postgres
environment:
POSTGRES_DB: mpc_system
POSTGRES_USER: mpc_user
POSTGRES_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
volumes:
- mpc-postgres-data:/var/lib/postgresql/data
- ./mpc-system/migrations:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mpc_user -d mpc_system"]
interval: 10s
timeout: 5s
retries: 5
networks:
- rwa-network
mpc-session-coordinator:
build:
context: ./mpc-system
dockerfile: services/session-coordinator/Dockerfile
container_name: mpc-session-coordinator
ports:
- "8081:8080"
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
SESSION_COORDINATOR_ADDR: mpc-session-coordinator:50051
MPC_JWT_SECRET_KEY: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
MPC_JWT_ISSUER: mpc-system
MESSAGE_ROUTER_ADDR: mpc-message-router:50051
ACCOUNT_SERVICE_ADDR: http://mpc-account-service:8080
depends_on:
mpc-postgres:
condition: service_healthy
mpc-message-router:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-message-router:
build:
context: ./mpc-system
dockerfile: services/message-router/Dockerfile
container_name: mpc-message-router
ports:
- "8082:8080"
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
SESSION_COORDINATOR_ADDR: mpc-session-coordinator:50051
depends_on:
mpc-postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-server-party-1:
build:
context: ./mpc-system
dockerfile: services/server-party/Dockerfile
container_name: mpc-server-party-1
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
MESSAGE_ROUTER_ADDR: mpc-message-router:50051
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
PARTY_ID: server-party-1
depends_on:
mpc-postgres:
condition: service_healthy
mpc-session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-server-party-2:
build:
context: ./mpc-system
dockerfile: services/server-party/Dockerfile
container_name: mpc-server-party-2
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
MESSAGE_ROUTER_ADDR: mpc-message-router:50051
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
PARTY_ID: server-party-2
depends_on:
mpc-postgres:
condition: service_healthy
mpc-session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-server-party-3:
build:
context: ./mpc-system
dockerfile: services/server-party/Dockerfile
container_name: mpc-server-party-3
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
MESSAGE_ROUTER_ADDR: mpc-message-router:50051
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
PARTY_ID: server-party-3
depends_on:
mpc-postgres:
condition: service_healthy
mpc-session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-server-party-api:
build:
context: ./mpc-system
dockerfile: services/server-party-api/Dockerfile
container_name: mpc-server-party-api
ports:
- "8083:8080"
environment:
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
SESSION_COORDINATOR_ADDR: mpc-session-coordinator:50051
MESSAGE_ROUTER_ADDR: mpc-message-router:50051
MPC_CRYPTO_MASTER_KEY: ${CRYPTO_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
MPC_API_KEY: ${MPC_API_KEY:-dev_mpc_api_key_for_testing}
PARTY_ID: delegate-party
PARTY_ROLE: delegate
depends_on:
mpc-session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
mpc-account-service:
build:
context: ./mpc-system
dockerfile: services/account/Dockerfile
container_name: mpc-account-service
ports:
- "4000:8080"
environment:
MPC_SERVER_GRPC_PORT: 50051
MPC_SERVER_HTTP_PORT: 8080
MPC_SERVER_ENVIRONMENT: development
MPC_LOGGER_LEVEL: debug
MPC_DATABASE_HOST: mpc-postgres
MPC_DATABASE_PORT: 5432
MPC_DATABASE_USER: mpc_user
MPC_DATABASE_PASSWORD: ${MPC_POSTGRES_PASSWORD:-mpc_dev_password}
MPC_DATABASE_DBNAME: mpc_system
MPC_DATABASE_SSLMODE: disable
SESSION_COORDINATOR_ADDR: mpc-session-coordinator:50051
MPC_COORDINATOR_URL: mpc-session-coordinator:50051
MPC_JWT_SECRET_KEY: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
MPC_API_KEY: ${MPC_API_KEY:-dev_mpc_api_key_for_testing}
ALLOWED_IPS: ""
depends_on:
mpc-postgres:
condition: service_healthy
mpc-session-coordinator:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
networks:
- rwa-network
# ============================================
# NestJS Backend Services
# ============================================
identity-service:
build:
context: ./services/identity-service
dockerfile: Dockerfile
container_name: rwa-identity-service
ports:
- "3000:3000"
environment:
APP_PORT: 3000
APP_ENV: development
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_identity?schema=public
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
JWT_ACCESS_EXPIRES_IN: 2h
JWT_REFRESH_EXPIRES_IN: 30d
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 0
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: identity-service
KAFKA_GROUP_ID: identity-service-group
MPC_SERVICE_URL: http://mpc-service:3006
MPC_MODE: remote
MPC_USE_EVENT_DRIVEN: "true"
BACKUP_SERVICE_URL: http://backup-service:3002
BACKUP_SERVICE_ENABLED: "true"
SERVICE_JWT_SECRET: ${SERVICE_JWT_SECRET:-dev_service_jwt_secret}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
mpc-service:
build:
context: ./services/mpc-service
dockerfile: Dockerfile
container_name: rwa-mpc-service
ports:
- "3006:3006"
environment:
NODE_ENV: development
APP_PORT: 3006
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_mpc?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 5
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: mpc-service
KAFKA_GROUP_ID: mpc-service-group
MPC_ACCOUNT_SERVICE_URL: http://mpc-account-service:8080
MPC_COORDINATOR_URL: http://mpc-session-coordinator:8080
MPC_SESSION_COORDINATOR_URL: http://mpc-session-coordinator:8080
MPC_MESSAGE_ROUTER_WS_URL: ws://mpc-message-router:8080
MPC_SERVER_PARTY_API_URL: http://mpc-server-party-api:8080
MPC_JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
MPC_API_KEY: ${MPC_API_KEY:-dev_mpc_api_key_for_testing}
BLOCKCHAIN_SERVICE_URL: http://blockchain-service:3012
SHARE_MASTER_KEY: ${CRYPTO_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
BACKUP_SERVICE_URL: http://backup-service:3002
BACKUP_SERVICE_ENABLED: "true"
SERVICE_JWT_SECRET: ${SERVICE_JWT_SECRET:-dev_service_jwt_secret}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
mpc-account-service:
condition: service_healthy
backup-service:
condition: service_healthy
networks:
- rwa-network
blockchain-service:
build:
context: ./services/blockchain-service
dockerfile: Dockerfile
container_name: rwa-blockchain-service
ports:
- "3012:3012"
environment:
NODE_ENV: development
PORT: 3012
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_blockchain?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 11
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: blockchain-service
KAFKA_GROUP_ID: blockchain-service-group
# 使用测试网
NETWORK_MODE: testnet
# KAVA Testnet 配置
KAVA_RPC_URL: https://evm.testnet.kava.io
KAVA_USDT_CONTRACT: "0xc12f6A4A7Fd0965085B044A67a39CcA2ff7fe0dF"
# BSC Testnet 配置
BSC_RPC_URL: https://data-seed-prebsc-1-s1.binance.org:8545
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
backup-service:
build:
context: ./services/backup-service
dockerfile: Dockerfile
container_name: rwa-backup-service
ports:
- "3002:3002"
environment:
APP_PORT: 3002
APP_ENV: development
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_backup?schema=public
SERVICE_JWT_SECRET: ${SERVICE_JWT_SECRET:-dev_service_jwt_secret}
ALLOWED_SERVICES: identity-service,recovery-service,mpc-service
BACKUP_ENCRYPTION_KEY: ${BACKUP_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
BACKUP_ENCRYPTION_KEY_ID: key-v1
depends_on:
postgres:
condition: service_healthy
networks:
- rwa-network
referral-service:
build:
context: ./services/referral-service
dockerfile: Dockerfile
container_name: rwa-referral-service
ports:
- "3004:3004"
environment:
NODE_ENV: development
APP_PORT: 3004
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_referral?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 4
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: referral-service
KAFKA_GROUP_ID: referral-service-group
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
wallet-service:
build:
context: ./services/wallet-service
dockerfile: Dockerfile
container_name: rwa-wallet-service
ports:
- "3001:3001"
environment:
NODE_ENV: development
APP_PORT: 3001
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_wallet?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 1
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: wallet-service
KAFKA_GROUP_ID: wallet-service-group
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
planting-service:
build:
context: ./services/planting-service
dockerfile: Dockerfile
container_name: rwa-planting-service
ports:
- "3003:3003"
environment:
NODE_ENV: development
APP_PORT: 3003
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_planting?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 2
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: planting-service
KAFKA_GROUP_ID: planting-service-group
WALLET_SERVICE_URL: http://wallet-service:3001
IDENTITY_SERVICE_URL: http://identity-service:3000
REFERRAL_SERVICE_URL: http://referral-service:3004
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
reward-service:
build:
context: ./services/reward-service
dockerfile: Dockerfile
container_name: rwa-reward-service
ports:
- "3005:3005"
environment:
NODE_ENV: development
APP_PORT: 3005
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_reward?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 4
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: reward-service
KAFKA_GROUP_ID: reward-service-group
REFERRAL_SERVICE_URL: http://referral-service:3004
AUTHORIZATION_SERVICE_URL: http://authorization-service:3009
WALLET_SERVICE_URL: http://wallet-service:3001
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
leaderboard-service:
build:
context: ./services/leaderboard-service
dockerfile: Dockerfile
container_name: rwa-leaderboard-service
ports:
- "3007:3007"
environment:
NODE_ENV: development
APP_PORT: 3007
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_leaderboard?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 6
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: leaderboard-service
KAFKA_GROUP_ID: leaderboard-service-group
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
reporting-service:
build:
context: ./services/reporting-service
dockerfile: Dockerfile
container_name: rwa-reporting-service
ports:
- "3008:3008"
environment:
NODE_ENV: development
APP_PORT: 3008
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_reporting?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 7
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: reporting-service
KAFKA_GROUP_ID: reporting-service-group
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
authorization-service:
build:
context: ./services/authorization-service
dockerfile: Dockerfile
container_name: rwa-authorization-service
ports:
- "3009:3009"
environment:
NODE_ENV: development
APP_PORT: 3009
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_authorization?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 8
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
KAFKA_BROKERS: kafka:29092
KAFKA_CLIENT_ID: authorization-service
KAFKA_GROUP_ID: authorization-service-group
REFERRAL_SERVICE_URL: http://referral-service:3004
REFERRAL_SERVICE_ENABLED: "true"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
kafka:
condition: service_healthy
networks:
- rwa-network
admin-service:
build:
context: ./services/admin-service
dockerfile: Dockerfile
container_name: rwa-admin-service
ports:
- "3010:3010"
environment:
NODE_ENV: development
APP_PORT: 3010
DATABASE_URL: postgresql://rwa_user:${POSTGRES_PASSWORD:-rwa_dev_password}@postgres:5432/rwa_admin?schema=public
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 9
JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_key_min_32_chars_long}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- rwa-network
# ============================================
# Networks
# ============================================
networks:
rwa-network:
driver: bridge
# ============================================
# Volumes
# ============================================
volumes:
postgres-data:
mpc-postgres-data:

View File

@ -0,0 +1,27 @@
#!/bin/bash
# =============================================================================
# Initialize Multiple PostgreSQL Databases
# =============================================================================
# This script is mounted to /docker-entrypoint-initdb.d/ and runs on first start
# Creates separate databases for each service
# =============================================================================
set -e
set -u
function create_database() {
local database=$1
echo "Creating database '$database'"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE DATABASE $database;
GRANT ALL PRIVILEGES ON DATABASE $database TO $POSTGRES_USER;
EOSQL
}
if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
create_database $db
done
echo "Multiple databases created"
fi

View File

@ -56,7 +56,12 @@ export class ReferralController {
@ApiOperation({ summary: '获取当前用户推荐信息' })
@ApiResponse({ status: 200, type: ReferralInfoResponseDto })
async getMyReferralInfo(@CurrentUser('userId') userId: bigint): Promise<ReferralInfoResponseDto> {
const query = new GetUserReferralInfoQuery(userId.toString()); // 转换为字符串
// 先通过 userId 查找 referral_relationships 获取 accountSequence
const relationship = await this.referralRepo.findByUserId(userId);
if (!relationship) {
throw new Error('用户推荐关系不存在');
}
const query = new GetUserReferralInfoQuery(relationship.accountSequence);
return this.referralService.getUserReferralInfo(query);
}

View File

@ -1271,12 +1271,24 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
///
Widget _buildPendingRewardItem(PendingRewardItem item) {
final remainingSeconds = item.remainingSeconds;
//
final remainingSeconds = item.remainingSeconds > 0 ? item.remainingSeconds : 0;
final hours = (remainingSeconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0');
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds';
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
amountParts.add('${_formatNumber(item.usdtAmount)} USDT');
}
if (item.hashpowerAmount > 0) {
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
}
// 0
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 USDT';
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
@ -1327,31 +1339,14 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
),
const SizedBox(height: 8),
//
Row(
children: [
if (item.usdtAmount > 0) ...[
Text(
'${_formatNumber(item.usdtAmount)} USDT',
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
if (item.hashpowerAmount > 0) const SizedBox(width: 16),
],
if (item.hashpowerAmount > 0)
Text(
'${_formatNumber(item.hashpowerAmount)} 算力',
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
],
Text(
amountText,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
//
if (item.memo.isNotEmpty) ...[