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
import { Provide, Inject } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, In, LessThan } from 'typeorm';
import { ReportJoblogEntity } from '../entity/joblog';
import { TaskServiceXinYueGps } from './taskservicexinyuegps';
 
/**
 * 芯跃三轮车定位实时同步执行器
 * 负责 joblog 状态机与同步周期节流,具体拉数由 TaskServiceXinYueGps 完成
 */
@Provide()
export class ExecutorXinYueVehicleGps {
  @InjectEntityModel(ReportJoblogEntity)
  reportJoblogEntity: Repository<ReportJoblogEntity>;
 
  @Inject()
  taskServiceXinYueGps: TaskServiceXinYueGps;
 
  async execute() {
    const pendingJobs = await this.reportJoblogEntity.find({
      where: {
        status: In(['INIT', 'FAILED']),
        jobName: 'XINYUE_VEHICLE_GPS',
      },
      order: {
        dataDate: 'ASC',
      },
    });
 
    for (const job of pendingJobs) {
      const intervalSeconds = await this.taskServiceXinYueGps.getSyncIntervalSeconds();
      if (this.shouldSkipByInterval(job, intervalSeconds)) {
        continue;
      }
 
      const lockResult = await this.reportJoblogEntity.update(
        { id: job.id, status: job.status },
        { status: 'RUNNING', startTime: new Date() }
      );
 
      if (lockResult.affected === 0) {
        continue;
      }
 
      try {
        const result = await this.taskServiceXinYueGps.syncRealtimeData();
        await this.reportJoblogEntity.update(job.id, {
          status: 'SUCCESS',
          recordsProcessed: result.recordsProcessed,
          endTime: new Date(),
          errorMessage: result.errorMessage || '',
        });
      } catch (error) {
        await this.reportJoblogEntity.update(job.id, {
          status: 'FAILED',
          endTime: new Date(),
          errorMessage: this.stringifyError(error),
          failedCount: () => 'failedCount + 1',
        });
      }
    }
  }
 
  /**
   * 将卡住的 RUNNING 任务洗回 INIT,避免进程中断后永远不跑
   */
  async recoverStuckJobs() {
    const expireAt = new Date(Date.now() - 10 * 60 * 1000);
    await this.reportJoblogEntity.update(
      {
        jobName: 'XINYUE_VEHICLE_GPS',
        status: 'RUNNING',
        startTime: LessThan(expireAt),
      },
      { status: 'INIT' }
    );
  }
 
  /**
   * 距上次结束未到同步周期则跳过,避免后台 cron 比参数更密时刷接口
   */
  private shouldSkipByInterval(job: ReportJoblogEntity, intervalSeconds: number): boolean {
    if (!intervalSeconds || intervalSeconds <= 0) {
      return false;
    }
    if (!job.endTime) {
      return false;
    }
    const elapsed = Date.now() - new Date(job.endTime).getTime();
    return elapsed < intervalSeconds * 1000;
  }
 
  private stringifyError(error: any): string {
    if (error instanceof Error) {
      return error.stack ?? error.message;
    }
    if (typeof error === 'string') {
      return error;
    }
    try {
      return JSON.stringify(error);
    } catch {
      return String(error);
    }
  }
}