diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 624c76ed..94edf3d7 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -557,7 +557,23 @@ "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(android\\): add 5-minute polling timeout mechanism for keygen/sign\n\nImplements Electron''s checkAndTriggerKeygen\\(\\) polling fallback:\n- Adds polling every 2 seconds with 5-minute timeout\n- Triggers keygen/sign via synthetic session_started event on in_progress status\n- Handles gRPC stream disconnection when app goes to background\n- Shows timeout error in UI via existing error mechanism\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", "Bash(go list:*)", "Bash(adb install:*)", - "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(tss\\): add real-time round progress from msg.Type\\(\\) parsing\n\nExtract current round number from tss-lib message type string using\nregex pattern `Round\\(\\\\d+\\)`. This enables real-time progress updates\n\\(1/4, 2/4... for keygen, 1/9, 2/9... for signing\\) instead of only\nshowing completion status.\n\nChanges across all three platforms:\n- tss-wasm/main.go: Add extractRoundFromMessageType\\(\\) and call\n OnProgress with parsed round on each outgoing message\n- service-party-android/tsslib/tsslib.go: Same implementation for\n Android gomobile binding\n- service-party-app/tss-party/main.go: Same implementation for\n Electron subprocess, with isKeygen parameter to distinguish\n keygen \\(4 rounds\\) vs signing \\(9 rounds\\)\n\nSafe fallback: Returns 0 if parsing fails, which doesn''t affect\nprotocol execution - only UI display.\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''\nfeat\\(tss\\): add real-time round progress from msg.Type\\(\\) parsing\n\nExtract current round number from tss-lib message type string using\nregex pattern `Round\\(\\\\d+\\)`. This enables real-time progress updates\n\\(1/4, 2/4... for keygen, 1/9, 2/9... for signing\\) instead of only\nshowing completion status.\n\nChanges across all three platforms:\n- tss-wasm/main.go: Add extractRoundFromMessageType\\(\\) and call\n OnProgress with parsed round on each outgoing message\n- service-party-android/tsslib/tsslib.go: Same implementation for\n Android gomobile binding\n- service-party-app/tss-party/main.go: Same implementation for\n Electron subprocess, with isKeygen parameter to distinguish\n keygen \\(4 rounds\\) vs signing \\(9 rounds\\)\n\nSafe fallback: Returns 0 if parsing fails, which doesn''t affect\nprotocol execution - only UI display.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(node --input-type=module -e:*)", + "Bash(npx solc:*)", + "Bash(node /c/Users/dong/Desktop/rwadurian/contracts/deploy.mjs:*)", + "Bash(npm init:*)", + "Bash(node deploy.mjs:*)", + "Bash(npx solcjs@0.8.19:*)", + "Bash(node compile.mjs:*)", + "Bash(node verify-sig.mjs:*)", + "Bash(node deploy-ethers.mjs:*)", + "Bash(node transfer-all.mjs:*)", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(android\\): add share export and import functionality\n\nAdd ability to backup wallet shares to files and restore from backups:\n\n- Add ShareBackup data class in Models.kt for backup format\n- Add exportShareBackup\\(\\) and importShareBackup\\(\\) in TssRepository\n- Add export/import state and methods in MainViewModel\n- Add file picker integration in MainActivity using ActivityResultContracts\n- Add import FAB button in WalletsScreen\n- Export saves as .tss-backup file with address and timestamp in filename\n- Import validates backup format and checks for duplicate wallets\n\nThe backup file contains all necessary data to restore a wallet share:\nsessionId, publicKey, encryptedShare, threshold, partyIndex, address.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(dir /s /b \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\identity-service\\\\src\\\\api\\\\controllers\\\\*.ts\")", + "Bash(dir /s /b \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\identity-service\\\\src\\\\infrastructure\\\\persistence\\\\repositories\\\\*.ts\")", + "Bash(head:*)", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(pending-actions\\): add user pending actions system\n\nAdd a fully optional pending actions system that allows admins to configure\nspecific tasks that users must complete after login.\n\nBackend \\(identity-service\\):\n- Add UserPendingAction model to Prisma schema\n- Add migration for user_pending_actions table\n- Add PendingActionService with full CRUD operations\n- Add user-facing API \\(GET list, POST complete\\)\n- Add admin API \\(CRUD, batch create\\)\n\nAdmin Web:\n- Add pending actions management page\n- Support single/batch create, edit, cancel, delete\n- View action details including completion time\n- Filter by userId, actionCode, status\n\nFlutter Mobile App:\n- Add PendingActionService and PendingActionCheckService\n- Add PendingActionsPage for forced task execution\n- Integrate into splash_page login flow\n- Users must complete all pending tasks in priority order\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(npm run type-check:*)" ], "deny": [], "ask": [] diff --git a/backend/services/identity-service/src/api/dto/response/pending-action.dto.ts b/backend/services/identity-service/src/api/dto/response/pending-action.dto.ts index c30d9c86..0b043ff6 100644 --- a/backend/services/identity-service/src/api/dto/response/pending-action.dto.ts +++ b/backend/services/identity-service/src/api/dto/response/pending-action.dto.ts @@ -10,6 +10,9 @@ export class PendingActionResponseDto { @ApiProperty({ example: '12345', description: '用户ID' }) userId: string; + @ApiPropertyOptional({ example: 'D25122700022', description: '用户账号序列号' }) + accountSequence?: string; + @ApiProperty({ example: 'ADOPTION_WIZARD', description: '操作代码' }) actionCode: string; diff --git a/backend/services/identity-service/src/application/services/pending-action.service.ts b/backend/services/identity-service/src/application/services/pending-action.service.ts index e9d6b996..7a3228df 100644 --- a/backend/services/identity-service/src/application/services/pending-action.service.ts +++ b/backend/services/identity-service/src/application/services/pending-action.service.ts @@ -224,8 +224,13 @@ export class PendingActionService { this.prisma.userPendingAction.count({ where }), ]); + // 批量获取 accountSequence + const accountSequenceMap = await this.getAccountSequenceMap(items.map((item) => item.userId)); + return { - items: items.map((action) => this.toResponseDto(action)), + items: items.map((action) => + this.toResponseDto(action, accountSequenceMap.get(action.userId.toString())), + ), total, page, limit, @@ -244,7 +249,8 @@ export class PendingActionService { throw new ApplicationError('操作不存在'); } - return this.toResponseDto(action); + const accountSequence = await this.getAccountSequence(action.userId); + return this.toResponseDto(action, accountSequence); } /** @@ -367,10 +373,11 @@ export class PendingActionService { }); } - private toResponseDto(action: any): PendingActionResponseDto { + private toResponseDto(action: any, accountSequence?: string): PendingActionResponseDto { return { id: action.id.toString(), userId: action.userId.toString(), + accountSequence, actionCode: action.actionCode, actionParams: action.actionParams as Record | null, status: action.status, @@ -381,4 +388,36 @@ export class PendingActionService { createdBy: action.createdBy, }; } + + /** + * 批量获取用户的 accountSequence + */ + private async getAccountSequenceMap(userIds: bigint[]): Promise> { + if (userIds.length === 0) return new Map(); + + const uniqueUserIds = [...new Set(userIds.map((id) => id.toString()))]; + const users = await this.prisma.userAccount.findMany({ + where: { userId: { in: uniqueUserIds.map((id) => BigInt(id)) } }, + select: { userId: true, accountSequence: true }, + }); + + const map = new Map(); + for (const user of users) { + if (user.accountSequence) { + map.set(user.userId.toString(), user.accountSequence); + } + } + return map; + } + + /** + * 获取单个用户的 accountSequence + */ + private async getAccountSequence(userId: bigint): Promise { + const user = await this.prisma.userAccount.findUnique({ + where: { userId }, + select: { accountSequence: true }, + }); + return user?.accountSequence ?? undefined; + } } diff --git a/frontend/admin-web/src/app/(dashboard)/pending-actions/page.tsx b/frontend/admin-web/src/app/(dashboard)/pending-actions/page.tsx index b4f245c2..a1ad9eec 100644 --- a/frontend/admin-web/src/app/(dashboard)/pending-actions/page.tsx +++ b/frontend/admin-web/src/app/(dashboard)/pending-actions/page.tsx @@ -474,7 +474,7 @@ export default function PendingActionsPage() { ID - 用户ID + 用户 操作类型 状态 优先级 @@ -489,7 +489,16 @@ export default function PendingActionsPage() { return ( {action.id} - {action.userId} + + {action.accountSequence && ( +
+ {action.accountSequence} +
+ )} +
+ ID: {action.userId} +
+ {getActionCodeLabel(action.actionCode)} @@ -826,8 +835,11 @@ export default function PendingActionsPage() { {viewingAction.id}
- 用户ID: - {viewingAction.userId} + 用户: + + {viewingAction.accountSequence && `${viewingAction.accountSequence} / `} + ID: {viewingAction.userId} +
操作类型: diff --git a/frontend/admin-web/src/app/(dashboard)/pending-actions/pending-actions.module.scss b/frontend/admin-web/src/app/(dashboard)/pending-actions/pending-actions.module.scss index cf7f9d79..7092a76c 100644 --- a/frontend/admin-web/src/app/(dashboard)/pending-actions/pending-actions.module.scss +++ b/frontend/admin-web/src/app/(dashboard)/pending-actions/pending-actions.module.scss @@ -136,6 +136,17 @@ font-weight: 500; } + &__accountSequence { + font-weight: 600; + color: $primary-color; + font-size: 13px; + } + + &__userId { + font-size: 12px; + color: $text-secondary; + } + &__actions { display: flex; gap: 8px; diff --git a/frontend/admin-web/src/types/pending-action.types.ts b/frontend/admin-web/src/types/pending-action.types.ts index c57e82b1..b4b330c4 100644 --- a/frontend/admin-web/src/types/pending-action.types.ts +++ b/frontend/admin-web/src/types/pending-action.types.ts @@ -24,6 +24,7 @@ export type ActionCode = (typeof ACTION_CODES)[keyof typeof ACTION_CODES] | stri export interface PendingAction { id: string; userId: string; + accountSequence?: string; // 用户账号序列号 (如 D25122700022) actionCode: string; actionParams: Record | null; status: PendingActionStatus;