oa_based/src/modules/user/user.service.ts

364 lines
11 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 { 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<UserEntity>,
@InjectRepository(RoleEntity)
private readonly roleRepository: Repository<RoleEntity>,
@InjectEntityManager() private entityManager: EntityManager,
private readonly paramConfigService: ParamConfigService,
private readonly qqService: QQService
) {}
async findUserById(id: number): Promise<UserEntity | undefined> {
return this.userRepository
.createQueryBuilder('user')
.where({
id,
status: UserStatus.Enabled
})
.getOne();
}
async findUserByUserName(username: string): Promise<UserEntity | undefined> {
return this.userRepository
.createQueryBuilder('user')
.where({
username,
status: UserStatus.Enabled
})
.getOne();
}
/**
* 获取用户信息
* @param uid user id
*/
async getAccountInfo(uid: number): Promise<AccountInfo> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<UserEntity> {
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<void | never> {
const rootUserId = await this.findRootUserId();
if (userIds.includes(rootUserId)) throw new BadRequestException('不能删除root用户!');
await this.userRepository.delete(userIds);
}
/**
* 查找超管的用户ID
*/
async findRootUserId(): Promise<number> {
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<Pagination<UserEntity>> {
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<UserEntity>(queryBuilder, {
page,
pageSize
});
}
/**
* 禁用用户
*/
async forbidden(uid: number): Promise<void> {
await this.redis.del(genAuthPVKey(uid));
await this.redis.del(genAuthTokenKey(uid));
await this.redis.del(genAuthPermKey(uid));
}
/**
* 禁用多个用户
*/
async multiForbidden(uids: number[]): Promise<void> {
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<void> {
// 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<void> {
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;
});
}
}