wangrong
3 天以前 95fa12fe6a260c1388391003faa796463264a36a
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { Provide, Inject } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { ReportJoblogEntity } from '../entity/joblog';
import { ExecutorSiteDeptDaily } from './ExecutorSiteDeptDaily'
import { ExecutorVehicleDaily } from './ExecutorVehicleDaily'
import { ExecutorXinYueVehicleGps } from './ExecutorXinYueVehicleGps'
 
/**
 *  定时任务-汇总站点/各个单位的各类垃圾汇总数据
 *  总入口,创建和调度任务,具体执行调用TaskSiteService 和  TaskDepartmentService
 */
 
 
@Provide()
export class ReportTaskService extends BaseService {
  @InjectEntityModel(ReportJoblogEntity)
  reportJoblogEntity: Repository<ReportJoblogEntity>;
 
  @Inject()
  executorSiteDeptDaily: ExecutorSiteDeptDaily;
 
  @Inject()
  executorVehicleDaily: ExecutorVehicleDaily;
 
  @Inject()
  executorXinYueVehicleGps: ExecutorXinYueVehicleGps;
 
 
 
  /**
   * 外部定时任务(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';
  }
 
  /**
   * 芯跃三轮车定位:定时拉取位置和电压
   * 后台任务管理配置:reportTaskService.syncXinYueVehicleGps()
   * 建议 cron 不大于系统参数 xinYue.syncIntervalSeconds(默认300秒),例如每分钟:0 * * * * *
   */
  async syncXinYueVehicleGps() {
    await this.ensureXinYueGpsJobInstance();
    await this.executorXinYueVehicleGps.recoverStuckJobs();
    await this.executorXinYueVehicleGps.execute();
    return '任务执行:芯跃三轮车定位同步';
  }
 
  /**
   * 检查并初始化任务实例(守护动作)
   */
  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: yesterdayStr, type: 'SORTER_DAILY', isToday: false },   // 新增
 
      { date: todayStr, type: 'SITE_DAILY', isToday: true },
      { date: todayStr, type: 'DEPT_DAILY', isToday: true },
      { date: todayStr, type: 'VEHICLE_DAILY', isToday: true },         // 新增
      { date: todayStr, type: 'SORTER_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) {
        // 并发写入时可能触发唯一索引冲突,直接忽略即可
      }
    }
  }
 
  /**
   * 初始化当天的芯跃定位同步任务,SUCCESS 洗回 INIT 以便按周期重拉
   */
  private async ensureXinYueGpsJobInstance() {
    const now = new Date();
    const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
    try {
      await this.reportJoblogEntity.update(
        { jobName: 'XY_VEHICLE_GPS' },
        { jobName: 'XINYUE_VEHICLE_GPS' }
      );
    } catch (err) {
      // 新旧任务名并存时忽略唯一索引冲突
    }
    try {
      const exist = await this.reportJoblogEntity.findOneBy({
        jobName: 'XINYUE_VEHICLE_GPS',
        dataDate: todayStr,
      });
      if (!exist) {
        const job = new ReportJoblogEntity();
        job.jobName = 'XINYUE_VEHICLE_GPS';
        job.dataDate = todayStr;
        job.status = 'INIT';
        await this.reportJoblogEntity.save(job);
        return;
      }
      if (exist.status === 'SUCCESS') {
        await this.reportJoblogEntity.update(exist.id, { status: 'INIT' });
      }
    } catch (err) {
      // 并发写入时可能触发唯一索引冲突,直接忽略即可
    }
  }
 
}