| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- import { In, Repository } from 'typeorm'
- import { FastifyInstance } from 'fastify'
- import { User, UserRole } from '../entities/user.entity'
- import bcrypt from 'bcryptjs'
- import { PaginationResponse } from '../dto/common.dto'
- export class UserService {
- private userRepository: Repository<User>
- constructor(app: FastifyInstance) {
- this.userRepository = app.dataSource.getRepository(User)
- }
- async create(
- password: string | null,
- name: string,
- role: UserRole = UserRole.USER,
- parentId?: number
- ): Promise<User> {
- const userData: Partial<User> = {
- name,
- role,
- parentId
- }
- if (password) {
- userData.password = await bcrypt.hash(password, 10)
- }
- const user = this.userRepository.create(userData)
- return this.userRepository.save(user)
- }
- async findByName(name: string): Promise<User | null> {
- return this.userRepository.findOne({ where: { name } })
- }
- async findById(id: number): Promise<User> {
- return this.userRepository.findOneOrFail({ where: { id } })
- }
- async validatePassword(user: User, password: string): Promise<boolean> {
- if (!user.password) {
- return false
- }
- return bcrypt.compare(password, user.password)
- }
- async resetPassword(userId: number, newPassword: string): Promise<void> {
- const user = await this.findById(userId)
- const hashedPassword = await bcrypt.hash(newPassword, 10)
- await this.userRepository.update(user.id, { password: hashedPassword })
- }
- async list(page: number, size: number, role?: UserRole | UserRole[]): Promise<PaginationResponse<Partial<User>>> {
- const [users, total] = await this.userRepository.findAndCount({
- skip: (Number(page) || 0) * (Number(size) || 20),
- take: Number(size) || 20,
- where: {
- role: role ? (role instanceof Array ? In(role) : role) : undefined
- }
- })
- return {
- content: users.map(user => ({
- id: user.id,
- name: user.name,
- role: user.role,
- createdAt: user.createdAt,
- updatedAt: user.updatedAt
- })),
- metadata: {
- total: Number(total),
- page: Number(page),
- size: Number(size)
- }
- }
- }
- async findAllUsers(): Promise<Partial<User>[]> {
- return this.userRepository.find({
- select: ['id', 'name']
- })
- }
- async updateUser(id: number, data: Partial<User>): Promise<User> {
- if (data.password) {
- data.password = await bcrypt.hash(data.password, 10)
- }
- await this.userRepository.update(id, data)
- return this.findById(id)
- }
- async findChildChannelUsers(parentId: number): Promise<Partial<User>[]> {
- const result: Partial<User>[] = []
- const findChildren = async (pid: number) => {
- const users = await this.userRepository.find({
- select: ['id', 'name', 'role'],
- where: { parentId: pid }
- })
- result.push(...users)
- for (const user of users) {
- await findChildren(user.id)
- }
- }
- await findChildren(parentId)
- return result
- }
- async findAllChildUsers(
- parentId: number,
- page: number = 0,
- size: number = 20
- ): Promise<PaginationResponse<Partial<User>>> {
- const pageNum = Number(page) || 0
- const sizeNum = Number(size) || 20
- const offset = pageNum * sizeNum
- const queryRunner = this.userRepository.manager.connection.createQueryRunner()
-
- try {
- // 获取表名(使用反引号包裹,因为 user 可能是 MySQL 保留字)
- const tableName = this.userRepository.metadata.tableName
- const escapedTableName = `\`${tableName}\``
- // 检测MySQL版本,MySQL 8.0+支持递归CTE
- const versionResult = await queryRunner.query('SELECT VERSION() as version')
- const version = versionResult[0]?.version || ''
- const majorVersion = parseInt(version.split('.')[0]) || 0
- const minorVersion = parseInt(version.split('.')[1]) || 0
- const supportsRecursiveCTE = majorVersion > 8 || (majorVersion === 8 && minorVersion >= 0)
- if (supportsRecursiveCTE) {
- // MySQL 8.0+ 使用递归CTE
- return await this.findAllChildUsersWithRecursiveCTE(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
- } else {
- // MySQL 5.7及以下使用递归查询替代方案
- return await this.findAllChildUsersWithIteration(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
- }
- } catch (error) {
- // 如果递归CTE失败,回退到迭代方案
- try {
- const tableName = this.userRepository.metadata.tableName
- const escapedTableName = `\`${tableName}\``
- return await this.findAllChildUsersWithIteration(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
- } catch (fallbackError) {
- throw new Error(`查询用户列表失败: ${error instanceof Error ? error.message : String(error)}`)
- }
- } finally {
- await queryRunner.release()
- }
- }
- private async findAllChildUsersWithRecursiveCTE(
- queryRunner: any,
- escapedTableName: string,
- parentId: number,
- pageNum: number,
- sizeNum: number,
- offset: number
- ): Promise<PaginationResponse<Partial<User>>> {
- // 先获取总数
- const totalQuery = `
- WITH RECURSIVE user_tree AS (
- -- 基础查询:当前用户
- SELECT id, name, role, parentId, createdAt, updatedAt
- FROM ${escapedTableName}
- WHERE id = ?
-
- UNION ALL
-
- -- 递归查询:所有子用户
- SELECT u.id, u.name, u.role, u.parentId, u.createdAt, u.updatedAt
- FROM ${escapedTableName} u
- INNER JOIN user_tree ut ON u.parentId = ut.id
- )
- SELECT COUNT(*) as total FROM user_tree
- `
-
- const totalResult = await queryRunner.query(totalQuery, [parentId])
- const total = parseInt(totalResult[0]?.total || '0', 10)
- // 获取分页数据
- const dataQuery = `
- WITH RECURSIVE user_tree AS (
- -- 基础查询:当前用户
- SELECT id, name, role, parentId, createdAt, updatedAt
- FROM ${escapedTableName}
- WHERE id = ?
-
- UNION ALL
-
- -- 递归查询:所有子用户
- SELECT u.id, u.name, u.role, u.parentId, u.createdAt, u.updatedAt
- FROM ${escapedTableName} u
- INNER JOIN user_tree ut ON u.parentId = ut.id
- )
- SELECT id, name, role, parentId, createdAt, updatedAt
- FROM user_tree
- ORDER BY id ASC
- LIMIT ? OFFSET ?
- `
-
- const users = await queryRunner.query(dataQuery, [parentId, sizeNum, offset])
- return {
- content: users.map((user: any) => ({
- id: user.id,
- name: user.name,
- role: user.role,
- parentId: user.parentId,
- createdAt: user.createdAt,
- updatedAt: user.updatedAt
- })),
- metadata: {
- total,
- page: pageNum,
- size: sizeNum
- }
- }
- }
- private async findAllChildUsersWithIteration(
- queryRunner: any,
- escapedTableName: string,
- parentId: number,
- pageNum: number,
- sizeNum: number,
- offset: number
- ): Promise<PaginationResponse<Partial<User>>> {
- // 使用迭代方式收集所有子用户ID(兼容MySQL 5.7)
- const allUserIds: number[] = [parentId] // 先添加父用户本身
- const processedIds = new Set<number>([parentId])
- let currentLevelIds = [parentId]
- while (currentLevelIds.length > 0) {
- const placeholders = currentLevelIds.map(() => '?').join(',')
- const query = `
- SELECT id, name, role, parentId, createdAt, updatedAt
- FROM ${escapedTableName}
- WHERE parentId IN (${placeholders})
- `
-
- const children = await queryRunner.query(query, currentLevelIds)
-
- // 准备下一层级
- const nextLevelIds: number[] = []
- for (const child of children) {
- if (!processedIds.has(child.id)) {
- allUserIds.push(child.id)
- processedIds.add(child.id)
- nextLevelIds.push(child.id)
- }
- }
-
- currentLevelIds = nextLevelIds
- }
- const total = allUserIds.length
- // 分页处理
- const paginatedIds = allUserIds.slice(offset, offset + sizeNum)
-
- if (paginatedIds.length === 0) {
- return {
- content: [],
- metadata: {
- total,
- page: pageNum,
- size: sizeNum
- }
- }
- }
- // 根据ID获取用户详细信息
- const placeholders = paginatedIds.map(() => '?').join(',')
- const dataQuery = `
- SELECT id, name, role, parentId, createdAt, updatedAt
- FROM ${escapedTableName}
- WHERE id IN (${placeholders})
- ORDER BY id ASC
- `
-
- const users = await queryRunner.query(dataQuery, paginatedIds)
- return {
- content: users.map((user: any) => ({
- id: user.id,
- name: user.name,
- role: user.role,
- parentId: user.parentId,
- createdAt: user.createdAt,
- updatedAt: user.updatedAt
- })),
- metadata: {
- total,
- page: pageNum,
- size: sizeNum
- }
- }
- }
- }
|