fix: 修复多个服务的 accountSequence 类型和推荐关系 bug

1. referral-service: 修复 userId 从临时值 0 导致的 "用户ID必须大于0" 错误
   - 从 accountSequence 提取数值部分作为 userId (去掉 "D" 前缀)
   - 避免依赖 identity-service 发送的临时 userId

2. 多服务 migration 修复: accountSequence/inviterSequence 类型从 BIGINT 改为 VARCHAR(12)
   - identity-service: account_sequence, inviter_sequence
   - authorization-service: account_sequence
   - blockchain-service: account_sequence
   - referral-service: account_sequence
   - reward-service: account_sequence
   - backup-service: account_sequence

3. mpc-service 与 backup-service 集成:
   - mpc-service: 添加 BACKUP_SERVICE_URL, BACKUP_SERVICE_ENABLED, SERVICE_JWT_SECRET
   - backup-service: ALLOWED_SERVICES 添加 mpc-service

🤖 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-12 12:29:11 -08:00
parent 4be9c1fb82
commit 75c49951b7
10 changed files with 430 additions and 424 deletions

View File

@ -1,14 +1,14 @@
-- Step 1: Add account_sequence columns to all tables first
ALTER TABLE "authorization_roles" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "monthly_assessments" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "monthly_bypasses" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "stickman_rankings" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "authorization_roles" ADD COLUMN "account_sequence" TEXT;
ALTER TABLE "monthly_assessments" ADD COLUMN "account_sequence" TEXT;
ALTER TABLE "monthly_bypasses" ADD COLUMN "account_sequence" TEXT;
ALTER TABLE "stickman_rankings" ADD COLUMN "account_sequence" TEXT;
-- Step 2: Backfill account_sequence from existing user_id (which is still String at this point)
UPDATE "authorization_roles" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
UPDATE "monthly_assessments" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
UPDATE "monthly_bypasses" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
UPDATE "stickman_rankings" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
UPDATE "authorization_roles" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
UPDATE "monthly_assessments" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
UPDATE "monthly_bypasses" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
UPDATE "stickman_rankings" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
-- Step 3: Make account_sequence NOT NULL
ALTER TABLE "authorization_roles" ALTER COLUMN "account_sequence" SET NOT NULL;

View File

@ -1,47 +1,47 @@
# =============================================================================
# Backup Service - Docker Compose (Development/Standalone)
# =============================================================================
# For production, use the root docker-compose.yml in ../
#
# For standalone development:
# 1. First start shared infrastructure: cd .. && ./deploy.sh up postgres redis kafka
# 2. Then: docker compose up -d --build
# =============================================================================
services:
backup-service:
build:
context: .
dockerfile: Dockerfile
container_name: rwa-backup-service
ports:
- "3002:3002"
environment:
# Application
- NODE_ENV=production
- APP_PORT=3002
- APP_ENV=production
# Database (shared PostgreSQL)
- DATABASE_URL=postgresql://rwa_user:rwa_secure_password@rwa-postgres:5432/rwa_backup?schema=public
# Service Authentication
- SERVICE_JWT_SECRET=${SERVICE_JWT_SECRET:-your-service-jwt-secret}
- ALLOWED_SERVICES=identity-service,recovery-service
# Backup Encryption
- BACKUP_ENCRYPTION_KEY=${BACKUP_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}
- BACKUP_ENCRYPTION_KEY_ID=${BACKUP_ENCRYPTION_KEY_ID:-key-v1}
# Rate Limits
- MAX_RETRIEVE_PER_DAY=3
- MAX_STORE_PER_MINUTE=10
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- rwa-network
networks:
rwa-network:
external: true
# =============================================================================
# Backup Service - Docker Compose (Development/Standalone)
# =============================================================================
# For production, use the root docker-compose.yml in ../
#
# For standalone development:
# 1. First start shared infrastructure: cd .. && ./deploy.sh up postgres redis kafka
# 2. Then: docker compose up -d --build
# =============================================================================
services:
backup-service:
build:
context: .
dockerfile: Dockerfile
container_name: rwa-backup-service
ports:
- "3002:3002"
environment:
# Application
- NODE_ENV=production
- APP_PORT=3002
- APP_ENV=production
# Database (shared PostgreSQL)
- DATABASE_URL=postgresql://rwa_user:rwa_secure_password@rwa-postgres:5432/rwa_backup?schema=public
# Service Authentication
- SERVICE_JWT_SECRET=${SERVICE_JWT_SECRET:-your-service-jwt-secret}
- ALLOWED_SERVICES=identity-service,recovery-service,mpc-service
# Backup Encryption
- BACKUP_ENCRYPTION_KEY=${BACKUP_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}
- BACKUP_ENCRYPTION_KEY_ID=${BACKUP_ENCRYPTION_KEY_ID:-key-v1}
# Rate Limits
- MAX_RETRIEVE_PER_DAY=3
- MAX_STORE_PER_MINUTE=10
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
networks:
- rwa-network
networks:
rwa-network:
external: true

View File

@ -2,7 +2,7 @@
CREATE TABLE "backup_shares" (
"share_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"account_sequence" BIGINT NOT NULL,
"account_sequence" VARCHAR(12) NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"party_index" INTEGER NOT NULL DEFAULT 2,
"threshold" INTEGER NOT NULL DEFAULT 2,

View File

@ -14,7 +14,7 @@ model BackupShare {
// 用户标识 (来自 identity-service)
userId BigInt @unique @map("user_id")
accountSequence String @unique @map("account_sequence") // 格式: D + YYMMDD + 5位序号
accountSequence String @unique @map("account_sequence") @db.VarChar(12) // 格式: D + YYMMDD + 5位序号
// MPC 密钥信息
publicKey String @unique @map("public_key") @db.VarChar(130)

View File

@ -5,7 +5,7 @@
-- AlterTable: monitored_addresses
-- Add new columns for system accounts and account_sequence
ALTER TABLE "monitored_addresses" ADD COLUMN "address_type" VARCHAR(20) NOT NULL DEFAULT 'USER';
ALTER TABLE "monitored_addresses" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "monitored_addresses" ADD COLUMN "account_sequence" VARCHAR(20);
ALTER TABLE "monitored_addresses" ADD COLUMN "system_account_type" VARCHAR(50);
ALTER TABLE "monitored_addresses" ADD COLUMN "system_account_id" BIGINT;
ALTER TABLE "monitored_addresses" ADD COLUMN "region_code" VARCHAR(10);
@ -16,7 +16,7 @@ ALTER TABLE "monitored_addresses" ALTER COLUMN "user_id" DROP NOT NULL;
-- AlterTable: deposit_transactions
-- Add new columns for system accounts and account_sequence
ALTER TABLE "deposit_transactions" ADD COLUMN "address_type" VARCHAR(20) NOT NULL DEFAULT 'USER';
ALTER TABLE "deposit_transactions" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "deposit_transactions" ADD COLUMN "account_sequence" VARCHAR(20);
ALTER TABLE "deposit_transactions" ADD COLUMN "system_account_type" VARCHAR(50);
ALTER TABLE "deposit_transactions" ADD COLUMN "system_account_id" BIGINT;
@ -26,7 +26,7 @@ ALTER TABLE "deposit_transactions" ALTER COLUMN "user_id" DROP NOT NULL;
-- CreateTable: recovery_mnemonics
CREATE TABLE "recovery_mnemonics" (
"id" BIGSERIAL NOT NULL,
"account_sequence" INTEGER NOT NULL,
"account_sequence" VARCHAR(20) NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"encrypted_mnemonic" TEXT NOT NULL,
"mnemonic_hash" VARCHAR(64) NOT NULL,

View File

@ -1,352 +1,352 @@
-- CreateTable
CREATE TABLE "user_accounts" (
"user_id" BIGSERIAL NOT NULL,
"account_sequence" BIGINT NOT NULL,
"phone_number" VARCHAR(20),
"nickname" VARCHAR(100) NOT NULL,
"avatar_url" TEXT,
"inviter_sequence" BIGINT,
"referral_code" VARCHAR(10) NOT NULL,
"kyc_status" VARCHAR(20) NOT NULL DEFAULT 'NOT_VERIFIED',
"real_name" VARCHAR(100),
"id_card_number" VARCHAR(20),
"id_card_front_url" VARCHAR(500),
"id_card_back_url" VARCHAR(500),
"kyc_verified_at" TIMESTAMP(3),
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"registered_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_login_at" TIMESTAMP(3),
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_accounts_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "user_devices" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"device_id" VARCHAR(100) NOT NULL,
"device_name" VARCHAR(100),
"device_info" JSONB,
"platform" VARCHAR(20),
"device_model" VARCHAR(100),
"os_version" VARCHAR(50),
"app_version" VARCHAR(20),
"screen_width" INTEGER,
"screen_height" INTEGER,
"locale" VARCHAR(10),
"timezone" VARCHAR(50),
"added_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_active_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "user_devices_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "wallet_addresses" (
"address_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"chain_type" VARCHAR(20) NOT NULL,
"address" VARCHAR(100) NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"address_digest" VARCHAR(66) NOT NULL,
"mpc_signature_r" VARCHAR(66) NOT NULL,
"mpc_signature_s" VARCHAR(66) NOT NULL,
"mpc_signature_v" INTEGER NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"bound_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "wallet_addresses_pkey" PRIMARY KEY ("address_id")
);
-- CreateTable
CREATE TABLE "account_sequence_generator" (
"id" INTEGER NOT NULL DEFAULT 1,
"current_sequence" BIGINT NOT NULL DEFAULT 0,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "account_sequence_generator_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_events" (
"event_id" BIGSERIAL NOT NULL,
"event_type" VARCHAR(50) NOT NULL,
"aggregate_id" VARCHAR(100) NOT NULL,
"aggregate_type" VARCHAR(50) NOT NULL,
"event_data" JSONB NOT NULL,
"user_id" BIGINT,
"occurred_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"version" INTEGER NOT NULL DEFAULT 1,
CONSTRAINT "user_events_pkey" PRIMARY KEY ("event_id")
);
-- CreateTable
CREATE TABLE "device_tokens" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"device_id" VARCHAR(100) NOT NULL,
"refresh_token_hash" VARCHAR(64) NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"revoked_at" TIMESTAMP(3),
CONSTRAINT "device_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "dead_letter_events" (
"id" BIGSERIAL NOT NULL,
"topic" VARCHAR(100) NOT NULL,
"event_id" VARCHAR(100) NOT NULL,
"event_type" VARCHAR(50) NOT NULL,
"aggregate_id" VARCHAR(100) NOT NULL,
"aggregate_type" VARCHAR(50) NOT NULL,
"payload" JSONB,
"error_message" TEXT NOT NULL,
"error_stack" TEXT,
"retry_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"processed_at" TIMESTAMP(3),
CONSTRAINT "dead_letter_events_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sms_codes" (
"id" BIGSERIAL NOT NULL,
"phone_number" VARCHAR(20) NOT NULL,
"code" VARCHAR(10) NOT NULL,
"purpose" VARCHAR(50) NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"used_at" TIMESTAMP(3),
CONSTRAINT "sms_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "mpc_key_shares" (
"share_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"party_index" INTEGER NOT NULL,
"threshold" INTEGER NOT NULL DEFAULT 2,
"total_parties" INTEGER NOT NULL DEFAULT 3,
"encrypted_share_data" TEXT NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"rotated_at" TIMESTAMP(3),
CONSTRAINT "mpc_key_shares_pkey" PRIMARY KEY ("share_id")
);
-- CreateTable
CREATE TABLE "mpc_sessions" (
"session_id" VARCHAR(50) NOT NULL,
"session_type" VARCHAR(20) NOT NULL,
"user_id" BIGINT,
"public_key" VARCHAR(130),
"status" VARCHAR(20) NOT NULL DEFAULT 'PENDING',
"error_message" TEXT,
"message_hash" VARCHAR(66),
"signature_r" VARCHAR(66),
"signature_s" VARCHAR(66),
"signature_v" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completed_at" TIMESTAMP(3),
CONSTRAINT "mpc_sessions_pkey" PRIMARY KEY ("session_id")
);
-- CreateTable
CREATE TABLE "referral_links" (
"link_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"referral_code" VARCHAR(10) NOT NULL,
"short_code" VARCHAR(10) NOT NULL,
"channel" VARCHAR(50),
"campaign_id" VARCHAR(50),
"click_count" INTEGER NOT NULL DEFAULT 0,
"register_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMP(3),
CONSTRAINT "referral_links_pkey" PRIMARY KEY ("link_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_account_sequence_key" ON "user_accounts"("account_sequence");
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_phone_number_key" ON "user_accounts"("phone_number");
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_referral_code_key" ON "user_accounts"("referral_code");
-- CreateIndex
CREATE INDEX "idx_phone" ON "user_accounts"("phone_number");
-- CreateIndex
CREATE INDEX "idx_sequence" ON "user_accounts"("account_sequence");
-- CreateIndex
CREATE INDEX "idx_referral_code" ON "user_accounts"("referral_code");
-- CreateIndex
CREATE INDEX "idx_inviter" ON "user_accounts"("inviter_sequence");
-- CreateIndex
CREATE INDEX "idx_kyc_status" ON "user_accounts"("kyc_status");
-- CreateIndex
CREATE INDEX "idx_status" ON "user_accounts"("status");
-- CreateIndex
CREATE INDEX "idx_device" ON "user_devices"("device_id");
-- CreateIndex
CREATE INDEX "idx_user" ON "user_devices"("user_id");
-- CreateIndex
CREATE INDEX "idx_last_active" ON "user_devices"("last_active_at");
-- CreateIndex
CREATE INDEX "idx_platform" ON "user_devices"("platform");
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_device" ON "user_devices"("user_id", "device_id");
-- CreateIndex
CREATE INDEX "idx_wallet_user" ON "wallet_addresses"("user_id");
-- CreateIndex
CREATE INDEX "idx_address" ON "wallet_addresses"("address");
-- CreateIndex
CREATE INDEX "idx_public_key" ON "wallet_addresses"("public_key");
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_chain" ON "wallet_addresses"("user_id", "chain_type");
-- CreateIndex
CREATE UNIQUE INDEX "uk_chain_address" ON "wallet_addresses"("chain_type", "address");
-- CreateIndex
CREATE INDEX "idx_aggregate" ON "user_events"("aggregate_type", "aggregate_id");
-- CreateIndex
CREATE INDEX "idx_event_type" ON "user_events"("event_type");
-- CreateIndex
CREATE INDEX "idx_event_user" ON "user_events"("user_id");
-- CreateIndex
CREATE INDEX "idx_occurred" ON "user_events"("occurred_at");
-- CreateIndex
CREATE UNIQUE INDEX "device_tokens_refresh_token_hash_key" ON "device_tokens"("refresh_token_hash");
-- CreateIndex
CREATE INDEX "idx_user_device_token" ON "device_tokens"("user_id", "device_id");
-- CreateIndex
CREATE INDEX "idx_expires" ON "device_tokens"("expires_at");
-- CreateIndex
CREATE INDEX "idx_topic" ON "dead_letter_events"("topic");
-- CreateIndex
CREATE INDEX "idx_dead_letter_event_type" ON "dead_letter_events"("event_type");
-- CreateIndex
CREATE INDEX "idx_dead_letter_created" ON "dead_letter_events"("created_at");
-- CreateIndex
CREATE INDEX "idx_processed" ON "dead_letter_events"("processed_at");
-- CreateIndex
CREATE INDEX "idx_phone_purpose" ON "sms_codes"("phone_number", "purpose");
-- CreateIndex
CREATE INDEX "idx_sms_expires" ON "sms_codes"("expires_at");
-- CreateIndex
CREATE UNIQUE INDEX "mpc_key_shares_user_id_key" ON "mpc_key_shares"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "mpc_key_shares_public_key_key" ON "mpc_key_shares"("public_key");
-- CreateIndex
CREATE INDEX "idx_mpc_public_key" ON "mpc_key_shares"("public_key");
-- CreateIndex
CREATE INDEX "idx_mpc_status" ON "mpc_key_shares"("status");
-- CreateIndex
CREATE INDEX "idx_session_type" ON "mpc_sessions"("session_type");
-- CreateIndex
CREATE INDEX "idx_session_user" ON "mpc_sessions"("user_id");
-- CreateIndex
CREATE INDEX "idx_session_status" ON "mpc_sessions"("status");
-- CreateIndex
CREATE INDEX "idx_session_created" ON "mpc_sessions"("created_at");
-- CreateTable
CREATE TABLE "recovery_mnemonics" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"encrypted_mnemonic" TEXT NOT NULL,
"mnemonic_hash" VARCHAR(64) NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"is_backed_up" BOOLEAN NOT NULL DEFAULT false,
"revoked_at" TIMESTAMP(3),
"revoked_reason" VARCHAR(200),
"replaced_by_id" BIGINT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "recovery_mnemonics_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_active_mnemonic" ON "recovery_mnemonics"("user_id", "status");
-- CreateIndex
CREATE INDEX "idx_recovery_user" ON "recovery_mnemonics"("user_id");
-- CreateIndex
CREATE INDEX "idx_recovery_public_key" ON "recovery_mnemonics"("public_key");
-- CreateIndex
CREATE INDEX "idx_recovery_status" ON "recovery_mnemonics"("status");
-- CreateIndex
CREATE UNIQUE INDEX "referral_links_short_code_key" ON "referral_links"("short_code");
-- CreateIndex
CREATE INDEX "idx_referral_link_user" ON "referral_links"("user_id");
-- CreateIndex
CREATE INDEX "idx_referral_link_code" ON "referral_links"("referral_code");
-- CreateIndex
CREATE INDEX "idx_referral_link_channel" ON "referral_links"("channel");
-- CreateIndex
CREATE INDEX "idx_referral_link_created" ON "referral_links"("created_at");
-- AddForeignKey
ALTER TABLE "user_devices" ADD CONSTRAINT "user_devices_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "wallet_addresses" ADD CONSTRAINT "wallet_addresses_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Initialize sequence generator
INSERT INTO "account_sequence_generator" ("id", "current_sequence", "updated_at")
VALUES (1, 0, CURRENT_TIMESTAMP)
ON CONFLICT ("id") DO NOTHING;
-- CreateTable
CREATE TABLE "user_accounts" (
"user_id" BIGSERIAL NOT NULL,
"account_sequence" VARCHAR(12) NOT NULL,
"phone_number" VARCHAR(20),
"nickname" VARCHAR(100) NOT NULL,
"avatar_url" TEXT,
"inviter_sequence" VARCHAR(12),
"referral_code" VARCHAR(10) NOT NULL,
"kyc_status" VARCHAR(20) NOT NULL DEFAULT 'NOT_VERIFIED',
"real_name" VARCHAR(100),
"id_card_number" VARCHAR(20),
"id_card_front_url" VARCHAR(500),
"id_card_back_url" VARCHAR(500),
"kyc_verified_at" TIMESTAMP(3),
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"registered_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_login_at" TIMESTAMP(3),
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "user_accounts_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "user_devices" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"device_id" VARCHAR(100) NOT NULL,
"device_name" VARCHAR(100),
"device_info" JSONB,
"platform" VARCHAR(20),
"device_model" VARCHAR(100),
"os_version" VARCHAR(50),
"app_version" VARCHAR(20),
"screen_width" INTEGER,
"screen_height" INTEGER,
"locale" VARCHAR(10),
"timezone" VARCHAR(50),
"added_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_active_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "user_devices_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "wallet_addresses" (
"address_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"chain_type" VARCHAR(20) NOT NULL,
"address" VARCHAR(100) NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"address_digest" VARCHAR(66) NOT NULL,
"mpc_signature_r" VARCHAR(66) NOT NULL,
"mpc_signature_s" VARCHAR(66) NOT NULL,
"mpc_signature_v" INTEGER NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"bound_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "wallet_addresses_pkey" PRIMARY KEY ("address_id")
);
-- CreateTable
CREATE TABLE "account_sequence_generator" (
"id" SERIAL NOT NULL,
"date_key" VARCHAR(6) NOT NULL,
"current_sequence" INTEGER NOT NULL DEFAULT 0,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "account_sequence_generator_pkey" PRIMARY KEY ("id")
);
-- CreateIndex: account_sequence_generator unique constraint
CREATE UNIQUE INDEX "account_sequence_generator_date_key_key" ON "account_sequence_generator"("date_key");
-- CreateTable
CREATE TABLE "user_events" (
"event_id" BIGSERIAL NOT NULL,
"event_type" VARCHAR(50) NOT NULL,
"aggregate_id" VARCHAR(100) NOT NULL,
"aggregate_type" VARCHAR(50) NOT NULL,
"event_data" JSONB NOT NULL,
"user_id" BIGINT,
"occurred_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"version" INTEGER NOT NULL DEFAULT 1,
CONSTRAINT "user_events_pkey" PRIMARY KEY ("event_id")
);
-- CreateTable
CREATE TABLE "device_tokens" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"device_id" VARCHAR(100) NOT NULL,
"refresh_token_hash" VARCHAR(64) NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"revoked_at" TIMESTAMP(3),
CONSTRAINT "device_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "dead_letter_events" (
"id" BIGSERIAL NOT NULL,
"topic" VARCHAR(100) NOT NULL,
"event_id" VARCHAR(100) NOT NULL,
"event_type" VARCHAR(50) NOT NULL,
"aggregate_id" VARCHAR(100) NOT NULL,
"aggregate_type" VARCHAR(50) NOT NULL,
"payload" JSONB,
"error_message" TEXT NOT NULL,
"error_stack" TEXT,
"retry_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"processed_at" TIMESTAMP(3),
CONSTRAINT "dead_letter_events_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sms_codes" (
"id" BIGSERIAL NOT NULL,
"phone_number" VARCHAR(20) NOT NULL,
"code" VARCHAR(10) NOT NULL,
"purpose" VARCHAR(50) NOT NULL,
"expires_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"used_at" TIMESTAMP(3),
CONSTRAINT "sms_codes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "mpc_key_shares" (
"share_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"party_index" INTEGER NOT NULL,
"threshold" INTEGER NOT NULL DEFAULT 2,
"total_parties" INTEGER NOT NULL DEFAULT 3,
"encrypted_share_data" TEXT NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"rotated_at" TIMESTAMP(3),
CONSTRAINT "mpc_key_shares_pkey" PRIMARY KEY ("share_id")
);
-- CreateTable
CREATE TABLE "mpc_sessions" (
"session_id" VARCHAR(50) NOT NULL,
"session_type" VARCHAR(20) NOT NULL,
"user_id" BIGINT,
"public_key" VARCHAR(130),
"status" VARCHAR(20) NOT NULL DEFAULT 'PENDING',
"error_message" TEXT,
"message_hash" VARCHAR(66),
"signature_r" VARCHAR(66),
"signature_s" VARCHAR(66),
"signature_v" INTEGER,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completed_at" TIMESTAMP(3),
CONSTRAINT "mpc_sessions_pkey" PRIMARY KEY ("session_id")
);
-- CreateTable
CREATE TABLE "referral_links" (
"link_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"referral_code" VARCHAR(10) NOT NULL,
"short_code" VARCHAR(10) NOT NULL,
"channel" VARCHAR(50),
"campaign_id" VARCHAR(50),
"click_count" INTEGER NOT NULL DEFAULT 0,
"register_count" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMP(3),
CONSTRAINT "referral_links_pkey" PRIMARY KEY ("link_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_account_sequence_key" ON "user_accounts"("account_sequence");
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_phone_number_key" ON "user_accounts"("phone_number");
-- CreateIndex
CREATE UNIQUE INDEX "user_accounts_referral_code_key" ON "user_accounts"("referral_code");
-- CreateIndex
CREATE INDEX "idx_phone" ON "user_accounts"("phone_number");
-- CreateIndex
CREATE INDEX "idx_sequence" ON "user_accounts"("account_sequence");
-- CreateIndex
CREATE INDEX "idx_referral_code" ON "user_accounts"("referral_code");
-- CreateIndex
CREATE INDEX "idx_inviter" ON "user_accounts"("inviter_sequence");
-- CreateIndex
CREATE INDEX "idx_kyc_status" ON "user_accounts"("kyc_status");
-- CreateIndex
CREATE INDEX "idx_status" ON "user_accounts"("status");
-- CreateIndex
CREATE INDEX "idx_device" ON "user_devices"("device_id");
-- CreateIndex
CREATE INDEX "idx_user" ON "user_devices"("user_id");
-- CreateIndex
CREATE INDEX "idx_last_active" ON "user_devices"("last_active_at");
-- CreateIndex
CREATE INDEX "idx_platform" ON "user_devices"("platform");
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_device" ON "user_devices"("user_id", "device_id");
-- CreateIndex
CREATE INDEX "idx_wallet_user" ON "wallet_addresses"("user_id");
-- CreateIndex
CREATE INDEX "idx_address" ON "wallet_addresses"("address");
-- CreateIndex
CREATE INDEX "idx_public_key" ON "wallet_addresses"("public_key");
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_chain" ON "wallet_addresses"("user_id", "chain_type");
-- CreateIndex
CREATE UNIQUE INDEX "uk_chain_address" ON "wallet_addresses"("chain_type", "address");
-- CreateIndex
CREATE INDEX "idx_aggregate" ON "user_events"("aggregate_type", "aggregate_id");
-- CreateIndex
CREATE INDEX "idx_event_type" ON "user_events"("event_type");
-- CreateIndex
CREATE INDEX "idx_event_user" ON "user_events"("user_id");
-- CreateIndex
CREATE INDEX "idx_occurred" ON "user_events"("occurred_at");
-- CreateIndex
CREATE UNIQUE INDEX "device_tokens_refresh_token_hash_key" ON "device_tokens"("refresh_token_hash");
-- CreateIndex
CREATE INDEX "idx_user_device_token" ON "device_tokens"("user_id", "device_id");
-- CreateIndex
CREATE INDEX "idx_expires" ON "device_tokens"("expires_at");
-- CreateIndex
CREATE INDEX "idx_topic" ON "dead_letter_events"("topic");
-- CreateIndex
CREATE INDEX "idx_dead_letter_event_type" ON "dead_letter_events"("event_type");
-- CreateIndex
CREATE INDEX "idx_dead_letter_created" ON "dead_letter_events"("created_at");
-- CreateIndex
CREATE INDEX "idx_processed" ON "dead_letter_events"("processed_at");
-- CreateIndex
CREATE INDEX "idx_phone_purpose" ON "sms_codes"("phone_number", "purpose");
-- CreateIndex
CREATE INDEX "idx_sms_expires" ON "sms_codes"("expires_at");
-- CreateIndex
CREATE UNIQUE INDEX "mpc_key_shares_user_id_key" ON "mpc_key_shares"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "mpc_key_shares_public_key_key" ON "mpc_key_shares"("public_key");
-- CreateIndex
CREATE INDEX "idx_mpc_public_key" ON "mpc_key_shares"("public_key");
-- CreateIndex
CREATE INDEX "idx_mpc_status" ON "mpc_key_shares"("status");
-- CreateIndex
CREATE INDEX "idx_session_type" ON "mpc_sessions"("session_type");
-- CreateIndex
CREATE INDEX "idx_session_user" ON "mpc_sessions"("user_id");
-- CreateIndex
CREATE INDEX "idx_session_status" ON "mpc_sessions"("status");
-- CreateIndex
CREATE INDEX "idx_session_created" ON "mpc_sessions"("created_at");
-- CreateTable
CREATE TABLE "recovery_mnemonics" (
"id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"public_key" VARCHAR(130) NOT NULL,
"encrypted_mnemonic" TEXT NOT NULL,
"mnemonic_hash" VARCHAR(64) NOT NULL,
"status" VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
"is_backed_up" BOOLEAN NOT NULL DEFAULT false,
"revoked_at" TIMESTAMP(3),
"revoked_reason" VARCHAR(200),
"replaced_by_id" BIGINT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "recovery_mnemonics_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "uk_user_active_mnemonic" ON "recovery_mnemonics"("user_id", "status");
-- CreateIndex
CREATE INDEX "idx_recovery_user" ON "recovery_mnemonics"("user_id");
-- CreateIndex
CREATE INDEX "idx_recovery_public_key" ON "recovery_mnemonics"("public_key");
-- CreateIndex
CREATE INDEX "idx_recovery_status" ON "recovery_mnemonics"("status");
-- CreateIndex
CREATE UNIQUE INDEX "referral_links_short_code_key" ON "referral_links"("short_code");
-- CreateIndex
CREATE INDEX "idx_referral_link_user" ON "referral_links"("user_id");
-- CreateIndex
CREATE INDEX "idx_referral_link_code" ON "referral_links"("referral_code");
-- CreateIndex
CREATE INDEX "idx_referral_link_channel" ON "referral_links"("channel");
-- CreateIndex
CREATE INDEX "idx_referral_link_created" ON "referral_links"("created_at");
-- AddForeignKey
ALTER TABLE "user_devices" ADD CONSTRAINT "user_devices_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "wallet_addresses" ADD CONSTRAINT "wallet_addresses_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -46,6 +46,10 @@ services:
MPC_COORDINATOR_TIMEOUT: 30000
# Share Encryption
SHARE_MASTER_KEY: ${SHARE_MASTER_KEY:-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}
# Backup Service
BACKUP_SERVICE_URL: ${BACKUP_SERVICE_URL:-http://rwa-backup-service:3002}
BACKUP_SERVICE_ENABLED: ${BACKUP_SERVICE_ENABLED:-true}
SERVICE_JWT_SECRET: ${SERVICE_JWT_SECRET:-your-service-jwt-secret-change-in-production}
# Timeouts
MPC_KEYGEN_TIMEOUT: 300000
MPC_SIGNING_TIMEOUT: 180000

View File

@ -2,7 +2,7 @@
CREATE TABLE "referral_relationships" (
"relationship_id" BIGSERIAL NOT NULL,
"user_id" BIGINT NOT NULL,
"account_sequence" INTEGER NOT NULL,
"account_sequence" VARCHAR(12) NOT NULL,
"referrer_id" BIGINT,
"root_user_id" BIGINT,
"my_referral_code" VARCHAR(20) NOT NULL,
@ -48,7 +48,7 @@ CREATE TABLE "direct_referrals" (
"direct_referral_id" BIGSERIAL NOT NULL,
"referrer_id" BIGINT NOT NULL,
"referral_id" BIGINT NOT NULL,
"referral_sequence" BIGINT NOT NULL,
"referral_sequence" VARCHAR(12) NOT NULL,
"referral_nickname" VARCHAR(100),
"referral_avatar" VARCHAR(255),
"personal_planting_count" INTEGER NOT NULL DEFAULT 0,

View File

@ -64,15 +64,17 @@ export class UserRegisteredHandler implements OnModuleInit {
`Processing ${event.eventType} event: accountSequence=${payload.accountSequence}, inviterSequence=${payload.inviterSequence}`,
);
// 使用 accountSequence 作为 userId因为 identity-service 的 userId 是内部自增ID
// 在事件发布时可能还是临时值 0而 accountSequence 是全局唯一的业务标识
// 注意userId 仍然需要是 bigint这里我们需要从 accountSequence 字符串中提取数值部分或使用其他方式
// 暂时保持原有逻辑,但 accountSequence 本身现在是字符串类型
// 从 accountSequence 提取数值部分作为 userId
// accountSequence 格式: D + YYMMDD + 5位序号 (例如: D25121200000)
// 去掉 "D" 前缀后得到 11 位数字,作为全局唯一的 userId
// 这样可以避免依赖 identity-service 的临时 userId (可能是 0)
const userIdFromSequence = BigInt(payload.accountSequence.substring(1)); // 去掉 "D" 前缀
const command = new CreateReferralRelationshipCommand(
BigInt(payload.userId), // 使用 userId
payload.accountSequence, // 现在是字符串格式
userIdFromSequence, // 使用从 accountSequence 提取的数值作为 userId
payload.accountSequence, // 完整的 accountSequence 字符串
null, // referrerCode - 不通过推荐码查找
payload.inviterSequence, // 通过 accountSequence 查找推荐人,现在是字符串格式
payload.inviterSequence, // 通过 accountSequence 查找推荐人
);
const result = await this.referralService.createReferralRelationship(command);

View File

@ -1,19 +1,19 @@
-- Add account_sequence column to reward_ledger_entries
ALTER TABLE "reward_ledger_entries" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "reward_ledger_entries" ADD COLUMN "account_sequence" VARCHAR(20);
-- Add indexes for account_sequence on reward_ledger_entries
CREATE INDEX "idx_account_status" ON "reward_ledger_entries"("account_sequence", "reward_status");
CREATE INDEX "idx_account_created" ON "reward_ledger_entries"("account_sequence", "created_at" DESC);
-- Add account_sequence column to reward_summaries
ALTER TABLE "reward_summaries" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "reward_summaries" ADD COLUMN "account_sequence" VARCHAR(20);
-- Add unique constraint and index for account_sequence on reward_summaries
CREATE UNIQUE INDEX "reward_summaries_account_sequence_key" ON "reward_summaries"("account_sequence");
CREATE INDEX "idx_summary_account" ON "reward_summaries"("account_sequence");
-- Add account_sequence column to settlement_records
ALTER TABLE "settlement_records" ADD COLUMN "account_sequence" BIGINT;
ALTER TABLE "settlement_records" ADD COLUMN "account_sequence" VARCHAR(20);
-- Add index for account_sequence on settlement_records
CREATE INDEX "idx_settlement_account" ON "settlement_records"("account_sequence");