From e2055483db6002893e4e07f2a14c04713baf9771 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 24 Nov 2025 07:25:33 +0000 Subject: [PATCH] . --- .../identity-service/.env.development | 2 +- .../services/identity-service/.env.example | 2 +- .../services/identity-service/.env.production | 2 +- .../identity-service/database/init.sql | 10 +- .../identity-service/docker-compose.yml | 22 ++- .../identity-service/prisma/schema.prisma | 2 +- .../identity-service/src/api/dto/index.ts | 185 +----------------- .../dto/request/auto-create-account.dto.ts | 30 +++ .../src/api/dto/request/bind-phone.dto.ts | 14 ++ .../src/api/dto/request/index.ts | 6 +- .../dto/request/recover-by-mnemonic.dto.ts | 23 +++ .../api/dto/request/recover-by-phone.dto.ts | 28 +++ .../src/api/dto/request/submit-kyc.dto.ts | 24 +++ .../src/api/dto/response/device.dto.ts | 18 ++ .../src/api/dto/response/index.ts | 3 +- .../src/api/dto/response/user-profile.dto.ts | 64 ++++++ 16 files changed, 234 insertions(+), 201 deletions(-) create mode 100644 backend/services/identity-service/src/api/dto/request/auto-create-account.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/request/bind-phone.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/request/recover-by-phone.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/request/submit-kyc.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/response/device.dto.ts create mode 100644 backend/services/identity-service/src/api/dto/response/user-profile.dto.ts diff --git a/backend/services/identity-service/.env.development b/backend/services/identity-service/.env.development index 2e3d5b0c..e00b0d6b 100644 --- a/backend/services/identity-service/.env.development +++ b/backend/services/identity-service/.env.development @@ -1,5 +1,5 @@ # Database -DATABASE_URL="mysql://root:password@localhost:3306/rwa_identity" +DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public" # JWT JWT_SECRET="dev-jwt-secret-key" diff --git a/backend/services/identity-service/.env.example b/backend/services/identity-service/.env.example index 68fc9998..cc9c2aa5 100644 --- a/backend/services/identity-service/.env.example +++ b/backend/services/identity-service/.env.example @@ -1,5 +1,5 @@ # Database -DATABASE_URL="mysql://root:password@localhost:3306/rwa_identity" +DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public" # JWT JWT_SECRET="your-super-secret-jwt-key-change-in-production" diff --git a/backend/services/identity-service/.env.production b/backend/services/identity-service/.env.production index f5e2c7c9..170f9bcb 100644 --- a/backend/services/identity-service/.env.production +++ b/backend/services/identity-service/.env.production @@ -1,5 +1,5 @@ # Database -DATABASE_URL="mysql://user:password@production-db:3306/rwa_identity" +DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:5432/${DB_NAME}?schema=public" # JWT JWT_SECRET="${JWT_SECRET}" diff --git a/backend/services/identity-service/database/init.sql b/backend/services/identity-service/database/init.sql index d860e112..f713b066 100644 --- a/backend/services/identity-service/database/init.sql +++ b/backend/services/identity-service/database/init.sql @@ -1,13 +1,7 @@ -- ============================================ --- Identity Context 数据库初始化 +-- Identity Context 数据库初始化 (PostgreSQL) -- ============================================ -CREATE DATABASE IF NOT EXISTS rwa_identity -DEFAULT CHARACTER SET utf8mb4 -COLLATE utf8mb4_unicode_ci; - -USE rwa_identity; - -- 初始化账户序列号生成器 INSERT INTO account_sequence_generator (id, current_sequence) VALUES (1, 0) -ON DUPLICATE KEY UPDATE id = id; +ON CONFLICT (id) DO NOTHING; diff --git a/backend/services/identity-service/docker-compose.yml b/backend/services/identity-service/docker-compose.yml index c21c34b5..6e8f77a9 100644 --- a/backend/services/identity-service/docker-compose.yml +++ b/backend/services/identity-service/docker-compose.yml @@ -6,7 +6,7 @@ services: ports: - "3000:3000" environment: - - DATABASE_URL=mysql://root:password@mysql:3306/rwa_identity + - DATABASE_URL=postgresql://postgres:password@postgres:5432/rwa_identity?schema=public - JWT_SECRET=your-super-secret-jwt-key-change-in-production - JWT_ACCESS_EXPIRES_IN=2h - JWT_REFRESH_EXPIRES_IN=30d @@ -16,24 +16,26 @@ services: - APP_PORT=3000 - APP_ENV=production depends_on: - mysql: + postgres: condition: service_healthy redis: condition: service_started kafka: condition: service_started - mysql: - image: mysql:8.0 + postgres: + image: postgres:16-alpine environment: - - MYSQL_ROOT_PASSWORD=password - - MYSQL_DATABASE=rwa_identity + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + - POSTGRES_DB=rwa_identity ports: - - "3306:3306" + - "5432:5432" volumes: - - mysql_data:/var/lib/mysql + - postgres_data:/var/lib/postgresql/data healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s timeout: 5s retries: 10 @@ -63,5 +65,5 @@ services: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 volumes: - mysql_data: + postgres_data: redis_data: diff --git a/backend/services/identity-service/prisma/schema.prisma b/backend/services/identity-service/prisma/schema.prisma index 4ae3fb27..e15bef5e 100644 --- a/backend/services/identity-service/prisma/schema.prisma +++ b/backend/services/identity-service/prisma/schema.prisma @@ -3,7 +3,7 @@ generator client { } datasource db { - provider = "mysql" + provider = "postgresql" url = env("DATABASE_URL") } diff --git a/backend/services/identity-service/src/api/dto/index.ts b/backend/services/identity-service/src/api/dto/index.ts index a301ced0..69d8003c 100644 --- a/backend/services/identity-service/src/api/dto/index.ts +++ b/backend/services/identity-service/src/api/dto/index.ts @@ -1,83 +1,13 @@ +// Request DTOs +export * from './request'; + +// Response DTOs +export * from './response'; + +// 其他通用DTOs import { IsString, IsOptional, IsNotEmpty, Matches, IsEnum, IsNumber } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -// ============ Request DTOs ============ - -export class AutoCreateAccountDto { - @ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' }) - @IsString() - @IsNotEmpty() - deviceId: string; - - @ApiPropertyOptional({ example: 'iPhone 15 Pro' }) - @IsOptional() - @IsString() - deviceName?: string; - - @ApiPropertyOptional({ example: 'ABC123' }) - @IsOptional() - @IsString() - @Matches(/^[A-Z0-9]{6}$/, { message: '推荐码格式错误' }) - inviterReferralCode?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - provinceCode?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - cityCode?: string; -} - -export class RecoverByMnemonicDto { - @ApiProperty({ example: 10001 }) - @IsNumber() - accountSequence: number; - - @ApiProperty({ example: 'abandon ability able about above absent absorb abstract absurd abuse access accident' }) - @IsString() - @IsNotEmpty() - mnemonic: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - newDeviceId: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - deviceName?: string; -} - -export class RecoverByPhoneDto { - @ApiProperty({ example: 10001 }) - @IsNumber() - accountSequence: number; - - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - newDeviceId: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - deviceName?: string; -} - export class AutoLoginDto { @ApiProperty() @IsString() @@ -155,18 +85,6 @@ export class LoginDto { deviceId: string; } -export class BindPhoneDto { - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; -} - export class UpdateProfileDto { @ApiPropertyOptional() @IsOptional() @@ -195,28 +113,6 @@ export class BindWalletDto { address: string; } -export class SubmitKYCDto { - @ApiProperty() - @IsString() - @IsNotEmpty() - realName: string; - - @ApiProperty() - @IsString() - @Matches(/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/, { message: '身份证号格式错误' }) - idCardNumber: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - idCardFrontUrl: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - idCardBackUrl: string; -} - export class RemoveDeviceDto { @ApiProperty() @IsString() @@ -224,8 +120,7 @@ export class RemoveDeviceDto { deviceId: string; } -// ============ Response DTOs ============ - +// Response DTOs export class AutoCreateAccountResponseDto { @ApiProperty() userId: string; @@ -285,67 +180,3 @@ export class LoginResponseDto { @ApiProperty() refreshToken: string; } - -export class UserProfileResponseDto { - @ApiProperty() - userId: string; - - @ApiProperty() - accountSequence: number; - - @ApiProperty({ nullable: true }) - phoneNumber: string | null; - - @ApiProperty() - nickname: string; - - @ApiProperty({ nullable: true }) - avatarUrl: string | null; - - @ApiProperty() - referralCode: string; - - @ApiProperty() - province: string; - - @ApiProperty() - city: string; - - @ApiProperty({ nullable: true }) - address: string | null; - - @ApiProperty() - walletAddresses: Array<{ chainType: string; address: string }>; - - @ApiProperty() - kycStatus: string; - - @ApiProperty({ nullable: true }) - kycInfo: { realName: string; idCardNumber: string } | null; - - @ApiProperty() - status: string; - - @ApiProperty() - registeredAt: Date; - - @ApiProperty({ nullable: true }) - lastLoginAt: Date | null; -} - -export class DeviceResponseDto { - @ApiProperty() - deviceId: string; - - @ApiProperty() - deviceName: string; - - @ApiProperty() - addedAt: Date; - - @ApiProperty() - lastActiveAt: Date; - - @ApiProperty() - isCurrent: boolean; -} diff --git a/backend/services/identity-service/src/api/dto/request/auto-create-account.dto.ts b/backend/services/identity-service/src/api/dto/request/auto-create-account.dto.ts new file mode 100644 index 00000000..82ca0f31 --- /dev/null +++ b/backend/services/identity-service/src/api/dto/request/auto-create-account.dto.ts @@ -0,0 +1,30 @@ +import { IsString, IsOptional, IsNotEmpty, Matches } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class AutoCreateAccountDto { + @ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' }) + @IsString() + @IsNotEmpty() + deviceId: string; + + @ApiPropertyOptional({ example: 'iPhone 15 Pro' }) + @IsOptional() + @IsString() + deviceName?: string; + + @ApiPropertyOptional({ example: 'ABC123' }) + @IsOptional() + @IsString() + @Matches(/^[A-Z0-9]{6}$/, { message: '推荐码格式错误' }) + inviterReferralCode?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + provinceCode?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + cityCode?: string; +} diff --git a/backend/services/identity-service/src/api/dto/request/bind-phone.dto.ts b/backend/services/identity-service/src/api/dto/request/bind-phone.dto.ts new file mode 100644 index 00000000..58a5b5c7 --- /dev/null +++ b/backend/services/identity-service/src/api/dto/request/bind-phone.dto.ts @@ -0,0 +1,14 @@ +import { IsString, Matches } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class BindPhoneDto { + @ApiProperty({ example: '13800138000' }) + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) + phoneNumber: string; + + @ApiProperty({ example: '123456' }) + @IsString() + @Matches(/^\d{6}$/, { message: '验证码格式错误' }) + smsCode: string; +} diff --git a/backend/services/identity-service/src/api/dto/request/index.ts b/backend/services/identity-service/src/api/dto/request/index.ts index 1eb8c864..16fffe6a 100644 --- a/backend/services/identity-service/src/api/dto/request/index.ts +++ b/backend/services/identity-service/src/api/dto/request/index.ts @@ -1 +1,5 @@ -export * from '../index'; +export * from './auto-create-account.dto'; +export * from './recover-by-mnemonic.dto'; +export * from './recover-by-phone.dto'; +export * from './bind-phone.dto'; +export * from './submit-kyc.dto'; diff --git a/backend/services/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts b/backend/services/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts new file mode 100644 index 00000000..b11209a3 --- /dev/null +++ b/backend/services/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts @@ -0,0 +1,23 @@ +import { IsString, IsOptional, IsNotEmpty, IsNumber } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class RecoverByMnemonicDto { + @ApiProperty({ example: 10001 }) + @IsNumber() + accountSequence: number; + + @ApiProperty({ example: 'abandon ability able about above absent absorb abstract absurd abuse access accident' }) + @IsString() + @IsNotEmpty() + mnemonic: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + newDeviceId: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + deviceName?: string; +} diff --git a/backend/services/identity-service/src/api/dto/request/recover-by-phone.dto.ts b/backend/services/identity-service/src/api/dto/request/recover-by-phone.dto.ts new file mode 100644 index 00000000..1a06f423 --- /dev/null +++ b/backend/services/identity-service/src/api/dto/request/recover-by-phone.dto.ts @@ -0,0 +1,28 @@ +import { IsString, IsOptional, IsNotEmpty, IsNumber, Matches } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class RecoverByPhoneDto { + @ApiProperty({ example: 10001 }) + @IsNumber() + accountSequence: number; + + @ApiProperty({ example: '13800138000' }) + @IsString() + @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) + phoneNumber: string; + + @ApiProperty({ example: '123456' }) + @IsString() + @Matches(/^\d{6}$/, { message: '验证码格式错误' }) + smsCode: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + newDeviceId: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + deviceName?: string; +} diff --git a/backend/services/identity-service/src/api/dto/request/submit-kyc.dto.ts b/backend/services/identity-service/src/api/dto/request/submit-kyc.dto.ts new file mode 100644 index 00000000..343025f4 --- /dev/null +++ b/backend/services/identity-service/src/api/dto/request/submit-kyc.dto.ts @@ -0,0 +1,24 @@ +import { IsString, IsNotEmpty, Matches } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +export class SubmitKycDto { + @ApiProperty({ example: '张三' }) + @IsString() + @IsNotEmpty() + realName: string; + + @ApiProperty({ example: '110101199001011234' }) + @IsString() + @Matches(/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/, { message: '身份证号格式错误' }) + idCardNumber: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + idCardFrontUrl: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + idCardBackUrl: string; +} diff --git a/backend/services/identity-service/src/api/dto/response/device.dto.ts b/backend/services/identity-service/src/api/dto/response/device.dto.ts new file mode 100644 index 00000000..8d2fbdfe --- /dev/null +++ b/backend/services/identity-service/src/api/dto/response/device.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DeviceDto { + @ApiProperty() + deviceId: string; + + @ApiProperty() + deviceName: string; + + @ApiProperty() + addedAt: Date; + + @ApiProperty() + lastActiveAt: Date; + + @ApiProperty() + isCurrent: boolean; +} diff --git a/backend/services/identity-service/src/api/dto/response/index.ts b/backend/services/identity-service/src/api/dto/response/index.ts index 1eb8c864..1e8628d1 100644 --- a/backend/services/identity-service/src/api/dto/response/index.ts +++ b/backend/services/identity-service/src/api/dto/response/index.ts @@ -1 +1,2 @@ -export * from '../index'; +export * from './user-profile.dto'; +export * from './device.dto'; diff --git a/backend/services/identity-service/src/api/dto/response/user-profile.dto.ts b/backend/services/identity-service/src/api/dto/response/user-profile.dto.ts new file mode 100644 index 00000000..ab311e3a --- /dev/null +++ b/backend/services/identity-service/src/api/dto/response/user-profile.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class WalletAddressDto { + @ApiProperty() + chainType: string; + + @ApiProperty() + address: string; +} + +export class KycInfoDto { + @ApiProperty() + realName: string; + + @ApiProperty() + idCardNumber: string; +} + +export class UserProfileDto { + @ApiProperty() + userId: string; + + @ApiProperty() + accountSequence: number; + + @ApiProperty({ nullable: true }) + phoneNumber: string | null; + + @ApiProperty() + nickname: string; + + @ApiProperty({ nullable: true }) + avatarUrl: string | null; + + @ApiProperty() + referralCode: string; + + @ApiProperty() + province: string; + + @ApiProperty() + city: string; + + @ApiProperty({ nullable: true }) + address: string | null; + + @ApiProperty({ type: [WalletAddressDto] }) + walletAddresses: WalletAddressDto[]; + + @ApiProperty() + kycStatus: string; + + @ApiProperty({ type: KycInfoDto, nullable: true }) + kycInfo: KycInfoDto | null; + + @ApiProperty() + status: string; + + @ApiProperty() + registeredAt: Date; + + @ApiProperty({ nullable: true }) + lastLoginAt: Date | null; +}