import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
ManyToMany,
JoinTable,
Index,
BeforeInsert,
BeforeUpdate,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
import { Role } from './role.entity';
import { AuditLog } from './audit-log.entity';
export enum UserStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
SUSPENDED = 'suspended',
PENDING = 'pending',
}
@Entity('users')
@Index(['email'], { unique: true })
@Index(['username'], { unique: true })
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 100, unique: true })
username: string;
@Column({ type: 'varchar', length: 255, unique: true })
email: string;
@Column({ type: 'varchar', length: 255 })
password: string;
@Column({ type: 'varchar', length: 100, nullable: true })
firstName?: string;
@Column({ type: 'varchar', length: 100, nullable: true })
lastName?: string;
@Column({
type: 'enum',
enum: UserStatus,
default: UserStatus.PENDING,
})
status: UserStatus;
@Column({ type: 'boolean', default: false })
emailVerified: boolean;
@Column({ type: 'varchar', length: 255, nullable: true })
emailVerificationToken?: string;
@Column({ type: 'varchar', length: 255, nullable: true })
passwordResetToken?: string;
@Column({ type: 'timestamp', nullable: true })
passwordResetExpires?: Date;
@Column({ type: 'timestamp', nullable: true })
lastLoginAt?: Date;
@Column({ type: 'inet', nullable: true })
lastLoginIp?: string;
@Column({ type: 'int', default: 0 })
loginAttempts: number;
@Column({ type: 'timestamp', nullable: true })
lockedUntil?: Date;
@Column({ type: 'jsonb', nullable: true })
preferences?: {
theme?: 'light' | 'dark';
language?: string;
timezone?: string;
notifications?: {
email?: boolean;
push?: boolean;
sms?: boolean;
};
[key: string]: any;
};
@Column({ type: 'jsonb', nullable: true })
metadata?: {
department?: string;
position?: string;
phone?: string;
avatar?: string;
[key: string]: any;
};
@ManyToMany(() => Role, role => role.users, { eager: true })
@JoinTable({
name: 'user_roles',
joinColumn: { name: 'userId', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'roleId', referencedColumnName: 'id' },
})
roles: Role[];
@OneToMany(() => AuditLog, auditLog => auditLog.user)
auditLogs: AuditLog[];
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@BeforeInsert()
@BeforeUpdate()
async hashPassword() {
if (this.password && !this.password.startsWith('$2b$')) {
const salt = await bcrypt.genSalt(12);
this.password = await bcrypt.hash(this.password, salt);
}
}
async validatePassword(password: string): Promise<boolean> {
return bcrypt.compare(password, this.password);
}
get isLocked(): boolean {
return this.lockedUntil ? new Date() < this.lockedUntil : false;
}
get isActive(): boolean {
return this.status === UserStatus.ACTIVE && this.emailVerified && !this.isLocked;
}
get fullName(): string {
return [this.firstName, this.lastName].filter(Boolean).join(' ') || this.username;
}
// 获取用户所有权限
get permissions(): string[] {
const permissions = new Set<string>();
this.roles?.forEach(role => {
role.permissions?.forEach(permission => {
permissions.add(permission.name);
});
});
return Array.from(permissions);
}
// 检查用户是否有特定权限
hasPermission(permissionName: string): boolean {
return this.permissions.includes(permissionName);
}
// 检查用户是否有特定角色
hasRole(roleName: string): boolean {
return this.roles?.some(role => role.name === roleName) || false;
}
// 增加登录失败次数
async incrementLoginAttempts(): Promise<void> {
this.loginAttempts += 1;
// 如果登录失败次数超过5次,锁定账户1小时
if (this.loginAttempts >= 5) {
this.lockedUntil = new Date(Date.now() + 60 * 60 * 1000); // 1小时后解锁
}
}
// 重置登录失败次数
resetLoginAttempts(): void {
this.loginAttempts = 0;
this.lockedUntil = null;
this.lastLoginAt = new Date();
}
// 生成密码重置令牌
generatePasswordResetToken(): string {
const token = Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
this.passwordResetToken = token;
this.passwordResetExpires = new Date(Date.now() + 60 * 60 * 1000); // 1小时有效
return token;
}
// 生成邮箱验证令牌
generateEmailVerificationToken(): string {
const token = Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
this.emailVerificationToken = token;
return token;
}
// 清理敏感信息(用于API响应)
toSafeObject() {
const { password, passwordResetToken, emailVerificationToken, ...safeUser } = this;
return safeUser;
}
}