task.controller.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
  2. import { TaskService } from '../services/task.service'
  3. import { UpdateTaskBody, ListTaskQuery, ListTaskItemQuery } from '../dto/task.dto'
  4. import { Task, TaskType } from '../entities/task.entity'
  5. export class TaskController {
  6. private taskService: TaskService
  7. constructor(app: FastifyInstance) {
  8. this.taskService = new TaskService(app)
  9. }
  10. private parseIntervalTime(input: unknown): { ok: true; value: string } | { ok: false; message: string } {
  11. if (input === undefined || input === null || input === '') {
  12. return { ok: true, value: '5-5' }
  13. }
  14. const raw = String(input).trim()
  15. // 允许: "10", "10-20", "10~20", "10,20", "10 20"
  16. const normalized = raw.replace('~', '-').replace(',', '-').replace(/\s+/g, '-')
  17. const parts = normalized.split('-').filter(Boolean)
  18. if (parts.length === 1) {
  19. const s = Number(parts[0])
  20. if (Number.isNaN(s) || s < 0) return { ok: false, message: 'intervalTime 必须为大于等于 0 的秒数或区间(如 10-20)' }
  21. return { ok: true, value: `${s}-${s}` }
  22. }
  23. if (parts.length === 2) {
  24. const a = Number(parts[0])
  25. const b = Number(parts[1])
  26. if (Number.isNaN(a) || Number.isNaN(b) || a < 0 || b < 0) {
  27. return { ok: false, message: 'intervalTime 必须为大于等于 0 的秒数区间(如 10-20)' }
  28. }
  29. const min = Math.min(a, b)
  30. const max = Math.max(a, b)
  31. return { ok: true, value: `${min}-${max}` }
  32. }
  33. return { ok: false, message: 'intervalTime 格式错误,示例:10 或 10-20' }
  34. }
  35. async create(request: FastifyRequest, reply: FastifyReply) {
  36. try {
  37. const userId = request.user.id
  38. const data = await request.file()
  39. if (!data) {
  40. return reply.code(400).send({ message: '请选择要上传的文件' })
  41. }
  42. const nameField = data.fields['name']
  43. const typeField = data.fields['type']
  44. const messageField = data.fields['message']
  45. const inviteLinkField = data.fields['inviteLink']
  46. // 新字段
  47. const accountLimitField = data.fields['accountLimit']
  48. const intervalTimeField = data.fields['intervalTime']
  49. const threadsField = data.fields['threads']
  50. const name =
  51. nameField && !Array.isArray(nameField) && 'value' in nameField ? (nameField.value as string) : undefined
  52. const type =
  53. typeField && !Array.isArray(typeField) && 'value' in typeField ? (typeField.value as string) : undefined
  54. const message =
  55. messageField && !Array.isArray(messageField) && 'value' in messageField
  56. ? (messageField.value as string)
  57. : undefined
  58. const inviteLink =
  59. inviteLinkField && !Array.isArray(inviteLinkField) && 'value' in inviteLinkField
  60. ? (inviteLinkField.value as string)
  61. : undefined
  62. const accountLimit =
  63. accountLimitField && !Array.isArray(accountLimitField) && 'value' in accountLimitField
  64. ? Number(accountLimitField.value)
  65. : undefined
  66. const intervalRaw =
  67. intervalTimeField && !Array.isArray(intervalTimeField) && 'value' in intervalTimeField
  68. ? intervalTimeField.value
  69. : undefined
  70. const intervalParsed = this.parseIntervalTime(intervalRaw)
  71. if (!intervalParsed.ok) {
  72. return reply.code(400).send({ message: intervalParsed.message })
  73. }
  74. const threads =
  75. threadsField && !Array.isArray(threadsField) && 'value' in threadsField ? Number(threadsField.value) : undefined
  76. if (!name) {
  77. return reply.code(400).send({ message: '任务名称不能为空' })
  78. }
  79. const taskType: TaskType =
  80. type === TaskType.INVITE_TO_GROUP || type === TaskType.SEND_MESSAGE ? (type as TaskType) : TaskType.SEND_MESSAGE
  81. if (accountLimit !== undefined && (isNaN(accountLimit) || accountLimit <= 0)) {
  82. return reply.code(400).send({ message: 'accountLimit 必须为大于 0 的数字' })
  83. }
  84. if (threads !== undefined && (isNaN(threads) || threads <= 0)) {
  85. return reply.code(400).send({ message: 'threads 必须为大于 0 的数字' })
  86. }
  87. if (threads !== undefined && threads > 10) {
  88. return reply.code(400).send({ message: 'threads 最大值为 10' })
  89. }
  90. // payload 仅存任务特有配置
  91. let payload: Record<string, any> = {}
  92. if (taskType === TaskType.SEND_MESSAGE) {
  93. if (!message) {
  94. return reply.code(400).send({ message: 'SEND_MESSAGE 任务 message 不能为空' })
  95. }
  96. payload = { message }
  97. } else if (taskType === TaskType.INVITE_TO_GROUP) {
  98. if (!inviteLink || !inviteLink.trim()) {
  99. return reply.code(400).send({ message: 'INVITE_TO_GROUP 任务 inviteLink 不能为空' })
  100. }
  101. payload = { inviteLink: inviteLink.trim() }
  102. }
  103. const buffer = await data.toBuffer()
  104. const task = await this.taskService.create({
  105. name,
  106. userId,
  107. buffer,
  108. type: taskType,
  109. payload,
  110. accountLimit,
  111. intervalTime: intervalParsed.value,
  112. threads
  113. })
  114. return reply.code(201).send({
  115. task: {
  116. id: task.id,
  117. name: task.name,
  118. type: task.type,
  119. payload: task.payload,
  120. accountLimit: task.accountLimit,
  121. intervalTime: task.intervalTime,
  122. threads: task.threads,
  123. processed: task.processed,
  124. success: task.success,
  125. total: task.total
  126. }
  127. })
  128. } catch (error) {
  129. return reply.code(500).send(error)
  130. }
  131. }
  132. async getById(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  133. try {
  134. const id = parseInt(request.params.id)
  135. if (isNaN(id)) {
  136. return reply.code(400).send({ message: '无效的任务ID' })
  137. }
  138. const task = await this.taskService.findById(id)
  139. return reply.send(task)
  140. } catch (error) {
  141. return reply.code(500).send({ error })
  142. }
  143. }
  144. async list(request: FastifyRequest<{ Querystring: ListTaskQuery }>, reply: FastifyReply) {
  145. try {
  146. const { page, size, userId } = request.query
  147. const result = await this.taskService.findAll(Number(page) || 0, Number(size) || 20, userId || request.user.id)
  148. return reply.send(result)
  149. } catch (error) {
  150. return reply.code(500).send({ error })
  151. }
  152. }
  153. async update(request: FastifyRequest<{ Body: UpdateTaskBody }>, reply: FastifyReply) {
  154. try {
  155. const { id, name, payload, total, processed, success, startedAt, accountLimit, intervalTime, threads } =
  156. request.body
  157. if (!id) {
  158. return reply.code(400).send({ message: '任务ID不能为空' })
  159. }
  160. const task = await this.taskService.findById(id)
  161. if (!task) {
  162. return reply.code(500).send({ message: '任务不存在' })
  163. }
  164. if (accountLimit !== undefined && (isNaN(Number(accountLimit)) || Number(accountLimit) <= 0)) {
  165. return reply.code(400).send({ message: 'accountLimit 必须为大于 0 的数字' })
  166. }
  167. if (intervalTime !== undefined) {
  168. const parsed = this.parseIntervalTime(intervalTime)
  169. if (!parsed.ok) return reply.code(400).send({ message: parsed.message })
  170. }
  171. if (threads !== undefined && (isNaN(Number(threads)) || Number(threads) <= 0)) {
  172. return reply.code(400).send({ message: 'threads 必须为大于 0 的数字' })
  173. }
  174. if (threads !== undefined && Number(threads) > 10) {
  175. return reply.code(400).send({ message: 'threads 最大值为 10' })
  176. }
  177. const updateData: Partial<Task> = {}
  178. if (name !== undefined) updateData.name = name
  179. if (payload !== undefined) updateData.payload = payload as any
  180. if (total !== undefined) updateData.total = total
  181. if (processed !== undefined) updateData.processed = processed
  182. if (success !== undefined) updateData.success = success
  183. if (startedAt !== undefined) updateData.startedAt = startedAt
  184. if (accountLimit !== undefined) updateData.accountLimit = Number(accountLimit)
  185. if (intervalTime !== undefined) {
  186. const parsed = this.parseIntervalTime(intervalTime)
  187. if (parsed.ok) updateData.intervalTime = parsed.value
  188. }
  189. if (threads !== undefined) updateData.threads = Math.min(10, Number(threads))
  190. await this.taskService.update(id, updateData)
  191. return reply.send({ message: '任务更新成功' })
  192. } catch (error) {
  193. return reply.code(500).send({ error })
  194. }
  195. }
  196. async delete(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  197. try {
  198. const id = parseInt(request.params.id)
  199. if (isNaN(id)) {
  200. return reply.code(400).send({ message: '无效的任务ID' })
  201. }
  202. const task = await this.taskService.findById(id)
  203. if (!task) {
  204. return reply.code(500).send({ message: '任务不存在' })
  205. }
  206. await this.taskService.delete(id)
  207. return reply.send({ message: '任务删除成功' })
  208. } catch (error) {
  209. return reply.code(500).send(error)
  210. }
  211. }
  212. async listTaskItems(request: FastifyRequest<{ Querystring: ListTaskItemQuery }>, reply: FastifyReply) {
  213. try {
  214. const { page, size, taskId, status } = request.query
  215. const result = await this.taskService.findTaskItems(
  216. Number(page) || 0,
  217. Number(size) || 20,
  218. taskId ? Number(taskId) : undefined,
  219. status
  220. )
  221. return reply.send(result)
  222. } catch (error) {
  223. return reply.code(500).send({
  224. message: '查询任务项失败',
  225. error: error instanceof Error ? error.message : '未知错误'
  226. })
  227. }
  228. }
  229. async startTask(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  230. try {
  231. const id = parseInt(request.params.id)
  232. if (isNaN(id)) {
  233. return reply.code(400).send({ message: '无效的任务ID' })
  234. }
  235. await this.taskService.startTask(id)
  236. return reply.send({ message: '任务启动成功' })
  237. } catch (error) {
  238. return reply.code(500).send({
  239. message: '启动任务失败',
  240. error: error instanceof Error ? error.message : '未知错误'
  241. })
  242. }
  243. }
  244. async pauseTask(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
  245. try {
  246. const id = parseInt(request.params.id)
  247. if (isNaN(id)) {
  248. return reply.code(400).send({ message: '无效的任务ID' })
  249. }
  250. await this.taskService.pauseTask(id)
  251. return reply.send({ message: '任务已暂停' })
  252. } catch (error) {
  253. return reply.code(500).send({
  254. message: '暂停任务失败',
  255. error: error instanceof Error ? error.message : '未知错误'
  256. })
  257. }
  258. }
  259. }