Browse Source

✨ feat: 优化任务分配方式

Pchen. 1 month ago
parent
commit
dee6520b2d
2 changed files with 35 additions and 5 deletions
  1. 2 2
      lib/PermissionCatalog.js
  2. 33 3
      lib/QK/TaskScheduler.js

+ 2 - 2
lib/PermissionCatalog.js

@@ -29,7 +29,7 @@ const DEFAULT_PERMISSION_POINTS = [
     { code: 'page.admin.goods.sendCountRequestList', name: '赠送审核', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'admin.goods.sendCountRequestList', remark: '访问乐跑次数赠送审核页面' },
     { code: 'page.service.createOrder', name: '提交工单', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'service.createOrder', remark: '访问用户提交工单页面' },
     { code: 'page.lepao.accountList', name: '乐跑账号', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'lepao.accountList', remark: '访问用户乐跑账号页面' },
-    { code: 'page.qk.taskList', name: '抢课助手', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'lepao.qkAssistant', remark: '授权后可访问校园乐跑下的抢课助手;拥有抢课管理页权限的管理员也可直接访问' },
+    { code: 'page.qk.taskList', name: '抢课助手', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'qk.taskList', remark: '访问抢课助手任务列表;拥有抢课管理页权限的管理员也可直接访问' },
     { code: 'page.admin.qk.client', name: '抢课客户端管理', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'qkManage.client', remark: '访问后台抢课客户端管理页面' },
     { code: 'page.admin.qk.task', name: '抢课任务管理', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'qkManage.task', remark: '访问后台抢课任务管理页面' },
     { code: 'page.admin.qk.batch', name: '抢课批次管理', category: PermissionCategory.PAGE, scope_type: 'page', page_route_name: 'qkManage.batch', remark: '访问后台抢课批次管理页面' },
@@ -63,7 +63,7 @@ const DEFAULT_PERMISSION_RESOURCE_RULES = [
     { resource_type: 'page', resource_key: 'admin.goods.sendCountRequestList', required_codes: ['page.admin.goods.sendCountRequestList'] },
     { resource_type: 'page', resource_key: 'service.createOrder', required_codes: ['page.service.createOrder'] },
     { resource_type: 'page', resource_key: 'lepao.accountList', required_codes: ['page.lepao.accountList'] },
-    { resource_type: 'page', resource_key: 'lepao.qkAssistant', required_codes: QK_ASSISTANT_ACCESS_CODES },
+    { resource_type: 'page', resource_key: 'qk.taskList', required_codes: QK_ASSISTANT_ACCESS_CODES },
     { resource_type: 'page', resource_key: 'qkManage.client', required_codes: ['page.admin.qk.client'] },
     { resource_type: 'page', resource_key: 'qkManage.task', required_codes: ['page.admin.qk.task'] },
     { resource_type: 'page', resource_key: 'qkManage.batch', required_codes: ['page.admin.qk.batch'] },

+ 33 - 3
lib/QK/TaskScheduler.js

@@ -65,6 +65,28 @@ class TaskScheduler {
         return Math.max(0, maxSlots - currentSlots)
     }
 
+    getInFlightTaskStatuses() {
+        return [TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING]
+    }
+
+    /** 同一学号在单次拉取中只保留最早创建的一条待分配任务 */
+    pickPullableTasks(rows, limit) {
+        const picked = []
+        const seenStudentNums = new Set()
+        for (const row of rows || []) {
+            const studentNum = String(row.student_num || '').trim()
+            if (!studentNum || seenStudentNums.has(studentNum)) {
+                continue
+            }
+            seenStudentNums.add(studentNum)
+            picked.push(row)
+            if (picked.length >= limit) {
+                break
+            }
+        }
+        return picked
+    }
+
     async countOnlineClients() {
         const now = Date.now()
         const threshold = now - this.heartbeatTtlSeconds * 1000
@@ -1067,16 +1089,24 @@ class TaskScheduler {
         const conn = await db.connect()
         try {
             await conn.beginTransaction()
-            const [rows] = await conn.execute(
+            const inFlightStatuses = this.getInFlightTaskStatuses()
+            const candidateLimit = Math.min(50, Math.max(safeCount, safeCount * 5))
+            const [candidateRows] = await conn.execute(
                 `SELECT t.*, b.jx0502zbid AS batch_jx0502zbid, b.name AS batch_name, b.enabled AS batch_enabled
                 FROM qk_task t
                 LEFT JOIN qk_batch b ON b.id = t.batch_id
                 WHERE t.status = ?
                 AND (t.batch_id IS NULL OR b.enabled = 1)
                 AND (t.exclude_client_id IS NULL OR t.exclude_client_id <> ?)
-                ORDER BY t.create_time ASC LIMIT ${safeCount} FOR UPDATE`,
-                [TASK_STATUS.PENDING, clientId]
+                AND NOT EXISTS (
+                    SELECT 1 FROM qk_task active
+                    WHERE active.student_num = t.student_num
+                    AND active.status IN (?, ?)
+                )
+                ORDER BY t.create_time ASC LIMIT ${candidateLimit} FOR UPDATE`,
+                [TASK_STATUS.PENDING, clientId, ...inFlightStatuses]
             )
+            const rows = this.pickPullableTasks(candidateRows, safeCount)
             const now = Date.now()
             const leaseExpireAt = now + this.leaseMs
             for (const row of rows) {