feat: 后端调整登陆逻辑

This commit is contained in:
2025-12-16 22:48:51 +08:00
parent b235ca8a6e
commit 70517058ae
13 changed files with 305 additions and 194 deletions

View File

@@ -11,21 +11,17 @@ import {
@Index(['sessionId', 'userId'])
export class UserSession {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 36 })
sessionId: string;
@Column({ length: 36 })
userId: string;
@Column({ nullable: true })
disabledReason?: string;
@CreateDateColumn({ precision: 3 })
createdAt: Date;
@DeleteDateColumn({ nullable: true, precision: 3 })
deletedAt: Date;
}
/**
* 考虑是否使用sessionId代替id以节省存储空间
*/

View File

@@ -95,3 +95,13 @@ export class User {
@Column({ type: 'jsonb', default: [] })
roles: RoleItem[];
}
export class UserPublicProfile {
userId: string;
nickname: string;
avatar: string | null;
email: string | null;
phone: string | null;
roles: RoleItem[];
createdAt: Date;
}

View File

@@ -8,39 +8,45 @@ export class UserSessionService {
constructor(
@InjectRepository(UserSession)
private readonly userSessionRepository: Repository<UserSession>,
) {}
) { }
async createSession(userId: string, sessionId: string): Promise<UserSession> {
async createSession(userId: string): Promise<UserSession> {
const session = this.userSessionRepository.create({
userId,
sessionId,
});
return await this.userSessionRepository.save(session);
return this.userSessionRepository.save(session);
}
async isSessionValid(userId: string, sessionId: string): Promise<boolean> {
/**
* @throws string 无效原因
*/
async isSessionValid(userId: string, sessionId: string): Promise<void> {
const session = await this.userSessionRepository.findOne({
where: {
userId,
sessionId,
deletedAt: null,
},
withDeleted: true,
});
return !!session;
}
async invalidateSession(userId: string, sessionId: string): Promise<void> {
const session = await this.userSessionRepository.findOne({
where: {
userId,
sessionId,
deletedAt: null,
},
});
if (session) {
await this.userSessionRepository.softDelete(session.id);
if (session === null) {
throw '登陆凭证无效';
}
if (session.deletedAt !== null) {
throw session.disabledReason || '登陆凭证无效';
}
return null;
}
async invalidateSession(userId: string, sessionId: string, reason?: string): Promise<void> {
await this.userSessionRepository.update(
{ userId, sessionId, deletedAt: null },
{
deletedAt: new Date(),
disabledReason: reason || null,
}
)
}
}

View File

@@ -2,12 +2,14 @@ import {
BadRequestException,
ConflictException,
Injectable,
InternalServerErrorException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { User, UserPublicProfile } from './entities/user.entity';
import { QueryFailedError, Repository } from 'typeorm';
import { createHash } from 'crypto';
import { v4 as uuid } from 'uuid';
import { BusinessException } from 'src/common/exceptions/business.exception';
type UserFindOptions = Partial<
Pick<User, 'userId' | 'username' | 'phone' | 'email'>
@@ -18,26 +20,54 @@ export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
) { }
/**
* @deprecated 尽量不使用该方法
*/
async findOne(
options: UserFindOptions | UserFindOptions[],
additionalOptions?: { withDeleted?: boolean },
): Promise<User | null> {
if (Object.keys(options).length === 0) {
throw new BadRequestException('查询条件不能为空');
if (Array.isArray(options)) {
if (options.length === 0) {
throw new BusinessException({
message: '查询条件不能为空',
});
}
const users = await this.userRepository.find({
where: options,
withDeleted: additionalOptions?.withDeleted ?? false,
take: 1,
});
return users[0] || null;
}
if (!options || typeof options !== 'object' || Object.keys(options).length === 0) {
throw new BusinessException({
message: '查询条件不能为空',
});
}
return this.userRepository.findOne({
where: options,
withDeleted: additionalOptions?.withDeleted || false,
withDeleted: additionalOptions?.withDeleted ?? false,
});
}
async findById(userId: string): Promise<User | null> {
/**
* 仅包含用户可见字段
*/
async findById(userId: string): Promise<UserPublicProfile | null> {
return this.userRepository.findOne({
select: {
avatar: true,
createdAt: true,
email: true,
nickname: true,
username: true,
phone: true,
roles: true,
userId: true,
},
where: {
userId,
},