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

373 lines
10 KiB
TypeScript
Raw Normal View History

2024-02-28 08:32:35 +08:00
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'
2024-02-28 08:32:35 +08:00
import { EntityManager, In, Like, Repository } from 'typeorm'
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')
.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,
}: 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),
2024-02-28 08:32:35 +08:00
})
if (deptId)
queryBuilder.andWhere('dept.id = :deptId', { deptId })
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
})
}
}