diff --git a/src/modules/user/constants.ts b/src/modules/user/constants.ts new file mode 100644 index 0000000..e316394 --- /dev/null +++ b/src/modules/user/constants.ts @@ -0,0 +1,33 @@ +/** + * 用户请求DTO验证组 + */ +export enum UserValidateGroup { + /** + * 创建用户验证 + */ + USER_CREATE = 'user_create', + /** + * 更新用户验证 + */ + USER_UPDATE = 'user_update', + /** + * 用户注册验证 + */ + USER_REGISTER = 'user_register', + /** + * 用户账户信息更新验证 + */ + ACCOUNT_UPDATE = 'account_update', + /** + * 用户密码更新验证 + */ + CHANGE_PASSWORD = 'change_password', +} + +/** + * 用户查询排序 + */ +export enum UserOrderType { + CREATED = 'createdAt', + UPDATED = 'updatedAt', +} diff --git a/src/modules/user/entities/UserEntity.ts b/src/modules/user/entities/UserEntity.ts index 03377f1..23c7f2f 100644 --- a/src/modules/user/entities/UserEntity.ts +++ b/src/modules/user/entities/UserEntity.ts @@ -11,6 +11,7 @@ import { } from 'typeorm'; import { CommentEntity, PostEntity } from '@/modules/content/entities'; +import { AccessTokenEntity } from '@/modules/user/entities/access.token.entity'; /** * 用户实体 @@ -94,4 +95,10 @@ export class UserEntity { */ @OneToMany(() => CommentEntity, (comment) => comment.author, { cascade: true }) comments: Relation[]; + + /** + * 登录token + */ + @OneToMany(() => AccessTokenEntity, (token) => token.user, { cascade: true }) + accessTokens: Relation[]; } diff --git a/src/modules/user/entities/access.token.entity.ts b/src/modules/user/entities/access.token.entity.ts index cbe271c..469e1cc 100644 --- a/src/modules/user/entities/access.token.entity.ts +++ b/src/modules/user/entities/access.token.entity.ts @@ -1,5 +1,6 @@ -import { Entity, OneToOne } from 'typeorm'; +import { Entity, ManyToOne, OneToOne, Relation } from 'typeorm'; +import { UserEntity } from '@/modules/user/entities/UserEntity'; import { BaseToken } from '@/modules/user/entities/base.token'; import { RefreshTokenEntity } from '@/modules/user/entities/refresh.token.entity'; @@ -13,4 +14,10 @@ export class AccessTokenEntity extends BaseToken { */ @OneToOne(() => RefreshTokenEntity, (token) => token.accessToken, { cascade: true }) refreshToken: string; + + /** + * 关联用户 + */ + @ManyToOne(() => UserEntity, (user) => user.accessTokens, { onDelete: 'CASCADE' }) + user: Relation; } diff --git a/src/modules/user/repositories/UserRepository.ts b/src/modules/user/repositories/UserRepository.ts new file mode 100644 index 0000000..4f2a8f3 --- /dev/null +++ b/src/modules/user/repositories/UserRepository.ts @@ -0,0 +1,12 @@ +import { BaseRepository } from '@/modules/database/base/repository'; +import { CustomRepository } from '@/modules/database/decorators/repository.decorator'; +import { UserEntity } from '@/modules/user/entities/UserEntity'; + +@CustomRepository(UserEntity) +export class UserRepository extends BaseRepository { + protected _qbName: string = 'user'; + + buildBaseQuery() { + return this.createQueryBuilder(this.qbName).orderBy(`${this.qbName}.createdAt`, 'DESC'); + } +} diff --git a/src/modules/user/subscribers/UserSubscriber.ts b/src/modules/user/subscribers/UserSubscriber.ts new file mode 100644 index 0000000..708524e --- /dev/null +++ b/src/modules/user/subscribers/UserSubscriber.ts @@ -0,0 +1,39 @@ +import { randomBytes } from 'node:crypto'; + +import { EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm'; + +import { BaseSubscriber } from '@/modules/database/base/subscriber'; +import { UserEntity } from '@/modules/user/entities/UserEntity'; +import { encrypt } from '@/modules/user/utils'; + +@EventSubscriber() +export class UserSubscriber extends BaseSubscriber { + protected entity = UserEntity; + + /** + * 生成随机用户名 + * @param event + * @protected + */ + protected async generateUserName(event: InsertEvent): Promise { + const username = `user_${randomBytes(4).toString('hex').slice(0, 8)}`; + const user = await event.manager.findOne(UserEntity, { where: { username } }); + return user ? this.generateUserName(event) : username; + } + + async beforeInsert(event: InsertEvent): Promise { + if (!event.entity.username) { + event.entity.username = await this.generateUserName(event); + } + if (!event.entity.password) { + event.entity.password = randomBytes(11).toString('hex').slice(0, 22); + } + event.entity.password = await encrypt(this.configure, event.entity.password); + } + + async beforeUpdate(event: UpdateEvent) { + if (this.isUpdated('password', event)) { + event.entity.password = await encrypt(this.configure, event.entity.password); + } + } +}