feat: 后端调整登陆逻辑
This commit is contained in:
@@ -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,以节省存储空间
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user