| | |
| | | import { InjectEntityModel } from '@midwayjs/typeorm'; |
| | | import { Repository, In } from 'typeorm'; |
| | | import { ReportJoblogEntity } from '../entity/joblog'; |
| | | import { TaskSiteService } from './tasksiteservice' |
| | | import { TaskDepartmentService } from './taskdepartmentservice' |
| | | import { ExecutorSiteDeptDaily } from './ExecutorSiteDeptDaily' |
| | | import { ExecutorVehicleDaily } from './ExecutorVehicleDaily' |
| | | |
| | | /** |
| | | * 定时任务-汇总站点/各个单位的各类垃圾汇总数据 |
| | |
| | | @InjectEntityModel(ReportJoblogEntity) |
| | | reportJoblogEntity: Repository<ReportJoblogEntity>; |
| | | |
| | | // 注入你具体的业务 Service,用于跑真正的汇总 SQL |
| | | @Inject() |
| | | taskSiteService: TaskSiteService; |
| | | executorSiteDeptDaily: ExecutorSiteDeptDaily; |
| | | |
| | | @Inject() |
| | | departmentCubeService: TaskDepartmentService; |
| | | executorVehicleDaily: ExecutorVehicleDaily; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 外部定时任务(Crontab)每小时调用的总入口 |
| | |
| | | // 1. 自动看门狗:确保昨天和今天的任务实例已经在表里初始化了 |
| | | await this.ensureJobInstancesCreated(); |
| | | |
| | | // 2. 执行引擎:捞出需要执行或重试的任务 |
| | | await this.executePendingJobs(); |
| | | // 2. 两类任务同时执行 |
| | | // 后续增加新的 task service 比如 更新 萤石云 token, 统计分拣员行为等等。 |
| | | |
| | | return '任务执行成功:ReportTaskService'; |
| | | await Promise.all([ |
| | | this.executorSiteDeptDaily.execute(), |
| | | this.executorVehicleDaily.execute() |
| | | |
| | | ]); |
| | | |
| | | |
| | | return '任务执行:ReportTaskService'; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 我们需要守护的目标日期和任务类型组合 |
| | | const targets = [ |
| | | { date: yesterdayStr, type: 'SITE_DAILY',isToday: false}, |
| | | { date: yesterdayStr, type: 'DEPT_DAILY',isToday: false}, |
| | | { date: todayStr, type: 'SITE_DAILY' ,isToday: true}, |
| | | { date: todayStr, type: 'DEPT_DAILY' ,isToday: true}, |
| | | { date: yesterdayStr, type: 'SITE_DAILY', isToday: false }, |
| | | { date: yesterdayStr, type: 'DEPT_DAILY', isToday: false }, |
| | | { date: yesterdayStr, type: 'VEHICLE_DAILY', isToday: false }, // 新增 |
| | | |
| | | { date: todayStr, type: 'SITE_DAILY', isToday: true }, |
| | | { date: todayStr, type: 'DEPT_DAILY', isToday: true }, |
| | | { date: todayStr, type: 'VEHICLE_DAILY', isToday: true }, // 新增 |
| | | |
| | | ]; |
| | | |
| | | for (const target of targets) { |
| | |
| | | job.dataDate = target.date; |
| | | job.status = 'INIT'; |
| | | await this.reportJoblogEntity.save(job); |
| | | }else if (target.isToday && exist.status === 'SUCCESS') { |
| | | } else if (target.isToday && exist.status === 'SUCCESS') { |
| | | // 2. 【核心修复】如果是今天的任务,且已经是 SUCCESS 状态,强行洗回 INIT 参与本小时的重算 |
| | | await this.reportJoblogEntity.update(exist.id, { |
| | | status: 'INIT' |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 捞出 INIT 和 FAILED 的任务,并串行严格按顺序执行 |
| | | */ |
| | | private async executePendingJobs() { |
| | | // 捞出所有待处理任务 |
| | | const pendingJobs = await this.reportJoblogEntity.find({ |
| | | where: { |
| | | status: In(['INIT', 'FAILED']), |
| | | }, |
| | | order: { |
| | | dataDate: 'ASC', // 先算历史,后算今天 |
| | | // 确保在同一天内,SITE_DAILY 先跑,DEPT_DAILY 后跑 |
| | | // 在 JS 数组中我们可以进一步控制排序,或者由业务逻辑严格控制依赖 |
| | | }, |
| | | }); |
| | | |
| | | for (const job of pendingJobs) { |
| | | // 严格依赖检查:如果是部门日表任务,必须确保同天的站点日表任务已经是 SUCCESS |
| | | if (job.jobName === 'DEPT_DAILY') { |
| | | const siteJob = await this.reportJoblogEntity.findOneBy({ |
| | | dataDate: job.dataDate, |
| | | jobName: 'SITE_DAILY', |
| | | }); |
| | | if (!siteJob || siteJob.status !== 'SUCCESS') { |
| | | // 源头明细还没算好/或还在运行,部门表任务必须等待,跳过本次执行 |
| | | continue; |
| | | } |
| | | } |
| | | |
| | | // 锁定任务状态为 RUNNING(轻量级乐观锁锁机制) |
| | | const lockResult = await this.reportJoblogEntity.update( |
| | | { id: job.id, status: job.status }, // 带有原始状态检查,防止被别的线程抢跑 |
| | | { status: 'RUNNING', startTime: new Date() } |
| | | ); |
| | | |
| | | if (lockResult.affected === 0) { |
| | | continue; // 没抢到锁,说明别的定时器在跑它,跳过 |
| | | } |
| | | |
| | | // 真正开始跑数据 |
| | | try { |
| | | let recordsProcessed = 0 ; |
| | | |
| | | if (job.jobName === 'SITE_DAILY') { |
| | | // 调用你写的 站点日表 提取逻辑 |
| | | recordsProcessed = await this.taskSiteService.aggregateDailyData(job.dataDate) || 0; |
| | | } else if (job.jobName === 'DEPT_DAILY') { |
| | | // 调用你写的 部门树轰炸 提取逻辑 |
| | | recordsProcessed = await this.departmentCubeService.aggregateDailyData(job.dataDate) || 0; |
| | | } |
| | | |
| | | // 成功:更新状态 |
| | | await this.reportJoblogEntity.update(job.id, { |
| | | status: 'SUCCESS', |
| | | recordsProcessed:recordsProcessed, |
| | | endTime: new Date(), |
| | | errorMessage: '', |
| | | }); |
| | | |
| | | } catch (error) { |
| | | // 失败:记录堆栈,等待下一小时重试 |
| | | |
| | | var errorMessage = ''; |
| | | if (error instanceof Error) { |
| | | errorMessage = error.stack ?? error.message; |
| | | } |
| | | if (typeof error === 'string') { |
| | | errorMessage = error; |
| | | } |
| | | try { |
| | | errorMessage = JSON.stringify(error); |
| | | } catch { |
| | | errorMessage = String(error); |
| | | } |
| | | |
| | | await this.reportJoblogEntity.update(job.id, { |
| | | status: 'FAILED', |
| | | endTime: new Date(), |
| | | errorMessage: errorMessage, |
| | | failedCount: () => 'failedCount + 1', |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | } |