setName('callback:queue') ->setDescription('Process messages from the Redis queue'); } protected function execute(Input $input, Output $output) { $this->queueService = new QueueService(); while (true) { $message = $this->queueService->pop('callback_queue'); if ($message) { // 处理消息 $this->processMessage($message); } else { // 暂停一段时间再检查队列 sleep(1); } } } // protected function processMessage($message) // { // $SybOil = new SybOil(); // $SybOil->saveLogStore('callback_queue队列数据处理:【'.$message.'】'); // // 拿到队列数据处理 // $data = json_decode($message, true); // // 检查解码是否成功 // if ($data === false) { // $SybOil->saveLogStore('callback_queue队列数据解码失败:' . json_last_error_msg()); // return; // } // // 确保 $data 中包含 header 和 res // if (!isset($data['header']) || !isset($data['res'])) { // $SybOil->saveLogStore('数据格式不正确:【' . $message . '】'); // return; // } // $headers = $data['header']; // $res = $data['res']; // // 此处可以根据业务逻辑处理接收到的数据 // try { // switch ($headers['method']) { // case "syb.notify.pay.result": // // 二维码交易结果通知 // if($res['oilCardType'] == 'card') { // $where = array( // ['oilCardNo', '=', $res['oilCardNo']], // ['goods_price', '=', $res['amount']], // ); // }else{ // $where = array( // ['shareNo', '=', $res['shareNo']], // ['goods_price', '=', $res['amount']], // ); // } // $orderGoods = Db::name('order_goods') // ->where($where) // ->find(); // if($orderGoods['order_goods_id']) { // $field['qrCode'] = $res['qrCode']; // if ($res['status'] == 'success') { // $field['use_status'] = 1; // } else { // $field['use_text'] = $res['errMessage']; // } // // 更新用户使用状态 // Db::name('order_goods') // ->where('order_goods_id', $orderGoods['order_goods_id']) // ->update($field); // }else{ // $SybOil->saveLogStore('未找到订单信息:【'.$message.'】'); // } // break; // case "syb.notify.oilCard.change": // // 油卡变动通知 // $where = array( // ['oilCardNo', '=', $res['oilCardNo']], // ['goods_price', '=', $res['amount']], // ); // $orderGoods = Db::name('order_goods') // ->where($where) // ->find(); // $res['order_id'] = $orderGoods['order_id']; // $res['order_goods_id'] = $orderGoods['order_goods_id']; // $res['user_id'] = $orderGoods['user_id']; // $res['store_id'] = $orderGoods['store_id']; // $RecordModel = new RecordModel(); // // 油卡变动记录写入结果 // $RecordModel->add($res); // // 更新油券使用状态 // if($orderGoods['order_goods_id'] && $res['optType'] == 'add_oil'){ // Db::name('order_goods') // ->where('order_goods_id', $orderGoods['order_goods_id']) // ->update(array( // 'use_status' => 1, // 'use_time' => time(), // 'use_text' => '加油', // )); // // 修改主订单为已完成 // Db::name('order') // ->where('order_id', $orderGoods['order_id']) // ->update(array( // 'order_status' => 30 // )); // // 更新用户使用状态 // Db::name('account') // ->where('account_id', $orderGoods['account_id']) // ->update(['last_called_at' => time(), 'state' => 0]); // $SybOil->saveLogStore('加油回调处理成功了:'.json_encode($res)); // }else{ // $SybOil->saveLogStore('没有处理结果:【'.$message.'】'); // } // break; // case "syb.notify.oilCard.share": // // 分油结果通知 // if($res['oilCardType'] == 'card') { // $where = array( // ['oilCardNo', '=', $res['oilCardNo']], // ['goods_price', '=', $res['amount']], // ); // }else{ // $where = array( // ['shareNo', '=', $res['shareNo']], // ['goods_price', '=', $res['amount']], // ); // } // $orderGoods = Db::name('order_goods') // ->where($where) // ->find(); // if($orderGoods['order_goods_id']) { // $field = ['oil_time' => time()]; // if ($res['status'] == 'success') { // $field['oil_status'] = 1; // } else { // $field['oil_status'] = 0; // $field['oil_text'] = $res['msg']; // } // Db::name('order_goods') // ->where('order_goods_id', $orderGoods['order_goods_id']) // ->update($field); // // $SybOil->saveLogStore('分油回调处理成功了:'.json_encode($res)); // }else{ // $this->queueService->push('callback_queue', json_encode(['header'=>$headers,'res'=>$res],320)); // $SybOil->saveLogStore('重新加入队列:【'.$message.'】'); // } // break; // default: // $SybOil->saveLogStore('队列数据处理异常,未知:【'.$message.'】'); // } // } catch (\Exception $e) { // $SybOil->saveLogStore( // '队列回调处理异常:' . $e->getMessage() . // ' 在文件:' . $e->getFile() . // ' 第:' . $e->getLine() . // ' 行。堆栈跟踪:' . $e->getTraceAsString() // ); // // } // } protected function processMessage($message) { $SybOil = new SybOil(); $SybOil->saveLogStore('callback_queue队列数据处理:【'.$message.'】'); // 拿到队列数据处理 $data = json_decode($message, true); // 检查解码是否成功 if ($data === false) { $SybOil->saveLogStore('callback_queue队列数据解码失败:' . json_last_error_msg()); return; } // 确保 $data 中包含 header 和 res if (!isset($data['header']) || !isset($data['res'])) { $SybOil->saveLogStore('数据格式不正确:【' . $message . '】'); return; } $headers = $data['header']; $res = $data['res']; // 此处可以根据业务逻辑处理接收到的数据 try { switch ($headers['method']) { case "use": // 油卡变动通知 $where = array( ['shareNo', '=', $res['trade_no']], ); $orderGoods = Db::name('order_goods') ->where($where) ->find(); $res['order_id'] = $orderGoods['order_id']; $res['order_goods_id'] = $orderGoods['order_goods_id']; $res['user_id'] = $orderGoods['user_id']; $res['store_id'] = $orderGoods['store_id']; $res['optType'] = 'add_oil'; $RecordModel = new RecordModel(); // 油卡变动记录写入结果 $RecordModel->add($res); // 更新油券使用状态 if($orderGoods['order_goods_id'] && $res['useStatus'] == '2'){ Db::name('order_goods') ->where('order_goods_id', $orderGoods['order_goods_id']) ->update(array( 'use_status' => 1, 'use_time' => time(), 'use_text' => '加油', )); // 订单下的券都已使用 if(Db::name('order_goods')->where([['order_id' , '=', $orderGoods['order_id']], ['use_status', '=', '0']])->count() == 0) { // 修改主订单为已完成 Db::name('order') ->where('order_id', $orderGoods['order_id']) ->update(array( 'order_status' => 30 )); } // 更新用户使用状态 Db::name('account') ->where('account_id', $orderGoods['account_id']) ->update(['last_called_at' => time(), 'state' => 0]); $SybOil->saveLogStore('加油回调处理成功了:'.json_encode($res)); }else{ $SybOil->saveLogStore('没有处理结果:【'.$message.'】'); } break; case "oil": // 分油结果通知 $where = array( ['shareNo', '=', $res['trade_no']], ); $orderGoods = Db::name('order_goods') ->where($where) ->find(); if($orderGoods['order_goods_id'] && $res['useStatus'] == '1') { $field = ['oil_time' => time()]; $field['oil_status'] = 1; $field['qrCode'] = $res['useUrl'] ?? ''; Db::name('order_goods') ->where('order_goods_id', $orderGoods['order_goods_id']) ->update($field); $SybOil->saveLogStore('分油回调处理成功了:'.json_encode($res)); }else{ // $this->queueService->push('callback_queue', json_encode(['header'=>$headers,'res'=>$res],320)); // $SybOil->saveLogStore('重新加入队列:【'.$message.'】'); } break; case "use_partners": // 油卡变动通知 $where = array( ['shareNo', '=', $res['trade_no']], ); $order = Db::name('partners_order') ->where($where) ->find(); $res['order_id'] = $order['order_id']; $res['order_goods_id'] = $order['goods_id']; $res['user_id'] = 0; $res['store_id'] = $order['store_id']; $res['optType'] = 'add_oil'; $RecordModel = new RecordModel(); // 油卡变动记录写入结果 $RecordModel->add($res); // 更新油券使用状态 if($order['order_id'] && $res['useStatus'] == '2'){ Db::name('partners_order') ->where('order_id', $order['order_id']) ->update(array( 'use_status' => 1, 'use_time' => time(), 'use_text' => '加油', )); $SybOil->saveLogStore('加油回调处理成功了:'.json_encode($res)); // 通知对接公司 $queueService = new QueueService(); $queueService->push('notify_queue', json_encode([ 'order_id' => $order['order_id'], 'optType' => 'add_oil', 'retry_count' => 0, ], 320)); }else{ $SybOil->saveLogStore('没有处理结果:【'.$message.'】'); } break; case "oil_partners": // 分油结果通知 $where = array( ['shareNo', '=', $res['trade_no']], ); $order = Db::name('partners_order') ->where($where) ->find(); if($order['order_id'] && $res['useStatus'] == '1') { $field = ['oil_time' => time()]; $field['oil_status'] = 1; $field['qrCode'] = $res['useUrl'] ?? ''; Db::name('partners_order') ->where('order_id', $order['order_id']) ->update($field); $SybOil->saveLogStore('分油回调处理成功了:'.json_encode($res)); // 通知对接公司 $queueService = new QueueService(); $queueService->push('notify_queue', json_encode([ 'order_id' => $order['order_id'], 'optType' => 'share_oil', 'retry_count' => 0, ], 320)); }else{ // $this->queueService->push('callback_queue', json_encode(['header'=>$headers,'res'=>$res],320)); // $SybOil->saveLogStore('重新加入队列:【'.$message.'】'); } break; case "qyt_consume": // 企油通 7.10 运单加油明细推送(一次可能多条;企油通可能重推 -> 全流程幂等) foreach ((array)($res['order_list'] ?? []) as $c) { $code = $c['waybill_no_code'] ?? ''; $order = Db::name('partners_order')->where('waybill_no_code', $code)->find(); if (!$order) { $SybOil->saveLogStore('企油通消费回调未匹配订单:' . json_encode($c, 320)); continue; } $payTime = !empty($c['pay_time']) ? (strtotime($c['pay_time']) ?: time()) : time(); // 一单可以分多次加油:每条推送是一次加油,consume_amount = 本次占用运单金额(分) $consumeFen = (int)($c['consume_amount'] ?? 0); $consumeYuan = number_format($consumeFen / 100, 2, '.', ''); $orderCode = trim((string)($c['order_code'] ?? '')); // 去重键:文档里 order_code 是选填,缺失时用本笔消费的特征字段做指纹, // 否则同一运单的第二笔加油会被唯一键误判为重复推送而丢掉(口径与迁移脚本回填一致) $consumeKey = $orderCode !== '' ? $orderCode : 'FP' . md5(implode('|', [ $c['quota_id'] ?? '', $c['pay_time'] ?? '', $c['consume_amount'] ?? '', $c['pay_fee'] ?? '', $c['station_code'] ?? '', $c['litter'] ?? '', ])); $consumeId = 0; try { // 流水落库与订单累计放在同一事务:唯一键挡住重推,重推既不重复累计也不重复通知 Db::transaction(function () use ($order, $c, $code, $orderCode, $consumeKey, $payTime, $consumeFen, $consumeYuan, &$consumeId) { $consumeId = (int)Db::name('qyt_consume')->insertGetId([ 'order_id' => $order['order_id'], 'waybill_no' => $c['waybill_no'] ?? '', 'waybill_no_code' => $code, 'order_code' => $orderCode, 'consume_key' => $consumeKey, 'station_code' => $c['station_code'] ?? '', 'station_name' => $c['station_name'] ?? '', 'oil_name' => $c['oil_name'] ?? '', 'litter' => $c['litter'] ?? '', 'oil_price' => (int)($c['oil_price'] ?? 0), 'actual_oil_price' => (int)($c['actual_oil_price'] ?? 0), 'pay_fee' => (int)($c['pay_fee'] ?? 0), 'consume_amount' => $consumeFen, 'pay_time' => $payTime, 'raw' => json_encode($c, 320), 'store_id' => $order['store_id'], 'create_time' => time(), ]); // 累计已加油金额 / 次数,并记下最近一次加油信息(原子自增,不读后写) Db::name('partners_order')->where('order_id', $order['order_id'])->update([ 'used_amount' => Db::raw('`used_amount` + ' . $consumeYuan), 'used_num' => Db::raw('`used_num` + 1'), 'use_text' => '加油', 'qyt_order_code' => $orderCode, 'station_name' => $c['station_name'] ?? '', 'oil_name' => $c['oil_name'] ?? '', 'litter' => $c['litter'] ?? '', 'pay_fee' => (int)($c['pay_fee'] ?? 0), 'consume_time' => $payTime, 'update_time' => time(), ]); // use_status 语义不变(0 未加油 / 1 有加油记录):只在第一笔加油时置 1 并记首次加油时间 Db::name('partners_order')->where('order_id', $order['order_id'])->where('use_status', 0)->update([ 'use_status' => 1, 'use_time' => time(), 'shareNo' => $orderCode !== '' ? $orderCode : $order['shareNo'], ]); }); } catch (\Exception $e) { if (strpos($e->getMessage(), 'Duplicate') === false) { throw $e; } $SybOil->saveLogStore('企油通消费回调重复,忽略(不重复累计、不重复通知):' . $code . '/' . $consumeKey); continue; } // 超额告警:累计加油 + 已退款 > 订单金额。上游额度按司机凭证累计, // 归属到本单的消费可能与本地可退金额算出的不一致,需要人工核对 $fresh = Db::name('partners_order')->where('order_id', $order['order_id']) ->field('total_price,used_amount,refund_amount,oil_status_zsy')->find(); if ($fresh && (float)$fresh['used_amount'] + (float)$fresh['refund_amount'] > (float)$fresh['total_price'] + 0.005) { $SybOil->saveLogStore('【告警】订单已加油+已退款超出订单金额 order=' . $order['order_id'] . ' total=' . $fresh['total_price'] . ' used=' . $fresh['used_amount'] . ' refund=' . $fresh['refund_amount']); } // 每笔加油都通知下单平台(amount = 本次占用金额) $notify = json_encode([ 'order_id' => $order['order_id'], 'optType' => 'add_oil', 'consume_id' => $consumeId, 'retry_count' => 0, ], 320); $queueService = new QueueService(); if ((int)($fresh['oil_status_zsy'] ?? 2) === 2) { $queueService->push('notify_queue', $notify); } else { // 7.8 发放结果还没确认就先收到了加油:延后 60 秒再通知,尽量让「分油成功」通知先到下游 $queueService->laterPush('notify_queue', $notify, 60); } $SybOil->saveLogStore('企油通消费回调处理成功:' . json_encode($c, 320)); } break; case "qyt_waybill_result": // 企油通 3.10「发放结果通知」:一个批次一条,整批明细都在 res.list 里。 // 判定与状态跃迁跟 7.8 轮询共用 QytWaybill,保证两条路结论一致、互相幂等。 $batchNo = (string)($res['batch_no'] ?? ''); $batchStatus = (int)($res['status'] ?? 0); $notifyTime = !empty($res['notify_time']) ? (strtotime((string)$res['notify_time']) ?: time()) : time(); $list = (array)($res['list'] ?? []); $stat = ['success' => 0, 'failed' => 0, 'pending' => 0, 'skipped' => 0, 'conflict' => 0, 'miss' => 0]; foreach ($list as $row) { $code = trim((string)($row['waybill_no_code'] ?? '')); if ($code === '') { $stat['miss']++; continue; } $order = Db::name('partners_order')->where('waybill_no_code', $code)->find(); if (!$order) { // 同一个 appId 下可能还挂着别的接入系统提交的批次,匹配不到不算异常 $stat['miss']++; continue; } // 留痕:这一单到底收没收到过推送、收了几次 QytWaybill::touchNotify((int)$order['order_id'], $notifyTime); // 每单串行,避免与 qyt:status 轮询同时跃迁 $lock = new RedisLock('qyt:status:' . $order['order_id'], 60); if (!$lock->acquireLock()) { // 轮询正在处理这一单,跳过即可:两条路的结论一样 $stat['skipped']++; continue; } try { // 锁内重读,拿到最新状态 $fresh = Db::name('partners_order')->where('order_id', $order['order_id'])->find(); $judged = QytWaybill::judge($batchStatus, [ 'handle_status' => (int)($row['handle_status'] ?? 0), 'error_msg' => (string)($row['error_msg'] ?? ''), 'remark' => (string)($row['remark'] ?? ''), ]); $stat[QytWaybill::apply($fresh ?: $order, $judged, '3.10 通知')]++; } finally { $lock->releaseLock(); } } $SybOil->saveLogStore('3.10 发放结果通知处理完成 batch_no=' . $batchNo . ' 批次状态=' . $batchStatus . ' 明细=' . count($list) . ' 成功=' . $stat['success'] . ' 失败=' . $stat['failed'] . ' 待处理=' . $stat['pending'] . ' 已是终态=' . $stat['skipped'] . ' 未匹配=' . $stat['miss'] . ($stat['conflict'] ? ' 【冲突=' . $stat['conflict'] . ',需人工核对】' : '')); break; default: $SybOil->saveLogStore('队列数据处理异常,未知:【'.$message.'】'); } } catch (\Exception $e) { $SybOil->saveLogStore( '队列回调处理异常:' . $e->getMessage() . ' 在文件:' . $e->getFile() . ' 第:' . $e->getLine() . ' 行。堆栈跟踪:' . $e->getTraceAsString() ); } } }