const API = require("../../lib/API"); const db = require('../../plugin/DataBase/db') const Redis = require('../../plugin/DataBase/Redis') const mq = require('../../plugin/mq') const { assertRunforgeTaskIngress, publishRunforgeTask } = require('../../plugin/mq/runforgeTaskMq') const { scheduleDelayedRunforgeTask } = require('../../plugin/mq/lepaoAutoScheduleRedis') const jkesRedisKeys = require('../../plugin/jkes/redisKeys') const { BaseStdResponse } = require("../../BaseStdResponse"); const { planJkesAutoRun } = require('../../plugin/jkes/monthPolicy') class StartAutoLepao extends API { constructor() { super(); this.noEncrypt() this.setPath('/Corn/StartAutoLepao'); this.setMethod('GET'); } async onRequest(req, res) { try { res.json({ ...BaseStdResponse.OK }) const day = new Date().getDay() const hour = new Date().getHours() this.logger.info('开始执行自动乐跑任务') let sql = ` SELECT name, student_num, auto_day, token, target_count FROM lepao_account WHERE auto_run = 1 AND (auto_time = ? OR (auto_time = -1 AND today_auto_time = ?)) AND JSON_CONTAINS(auto_day, CAST(? AS JSON)) ` let r = await db.query(sql, [hour, hour, day]) if (!r) return this.logger.error('获取自动乐跑账号失败!') const nowMs = Date.now() const hourEnd = new Date() hourEnd.setHours(hourEnd.getHours() + 1, 0, 0, 0) const spreadWindowMs = Math.max(0, hourEnd.getTime() - nowMs) let channel try { channel = await mq.getChannel('lepao_corn') await assertRunforgeTaskIngress(channel, this.logger) } catch (err) { this.logger.error(`自动乐跑:连接 MQ 或声明拓扑失败:${err.message || err}`) return } for (const item of r) { const { name, student_num, auto_day, token, target_count } = item const isSuccess = await Redis.get(jkesRedisKeys.lepaoSuccess(student_num)) if (isSuccess) { this.logger.info(`${name}(${student_num})当天已乐跑成功,不执行自动乐跑,如仍需跑步请手动发起`) continue } const plan = await planJkesAutoRun(student_num, auto_day, token, { monthTargetKm: target_count, stopAfterMinimum: true }) if (!plan.run) { this.logger.info(`${name}(${student_num}) JKES 自动乐跑跳过:${plan.reason}`) continue } const delayMs = spreadWindowMs > 0 ? Math.floor(Math.random() * spreadWindowMs) : 0 const fireAt = nowMs + delayMs const taskId = `lepao:auto:${fireAt}:${student_num}` const payload = { id: taskId, type: 'lepao.startRun', data: { taskId, account: student_num, targetKm: plan.targetKm, autoDoubleSlot: plan.targetKm >= 2 }, retry: 0 } if (delayMs > 0) { try { await scheduleDelayedRunforgeTask(fireAt, payload, { name, account: student_num, delayMs }) this.logger.info( `${name}(${student_num})已写入 Redis 调度(约 ${Math.round(delayMs / 1000)}s 后进 MQ)` ) } catch (err) { this.logger.error(`${name}(${student_num})Redis 调度失败:${err.message || err}`) } continue } try { publishRunforgeTask(channel, payload) this.logger.info(`${name}(${student_num})已投递自动乐跑任务`) } catch (err) { this.logger.error(`${name}(${student_num})乐跑投递失败:${err.message || err}`) } } } catch (error) { this.logger.error(error) } } } module.exports.StartAutoLepao = StartAutoLepao;