This commit is contained in:
hailin 2025-11-24 07:25:33 +00:00
parent 44d2e2ad80
commit e2055483db
16 changed files with 234 additions and 201 deletions

View File

@ -1,5 +1,5 @@
# Database # Database
DATABASE_URL="mysql://root:password@localhost:3306/rwa_identity" DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public"
# JWT # JWT
JWT_SECRET="dev-jwt-secret-key" JWT_SECRET="dev-jwt-secret-key"

View File

@ -1,5 +1,5 @@
# Database # Database
DATABASE_URL="mysql://root:password@localhost:3306/rwa_identity" DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public"
# JWT # JWT
JWT_SECRET="your-super-secret-jwt-key-change-in-production" JWT_SECRET="your-super-secret-jwt-key-change-in-production"

View File

@ -1,5 +1,5 @@
# Database # 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
JWT_SECRET="${JWT_SECRET}" JWT_SECRET="${JWT_SECRET}"

View File

@ -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) INSERT INTO account_sequence_generator (id, current_sequence) VALUES (1, 0)
ON DUPLICATE KEY UPDATE id = id; ON CONFLICT (id) DO NOTHING;

View File

@ -6,7 +6,7 @@ services:
ports: ports:
- "3000:3000" - "3000:3000"
environment: 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_SECRET=your-super-secret-jwt-key-change-in-production
- JWT_ACCESS_EXPIRES_IN=2h - JWT_ACCESS_EXPIRES_IN=2h
- JWT_REFRESH_EXPIRES_IN=30d - JWT_REFRESH_EXPIRES_IN=30d
@ -16,24 +16,26 @@ services:
- APP_PORT=3000 - APP_PORT=3000
- APP_ENV=production - APP_ENV=production
depends_on: depends_on:
mysql: postgres:
condition: service_healthy condition: service_healthy
redis: redis:
condition: service_started condition: service_started
kafka: kafka:
condition: service_started condition: service_started
mysql: postgres:
image: mysql:8.0 image: postgres:16-alpine
environment: environment:
- MYSQL_ROOT_PASSWORD=password - POSTGRES_USER=postgres
- MYSQL_DATABASE=rwa_identity - POSTGRES_PASSWORD=password
- POSTGRES_DB=rwa_identity
ports: ports:
- "3306:3306" - "5432:5432"
volumes: volumes:
- mysql_data:/var/lib/mysql - postgres_data:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s timeout: 5s
retries: 10 retries: 10
@ -63,5 +65,5 @@ services:
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
volumes: volumes:
mysql_data: postgres_data:
redis_data: redis_data:

View File

@ -3,7 +3,7 @@ generator client {
} }
datasource db { datasource db {
provider = "mysql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }

View File

@ -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 { IsString, IsOptional, IsNotEmpty, Matches, IsEnum, IsNumber } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; 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 { export class AutoLoginDto {
@ApiProperty() @ApiProperty()
@IsString() @IsString()
@ -155,18 +85,6 @@ export class LoginDto {
deviceId: string; 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 { export class UpdateProfileDto {
@ApiPropertyOptional() @ApiPropertyOptional()
@IsOptional() @IsOptional()
@ -195,28 +113,6 @@ export class BindWalletDto {
address: string; 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 { export class RemoveDeviceDto {
@ApiProperty() @ApiProperty()
@IsString() @IsString()
@ -224,8 +120,7 @@ export class RemoveDeviceDto {
deviceId: string; deviceId: string;
} }
// ============ Response DTOs ============ // Response DTOs
export class AutoCreateAccountResponseDto { export class AutoCreateAccountResponseDto {
@ApiProperty() @ApiProperty()
userId: string; userId: string;
@ -285,67 +180,3 @@ export class LoginResponseDto {
@ApiProperty() @ApiProperty()
refreshToken: string; 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1 +1,2 @@
export * from '../index'; export * from './user-profile.dto';
export * from './device.dto';

View File

@ -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;
}