diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8ae78e3e..d7bf05de 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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): Promise {\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 \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 \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 \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 \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 \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 \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 \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 \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 \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 \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": [] diff --git a/backend/.env.windows b/backend/.env.windows new file mode 100644 index 00000000..0fb4d76a --- /dev/null +++ b/backend/.env.windows @@ -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 diff --git a/backend/docker-compose.windows.yml b/backend/docker-compose.windows.yml new file mode 100644 index 00000000..8a442dab --- /dev/null +++ b/backend/docker-compose.windows.yml @@ -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: diff --git a/backend/scripts/init-multiple-databases.sh b/backend/scripts/init-multiple-databases.sh new file mode 100644 index 00000000..a2a7abe9 --- /dev/null +++ b/backend/scripts/init-multiple-databases.sh @@ -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 diff --git a/backend/services/referral-service/src/api/controllers/referral.controller.ts b/backend/services/referral-service/src/api/controllers/referral.controller.ts index 71f8328c..52d96cf4 100644 --- a/backend/services/referral-service/src/api/controllers/referral.controller.ts +++ b/backend/services/referral-service/src/api/controllers/referral.controller.ts @@ -56,7 +56,12 @@ export class ReferralController { @ApiOperation({ summary: '获取当前用户推荐信息' }) @ApiResponse({ status: 200, type: ReferralInfoResponseDto }) async getMyReferralInfo(@CurrentUser('userId') userId: bigint): Promise { - 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); } diff --git a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart index 1ff28b84..52f8600c 100644 --- a/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart +++ b/frontend/mobile-app/lib/features/profile/presentation/pages/profile_page.dart @@ -1271,12 +1271,24 @@ class _ProfilePageState extends ConsumerState { /// 构建单条待领取奖励项 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 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 { ), 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) ...[