|
|
@@ -5,7 +5,6 @@ import { SmsTask } from '../entities/sms-task.entity'
|
|
|
import { TaskItemStatus, TaskStatus } from '../enum/task.enum'
|
|
|
import { SmsTaskItem } from '../entities/sms-task-item.entity'
|
|
|
import { xinsService } from '../services/sms/xins.service'
|
|
|
-import { GetReportResult } from '../services/sms/sms.types'
|
|
|
|
|
|
/**
|
|
|
* SMS 任务定时调度器
|
|
|
@@ -35,13 +34,13 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
})
|
|
|
|
|
|
if (runningTasks.length > 0) {
|
|
|
- this.app.log.warn(`发现 ${runningTasks.length} 个异常的 RUNNING 状态任务,正在恢复...`)
|
|
|
+ this.app.log.info(`发现 ${runningTasks.length} 个异常的 RUNNING 状态任务,正在恢复...`)
|
|
|
for (const task of runningTasks) {
|
|
|
await this.smsTaskRepository.update(task.id, {
|
|
|
status: TaskStatus.PENDING
|
|
|
})
|
|
|
- this.app.log.info(`任务 ${task.id} 已恢复为 PENDING 状态`)
|
|
|
}
|
|
|
+ this.app.log.info(`所有异常任务恢复完成`)
|
|
|
}
|
|
|
|
|
|
super.start()
|
|
|
@@ -74,15 +73,17 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
})
|
|
|
|
|
|
if (queuedTask) {
|
|
|
+ this.app.log.info(`发现队列任务 ${queuedTask.id},升级为 PENDING 状态`)
|
|
|
await this.smsTaskRepository.update(queuedTask.id, {
|
|
|
status: TaskStatus.PENDING
|
|
|
})
|
|
|
- this.app.log.info(`任务 ${queuedTask.id} 从队列中升级为 pending 状态`)
|
|
|
}
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- this.app.log.info(`开始处理 pending 任务: ${pendingTask.id}`)
|
|
|
+ this.app.log.info(
|
|
|
+ `开始处理任务: ${pendingTask.id} (${pendingTask.name}), 总数: ${pendingTask.total}`
|
|
|
+ )
|
|
|
|
|
|
// 更新为 RUNNING 状态,并记录开始时间
|
|
|
await this.smsTaskRepository.update(pendingTask.id, {
|
|
|
@@ -106,7 +107,7 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
})
|
|
|
|
|
|
if (!currentTask || currentTask.status !== TaskStatus.RUNNING) {
|
|
|
- this.app.log.warn(`任务 ${pendingTask.id} 被中断,当前状态: ${currentTask?.status || '不存在'}`)
|
|
|
+ this.app.log.warn(`⚠️ 任务 ${pendingTask.id} 被中断,当前状态: ${currentTask?.status || '不存在'}`)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -129,6 +130,7 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
// 串行处理批次数据
|
|
|
for (let i = 0; i < smsTaskItems.length; i += batchSize) {
|
|
|
const batch = smsTaskItems.slice(i, i + batchSize)
|
|
|
+
|
|
|
const batchSuccess = await this.sendBatch(batch, pendingTask.message)
|
|
|
|
|
|
totalBatches++
|
|
|
@@ -140,13 +142,14 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
const progress = await this.getTaskProgress(pendingTask.id)
|
|
|
this.app.log.info(
|
|
|
`任务 ${pendingTask.id} 进度: ${progress.processed}/${progress.total} (${progress.percentage}%), ` +
|
|
|
- `成功率: ${progress.successRate}%, 批次: ${totalBatches}, 失败批次: ${failedBatches}`
|
|
|
+ `成功率: ${progress.successRate}%, 累计批次: ${totalBatches}, 累计失败批次: ${failedBatches}`
|
|
|
)
|
|
|
|
|
|
// 实时检查失败率,如果超过 70% 且至少处理了 3 个批次,立即停止
|
|
|
const currentFailureRate = totalBatches > 0 ? (failedBatches / totalBatches) * 100 : 0
|
|
|
+
|
|
|
if (totalBatches >= 3 && currentFailureRate > 70) {
|
|
|
- this.app.log.error(`任务 ${pendingTask.id} 失败率过高 (${currentFailureRate.toFixed(2)}%),立即停止任务`)
|
|
|
+ this.app.log.error(`🛑 任务 ${pendingTask.id} 失败率过高 (${currentFailureRate.toFixed(2)}%),立即停止任务`)
|
|
|
await this.smsTaskRepository.update(pendingTask.id, {
|
|
|
status: TaskStatus.ERROR
|
|
|
})
|
|
|
@@ -168,10 +171,11 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
status: finalStatus
|
|
|
})
|
|
|
|
|
|
- this.app.log.info(
|
|
|
- `任务 ${pendingTask.id} 处理完成,状态: ${finalStatus}, ` +
|
|
|
- `总批次: ${totalBatches}, 失败批次: ${failedBatches}, 失败率: ${failureRate.toFixed(2)}%`
|
|
|
- )
|
|
|
+ if (finalStatus === TaskStatus.COMPLETED) {
|
|
|
+ this.app.log.info(`任务 ${pendingTask.id} 完成,总批次: ${totalBatches}, 失败批次: ${failedBatches}`)
|
|
|
+ } else {
|
|
|
+ this.app.log.error(`❌ 任务 ${pendingTask.id} 失败率过高 (${failureRate.toFixed(2)}%),标记为错误状态`)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -181,7 +185,7 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
private async sendBatch(batch: SmsTaskItem[], message: string): Promise<boolean> {
|
|
|
// 边界检查
|
|
|
if (!batch || batch.length === 0) {
|
|
|
- this.app.log.warn('批次为空,跳过处理')
|
|
|
+ this.app.log.warn('⚠️ 批次为空,跳过处理')
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
@@ -190,46 +194,27 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
|
|
|
const sendResult = await this.xins.sendSms(phoneNumbers, message)
|
|
|
|
|
|
- // 获取报告,重试 10 次,每次间隔 3s
|
|
|
- let reportResult: GetReportResult | null = null
|
|
|
- const maxRetries = 10
|
|
|
- const retryInterval = 3000
|
|
|
-
|
|
|
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
|
- const currentReport = await this.xins.getReport({ msgid: sendResult.msgid })
|
|
|
-
|
|
|
- const hasResult =
|
|
|
- currentReport.phoneStatusList.length > 0 &&
|
|
|
- batch.some(item => currentReport.phoneStatusList.some(status => status.number === item.target))
|
|
|
-
|
|
|
- if (hasResult) {
|
|
|
- reportResult = currentReport
|
|
|
- this.app.log.info(`批次报告获取成功 (尝试 ${attempt}/${maxRetries})`)
|
|
|
- break
|
|
|
- }
|
|
|
-
|
|
|
- if (attempt < maxRetries) {
|
|
|
- this.app.log.info(`批次报告暂无结果,等待 ${retryInterval / 1000} 秒后重试 (尝试 ${attempt}/${maxRetries})`)
|
|
|
- await new Promise(resolve => setTimeout(resolve, retryInterval))
|
|
|
- } else {
|
|
|
- this.app.log.warn(`批次报告获取失败:${maxRetries} 次尝试后仍无结果`)
|
|
|
- reportResult = null
|
|
|
- }
|
|
|
- }
|
|
|
+ // 获取报告
|
|
|
+ const reportResult = await this.xins.getReport({ msgid: sendResult.msgid })
|
|
|
|
|
|
// 更新状态
|
|
|
- const phoneStatusMap = reportResult
|
|
|
- ? new Map(
|
|
|
- reportResult.phoneStatusList.map(status => [
|
|
|
- status.number,
|
|
|
- status.status === 'success'
|
|
|
- ? TaskItemStatus.SUCCESS
|
|
|
- : status.status === 'waiting'
|
|
|
- ? TaskItemStatus.WAITING
|
|
|
- : TaskItemStatus.FAILED
|
|
|
- ])
|
|
|
- )
|
|
|
- : null
|
|
|
+ const phoneStatusMap =
|
|
|
+ reportResult.phoneStatusList.length > 0
|
|
|
+ ? new Map(
|
|
|
+ reportResult.phoneStatusList.map(status => [
|
|
|
+ status.number,
|
|
|
+ status.status === 'success'
|
|
|
+ ? TaskItemStatus.SUCCESS
|
|
|
+ : status.status === 'waiting'
|
|
|
+ ? TaskItemStatus.WAITING
|
|
|
+ : TaskItemStatus.FAILED
|
|
|
+ ])
|
|
|
+ )
|
|
|
+ : null
|
|
|
+
|
|
|
+ if (!phoneStatusMap) {
|
|
|
+ this.app.log.warn(`⚠️ 未获取到任何报告,所有项将标记为 FAILED`)
|
|
|
+ }
|
|
|
|
|
|
const now = new Date()
|
|
|
let processed = 0
|
|
|
@@ -243,6 +228,7 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
successed++
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
await this.smsTaskItemRepository.save(batch)
|
|
|
|
|
|
// 更新任务的发送数和成功数
|
|
|
@@ -254,9 +240,10 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
|
|
|
return true
|
|
|
} catch (error) {
|
|
|
- this.app.log.error(`发送短信失败: ${error}`)
|
|
|
+ this.app.log.error(`❌ 发送短信失败: ${error}`)
|
|
|
|
|
|
const batchIds = batch.map(i => i.id)
|
|
|
+
|
|
|
const now = new Date()
|
|
|
await this.smsTaskItemRepository.update(
|
|
|
{ id: In(batchIds) },
|
|
|
@@ -289,6 +276,7 @@ export class SmsTaskScheduler extends BaseScheduler {
|
|
|
})
|
|
|
|
|
|
if (!task) {
|
|
|
+ this.app.log.warn(`⚠️ 任务 ${taskId} 不存在,返回空进度`)
|
|
|
return {
|
|
|
processed: 0,
|
|
|
total: 0,
|