Browse Source

✨ feat: 增加管理端开启、暂停任务权限

Pchen. 1 month ago
parent
commit
09855d3eed
3 changed files with 105 additions and 0 deletions
  1. 48 0
      apis/QK/Admin/Admin.js
  2. 2 0
      lib/PermissionCatalog.js
  3. 55 0
      lib/QK/TaskScheduler.js

+ 48 - 0
apis/QK/Admin/Admin.js

@@ -164,6 +164,52 @@ class RetryTask extends API {
     }
 }
 
+class StartTask extends API {
+    constructor() {
+        super()
+        this.setPath('/Admin/QK/Task/Start')
+        this.setMethod('POST')
+    }
+
+    async onRequest(req, res) {
+        try {
+            const { id } = req.body
+            if (!id) {
+                return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
+            }
+            await scheduler.adminStartTask(id)
+            this.logger.info(`[QK][API][AdminStartTask] 管理员开启任务 taskId=${id}`)
+            return res.json({ ...BaseStdResponse.OK })
+        } catch (err) {
+            this.logger.error(`管理员开启抢课任务失败:${err.stack || err}`)
+            return fail(res, err, '开启抢课任务失败')
+        }
+    }
+}
+
+class PauseTask extends API {
+    constructor() {
+        super()
+        this.setPath('/Admin/QK/Task/Pause')
+        this.setMethod('POST')
+    }
+
+    async onRequest(req, res) {
+        try {
+            const { id } = req.body
+            if (!id) {
+                return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
+            }
+            await scheduler.adminPauseTask(id)
+            this.logger.info(`[QK][API][AdminPauseTask] 管理员暂停任务 taskId=${id}`)
+            return res.json({ ...BaseStdResponse.OK })
+        } catch (err) {
+            this.logger.error(`管理员暂停抢课任务失败:${err.stack || err}`)
+            return fail(res, err, '暂停抢课任务失败')
+        }
+    }
+}
+
 class ListReport extends API {
     constructor() {
         super()
@@ -236,6 +282,8 @@ module.exports = {
     UpdateTask,
     CancelTask,
     RetryTask,
+    StartTask,
+    PauseTask,
     ListReport,
     ListBatch,
     SaveBatch

+ 2 - 0
lib/PermissionCatalog.js

@@ -113,6 +113,8 @@ const DEFAULT_PERMISSION_RESOURCE_RULES = [
     { resource_type: 'api', resource_key: 'POST /Admin/QK/Task/Update', api_method: 'POST', api_path: '/Admin/QK/Task/Update', required_codes: ['action.qk.admin.taskManage'] },
     { resource_type: 'api', resource_key: 'DELETE /Admin/QK/Task/Cancel', api_method: 'DELETE', api_path: '/Admin/QK/Task/Cancel', required_codes: ['action.qk.admin.taskManage'] },
     { resource_type: 'api', resource_key: 'POST /Admin/QK/Task/Retry', api_method: 'POST', api_path: '/Admin/QK/Task/Retry', required_codes: ['action.qk.admin.taskManage'] },
+    { resource_type: 'api', resource_key: 'POST /Admin/QK/Task/Start', api_method: 'POST', api_path: '/Admin/QK/Task/Start', required_codes: ['action.qk.admin.taskManage'] },
+    { resource_type: 'api', resource_key: 'POST /Admin/QK/Task/Pause', api_method: 'POST', api_path: '/Admin/QK/Task/Pause', required_codes: ['action.qk.admin.taskManage'] },
     { resource_type: 'api', resource_key: 'GET /QK/Batch', api_method: 'GET', api_path: '/QK/Batch', required_codes: QK_ASSISTANT_ACCESS_CODES },
     { resource_type: 'api', resource_key: 'GET /Admin/QK/Batch/List', api_method: 'GET', api_path: '/Admin/QK/Batch/List', required_codes: ['page.admin.qk.batch'] },
     { resource_type: 'api', resource_key: 'POST /Admin/QK/Batch/Save', api_method: 'POST', api_path: '/Admin/QK/Batch/Save', required_codes: ['action.qk.admin.batchManage'] },

+ 55 - 0
lib/QK/TaskScheduler.js

@@ -824,6 +824,61 @@ class TaskScheduler {
         this.logInfo('adminRetryTask', '管理员已重试抢课任务', { taskId })
     }
 
+    async adminStartTask(taskId) {
+        const rows = await db.query('SELECT * FROM qk_task WHERE id = ?', [taskId])
+        if (!rows || rows.length === 0) {
+            throw new Error('任务不存在')
+        }
+        const task = rows[0]
+        if (![TASK_STATUS.PAUSED, TASK_STATUS.FAILED].includes(task.status)) {
+            throw new Error('仅未开始或失败的任务可开启')
+        }
+        if (task.batch_id) {
+            await this.requireEnabledBatch(task.batch_id)
+        }
+        const now = Date.now()
+        const result = await db.query(
+            `UPDATE qk_task SET status = ?, update_time = ?, assigned_client_id = NULL, lease_expire_at = NULL,
+            assigned_at = NULL, exclude_client_id = NULL, result_json = NULL, error_msg = NULL, finished_time = NULL
+            WHERE id = ? AND status IN (?, ?)`,
+            [TASK_STATUS.PENDING, now, taskId, TASK_STATUS.PAUSED, TASK_STATUS.FAILED]
+        )
+        if (!result || result.affectedRows <= 0) {
+            throw new Error('开启抢课任务失败')
+        }
+        await this.logTask(taskId, null, 'admin_started', '管理员开启抢课任务,等待分配')
+        this.logInfo('adminStartTask', '管理员已开启抢课任务', { taskId })
+    }
+
+    async adminPauseTask(taskId) {
+        const rows = await db.query('SELECT * FROM qk_task WHERE id = ?', [taskId])
+        if (!rows || rows.length === 0) {
+            throw new Error('任务不存在')
+        }
+        const task = rows[0]
+        if (![TASK_STATUS.PENDING, TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING].includes(task.status)) {
+            throw new Error('当前状态不可暂停')
+        }
+        const now = Date.now()
+        const wasAssigned = [TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING].includes(task.status)
+        if (wasAssigned && task.assigned_client_id) {
+            await this.decrementClientSlots(task.assigned_client_id, now)
+        }
+        const result = await db.query(
+            `UPDATE qk_task SET status = ?, update_time = ?, assigned_client_id = NULL, lease_expire_at = NULL,
+            assigned_at = NULL, exclude_client_id = NULL
+            WHERE id = ? AND status IN (?, ?, ?)`,
+            [TASK_STATUS.PAUSED, now, taskId, TASK_STATUS.PENDING, TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING]
+        )
+        if (!result || result.affectedRows <= 0) {
+            throw new Error('暂停抢课任务失败')
+        }
+        await this.logTask(taskId, task.assigned_client_id, 'admin_paused', wasAssigned ? '管理员暂停任务,已收回客户端' : '管理员暂停抢课任务')
+        this.logInfo('adminPauseTask', '管理员已暂停抢课任务', { taskId }, {
+            released_from_client: wasAssigned ? task.assigned_client_id : null
+        })
+    }
+
     async authenticateClient(clientId, clientSecret) {
         if (!clientId || !clientSecret) {
             this.logWarn('authClient', '客户端认证失败:缺少凭证', { clientId: clientId || 'unknown' })