From ee4d54e8d7c3dbf673f83baf22f850357d4f6461 Mon Sep 17 00:00:00 2001 From: wangzhongjie Date: Wed, 20 Aug 2025 17:38:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E6=96=B0=E8=AF=84?= =?UTF-8?q?=E5=88=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/ets/pages/Judge/JudgeBusiness.ets | 2 +- entry/src/main/ets/pages/Judge/JudgeIng.ets | 39 ++++-- .../ets/pages/Judge/ProcessDataProcessing.ets | 5 + .../ets/pages/Judge/ProcessDataTaskPool.ets | 123 ++++++++++-------- .../ets/utils/business/DifferentialSignal.ets | 9 +- 5 files changed, 102 insertions(+), 76 deletions(-) diff --git a/entry/src/main/ets/pages/Judge/JudgeBusiness.ets b/entry/src/main/ets/pages/Judge/JudgeBusiness.ets index 3709167..d463dda 100644 --- a/entry/src/main/ets/pages/Judge/JudgeBusiness.ets +++ b/entry/src/main/ets/pages/Judge/JudgeBusiness.ets @@ -55,9 +55,9 @@ export default class JudgeBusiness { public kfArr?: MarkRule[] public carztStr: string public ksjs?: JudgeKSJS + public plcData?: PLCType private judgeUI: JudgeUI private tempData?: PLCType - private plcData?: PLCType // 是否发送udp private isUdpEnd: boolean = false diff --git a/entry/src/main/ets/pages/Judge/JudgeIng.ets b/entry/src/main/ets/pages/Judge/JudgeIng.ets index e8117a9..42e2793 100644 --- a/entry/src/main/ets/pages/Judge/JudgeIng.ets +++ b/entry/src/main/ets/pages/Judge/JudgeIng.ets @@ -13,6 +13,7 @@ import { dConsole } from '../../utils/LogWorker'; import VoiceAnnounce from '../judgeSDK/utils/voiceAnnouncements'; import JudgeBusiness from './JudgeBusiness'; import { examJudgeArtificialItem, examJudgeSoundEnd } from './JudgeSDKUtils'; +import { ProjectStart, UploadProgressPhoto } from './ProcessDataProcessing'; import { CurrentProjectConversion, DeductionProjectConversion, DetectingDifferences, GetCarStatus } from './utils'; export const JudgingFn = async (strData: string, callBack: Function, judgeUI: JudgeUI, that: JudgeBusiness) => { @@ -230,6 +231,15 @@ export const SetJudgeItem = async (itemno: string, type: 1 | 2) => { dConsole.info(JudgeTag, `人工评判${type == 1 ? '进入' : '取消'}项目-${itemno}`) } +/** + * 改变考试状态 + * old goVoiceAnnounce + * @param event + * @param xmdm + * @param kf + * @param judgeUI + * @param that + */ const changeExamStatus = async (event: number, xmdm: number, kf: MarkRule[], judgeUI: JudgeUI, that: JudgeBusiness) => { switch (event) { case 1: { @@ -242,20 +252,25 @@ const changeExamStatus = async (event: number, xmdm: number, kf: MarkRule[], jud } }, 200) if (!project.isEnd) { - that.judgeTask.addTask(async () => { - dConsole.info(JudgeTag, `项目开始-${xmdm}-${project.name}`) - await beginProject(xmdm) - }, { - isDelay: true - }) - that.judgeTask.addTask(async () => { - dConsole.info(JudgeTag, `项目-${xmdm}-上传照片 start`) - await uploadProgressPhoto(xmdm) - }, { - isDelay: true - }) + // that.judgeTask.addTask(async () => { + // dConsole.info(JudgeTag, `项目开始-${xmdm}-${project.name}`) + // await beginProject(xmdm) + // }, { + // isDelay: true + // }) + ProjectStart(xmdm, that.xmxh, judgeUI) + // that.judgeTask.addTask(async () => { + // dConsole.info(JudgeTag, `项目-${xmdm}-上传照片 start`) + // await uploadProgressPhoto(xmdm) + // }, { + // isDelay: true + // }) + UploadProgressPhoto(xmdm, that.plcData, judgeUI) } break; } + case 2: { + break; + } } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/Judge/ProcessDataProcessing.ets b/entry/src/main/ets/pages/Judge/ProcessDataProcessing.ets index c704e73..734dfde 100644 --- a/entry/src/main/ets/pages/Judge/ProcessDataProcessing.ets +++ b/entry/src/main/ets/pages/Judge/ProcessDataProcessing.ets @@ -16,6 +16,7 @@ import { import { GetPhotoBase64 } from '../../utils/Common'; import dayTs from '../../utils/Date'; import { dConsole } from '../../utils/LogWorker'; +import { ProcessDataTaskPoolInstance } from './ProcessDataTaskPool'; /** * beginProject 项目开始 @@ -44,6 +45,7 @@ export const ProjectStart = (ksxm: number, xmxh: string, judgeUI: JudgeUI) => { drvexam } dConsole.log(ProcessDataTag, "项目开始数据处理", data) + ProcessDataTaskPoolInstance.addTask(data) } /** @@ -72,6 +74,7 @@ export const UploadProgressPhoto = async (ksxm: number, plcData: PLCType, judgeU drvexam }; dConsole.log(ProcessDataTag, "上传过程照片数据处理", data); + ProcessDataTaskPoolInstance.addTask(data) } /** @@ -133,6 +136,7 @@ export const DeductPoints = (ksxm: number, kf: MarkRule, xmmcEndCode: string, ju drvexam } dConsole.log(ProcessDataTag, "扣分上传数据", data) + ProcessDataTaskPoolInstance.addTask(data) } /** @@ -163,6 +167,7 @@ export const ProjectEnd = (ksxm: number, xmxh: string, judgeUI: JudgeUI) => { drvexam } dConsole.log(ProcessDataTag, "结束项目数据", data) + ProcessDataTaskPoolInstance.addTask(data) } diff --git a/entry/src/main/ets/pages/Judge/ProcessDataTaskPool.ets b/entry/src/main/ets/pages/Judge/ProcessDataTaskPool.ets index a9ced7b..348cf68 100644 --- a/entry/src/main/ets/pages/Judge/ProcessDataTaskPool.ets +++ b/entry/src/main/ets/pages/Judge/ProcessDataTaskPool.ets @@ -5,10 +5,14 @@ import http from '@ohos.net.http'; import Request from '../../utils/Request'; export class ProcessDataTaskPool { - private queue: RegulatoryInterfaceParams[] = [] + private queue: RegulatoryInterfaceParams[] = []; private isProcessing: boolean = false; /** 最大重试次数。1次初次尝试 + 5次重试 = 总共6次尝试。 */ private readonly maxRetries = 5; + /** 最小处理时间(毫秒) */ + private readonly minProcessingTime = 2000; + /** 记录每个任务的开始处理时间 */ + private taskStartTime: number = 0; public addTask(dataItem: RegulatoryInterfaceParams): void { console.info(`[Queue] 新任务已添加: ${JSON.stringify(dataItem)},当前队列长度: ${this.queue.length + 1}`); @@ -23,11 +27,10 @@ export class ProcessDataTaskPool { private triggerProcessing(): void { if (this.isProcessing) { console.log('[Queue] 处理器正在运行中,新任务将在稍后被处理。'); - return; // 如果已经在处理,则直接返回,新任务会被正在运行的循环消费掉 + return; } - // 使用 Promise.resolve().then() 来确保 processQueue 在下一个事件循环中异步执行 - // 这可以防止阻塞当前的 addTask 调用 - Promise.resolve().then(() => this.processQueue()); + // [优化] 直接调用 async 函数,它会立即返回一个 Promise,不会阻塞当前执行流,效果与 Promise.resolve().then() 相同但更简洁。 + this.processQueue(); } private async processQueue(): Promise { @@ -36,33 +39,52 @@ export class ProcessDataTaskPool { while (this.queue.length > 0) { const taskData = this.queue[0]; // 查看队首任务 + this.taskStartTime = Date.now(); // 记录任务开始时间 try { console.log(`[Queue] 开始处理任务: ${JSON.stringify(taskData)}`); - // 此方法若成功则正常返回,若永久失败则会抛出错误 - let obj: WuxiExamType = { + + // 预先记录将要处理的数据 + const obj: WuxiExamType = { xtlb: taskData.xtlb, jkxlh: taskData.jkxlh, jkid: taskData.jkid, drvexam: { zp: "", }, - } - dConsole.writeProcessData(ProcessDataEnumType.WuxiExam, JSON.stringify(obj)) + }; + dConsole.writeProcessData(ProcessDataEnumType.WuxiExam, JSON.stringify(obj)); + + // 此方法若成功则正常返回,若永久失败则会抛出错误 await this.processSingleTaskWithRetries(taskData); + // 任务成功,将其从队列中移除 this.queue.shift(); console.log(`[Queue] ✅ 任务处理成功,已从队列移除。剩余任务: ${this.queue.length}`); + + // 计算任务实际耗时 + const elapsedTime = Date.now() - this.taskStartTime; + // 如果处理时间小于最小要求时间,则延迟剩余时间 + if (elapsedTime < this.minProcessingTime) { + const delayTime = this.minProcessingTime - elapsedTime; + console.log(`[Queue] 任务处理耗时 ${elapsedTime}ms,需要延迟 ${delayTime}ms 以满足最小处理时间要求`); + await this.delay(delayTime); + } } catch (error) { - // 捕获到永久失败的错误 - console.error(`[Queue] 🔥 致命错误: ${(error as Error).message}`); - console.error('[Queue] 队列已停止,后续任务将不会被处理。'); + // **[健壮性改进]** 捕获到永久失败的错误。 + // 原有逻辑会清空整个队列,导致后续任务丢失。 + // 优化后的逻辑是:仅移除当前失败的任务,并记录错误,然后继续处理队列中的下一个任务。 + console.error(`[Queue] 🔥 任务永久失败: ${(error as Error).message}`); + const failedTask = this.queue.shift(); // 移除失败的任务 + console.error(`[Queue] 失败的任务已被移除: ${JSON.stringify(failedTask)},将继续处理下一个任务。`); - // (可选)可以在此处清空队列,防止下次意外启动时处理旧任务 - this.queue = []; - - // 终止循环 - break; + // 即使任务失败,也需要满足最小处理时间要求 + const elapsedTime = Date.now() - this.taskStartTime; + if (elapsedTime < this.minProcessingTime) { + const delayTime = this.minProcessingTime - elapsedTime; + console.log(`[Queue] 失败任务处理耗时 ${elapsedTime}ms,需要延迟 ${delayTime}ms 以满足最小处理时间要求`); + await this.delay(delayTime); + } } } @@ -70,27 +92,40 @@ export class ProcessDataTaskPool { console.log('[Queue] 处理器已停止。'); } + /** + * 延迟指定时间 + * @param ms 延迟的毫秒数 + */ + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + private async processSingleTaskWithRetries(taskData: RegulatoryInterfaceParams): Promise { // 1 次初次尝试 + 5 次重试 for (let attempt = 0; attempt <= this.maxRetries; attempt++) { const attemptNum = attempt + 1; try { const attemptType = attempt === 0 ? '初次尝试' : `重试 ${attempt}`; - console.log(`[Queue] 开始上传 (${attemptType}, 总共第 ${attemptNum} 次): ${JSON.stringify(taskData)}`); + console.log(`[Queue] 开始上传 (${attemptType}, 总共第 ${attemptNum} 次)`); + + // **注意**: 这里传递的是 taskData 的引用,为了防止 worker 中意外修改原始数据, + // 最佳实践是在 worker 内部或传递前进行深拷贝。 + // 但根据我们下面的修复,worker 不再修改原始数据,所以这里是安全的。 const result: WR = await taskpool.execute(uploadWorkerTask, taskData); - dConsole.writeProcessData(ProcessDataEnumType.WuxiExam, JSON.stringify(result)) + + dConsole.writeProcessData(ProcessDataEnumType.WuxiExam, JSON.stringify(result)); if (result.code === 1) { console.log(`[Queue] ✔️ 上传成功 (在第 ${attemptNum} 次尝试)`); return; // 成功,立即返回 } console.warn(`[Queue] ❌ 上传失败 (第 ${attemptNum} 次)。响应: ${result.message}`); } catch (e) { - console.error(`[Queue] ❌ TaskPool 执行错误 (第 ${attemptNum} 次): ${e}`); + // **[健壮性改进]** 现在可以捕获从 Worker 抛出的更详细的异常信息 + console.error(`[Queue] ❌ TaskPool 执行或网络错误 (第 ${attemptNum} 次): ${e}`); } - // 如果这是最后一次尝试且依然失败,则不再等待,直接跳出循环去抛出错误 if (attempt === this.maxRetries) { - break; + break; // 最后一次尝试失败后,跳出循环去抛出错误 } } @@ -99,26 +134,28 @@ export class ProcessDataTaskPool { } } +export const ProcessDataTaskPoolInstance = new ProcessDataTaskPool(); /** * 这是将在 Worker 线程中执行的任务函数。 * 它负责执行单次的上传尝试。 - * * @param data 需要上传的单条数据项 * @returns 一个包含本次上传尝试结果的对象 */ export async function uploadWorkerTask(data: RegulatoryInterfaceParams): Promise { - let singlePlay: boolean = false - let isJGNew = false + // 这两个变量似乎未在逻辑中使用,暂时保持原样 + let singlePlay: boolean = false; + let isJGNew = false; try { const response = await sendProcessData(data, singlePlay, isJGNew); - // 根据返回的 code 判断是否成功 - return response + return response; } catch (err) { - // 捕获请求过程中可能出现的异常 + // [健壮性改进] 捕获请求过程中的异常,并重新抛出。 + // 这能让主线程的 taskpool.execute() promise 变为 rejected 状态, + // 从而被 processSingleTaskWithRetries 中的 try-catch 捕获,保留了详细的错误信息。 const error = err as Error; console.error(`[Worker] 上传时发生异常: ${error.message}`); - return { code: 20038 }; + throw error; // 重新抛出异常 } } @@ -139,32 +176,8 @@ async function sendProcessData(data: RegulatoryInterfaceParams, singlePlay: bool return await Request({ host: JGHOST, url: '/dems_ws/services/TmriOutAccess?wsdl', - data: ` - - - - ${data.xtlb} - ${data.jkxlh} - ${data.jkid} - - - - - ${drvexamArr} - - - ]]> - - - - `, + data: ` ${data.xtlb} ${data.jkxlh} ${data.jkid} ${drvexamArr} ]]> `, method: http.RequestMethod.POST, xml: true }) -} - +} \ No newline at end of file diff --git a/entry/src/main/ets/utils/business/DifferentialSignal.ets b/entry/src/main/ets/utils/business/DifferentialSignal.ets index a7a9987..e905e2c 100644 --- a/entry/src/main/ets/utils/business/DifferentialSignal.ets +++ b/entry/src/main/ets/utils/business/DifferentialSignal.ets @@ -16,19 +16,12 @@ class differentialSignal { console.error(TCPTag, "TCP发生错误") this.differentialSignalTcp.reBind() }) - // let config: EnvironmentConfigurationType = - // AppStorage.get("EnvironmentConfiguration") || { - // tcplocalIp: "", - // tcplocalIpPort: "", - // tcpOppositeIp: "", - // tcpOppositePort: "" - // } console.log(TCPTag, "初始化", JSON.stringify(config)) if (config.tcplocalIp || config.tcplocalIpPort || config.tcpOppositeIp || config.tcpOppositePort) { this.differentialSignalTcp.init(config.tcplocalIp || "", config.tcplocalIpPort || "", config.tcpOppositeIp || "", config.tcpOppositePort || ""); } else { - console.log(TCPTag, "未配置差分信号TCP信息,请在环境配置中设置") + console.error(TCPTag, "未配置差分信号TCP信息,请在环境配置中设置") } }