Files
invest-mind-store/apps/api/src/modules/user/user.service.ts
T

241 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Injectable,
NotFoundException,
ConflictException,
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { QueryUserDto } from './dto/query-user.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
/**
* 注册用户
*/
async create(createUserDto: CreateUserDto): Promise<User> {
// 检查用户名是否已存在
const existingByUsername = await this.userRepository.findOne({
where: { username: createUserDto.username },
});
if (existingByUsername) {
throw new ConflictException(
`用户名 "${createUserDto.username}" 已存在`,
);
}
// 检查邮箱是否已存在
const existingByEmail = await this.userRepository.findOne({
where: { email: createUserDto.email },
});
if (existingByEmail) {
throw new ConflictException(`邮箱 "${createUserDto.email}" 已存在`);
}
// 检查 phone 是否已存在(如果提供了)
/* if (createUserDto.phone) {
const existingByPhone = await this.userRepository.findOne({
where: { phone: createUserDto.phone },
});
if (existingByPhone) {
throw new ConflictException(
`Phone "${createUserDto.phone}" already exists`,
);
}
}
// 检查 openId 是否已存在(如果提供了)
if (createUserDto.openId) {
const existingByOpenId = await this.userRepository.findOne({
where: { openId: createUserDto.openId },
});
if (existingByOpenId) {
throw new ConflictException(
`OpenId "${createUserDto.openId}" already exists`,
);
}
}
// 检查 unionId 是否已存在(如果提供了)
if (createUserDto.unionId) {
const existingByUnionId = await this.userRepository.findOne({
where: { unionId: createUserDto.unionId },
});
if (existingByUnionId) {
throw new ConflictException(
`UnionId "${createUserDto.unionId}" already exists`,
);
}
} */
// 使用 bcrypt 加密密码
const saltRounds = 10;
const passwordHash = await bcrypt.hash(
createUserDto.password,
saltRounds,
);
// 创建用户
const user = this.userRepository.create({
username: createUserDto.username,
passwordHash,
email: createUserDto.email,
nickname: createUserDto.nickname,
avatarUrl: createUserDto.avatarUrl,
phone: createUserDto.phone,
openId: createUserDto.openId,
unionId: createUserDto.unionId,
status: 'active',
role: createUserDto.role || 'user', // 默认为普通用户
});
return this.userRepository.save(user);
}
/**
* 查询所有用户
*/
async findAll(): Promise<User[]> {
return this.userRepository.find({
order: {
createdAt: 'DESC',
},
});
}
/**
* 根据 username 或 email 查询单个用户
*/
async findOne(queryDto: QueryUserDto): Promise<User> {
if (!queryDto.username && !queryDto.email) {
throw new BadRequestException('必须提供用户名或邮箱');
}
const where: { username?: string; email?: string } = {};
if (queryDto.username) {
where.username = queryDto.username;
}
if (queryDto.email) {
where.email = queryDto.email;
}
const user = await this.userRepository.findOne({ where });
if (!user) {
const identifier = queryDto.username || queryDto.email;
throw new NotFoundException(
`未找到${queryDto.username ? '用户名' : '邮箱'}为 "${identifier}" 的用户`,
);
}
return user;
}
/**
* 根据 ID 查询单个用户
*/
async findOneById(id: number): Promise<User> {
const user = await this.userRepository.findOne({
where: { userId: id },
});
if (!user) {
throw new NotFoundException(`未找到ID为 ${id} 的用户`);
}
return user;
}
/**
* 更新用户信息(不允许修改 username、openId、unionId
*/
async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOneById(id);
// 如果更新邮箱,检查是否与其他用户冲突
if (updateUserDto.email && updateUserDto.email !== user.email) {
const existingByEmail = await this.userRepository.findOne({
where: { email: updateUserDto.email },
});
if (existingByEmail) {
throw new ConflictException(
`邮箱 "${updateUserDto.email}" 已存在`,
);
}
}
// 如果更新 phone,检查是否与其他用户冲突
if (updateUserDto.phone && updateUserDto.phone !== user.phone) {
const existingByPhone = await this.userRepository.findOne({
where: { phone: updateUserDto.phone },
});
if (existingByPhone) {
throw new ConflictException(
`手机号 "${updateUserDto.phone}" 已存在`,
);
}
}
// 更新用户信息
Object.assign(user, updateUserDto);
return this.userRepository.save(user);
}
/**
* 修改密码(需要先验证旧密码)
*/
async changePassword(
id: number,
changePasswordDto: ChangePasswordDto,
): Promise<void> {
const user = await this.findOneById(id);
// 验证旧密码
const isOldPasswordValid = await bcrypt.compare(
changePasswordDto.oldPassword,
user.passwordHash,
);
if (!isOldPasswordValid) {
throw new BadRequestException('旧密码错误');
}
// 加密新密码
const saltRounds = 10;
const newPasswordHash = await bcrypt.hash(
changePasswordDto.newPassword,
saltRounds,
);
// 更新密码
user.passwordHash = newPasswordHash;
await this.userRepository.save(user);
}
/**
* 删除用户(软删除,更新状态为 deleted)
*/
async remove(id: number): Promise<void> {
const user = await this.findOneById(id);
user.status = 'deleted';
await this.userRepository.save(user);
}
}