364 lines
11 KiB
TypeScript
364 lines
11 KiB
TypeScript
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;
|
||
});
|
||
}
|
||
}
|