rwadurian/backend/services/auth-service/prisma/schema.prisma

258 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ============================================================================
// 2.0 用户表
// ============================================================================
model User {
id BigInt @id @default(autoincrement())
// 基本信息
phone String @unique
passwordHash String @map("password_hash")
// 统一关联键 (跨所有服务)
// V1: 12位 (D + 6位日期 + 5位序号), 如 D2512110008
// V2: 15位 (D + 6位日期 + 8位序号), 如 D25121100000008
accountSequence String @unique @map("account_sequence")
// 状态
status UserStatus @default(ACTIVE)
// KYC 实名认证
kycStatus KycStatus @default(PENDING)
realName String? @map("real_name")
idCardNo String? @map("id_card_no")
idCardFront String? @map("id_card_front") // 身份证正面照片路径
idCardBack String? @map("id_card_back") // 身份证背面照片路径
kycSubmittedAt DateTime? @map("kyc_submitted_at")
kycVerifiedAt DateTime? @map("kyc_verified_at")
kycRejectReason String? @map("kyc_reject_reason")
// 安全
loginFailCount Int @default(0) @map("login_fail_count")
lockedUntil DateTime? @map("locked_until")
lastLoginAt DateTime? @map("last_login_at")
lastLoginIp String? @map("last_login_ip")
// 时间戳
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// 关联
refreshTokens RefreshToken[]
loginLogs LoginLog[]
smsLogs SmsLog[]
@@index([phone])
@@index([accountSequence])
@@index([status])
@@index([kycStatus])
@@map("users")
}
enum UserStatus {
ACTIVE
DISABLED
DELETED
}
enum KycStatus {
PENDING // 待提交
SUBMITTED // 已提交待审核
VERIFIED // 已认证
REJECTED // 已拒绝
}
// ============================================================================
// CDC 同步的 1.0 用户(只读)
// ============================================================================
model SyncedLegacyUser {
id BigInt @id @default(autoincrement())
// 1.0 用户数据
legacyId BigInt @unique @map("legacy_id") // 1.0 的 user.id
accountSequence String @unique @map("account_sequence")
phone String? // 系统账户可能没有手机号
passwordHash String? @map("password_hash") // 系统账户可能没有密码
status String
legacyCreatedAt DateTime @map("legacy_created_at")
// 迁移状态
migratedToV2 Boolean @default(false) @map("migrated_to_v2")
migratedAt DateTime? @map("migrated_at")
// CDC 元数据
sourceSequenceNum BigInt @map("source_sequence_num")
syncedAt DateTime @default(now()) @map("synced_at")
@@index([phone])
@@index([accountSequence])
@@index([migratedToV2])
@@map("synced_legacy_users")
}
// ============================================================================
// 刷新令牌
// ============================================================================
model RefreshToken {
id BigInt @id @default(autoincrement())
userId BigInt @map("user_id")
token String @unique
deviceInfo String? @map("device_info")
ipAddress String? @map("ip_address")
expiresAt DateTime @map("expires_at")
createdAt DateTime @default(now()) @map("created_at")
revokedAt DateTime? @map("revoked_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([token])
@@index([expiresAt])
@@map("refresh_tokens")
}
// ============================================================================
// 短信验证码
// ============================================================================
model SmsVerification {
id BigInt @id @default(autoincrement())
phone String
code String
type SmsVerificationType
expiresAt DateTime @map("expires_at")
verifiedAt DateTime? @map("verified_at")
attempts Int @default(0)
createdAt DateTime @default(now()) @map("created_at")
@@index([phone, type])
@@index([expiresAt])
@@map("sms_verifications")
}
enum SmsVerificationType {
REGISTER // 注册
LOGIN // 登录
RESET_PASSWORD // 重置密码
CHANGE_PHONE // 更换手机号
}
// ============================================================================
// 短信发送日志
// ============================================================================
model SmsLog {
id BigInt @id @default(autoincrement())
userId BigInt? @map("user_id")
phone String
type SmsVerificationType
content String?
status SmsStatus @default(PENDING)
provider String? // 短信服务商
providerId String? @map("provider_id") // 服务商返回的ID
errorMsg String? @map("error_msg")
createdAt DateTime @default(now()) @map("created_at")
user User? @relation(fields: [userId], references: [id])
@@index([phone])
@@index([userId])
@@index([createdAt])
@@map("sms_logs")
}
enum SmsStatus {
PENDING
SENT
DELIVERED
FAILED
}
// ============================================================================
// 登录日志
// ============================================================================
model LoginLog {
id BigInt @id @default(autoincrement())
userId BigInt? @map("user_id")
phone String
type LoginType
success Boolean
failReason String? @map("fail_reason")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
deviceInfo String? @map("device_info")
createdAt DateTime @default(now()) @map("created_at")
user User? @relation(fields: [userId], references: [id])
@@index([userId])
@@index([phone])
@@index([createdAt])
@@map("login_logs")
}
enum LoginType {
PASSWORD // 密码登录
SMS_CODE // 验证码登录
LEGACY_MIGRATE // 1.0 用户迁移登录
}
// ============================================================================
// 每日序号计数器(用于生成 accountSequence
// ============================================================================
model DailySequenceCounter {
id BigInt @id @default(autoincrement())
dateKey String @unique @map("date_key") // 格式: YYMMDD
lastSeq Int @default(0) @map("last_seq")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("daily_sequence_counters")
}
// ============================================================================
// 发件箱(事件发布)
// ============================================================================
model OutboxEvent {
id BigInt @id @default(autoincrement())
aggregateType String @map("aggregate_type")
aggregateId String @map("aggregate_id")
eventType String @map("event_type")
payload Json
topic String
key String
status OutboxStatus @default(PENDING)
retryCount Int @default(0) @map("retry_count")
maxRetries Int @default(3) @map("max_retries")
lastError String? @map("last_error")
publishedAt DateTime? @map("published_at")
nextRetryAt DateTime? @map("next_retry_at")
createdAt DateTime @default(now()) @map("created_at")
@@index([status])
@@index([nextRetryAt])
@@map("outbox_events")
}
enum OutboxStatus {
PENDING
PUBLISHED
FAILED
}