|
@@ -4,6 +4,7 @@ const config = require('../../config.json')
|
|
|
const db = require('../../plugin/DataBase/db')
|
|
const db = require('../../plugin/DataBase/db')
|
|
|
const Redis = require('../../plugin/DataBase/Redis')
|
|
const Redis = require('../../plugin/DataBase/Redis')
|
|
|
const Logger = require('../Logger')
|
|
const Logger = require('../Logger')
|
|
|
|
|
+const AccessControl = require('../AccessControl')
|
|
|
|
|
|
|
|
const TASK_STATUS = {
|
|
const TASK_STATUS = {
|
|
|
PAUSED: 'paused',
|
|
PAUSED: 'paused',
|
|
@@ -49,6 +50,13 @@ class TaskScheduler {
|
|
|
)
|
|
)
|
|
|
this.logInfo('ensureSchema', '已添加 qk_task.auto_relogin 字段')
|
|
this.logInfo('ensureSchema', '已添加 qk_task.auto_relogin 字段')
|
|
|
}
|
|
}
|
|
|
|
|
+ const jwRows = await db.query("SHOW COLUMNS FROM qk_task LIKE 'jw_account_id'")
|
|
|
|
|
+ if (!jwRows || jwRows.length === 0) {
|
|
|
|
|
+ await db.query(
|
|
|
|
|
+ 'ALTER TABLE qk_task ADD COLUMN jw_account_id INT NULL DEFAULT NULL AFTER student_num'
|
|
|
|
|
+ )
|
|
|
|
|
+ this.logInfo('ensureSchema', '已添加 qk_task.jw_account_id 字段')
|
|
|
|
|
+ }
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
this.logWarn('ensureSchema', '抢课任务表结构检查失败', {}, err)
|
|
this.logWarn('ensureSchema', '抢课任务表结构检查失败', {}, err)
|
|
|
}
|
|
}
|
|
@@ -339,6 +347,49 @@ class TaskScheduler {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ async resolveTaskAccount(ownerUuid, payload, { requireAccount = true } = {}) {
|
|
|
|
|
+ const jwAccountId = Number(payload.jw_account_id || payload.jwAccountId)
|
|
|
|
|
+ if (jwAccountId) {
|
|
|
|
|
+ const account = await AccessControl.getVerifiedJwAccount(ownerUuid, jwAccountId)
|
|
|
|
|
+ if (!account) {
|
|
|
|
|
+ throw new Error('统一身份认证账号不存在或未验证')
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ student_num: account.username,
|
|
|
|
|
+ password: account.password,
|
|
|
|
|
+ jw_account_id: account.id
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const studentNum = String(payload.student_num || payload.user || '').trim()
|
|
|
|
|
+ const password = payload.password || payload.pass
|
|
|
|
|
+ if (studentNum && password) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ student_num: studentNum,
|
|
|
|
|
+ password,
|
|
|
|
|
+ jw_account_id: payload.jw_account_id || null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (requireAccount) {
|
|
|
|
|
+ throw new Error('请选择统一身份认证账号')
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async inferJwAccountId(ownerUuid, studentNum, currentId) {
|
|
|
|
|
+ if (currentId) {
|
|
|
|
|
+ return Number(currentId)
|
|
|
|
|
+ }
|
|
|
|
|
+ const username = String(studentNum || '').trim()
|
|
|
|
|
+ if (!username) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+ const rows = await db.query(
|
|
|
|
|
+ 'SELECT id FROM jw_account WHERE create_user = ? AND username = ? AND state = 1 LIMIT 1',
|
|
|
|
|
+ [ownerUuid, username]
|
|
|
|
|
+ )
|
|
|
|
|
+ return rows?.[0]?.id || null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
serializeTask(row, includeSecret = false) {
|
|
serializeTask(row, includeSecret = false) {
|
|
|
const result = { ...row }
|
|
const result = { ...row }
|
|
|
result.enable_ggxxk = Number(result.enable_ggxxk) === 1
|
|
result.enable_ggxxk = Number(result.enable_ggxxk) === 1
|
|
@@ -640,17 +691,19 @@ class TaskScheduler {
|
|
|
const { courses, courseGroups, intervalMs } = validated
|
|
const { courses, courseGroups, intervalMs } = validated
|
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
|
await this.requireEnabledBatch(batchId)
|
|
await this.requireEnabledBatch(batchId)
|
|
|
|
|
+ const account = await this.resolveTaskAccount(uuid, payload, { requireAccount: true })
|
|
|
const time = Date.now()
|
|
const time = Date.now()
|
|
|
const sql = `INSERT INTO qk_task
|
|
const sql = `INSERT INTO qk_task
|
|
|
- (create_user, name, batch_id, jx0502zbid, student_num, password_enc, courses, course_groups, enable_ggxxk, auto_relogin, interval_ms, status, create_time, update_time)
|
|
|
|
|
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
|
|
|
|
+ (create_user, name, batch_id, jx0502zbid, student_num, jw_account_id, password_enc, courses, course_groups, enable_ggxxk, auto_relogin, interval_ms, status, create_time, update_time)
|
|
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
|
const result = await db.query(sql, [
|
|
const result = await db.query(sql, [
|
|
|
uuid,
|
|
uuid,
|
|
|
payload.name,
|
|
payload.name,
|
|
|
batchId,
|
|
batchId,
|
|
|
'',
|
|
'',
|
|
|
- payload.student_num || payload.user,
|
|
|
|
|
- this.encryptPassword(payload.password || payload.pass),
|
|
|
|
|
|
|
+ account.student_num,
|
|
|
|
|
+ account.jw_account_id,
|
|
|
|
|
+ this.encryptPassword(account.password),
|
|
|
JSON.stringify(courses),
|
|
JSON.stringify(courses),
|
|
|
JSON.stringify(courseGroups),
|
|
JSON.stringify(courseGroups),
|
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
@@ -667,7 +720,8 @@ class TaskScheduler {
|
|
|
this.logInfo('createTask', '抢课任务已创建', { taskId: result.insertId, uuid }, {
|
|
this.logInfo('createTask', '抢课任务已创建', { taskId: result.insertId, uuid }, {
|
|
|
name: payload.name,
|
|
name: payload.name,
|
|
|
batch_id: batchId,
|
|
batch_id: batchId,
|
|
|
- student_num: payload.student_num || payload.user,
|
|
|
|
|
|
|
+ student_num: account.student_num,
|
|
|
|
|
+ jw_account_id: account.jw_account_id,
|
|
|
courses_count: courses.length,
|
|
courses_count: courses.length,
|
|
|
course_groups_count: courseGroups.length,
|
|
course_groups_count: courseGroups.length,
|
|
|
interval_ms: intervalMs,
|
|
interval_ms: intervalMs,
|
|
@@ -694,11 +748,13 @@ class TaskScheduler {
|
|
|
const { courses, courseGroups, intervalMs } = validated
|
|
const { courses, courseGroups, intervalMs } = validated
|
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
|
await this.requireEnabledBatch(batchId)
|
|
await this.requireEnabledBatch(batchId)
|
|
|
- const passwordSql = payload.password || payload.pass ? ', password_enc = ?' : ''
|
|
|
|
|
|
|
+ const account = await this.resolveTaskAccount(uuid, payload, { requireAccount: true })
|
|
|
const params = [
|
|
const params = [
|
|
|
payload.name,
|
|
payload.name,
|
|
|
batchId,
|
|
batchId,
|
|
|
- payload.student_num || payload.user,
|
|
|
|
|
|
|
+ account.student_num,
|
|
|
|
|
+ account.jw_account_id,
|
|
|
|
|
+ this.encryptPassword(account.password),
|
|
|
JSON.stringify(courses),
|
|
JSON.stringify(courses),
|
|
|
JSON.stringify(courseGroups),
|
|
JSON.stringify(courseGroups),
|
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
@@ -707,11 +763,8 @@ class TaskScheduler {
|
|
|
TASK_STATUS.PAUSED,
|
|
TASK_STATUS.PAUSED,
|
|
|
Date.now()
|
|
Date.now()
|
|
|
]
|
|
]
|
|
|
- if (passwordSql) {
|
|
|
|
|
- params.splice(3, 0, this.encryptPassword(payload.password || payload.pass))
|
|
|
|
|
- }
|
|
|
|
|
params.push(taskId, uuid)
|
|
params.push(taskId, uuid)
|
|
|
- const sql = `UPDATE qk_task SET name = ?, batch_id = ?, student_num = ?${passwordSql}, courses = ?, course_groups = ?, enable_ggxxk = ?, auto_relogin = ?, interval_ms = ?, status = ?, jx0502zbid = '', update_time = ?, assigned_client_id = NULL, lease_expire_at = NULL, result_json = NULL, error_msg = NULL, finished_time = NULL WHERE id = ? AND create_user = ?`
|
|
|
|
|
|
|
+ const sql = `UPDATE qk_task SET name = ?, batch_id = ?, student_num = ?, jw_account_id = ?, password_enc = ?, courses = ?, course_groups = ?, enable_ggxxk = ?, auto_relogin = ?, interval_ms = ?, status = ?, jx0502zbid = '', update_time = ?, assigned_client_id = NULL, lease_expire_at = NULL, result_json = NULL, error_msg = NULL, finished_time = NULL WHERE id = ? AND create_user = ?`
|
|
|
const result = await db.query(sql, params)
|
|
const result = await db.query(sql, params)
|
|
|
if (!result || result.affectedRows <= 0) {
|
|
if (!result || result.affectedRows <= 0) {
|
|
|
throw new Error('更新抢课任务失败')
|
|
throw new Error('更新抢课任务失败')
|
|
@@ -719,11 +772,12 @@ class TaskScheduler {
|
|
|
await this.logTask(taskId, null, 'updated', '用户更新抢课任务')
|
|
await this.logTask(taskId, null, 'updated', '用户更新抢课任务')
|
|
|
this.logInfo('updateTask', '抢课任务已更新(未开始)', { taskId, uuid }, {
|
|
this.logInfo('updateTask', '抢课任务已更新(未开始)', { taskId, uuid }, {
|
|
|
name: payload.name,
|
|
name: payload.name,
|
|
|
- student_num: payload.student_num || payload.user,
|
|
|
|
|
|
|
+ student_num: account.student_num,
|
|
|
|
|
+ jw_account_id: account.jw_account_id,
|
|
|
courses_count: courses.length,
|
|
courses_count: courses.length,
|
|
|
course_groups_count: courseGroups.length,
|
|
course_groups_count: courseGroups.length,
|
|
|
interval_ms: intervalMs,
|
|
interval_ms: intervalMs,
|
|
|
- password_changed: !!(payload.password || payload.pass)
|
|
|
|
|
|
|
+ account_changed: true
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -742,6 +796,12 @@ class TaskScheduler {
|
|
|
where.push('t.name LIKE ?')
|
|
where.push('t.name LIKE ?')
|
|
|
params.push(`%${filters.name}%`)
|
|
params.push(`%${filters.name}%`)
|
|
|
}
|
|
}
|
|
|
|
|
+ const courseName = String(filters.course_name || filters.course || '').trim()
|
|
|
|
|
+ if (courseName) {
|
|
|
|
|
+ const keyword = `%${courseName}%`
|
|
|
|
|
+ where.push('(t.courses LIKE ? OR t.course_groups LIKE ?)')
|
|
|
|
|
+ params.push(keyword, keyword)
|
|
|
|
|
+ }
|
|
|
if (filters.batch_id) {
|
|
if (filters.batch_id) {
|
|
|
where.push('t.batch_id = ?')
|
|
where.push('t.batch_id = ?')
|
|
|
params.push(Number(filters.batch_id))
|
|
params.push(Number(filters.batch_id))
|
|
@@ -769,9 +829,11 @@ class TaskScheduler {
|
|
|
if (!rows || rows.length === 0) {
|
|
if (!rows || rows.length === 0) {
|
|
|
return null
|
|
return null
|
|
|
}
|
|
}
|
|
|
|
|
+ const task = this.serializeTask(rows[0], false)
|
|
|
|
|
+ task.jw_account_id = await this.inferJwAccountId(uuid, task.student_num, task.jw_account_id)
|
|
|
const logs = await db.query('SELECT client_id, event, message, payload_json, create_time FROM qk_task_log WHERE task_id = ? ORDER BY create_time DESC LIMIT 100', [taskId])
|
|
const logs = await db.query('SELECT client_id, event, message, payload_json, create_time FROM qk_task_log WHERE task_id = ? ORDER BY create_time DESC LIMIT 100', [taskId])
|
|
|
return {
|
|
return {
|
|
|
- task: this.serializeTask(rows[0], true),
|
|
|
|
|
|
|
+ task,
|
|
|
logs: (logs || []).map(log => {
|
|
logs: (logs || []).map(log => {
|
|
|
if (typeof log.payload_json === 'string' && log.payload_json) {
|
|
if (typeof log.payload_json === 'string' && log.payload_json) {
|
|
|
try {
|
|
try {
|
|
@@ -863,7 +925,9 @@ class TaskScheduler {
|
|
|
if (!rows || rows.length === 0) {
|
|
if (!rows || rows.length === 0) {
|
|
|
return null
|
|
return null
|
|
|
}
|
|
}
|
|
|
- return this.serializeTask(rows[0], true)
|
|
|
|
|
|
|
+ const task = this.serializeTask(rows[0], false)
|
|
|
|
|
+ task.jw_account_id = await this.inferJwAccountId(rows[0].create_user, task.student_num, task.jw_account_id)
|
|
|
|
|
+ return task
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async adminUpdateTask(taskId, payload) {
|
|
async adminUpdateTask(taskId, payload) {
|
|
@@ -886,24 +950,23 @@ class TaskScheduler {
|
|
|
const wasAssigned = [TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING].includes(task.status)
|
|
const wasAssigned = [TASK_STATUS.ASSIGNED, TASK_STATUS.RUNNING].includes(task.status)
|
|
|
const releasedClientId = wasAssigned ? task.assigned_client_id : null
|
|
const releasedClientId = wasAssigned ? task.assigned_client_id : null
|
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
const batchId = this.resolveTaskBatchId(payload)
|
|
|
- const passwordSql = payload.password || payload.pass ? ', password_enc = ?' : ''
|
|
|
|
|
|
|
+ const account = await this.resolveTaskAccount(task.create_user, payload, { requireAccount: true })
|
|
|
const params = [
|
|
const params = [
|
|
|
payload.name,
|
|
payload.name,
|
|
|
batchId,
|
|
batchId,
|
|
|
- payload.student_num || payload.user,
|
|
|
|
|
|
|
+ account.student_num,
|
|
|
|
|
+ account.jw_account_id,
|
|
|
|
|
+ this.encryptPassword(account.password),
|
|
|
JSON.stringify(courses),
|
|
JSON.stringify(courses),
|
|
|
JSON.stringify(courseGroups),
|
|
JSON.stringify(courseGroups),
|
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
payload.enable_ggxxk || payload.ENABLE_GGXXK ? 1 : 0,
|
|
|
this.parseAutoRelogin(payload) ? 1 : 0,
|
|
this.parseAutoRelogin(payload) ? 1 : 0,
|
|
|
intervalMs,
|
|
intervalMs,
|
|
|
TASK_STATUS.PAUSED,
|
|
TASK_STATUS.PAUSED,
|
|
|
- now
|
|
|
|
|
|
|
+ now,
|
|
|
|
|
+ taskId
|
|
|
]
|
|
]
|
|
|
- if (passwordSql) {
|
|
|
|
|
- params.splice(3, 0, this.encryptPassword(payload.password || payload.pass))
|
|
|
|
|
- }
|
|
|
|
|
- params.push(taskId)
|
|
|
|
|
- const sql = `UPDATE qk_task SET name = ?, batch_id = ?, student_num = ?${passwordSql}, courses = ?, course_groups = ?, enable_ggxxk = ?, auto_relogin = ?, interval_ms = ?, status = ?, jx0502zbid = '', 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 = ?`
|
|
|
|
|
|
|
+ const sql = `UPDATE qk_task SET name = ?, batch_id = ?, student_num = ?, jw_account_id = ?, password_enc = ?, courses = ?, course_groups = ?, enable_ggxxk = ?, auto_relogin = ?, interval_ms = ?, status = ?, jx0502zbid = '', 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 = ?`
|
|
|
const result = await db.query(sql, params)
|
|
const result = await db.query(sql, params)
|
|
|
if (!result || result.affectedRows <= 0) {
|
|
if (!result || result.affectedRows <= 0) {
|
|
|
throw new Error('更新抢课任务失败')
|
|
throw new Error('更新抢课任务失败')
|
|
@@ -914,7 +977,8 @@ class TaskScheduler {
|
|
|
await this.logTask(taskId, task.assigned_client_id, 'admin_updated', wasAssigned ? '管理员更新任务并收回(未开始)' : '管理员更新抢课任务')
|
|
await this.logTask(taskId, task.assigned_client_id, 'admin_updated', wasAssigned ? '管理员更新任务并收回(未开始)' : '管理员更新抢课任务')
|
|
|
this.logInfo('adminUpdateTask', '管理员已更新抢课任务', { taskId }, {
|
|
this.logInfo('adminUpdateTask', '管理员已更新抢课任务', { taskId }, {
|
|
|
name: payload.name,
|
|
name: payload.name,
|
|
|
- student_num: payload.student_num || payload.user,
|
|
|
|
|
|
|
+ student_num: account.student_num,
|
|
|
|
|
+ jw_account_id: account.jw_account_id,
|
|
|
released_from_client: wasAssigned ? task.assigned_client_id : null
|
|
released_from_client: wasAssigned ? task.assigned_client_id : null
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
@@ -1565,6 +1629,13 @@ class TaskScheduler {
|
|
|
params.push(`%${filters.username}%`)
|
|
params.push(`%${filters.username}%`)
|
|
|
countParams.push(`%${filters.username}%`)
|
|
countParams.push(`%${filters.username}%`)
|
|
|
}
|
|
}
|
|
|
|
|
+ const courseName = String(filters.course_name || filters.course || '').trim()
|
|
|
|
|
+ if (courseName) {
|
|
|
|
|
+ const keyword = `%${courseName}%`
|
|
|
|
|
+ where.push('(t.courses LIKE ? OR t.course_groups LIKE ?)')
|
|
|
|
|
+ params.push(keyword, keyword)
|
|
|
|
|
+ countParams.push(keyword, keyword)
|
|
|
|
|
+ }
|
|
|
if (filters.batch_id) {
|
|
if (filters.batch_id) {
|
|
|
where.push('t.batch_id = ?')
|
|
where.push('t.batch_id = ?')
|
|
|
params.push(Number(filters.batch_id))
|
|
params.push(Number(filters.batch_id))
|