Browse Source

task 相关

wuyi 1 tháng trước cách đây
mục cha
commit
1f5592e3d6

+ 2 - 1
src/app.ts

@@ -15,6 +15,7 @@ import fishRoutes from './routes/fish.routes'
 import fishFriendsRoutes from './routes/fish-friends.routes'
 import fishFriendsRoutes from './routes/fish-friends.routes'
 import messagesRoutes from './routes/messages.routes'
 import messagesRoutes from './routes/messages.routes'
 import tgMsgSendRoutes from './routes/tg-msg-send.routes'
 import tgMsgSendRoutes from './routes/tg-msg-send.routes'
+import taskRoutes from './routes/task.routes'
 
 
 const options: FastifyEnvOptions = {
 const options: FastifyEnvOptions = {
   schema: schema,
   schema: schema,
@@ -86,7 +87,7 @@ export const createApp = async () => {
   app.register(fishFriendsRoutes, { prefix: '/api/fish-friends' })
   app.register(fishFriendsRoutes, { prefix: '/api/fish-friends' })
   app.register(messagesRoutes, { prefix: '/api/messages' })
   app.register(messagesRoutes, { prefix: '/api/messages' })
   app.register(tgMsgSendRoutes, { prefix: '/api/tg-msg-send' })
   app.register(tgMsgSendRoutes, { prefix: '/api/tg-msg-send' })
-
+  app.register(taskRoutes, { prefix: '/api/tasks' })
   const dataSource = createDataSource(app)
   const dataSource = createDataSource(app)
   await dataSource.initialize()
   await dataSource.initialize()
   app.decorate('dataSource', dataSource)
   app.decorate('dataSource', dataSource)

+ 148 - 0
src/controllers/task.controller.ts

@@ -0,0 +1,148 @@
+import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
+import { TaskService } from '../services/task.service'
+import { UpdateTaskBody, ListTaskQuery, CreateTaskItemBody, ListTaskItemQuery } from '../dto/task.dto'
+import { Task } from '../entities/task.entity'
+
+export class TaskController {
+  private taskService: TaskService
+
+  constructor(app: FastifyInstance) {
+    this.taskService = new TaskService(app)
+  }
+
+  async create(request: FastifyRequest, reply: FastifyReply) {
+    try {
+      const userId = request.user.id
+
+      const data = await request.file()
+      if (!data) {
+        return reply.code(400).send({ message: '请选择要上传的文件' })
+      }
+
+      const nameField = data.fields['name']
+      const messageField = data.fields['message']
+
+      const name =
+        nameField && !Array.isArray(nameField) && 'value' in nameField ? (nameField.value as string) : undefined
+      const message =
+        messageField && !Array.isArray(messageField) && 'value' in messageField
+          ? (messageField.value as string)
+          : undefined
+
+      if (!name || !message) {
+        return reply.code(400).send({ message: '任务名称和消息内容不能为空' })
+      }
+
+      const buffer = await data.toBuffer()
+
+      const task = await this.taskService.create({
+        name,
+        message,
+        userId,
+        buffer
+      })
+
+      return reply.code(201).send({
+        task: {
+          id: task.id,
+          name: task.name,
+          message: task.message
+        }
+      })
+    } catch (error) {
+      return reply.code(500).send(error)
+    }
+  }
+
+  async getById(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
+    try {
+      const id = parseInt(request.params.id)
+      if (isNaN(id)) {
+        return reply.code(400).send({ message: '无效的任务ID' })
+      }
+
+      const task = await this.taskService.findById(id)
+      return reply.send(task)
+    } catch (error) {
+      return reply.code(500).send({ error })
+    }
+  }
+
+  async list(request: FastifyRequest<{ Querystring: ListTaskQuery }>, reply: FastifyReply) {
+    try {
+      const { page, size, userId } = request.query
+      const result = await this.taskService.findAll(Number(page) || 0, Number(size) || 20, userId || request.user.id)
+
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send({ error })
+    }
+  }
+
+  async update(request: FastifyRequest<{ Body: UpdateTaskBody }>, reply: FastifyReply) {
+    try {
+      const { id, name, message, total, sent, successCount, startedAt } = request.body
+
+      if (!id) {
+        return reply.code(400).send({ message: '任务ID不能为空' })
+      }
+
+      const task = await this.taskService.findById(id)
+      if (!task) {
+        return reply.code(500).send({ message: '任务不存在' })
+      }
+
+      const updateData: Partial<Task> = {}
+      if (name !== undefined) updateData.name = name
+      if (message !== undefined) updateData.message = message
+      if (total !== undefined) updateData.total = total
+      if (sent !== undefined) updateData.sent = sent
+      if (successCount !== undefined) updateData.successCount = successCount
+      if (startedAt !== undefined) updateData.startedAt = startedAt
+
+      await this.taskService.update(id, updateData)
+
+      return reply.send({ message: '任务更新成功' })
+    } catch (error) {
+      return reply.code(500).send({ error })
+    }
+  }
+
+  async delete(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
+    try {
+      const id = parseInt(request.params.id)
+      if (isNaN(id)) {
+        return reply.code(400).send({ message: '无效的任务ID' })
+      }
+
+      const task = await this.taskService.findById(id)
+      if (!task) {
+        return reply.code(500).send({ message: '任务不存在' })
+      }
+
+      await this.taskService.delete(id)
+      return reply.send({ message: '任务删除成功' })
+    } catch (error) {
+      return reply.code(500).send(error)
+    }
+  }
+
+  async listTaskItems(request: FastifyRequest<{ Querystring: ListTaskItemQuery }>, reply: FastifyReply) {
+    try {
+      const { page, size, taskId, status } = request.query
+      const result = await this.taskService.findTaskItems(
+        Number(page) || 0,
+        Number(size) || 20,
+        taskId ? Number(taskId) : undefined,
+        status
+      )
+
+      return reply.send(result)
+    } catch (error) {
+      return reply.code(500).send({
+        message: '查询任务项失败',
+        error: error instanceof Error ? error.message : '未知错误'
+      })
+    }
+  }
+}

+ 34 - 0
src/dto/task.dto.ts

@@ -0,0 +1,34 @@
+import { FastifyRequest } from 'fastify'
+import { Pagination } from './common.dto'
+
+export interface CreateTaskBody {
+  name: string
+  message: string
+  file: any
+  userId?: number
+}
+
+export interface UpdateTaskBody {
+  id: number
+  name?: string
+  message?: string
+  total?: number
+  sent?: number
+  successCount?: number
+  startedAt?: Date
+}
+
+export interface ListTaskQuery extends Pagination {
+  userId?: number
+}
+
+export interface CreateTaskItemBody {
+  taskId: number
+  target: string
+}
+
+export interface ListTaskItemQuery extends Pagination {
+  taskId?: number
+  status?: string
+}
+

+ 35 - 0
src/entities/task-item.entity.ts

@@ -0,0 +1,35 @@
+import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
+
+export enum TaskItemStatus {
+  PENDING = 'pending',
+  SUCCESS = 'success',
+  FAILED = 'failed'
+}
+
+@Entity()
+export class TaskItem {
+  @PrimaryGeneratedColumn()
+  id: number
+
+  @Column()
+  taskId: number
+
+  @Column()
+  target: string
+
+  @Column({
+    type: 'enum',
+    enum: TaskItemStatus,
+    default: TaskItemStatus.PENDING
+  })
+  status: TaskItemStatus
+
+  @Column({ type: 'datetime', precision: 6, default: null })
+  sentAt: Date
+
+  @CreateDateColumn()
+  createdAt: Date
+
+  @UpdateDateColumn()
+  updatedAt: Date
+}

+ 37 - 0
src/entities/task.entity.ts

@@ -0,0 +1,37 @@
+import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'
+
+@Entity()
+export class Task {
+  @PrimaryGeneratedColumn()
+  id: number
+
+  @Column()
+  name: string
+
+  @Column()
+  userId: number
+
+  @Column({ type: 'text', nullable: false })
+  message: string
+
+  @Column({ default: 0 })
+  total: number
+
+  @Column({ default: 0 })
+  sent: number
+
+  @Column({ default: 0 })
+  successCount: number
+
+  @Column({ default: false })
+  delFlag: boolean
+
+  @Column({ type: 'datetime', precision: 6, default: null })
+  startedAt: Date
+
+  @CreateDateColumn()
+  createdAt: Date
+
+  @UpdateDateColumn()
+  updatedAt: Date
+}

+ 41 - 0
src/routes/task.routes.ts

@@ -0,0 +1,41 @@
+import { FastifyInstance } from 'fastify'
+import { TaskController } from '../controllers/task.controller'
+import { hasRole } from '../middlewares/auth.middleware'
+import { UpdateTaskBody, ListTaskQuery, CreateTaskItemBody, ListTaskItemQuery } from '../dto/task.dto'
+import { UserRole } from '../entities/user.entity'
+
+export default async function taskRoutes(fastify: FastifyInstance) {
+  const taskController = new TaskController(fastify)
+
+  fastify.post('/', { onRequest: [hasRole(UserRole.ADMIN)] }, taskController.create.bind(taskController))
+
+  fastify.get<{ Querystring: ListTaskQuery }>(
+    '/',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    taskController.list.bind(taskController)
+  )
+
+  fastify.get<{ Params: { id: string } }>(
+    '/:id',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    taskController.getById.bind(taskController)
+  )
+
+  fastify.post<{ Body: UpdateTaskBody }>(
+    '/update',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    taskController.update.bind(taskController)
+  )
+
+  fastify.delete<{ Params: { id: string } }>(
+    '/:id',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    taskController.delete.bind(taskController)
+  )
+
+  fastify.get<{ Querystring: ListTaskItemQuery }>(
+    '/items',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    taskController.listTaskItems.bind(taskController)
+  )
+}

+ 111 - 0
src/services/task.service.ts

@@ -0,0 +1,111 @@
+import { Repository } from 'typeorm'
+import { FastifyInstance } from 'fastify'
+import { Task } from '../entities/task.entity'
+import { TaskItem, TaskItemStatus } from '../entities/task-item.entity'
+import { PaginationResponse } from '../dto/common.dto'
+
+export class TaskService {
+  private taskRepository: Repository<Task>
+  private taskItemRepository: Repository<TaskItem>
+
+  constructor(app: FastifyInstance) {
+    this.taskRepository = app.dataSource.getRepository(Task)
+    this.taskItemRepository = app.dataSource.getRepository(TaskItem)
+  }
+
+  async create(data: { name: string; message: string; userId: number; buffer: Buffer }): Promise<Task> {
+    const task = this.taskRepository.create({
+      name: data.name,
+      message: data.message,
+      userId: data.userId
+    })
+    const savedTask = await this.taskRepository.save(task)
+    const total = await this.createTaskItemByBuffer({ taskId: savedTask.id, buffer: data.buffer })
+    await this.taskRepository.update(savedTask.id, { total })
+    return await this.taskRepository.findOneOrFail({ where: { id: savedTask.id } })
+  }
+
+  async findById(id: number): Promise<Task> {
+    return await this.taskRepository.findOneOrFail({ where: { id, delFlag: false } })
+  }
+
+  async findAll(page: number = 0, size: number = 20, userId?: number): Promise<PaginationResponse<Task>> {
+    const where = userId ? { userId } : {}
+    const [tasks, total] = await this.taskRepository.findAndCount({
+      where,
+      skip: (Number(page) || 0) * (Number(size) || 20),
+      take: Number(size) || 20,
+      order: {
+        createdAt: 'DESC'
+      }
+    })
+
+    return {
+      content: tasks,
+      metadata: {
+        total: Number(total),
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
+
+  async update(id: number, data: Partial<Task>): Promise<void> {
+    await this.taskRepository.update(id, data)
+  }
+
+  async delete(id: number): Promise<void> {
+    await this.taskRepository.update(id, { delFlag: true })
+  }
+
+  async createTaskItemByBuffer(data: { taskId: number; buffer: Buffer }): Promise<number> {
+    const content = data.buffer.toString('utf-8')
+    const lines = content.split('\n').filter(line => line.trim())
+    if (lines.length === 0) {
+      return 0
+    }
+    const taskItems = lines.map(line =>
+      this.taskItemRepository.create({
+        taskId: data.taskId,
+        target: line.trim(),
+        status: TaskItemStatus.PENDING
+        
+      })
+    )
+    await this.taskItemRepository.save(taskItems)
+    return taskItems.length
+  }
+
+  async findTaskItems(
+    page: number = 0,
+    size: number = 20,
+    taskId?: number,
+    status?: string
+  ): Promise<PaginationResponse<TaskItem>> {
+    const where: any = {}
+    if (taskId) {
+      where.taskId = taskId
+    }
+    if (status) {
+      where.status = status
+    }
+
+    const [taskItems, total] = await this.taskItemRepository.findAndCount({
+      where,
+      skip: (Number(page) || 0) * (Number(size) || 20),
+      take: Number(size) || 20,
+      order: {
+        createdAt: 'DESC'
+      }
+    })
+
+    return {
+      content: taskItems,
+      metadata: {
+        total: Number(total),
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
+}