import { DomainError } from '@/shared/exceptions/domain.exception'; import { UserId, AccountSequence, PhoneNumber, ReferralCode, DeviceInfo, ChainType, KYCInfo, KYCStatus, AccountStatus, } from '@/domain/value-objects'; import { WalletAddress } from '@/domain/entities/wallet-address.entity'; import { DomainEvent, UserAccountAutoCreatedEvent, UserAccountCreatedEvent, DeviceAddedEvent, DeviceRemovedEvent, PhoneNumberBoundEvent, WalletAddressBoundEvent, MultipleWalletAddressesBoundEvent, KYCSubmittedEvent, KYCVerifiedEvent, KYCRejectedEvent, UserAccountFrozenEvent, UserAccountDeactivatedEvent, } from '@/domain/events'; export class UserAccount { private readonly _userId: UserId; private readonly _accountSequence: AccountSequence; private _devices: Map; private _phoneNumber: PhoneNumber | null; private _nickname: string; private _avatarUrl: string | null; private readonly _inviterSequence: AccountSequence | null; private readonly _referralCode: ReferralCode; private _walletAddresses: Map; private _kycInfo: KYCInfo | null; private _kycStatus: KYCStatus; private _status: AccountStatus; private readonly _registeredAt: Date; private _lastLoginAt: Date | null; private _updatedAt: Date; private _domainEvents: DomainEvent[] = []; // Getters get userId(): UserId { return this._userId; } get accountSequence(): AccountSequence { return this._accountSequence; } get phoneNumber(): PhoneNumber | null { return this._phoneNumber; } get nickname(): string { return this._nickname; } get avatarUrl(): string | null { return this._avatarUrl; } get inviterSequence(): AccountSequence | null { return this._inviterSequence; } get referralCode(): ReferralCode { return this._referralCode; } get kycInfo(): KYCInfo | null { return this._kycInfo; } get kycStatus(): KYCStatus { return this._kycStatus; } get status(): AccountStatus { return this._status; } get registeredAt(): Date { return this._registeredAt; } get lastLoginAt(): Date | null { return this._lastLoginAt; } get updatedAt(): Date { return this._updatedAt; } get isActive(): boolean { return this._status === AccountStatus.ACTIVE; } get isKYCVerified(): boolean { return this._kycStatus === KYCStatus.VERIFIED; } get domainEvents(): DomainEvent[] { return [...this._domainEvents]; } private constructor( userId: UserId, accountSequence: AccountSequence, devices: Map, phoneNumber: PhoneNumber | null, nickname: string, avatarUrl: string | null, inviterSequence: AccountSequence | null, referralCode: ReferralCode, walletAddresses: Map, kycInfo: KYCInfo | null, kycStatus: KYCStatus, status: AccountStatus, registeredAt: Date, lastLoginAt: Date | null, updatedAt: Date, ) { this._userId = userId; this._accountSequence = accountSequence; this._devices = devices; this._phoneNumber = phoneNumber; this._nickname = nickname; this._avatarUrl = avatarUrl; this._inviterSequence = inviterSequence; this._referralCode = referralCode; this._walletAddresses = walletAddresses; this._kycInfo = kycInfo; this._kycStatus = kycStatus; this._status = status; this._registeredAt = registeredAt; this._lastLoginAt = lastLoginAt; this._updatedAt = updatedAt; } static createAutomatic(params: { accountSequence: AccountSequence; initialDeviceId: string; deviceName?: string; deviceInfo?: Record; // 完整的设备信息 JSON inviterSequence: AccountSequence | null; nickname?: string; avatarSvg?: string; }): UserAccount { const devices = new Map(); devices.set(params.initialDeviceId, new DeviceInfo( params.initialDeviceId, params.deviceName || '未命名设备', new Date(), new Date(), params.deviceInfo, // 传递完整的 JSON )); // UserID将由数据库自动生成(autoincrement),这里使用临时值0 const nickname = params.nickname || `用户${params.accountSequence.value}`; const avatarUrl = params.avatarSvg || null; const account = new UserAccount( UserId.create(0), params.accountSequence, devices, null, nickname, avatarUrl, params.inviterSequence, ReferralCode.generate(), new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE, new Date(), null, new Date(), ); account.addDomainEvent(new UserAccountAutoCreatedEvent({ userId: account.userId.toString(), accountSequence: params.accountSequence.value, initialDeviceId: params.initialDeviceId, inviterSequence: params.inviterSequence?.value || null, registeredAt: account._registeredAt, })); return account; } static create(params: { accountSequence: AccountSequence; phoneNumber: PhoneNumber; initialDeviceId: string; deviceName?: string; deviceInfo?: Record; // 完整的设备信息 JSON inviterSequence: AccountSequence | null; }): UserAccount { const devices = new Map(); devices.set(params.initialDeviceId, new DeviceInfo( params.initialDeviceId, params.deviceName || '未命名设备', new Date(), new Date(), params.deviceInfo, )); // UserID将由数据库自动生成(autoincrement),这里使用临时值0 const account = new UserAccount( UserId.create(0), params.accountSequence, devices, params.phoneNumber, `用户${params.accountSequence.value}`, null, params.inviterSequence, ReferralCode.generate(), new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE, new Date(), null, new Date(), ); account.addDomainEvent(new UserAccountCreatedEvent({ userId: account.userId.toString(), accountSequence: params.accountSequence.value, phoneNumber: params.phoneNumber.value, initialDeviceId: params.initialDeviceId, inviterSequence: params.inviterSequence?.value || null, registeredAt: account._registeredAt, })); return account; } static reconstruct(params: { userId: string; accountSequence: string; devices: DeviceInfo[]; phoneNumber: string | null; nickname: string; avatarUrl: string | null; inviterSequence: string | null; referralCode: string; walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null; kycStatus: KYCStatus; status: AccountStatus; registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date; }): UserAccount { const deviceMap = new Map(); params.devices.forEach(d => deviceMap.set(d.deviceId, d)); const walletMap = new Map(); params.walletAddresses.forEach(w => walletMap.set(w.chainType, w)); return new UserAccount( UserId.create(params.userId), AccountSequence.create(params.accountSequence), deviceMap, params.phoneNumber ? PhoneNumber.create(params.phoneNumber) : null, params.nickname, params.avatarUrl, params.inviterSequence ? AccountSequence.create(params.inviterSequence) : null, ReferralCode.create(params.referralCode), walletMap, params.kycInfo, params.kycStatus, params.status, params.registeredAt, params.lastLoginAt, params.updatedAt, ); } addDevice(deviceId: string, deviceName?: string, deviceInfo?: Record): void { this.ensureActive(); if (this._devices.size >= 5 && !this._devices.has(deviceId)) { throw new DomainError('最多允许5个设备同时登录'); } if (this._devices.has(deviceId)) { const device = this._devices.get(deviceId)!; device.updateActivity(); if (deviceInfo) { device.updateDeviceInfo(deviceInfo); } } else { this._devices.set(deviceId, new DeviceInfo( deviceId, deviceName || '未命名设备', new Date(), new Date(), deviceInfo, )); this.addDomainEvent(new DeviceAddedEvent({ userId: this.userId.toString(), accountSequence: this.accountSequence.value, deviceId, deviceName: deviceName || '未命名设备', })); } this._updatedAt = new Date(); } removeDevice(deviceId: string): void { this.ensureActive(); if (!this._devices.has(deviceId)) throw new DomainError('设备不存在'); if (this._devices.size <= 1) throw new DomainError('至少保留一个设备'); this._devices.delete(deviceId); this._updatedAt = new Date(); this.addDomainEvent(new DeviceRemovedEvent({ userId: this.userId.toString(), deviceId })); } isDeviceAuthorized(deviceId: string): boolean { return this._devices.has(deviceId); } getAllDevices(): DeviceInfo[] { return Array.from(this._devices.values()); } updateProfile(params: { nickname?: string; avatarUrl?: string }): void { this.ensureActive(); if (params.nickname) this._nickname = params.nickname; if (params.avatarUrl !== undefined) this._avatarUrl = params.avatarUrl; this._updatedAt = new Date(); } bindPhoneNumber(phoneNumber: PhoneNumber): void { this.ensureActive(); if (this._phoneNumber) throw new DomainError('已绑定手机号,不可重复绑定'); this._phoneNumber = phoneNumber; this._updatedAt = new Date(); this.addDomainEvent(new PhoneNumberBoundEvent({ userId: this.userId.toString(), phoneNumber: phoneNumber.value })); } bindWalletAddress(chainType: ChainType, address: string): void { this.ensureActive(); if (this._walletAddresses.has(chainType)) throw new DomainError(`已绑定${chainType}地址`); const walletAddress = WalletAddress.create({ userId: this.userId, chainType, address }); this._walletAddresses.set(chainType, walletAddress); this._updatedAt = new Date(); this.addDomainEvent(new WalletAddressBoundEvent({ userId: this.userId.toString(), chainType, address })); } bindMultipleWalletAddresses(wallets: Map): void { this.ensureActive(); for (const [chainType, wallet] of wallets) { if (this._walletAddresses.has(chainType)) throw new DomainError(`已绑定${chainType}地址`); this._walletAddresses.set(chainType, wallet); } this._updatedAt = new Date(); this.addDomainEvent(new MultipleWalletAddressesBoundEvent({ userId: this.userId.toString(), addresses: Array.from(wallets.entries()).map(([chainType, wallet]) => ({ chainType, address: wallet.address })), })); } submitKYC(kycInfo: KYCInfo): void { this.ensureActive(); if (this._kycStatus === KYCStatus.VERIFIED) throw new DomainError('已通过KYC认证,不可重复提交'); this._kycInfo = kycInfo; this._kycStatus = KYCStatus.PENDING; this._updatedAt = new Date(); this.addDomainEvent(new KYCSubmittedEvent({ userId: this.userId.toString(), realName: kycInfo.realName, idCardNumber: kycInfo.idCardNumber, })); } approveKYC(): void { if (this._kycStatus !== KYCStatus.PENDING) throw new DomainError('只有待审核状态才能通过KYC'); this._kycStatus = KYCStatus.VERIFIED; this._updatedAt = new Date(); this.addDomainEvent(new KYCVerifiedEvent({ userId: this.userId.toString(), verifiedAt: new Date() })); } rejectKYC(reason: string): void { if (this._kycStatus !== KYCStatus.PENDING) throw new DomainError('只有待审核状态才能拒绝KYC'); this._kycStatus = KYCStatus.REJECTED; this._updatedAt = new Date(); this.addDomainEvent(new KYCRejectedEvent({ userId: this.userId.toString(), reason })); } recordLogin(): void { this.ensureActive(); this._lastLoginAt = new Date(); this._updatedAt = new Date(); } freeze(reason: string): void { if (this._status === AccountStatus.FROZEN) throw new DomainError('账户已冻结'); this._status = AccountStatus.FROZEN; this._updatedAt = new Date(); this.addDomainEvent(new UserAccountFrozenEvent({ userId: this.userId.toString(), reason })); } unfreeze(): void { if (this._status !== AccountStatus.FROZEN) throw new DomainError('账户未冻结'); this._status = AccountStatus.ACTIVE; this._updatedAt = new Date(); } deactivate(): void { if (this._status === AccountStatus.DEACTIVATED) throw new DomainError('账户已注销'); this._status = AccountStatus.DEACTIVATED; this._updatedAt = new Date(); this.addDomainEvent(new UserAccountDeactivatedEvent({ userId: this.userId.toString(), deactivatedAt: new Date() })); } getWalletAddress(chainType: ChainType): WalletAddress | null { return this._walletAddresses.get(chainType) || null; } getAllWalletAddresses(): WalletAddress[] { return Array.from(this._walletAddresses.values()); } private ensureActive(): void { if (this._status !== AccountStatus.ACTIVE) throw new DomainError('账户已冻结或注销'); } private addDomainEvent(event: DomainEvent): void { this._domainEvents.push(event); } clearDomainEvents(): void { this._domainEvents = []; } }