import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import Redis from 'ioredis'; import { isEmpty, isNil, isNumber } from 'lodash'; import { EntityManager, In, Like, Repository } from 'typeorm'; import pinyin from 'pinyin'; import { BusinessException } from '~/common/exceptions/biz.exception'; import { ErrorEnum } from '~/constants/error-code.constant'; import { ROOT_ROLE_ID, SYS_USER_INITPASSWORD } from '~/constants/system.constant'; import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey'; import { paginate } from '~/helper/paginate'; import { Pagination } from '~/helper/paginate/pagination'; import { AccountUpdateDto } from '~/modules/auth/dto/account.dto'; import { RegisterDto } from '~/modules/auth/dto/auth.dto'; import { QQService } from '~/shared/helper/qq.service'; import { md5, randomValue } from '~/utils'; import { DeptEntity } from '../system/dept/dept.entity'; import { ParamConfigService } from '../system/param-config/param-config.service'; import { RoleEntity } from '../system/role/role.entity'; import { UserStatus } from './constant'; import { PasswordUpdateDto } from './dto/password.dto'; import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto'; import { UserEntity } from './user.entity'; import { AccountInfo } from './user.model'; @Injectable() export class UserService { constructor( @InjectRedis() private readonly redis: Redis, @InjectRepository(UserEntity) private readonly userRepository: Repository, @InjectRepository(RoleEntity) private readonly roleRepository: Repository, @InjectEntityManager() private entityManager: EntityManager, private readonly paramConfigService: ParamConfigService, private readonly qqService: QQService ) {} async findUserById(id: number): Promise { return this.userRepository .createQueryBuilder('user') .where({ id, status: UserStatus.Enabled }) .getOne(); } async findUserByUserName(username: string): Promise { return this.userRepository .createQueryBuilder('user') .where({ username, status: UserStatus.Enabled }) .getOne(); } /** * 获取用户信息 * @param uid user id */ async getAccountInfo(uid: number): Promise { const user: UserEntity = await this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.roles', 'role') .leftJoinAndSelect('user.dept', 'dept') .where(`user.id = :uid`, { uid }) .getOne(); if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND); delete user?.psalt; return user; } /** * 更新个人信息 */ async updateAccountInfo(uid: number, info: AccountUpdateDto): Promise { const user = await this.userRepository.findOneBy({ id: uid }); if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND); const data = { ...(info.nickname ? { nickname: info.nickname } : null), ...(info.avatar ? { avatar: info.avatar } : null), ...(info.email ? { email: info.email } : null), ...(info.phone ? { phone: info.phone } : null), ...(info.qq ? { qq: info.qq } : null), ...(info.remark ? { remark: info.remark } : null) }; if (!info.avatar && info.qq) { // 如果qq不等于原qq,则更新qq头像 if (info.qq !== user.qq) data.avatar = await this.qqService.getAvater(info.qq); } await this.userRepository.update(uid, data); } /** * 更改密码 */ async updatePassword(uid: number, dto: PasswordUpdateDto): Promise { const user = await this.userRepository.findOneBy({ id: uid }); if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND); const comparePassword = md5(`${dto.oldPassword}${user.psalt}`); // 原密码不一致,不允许更改 if (user.password !== comparePassword) throw new BusinessException(ErrorEnum.PASSWORD_MISMATCH); const password = md5(`${dto.newPassword}${user.psalt}`); await this.userRepository.update({ id: uid }, { password }); await this.upgradePasswordV(user.id); } /** * 直接更改密码 */ async forceUpdatePassword(uid: number, password: string): Promise { const user = await this.userRepository.findOneBy({ id: uid }); const newPassword = md5(`${password}${user.psalt}`); await this.userRepository.update({ id: uid }, { password: newPassword }); await this.upgradePasswordV(user.id); } /** * 增加系统用户,如果返回false则表示已存在该用户 */ async create({ username, password, roleIds, deptId, ...data }: UserDto): Promise { const exists = await this.userRepository.findOneBy({ username }); if (!isEmpty(exists)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); await this.entityManager.transaction(async manager => { const salt = randomValue(32); if (!password) { const initPassword = await this.paramConfigService.findValueByKey(SYS_USER_INITPASSWORD); password = md5(`${initPassword ?? '123456'}${salt}`); } else { password = md5(`${password ?? '123456'}${salt}`); } const u = manager.create(UserEntity, { username, password, ...data, psalt: salt, roles: await this.roleRepository.findBy({ id: In(roleIds) }), dept: await DeptEntity.findOneBy({ id: deptId }) }); const result = await manager.save(u); return result; }); } /** * 更新用户信息 */ async update( id: number, { password, deptId, roleIds, status, ...data }: UserUpdateDto ): Promise { await this.entityManager.transaction(async manager => { if (password) await this.forceUpdatePassword(id, password); await manager.update(UserEntity, id, { ...data, status }); const user = await this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.roles', 'roles') .leftJoinAndSelect('user.dept', 'dept') .where('user.id = :id', { id }) .getOne(); await manager .createQueryBuilder() .relation(UserEntity, 'roles') .of(id) .addAndRemove(roleIds, user.roles); await manager.createQueryBuilder().relation(UserEntity, 'dept').of(id).set(deptId); if (status === 0) { // 禁用状态 await this.forbidden(id); } }); } /** * 查找用户信息 * @param id 用户id */ async info(id: number): Promise { const user = await this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.roles', 'roles') .leftJoinAndSelect('user.dept', 'dept') .where('user.id = :id', { id }) .getOne(); delete user.password; delete user.psalt; return user; } /** * 根据ID列表删除用户 */ async delete(userIds: number[]): Promise { const rootUserId = await this.findRootUserId(); if (userIds.includes(rootUserId)) throw new BadRequestException('不能删除root用户!'); await this.userRepository.delete(userIds); } /** * 查找超管的用户ID */ async findRootUserId(): Promise { const user = await this.userRepository.findOneBy({ roles: { id: ROOT_ROLE_ID } }); return user.id; } /** * 查询用户列表 */ async list({ page, pageSize, username, nickname, deptId, email, status, keyword }: UserQueryDto): Promise> { const queryBuilder = this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.dept', 'dept') .leftJoinAndSelect('user.roles', 'role') // .where('user.id NOT IN (:...ids)', { ids: [rootUserId, uid] }) .where({ ...(username ? { username: Like(`%${username}%`) } : null), ...(nickname ? { nickname: Like(`%${nickname}%`) } : null), ...(email ? { email: Like(`%${email}%`) } : null), ...(isNumber(status) ? { status } : null) }); if (deptId) queryBuilder.andWhere('dept.id = :deptId', { deptId }); if (keyword) { //关键字模糊查询product的name,productNumber,productSpecification queryBuilder.andWhere( `(user.nickname like :keyword or user.namePinyin like :keyword or dept.name like :keyword or user.phone like :keyword or user.email like :keyword or user.qq like :keyword or user.remark like :keyword )`, { keyword: `%${keyword}%` } ); } return paginate(queryBuilder, { page, pageSize }); } /** * 禁用用户 */ async forbidden(uid: number): Promise { await this.redis.del(genAuthPVKey(uid)); await this.redis.del(genAuthTokenKey(uid)); await this.redis.del(genAuthPermKey(uid)); } /** * 禁用多个用户 */ async multiForbidden(uids: number[]): Promise { if (uids) { const pvs: string[] = []; const ts: string[] = []; const ps: string[] = []; uids.forEach(uid => { pvs.push(genAuthPVKey(uid)); ts.push(genAuthTokenKey(uid)); ps.push(genAuthPermKey(uid)); }); await this.redis.del(pvs); await this.redis.del(ts); await this.redis.del(ps); } } /** * 升级用户版本密码 */ async upgradePasswordV(id: number): Promise { // admin:passwordVersion:${param.id} const v = await this.redis.get(genAuthPVKey(id)); if (!isEmpty(v)) await this.redis.set(genAuthPVKey(id), Number.parseInt(v) + 1); } /** * 判断用户名是否存在 */ async exist(username: string) { const user = await this.userRepository.findOneBy({ username }); if (isNil(user)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); return true; } /** * 注册 */ async register({ username, ...data }: RegisterDto): Promise { const exists = await this.userRepository.findOneBy({ username }); if (!isEmpty(exists)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); await this.entityManager.transaction(async manager => { const salt = randomValue(32); const password = md5(`${data.password ?? 'a123456'}${salt}`); const u = manager.create(UserEntity, { username, password, status: 1, psalt: salt }); const user = await manager.save(u); return user; }); } }