wangzhibo
2026-07-28 920cec41cedfde27f89d7c19b78e03147b926ab5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { Provide, Inject } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, In } from 'typeorm';
import { ReportJoblogEntity } from '../entity/joblog';
import { ExecutorSiteDeptDaily } from './ExecutorSiteDeptDaily'
import { ExecutorVehicleDaily } from './ExecutorVehicleDaily'
 
/**
 *  定时任务-汇总站点/各个单位的各类垃圾汇总数据
 *  总入口,创建和调度任务,具体执行调用TaskSiteService 和  TaskDepartmentService
 */
 
 
@Provide()
export class ReportTaskService extends BaseService {
  @InjectEntityModel(ReportJoblogEntity)
  reportJoblogEntity: Repository<ReportJoblogEntity>;
 
  @Inject()
  executorSiteDeptDaily: ExecutorSiteDeptDaily;
 
  @Inject()
  executorVehicleDaily: ExecutorVehicleDaily;
 
 
 
  /**
   * 外部定时任务(Crontab)每小时调用的总入口
   * 建议表达式: 0 5 * * * * (每小时的第5分钟执行)
   */
  async clockIn() {
    // 1. 自动看门狗:确保昨天和今天的任务实例已经在表里初始化了
    await this.ensureJobInstancesCreated();
 
    // 2. 两类任务同时执行
    // 后续增加新的 task service 比如 更新 萤石云 token, 统计分拣员行为等等。
 
    await Promise.all([
      this.executorSiteDeptDaily.execute(),
      this.executorVehicleDaily.execute()
 
    ]);
 
 
    return '任务执行:ReportTaskService';
  }
 
  /**
   * 检查并初始化任务实例(守护动作)
   */
  private async ensureJobInstancesCreated() {
    const now = new Date();
    const year = now.getFullYear();
    const month = String(now.getMonth() + 1).padStart(2, '0');
    const day = String(now.getDate()).padStart(2, '0');
    const yesterdayDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
    const yYear = yesterdayDate.getFullYear();
    const yMonth = String(yesterdayDate.getMonth() + 1).padStart(2, '0');
    const yDay = String(yesterdayDate.getDate()).padStart(2, '0');
 
    const todayStr = `${year}-${month}-${day}`;
    const yesterdayStr = `${yYear}-${yMonth}-${yDay}`;
 
    // 我们需要守护的目标日期和任务类型组合
    const targets = [
      { 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) {
      try {
        // 利用 try-catch 和数据库唯一索引,防并发同时也能实现“不存在则创建”
        const exist = await this.reportJoblogEntity.findOneBy({
          jobName: target.type,
          dataDate: target.date,
        });
        if (!exist) {
          const job = new ReportJoblogEntity();
          job.jobName = target.type;
          job.dataDate = target.date;
          job.status = 'INIT';
          await this.reportJoblogEntity.save(job);
        } else if (target.isToday && exist.status === 'SUCCESS') {
          // 2. 【核心修复】如果是今天的任务,且已经是 SUCCESS 状态,强行洗回 INIT 参与本小时的重算
          await this.reportJoblogEntity.update(exist.id, {
            status: 'INIT'
          });
        }
      } catch (err) {
        // 并发写入时可能触发唯一索引冲突,直接忽略即可
      }
    }
  }
 
 
}