user.service.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import { In, Repository } from 'typeorm'
  2. import { FastifyInstance } from 'fastify'
  3. import { User, UserRole } from '../entities/user.entity'
  4. import bcrypt from 'bcryptjs'
  5. import { PaginationResponse } from '../dto/common.dto'
  6. export class UserService {
  7. private userRepository: Repository<User>
  8. constructor(app: FastifyInstance) {
  9. this.userRepository = app.dataSource.getRepository(User)
  10. }
  11. async create(
  12. password: string | null,
  13. name: string,
  14. role: UserRole = UserRole.USER,
  15. parentId?: number
  16. ): Promise<User> {
  17. const userData: Partial<User> = {
  18. name,
  19. role,
  20. parentId
  21. }
  22. if (password) {
  23. userData.password = await bcrypt.hash(password, 10)
  24. }
  25. const user = this.userRepository.create(userData)
  26. return this.userRepository.save(user)
  27. }
  28. async findByName(name: string): Promise<User | null> {
  29. return this.userRepository.findOne({ where: { name } })
  30. }
  31. async findById(id: number): Promise<User> {
  32. return this.userRepository.findOneOrFail({ where: { id } })
  33. }
  34. async validatePassword(user: User, password: string): Promise<boolean> {
  35. if (!user.password) {
  36. return false
  37. }
  38. return bcrypt.compare(password, user.password)
  39. }
  40. async resetPassword(userId: number, newPassword: string): Promise<void> {
  41. const user = await this.findById(userId)
  42. const hashedPassword = await bcrypt.hash(newPassword, 10)
  43. await this.userRepository.update(user.id, { password: hashedPassword })
  44. }
  45. async list(page: number, size: number, role?: UserRole | UserRole[]): Promise<PaginationResponse<Partial<User>>> {
  46. const [users, total] = await this.userRepository.findAndCount({
  47. skip: (Number(page) || 0) * (Number(size) || 20),
  48. take: Number(size) || 20,
  49. where: {
  50. role: role ? (role instanceof Array ? In(role) : role) : undefined
  51. }
  52. })
  53. return {
  54. content: users.map(user => ({
  55. id: user.id,
  56. name: user.name,
  57. role: user.role,
  58. createdAt: user.createdAt,
  59. updatedAt: user.updatedAt
  60. })),
  61. metadata: {
  62. total: Number(total),
  63. page: Number(page),
  64. size: Number(size)
  65. }
  66. }
  67. }
  68. async findAllUsers(): Promise<Partial<User>[]> {
  69. return this.userRepository.find({
  70. select: ['id', 'name']
  71. })
  72. }
  73. async updateUser(id: number, data: Partial<User>): Promise<User> {
  74. if (data.password) {
  75. data.password = await bcrypt.hash(data.password, 10)
  76. }
  77. await this.userRepository.update(id, data)
  78. return this.findById(id)
  79. }
  80. async findChildChannelUsers(parentId: number): Promise<Partial<User>[]> {
  81. const result: Partial<User>[] = []
  82. const findChildren = async (pid: number) => {
  83. const users = await this.userRepository.find({
  84. select: ['id', 'name', 'role'],
  85. where: { parentId: pid }
  86. })
  87. result.push(...users)
  88. for (const user of users) {
  89. await findChildren(user.id)
  90. }
  91. }
  92. await findChildren(parentId)
  93. return result
  94. }
  95. async findAllChildUsers(
  96. parentId: number,
  97. page: number = 0,
  98. size: number = 20
  99. ): Promise<PaginationResponse<Partial<User>>> {
  100. const pageNum = Number(page) || 0
  101. const sizeNum = Number(size) || 20
  102. const offset = pageNum * sizeNum
  103. const queryRunner = this.userRepository.manager.connection.createQueryRunner()
  104. try {
  105. // 获取表名(使用反引号包裹,因为 user 可能是 MySQL 保留字)
  106. const tableName = this.userRepository.metadata.tableName
  107. const escapedTableName = `\`${tableName}\``
  108. // 检测MySQL版本,MySQL 8.0+支持递归CTE
  109. const versionResult = await queryRunner.query('SELECT VERSION() as version')
  110. const version = versionResult[0]?.version || ''
  111. const majorVersion = parseInt(version.split('.')[0]) || 0
  112. const minorVersion = parseInt(version.split('.')[1]) || 0
  113. const supportsRecursiveCTE = majorVersion > 8 || (majorVersion === 8 && minorVersion >= 0)
  114. if (supportsRecursiveCTE) {
  115. // MySQL 8.0+ 使用递归CTE
  116. return await this.findAllChildUsersWithRecursiveCTE(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
  117. } else {
  118. // MySQL 5.7及以下使用递归查询替代方案
  119. return await this.findAllChildUsersWithIteration(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
  120. }
  121. } catch (error) {
  122. // 如果递归CTE失败,回退到迭代方案
  123. try {
  124. const tableName = this.userRepository.metadata.tableName
  125. const escapedTableName = `\`${tableName}\``
  126. return await this.findAllChildUsersWithIteration(queryRunner, escapedTableName, parentId, pageNum, sizeNum, offset)
  127. } catch (fallbackError) {
  128. throw new Error(`查询用户列表失败: ${error instanceof Error ? error.message : String(error)}`)
  129. }
  130. } finally {
  131. await queryRunner.release()
  132. }
  133. }
  134. private async findAllChildUsersWithRecursiveCTE(
  135. queryRunner: any,
  136. escapedTableName: string,
  137. parentId: number,
  138. pageNum: number,
  139. sizeNum: number,
  140. offset: number
  141. ): Promise<PaginationResponse<Partial<User>>> {
  142. // 先获取总数
  143. const totalQuery = `
  144. WITH RECURSIVE user_tree AS (
  145. -- 基础查询:当前用户
  146. SELECT id, name, role, parentId, createdAt, updatedAt
  147. FROM ${escapedTableName}
  148. WHERE id = ?
  149. UNION ALL
  150. -- 递归查询:所有子用户
  151. SELECT u.id, u.name, u.role, u.parentId, u.createdAt, u.updatedAt
  152. FROM ${escapedTableName} u
  153. INNER JOIN user_tree ut ON u.parentId = ut.id
  154. )
  155. SELECT COUNT(*) as total FROM user_tree
  156. `
  157. const totalResult = await queryRunner.query(totalQuery, [parentId])
  158. const total = parseInt(totalResult[0]?.total || '0', 10)
  159. // 获取分页数据
  160. const dataQuery = `
  161. WITH RECURSIVE user_tree AS (
  162. -- 基础查询:当前用户
  163. SELECT id, name, role, parentId, createdAt, updatedAt
  164. FROM ${escapedTableName}
  165. WHERE id = ?
  166. UNION ALL
  167. -- 递归查询:所有子用户
  168. SELECT u.id, u.name, u.role, u.parentId, u.createdAt, u.updatedAt
  169. FROM ${escapedTableName} u
  170. INNER JOIN user_tree ut ON u.parentId = ut.id
  171. )
  172. SELECT id, name, role, parentId, createdAt, updatedAt
  173. FROM user_tree
  174. ORDER BY id ASC
  175. LIMIT ? OFFSET ?
  176. `
  177. const users = await queryRunner.query(dataQuery, [parentId, sizeNum, offset])
  178. return {
  179. content: users.map((user: any) => ({
  180. id: user.id,
  181. name: user.name,
  182. role: user.role,
  183. parentId: user.parentId,
  184. createdAt: user.createdAt,
  185. updatedAt: user.updatedAt
  186. })),
  187. metadata: {
  188. total,
  189. page: pageNum,
  190. size: sizeNum
  191. }
  192. }
  193. }
  194. private async findAllChildUsersWithIteration(
  195. queryRunner: any,
  196. escapedTableName: string,
  197. parentId: number,
  198. pageNum: number,
  199. sizeNum: number,
  200. offset: number
  201. ): Promise<PaginationResponse<Partial<User>>> {
  202. // 使用迭代方式收集所有子用户ID(兼容MySQL 5.7)
  203. const allUserIds: number[] = [parentId] // 先添加父用户本身
  204. const processedIds = new Set<number>([parentId])
  205. let currentLevelIds = [parentId]
  206. while (currentLevelIds.length > 0) {
  207. const placeholders = currentLevelIds.map(() => '?').join(',')
  208. const query = `
  209. SELECT id, name, role, parentId, createdAt, updatedAt
  210. FROM ${escapedTableName}
  211. WHERE parentId IN (${placeholders})
  212. `
  213. const children = await queryRunner.query(query, currentLevelIds)
  214. // 准备下一层级
  215. const nextLevelIds: number[] = []
  216. for (const child of children) {
  217. if (!processedIds.has(child.id)) {
  218. allUserIds.push(child.id)
  219. processedIds.add(child.id)
  220. nextLevelIds.push(child.id)
  221. }
  222. }
  223. currentLevelIds = nextLevelIds
  224. }
  225. const total = allUserIds.length
  226. // 分页处理
  227. const paginatedIds = allUserIds.slice(offset, offset + sizeNum)
  228. if (paginatedIds.length === 0) {
  229. return {
  230. content: [],
  231. metadata: {
  232. total,
  233. page: pageNum,
  234. size: sizeNum
  235. }
  236. }
  237. }
  238. // 根据ID获取用户详细信息
  239. const placeholders = paginatedIds.map(() => '?').join(',')
  240. const dataQuery = `
  241. SELECT id, name, role, parentId, createdAt, updatedAt
  242. FROM ${escapedTableName}
  243. WHERE id IN (${placeholders})
  244. ORDER BY id ASC
  245. `
  246. const users = await queryRunner.query(dataQuery, paginatedIds)
  247. return {
  248. content: users.map((user: any) => ({
  249. id: user.id,
  250. name: user.name,
  251. role: user.role,
  252. parentId: user.parentId,
  253. createdAt: user.createdAt,
  254. updatedAt: user.updatedAt
  255. })),
  256. metadata: {
  257. total,
  258. page: pageNum,
  259. size: sizeNum
  260. }
  261. }
  262. }
  263. }