Files
oil/server-tp6/app/api/controller/QytNotify.php
T
vipnub fafd3e2c16 1、合作商余额查询
2、分油账户司机可用额度
2026-10-20 23:21:10 +08:00

153 lines
7.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | 企油通 · 回调接收
// +----------------------------------------------------------------------
// | 入口:
// | public/qytconsume.php -> /qytNotify/consume 企油通 7.10「运单加油明细推送」
// | public/qytwaybill.php -> /qytNotify/waybill 企油通 3.10「发放结果通知」
// |
// | 两个回调都是 multipart/form-data,业务参数在表单字段里(JSON 字符串):
// | 7.10 -> params 我方须返回 {"code":"100","msg":"Success"}
// | 3.10 -> data + sign 我方只要返回 HTTP 2xx 即算成功,响应体不做要求
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\api\controller;
use app\common\crypto\Qyt as QytCrypto;
use app\common\service\QueueService;
use app\common\service\QytOil;
use think\facade\Request;
class QytNotify extends Controller
{
/**
* 7.10 运单加油明细推送
* @return \think\response\Json
*/
public function consume()
{
$request = Request::instance();
// ⚠️ 企油通用 multipart/form-data 推送,业务参数在表单字段 params 里(JSON 字符串)。
// 这种 Content-Type 下 PHP 不会填充 php://input,读请求体恒为空 —— 必须取表单字段。
// 回退读请求体只是为了兼容 JSON 推送与本地测试脚本。
// ⚠️ 这里必须直接读 $_POST:cores\Request 给所有入参挂了全局过滤器
// ['my_trim','my_htmlspecialchars','filter_emoji'],走 $request->post() 取到的 JSON
// 引号会被转义成 &quot;,json_decode 必然失败(回调会被静默当成空明细忽略)
$formParams = isset($_POST['params']) ? trim((string) $_POST['params']) : '';
$raw = $request->getContent();
(new QytOil())->saveLog('7.10 消费回调原文[' . $request->contentType() . ']:'
. ($formParams !== '' ? 'params=' . $formParams : $raw));
if ($formParams !== '') {
$params = json_decode($formParams, true);
// 兜底:若上游或中间层做过 HTML 转义,反转义后再解析一次
if (!is_array($params) && strpos($formParams, '&quot;') !== false) {
$params = json_decode(htmlspecialchars_decode($formParams, ENT_QUOTES), true);
}
} else {
// 兼容:外层信封 {appId,timestamp,sign,params(JSON字符串)} 或 直接推 body
$body = json_decode($raw, true);
$params = $body;
if (is_array($body) && isset($body['params'])) {
$params = is_string($body['params']) ? json_decode($body['params'], true) : $body['params'];
}
}
// TODO 验签(规则待对接人确认)
// if (is_array($body) && !empty($body['sign'])) {
// $crypto = new QytCrypto();
// if (!$crypto->verify($params, $body['sign'])) {
// return json(['code' => '99', 'msg' => '验签失败']);
// }
// }
// 无明细也回 100,避免企油通重复推送
if (is_array($params) && !empty($params['order_list'])) {
(new QueueService())->push('callback_queue', json_encode([
'header' => ['method' => 'qyt_consume'],
'res' => $params,
], 320));
} else {
(new QytOil())->saveLog('7.10 消费回调无 order_list,已忽略');
}
return json(['code' => '100', 'msg' => 'Success']);
}
/**
* 3.10 发放结果通知(运单油费 / 企业福利)
*
* 企油通把 7.7 提交的批次处理完后(全部成功 / 部分成功 / 处理失败)主动推过来,
* 一个批次一条,内容与 7.8 查询结果一致。接上之后就不必再靠轮询等结果。
*
* 约定(见文档 3.10):
* · form-data,字段 data(JSON 字符串)+ sign
* · scene 区分场景:oil=运单油费下发(7.7),welfare=员工福利下发(9.3)
* · 接收端返回 HTTP 2xx 即视为成功;非 2xx 或 5 秒内无响应会重试,最多 5 次
* · 同一批次可能收到多次(重试 / 批次重新提交),必须幂等
* · 明细最多约 1 万条,报文可能很大
*
* 所以这里只做「尽快收下、丢进队列、立刻回 200」,真正的状态跃迁交给
* callback:queue 异步做,避免大报文处理超过上游 5 秒超时导致无谓重试。
*
* @return \think\response\Json
*/
public function waybill()
{
$request = Request::instance();
// ⚠️ 必须直接读 $_POST:cores\Request 给所有入参挂了全局过滤器
// ['my_trim','my_htmlspecialchars','filter_emoji'],走 $request->post() 取到的
// JSON 引号会被转义成 &quot;,json_decode 必然失败(通知会被静默丢掉)
$formData = isset($_POST['data']) ? trim((string) $_POST['data']) : '';
$sign = isset($_POST['sign']) ? trim((string) $_POST['sign']) : '';
$raw = $request->getContent();
$payload = $formData !== '' ? $formData : $raw;
$qyt = new QytOil();
// 明细可能上万条,日志只留头部,完整报文靠 batch_no 去企油通后台对
$qyt->saveLog('3.10 发放结果通知原文[' . $request->contentType() . '](截断):'
. mb_substr($payload, 0, 4000) . (mb_strlen($payload) > 4000 ? ' …(已截断)' : ''));
$data = json_decode($payload, true);
// 兜底:若上游或中间层做过 HTML 转义,反转义后再解析一次
if (!is_array($data) && strpos($payload, '&quot;') !== false) {
$data = json_decode(htmlspecialchars_decode($payload, ENT_QUOTES), true);
}
// 兼容:直接推 JSON body 且外层包了一层 {data: ..., sign: ...}
if (is_array($data) && isset($data['data'])) {
$sign === '' && $sign = (string) ($data['sign'] ?? '');
$data = is_string($data['data']) ? json_decode($data['data'], true) : $data['data'];
}
if (!is_array($data) || empty($data['batch_no'])) {
$qyt->saveLog('3.10 发放结果通知解析失败或缺少 batch_no,已忽略');
// 仍然回 2xx:报文我方解析不了,让上游重试 5 次也没有意义,
// 这类单由 7.8 轮询兜底(轮询并没有下线)
return json(['code' => '100', 'msg' => 'Success']);
}
// TODO 验签(对 data 原串签名,规则见开发指南 2.5,待对接人给到平台公钥与算法确认)
// if ($sign !== '' && !(new QytCrypto())->verifyNotify($payload, $sign)) {
// $qyt->saveLog('3.10 发放结果通知验签失败 batch_no=' . $data['batch_no']);
// return json(['code' => '99', 'msg' => 'sign error']);
// }
// 只处理运单油费(7.7)。员工福利(9.3)我方没有对接,收下回 2xx 即可,
// 否则上游会按失败重试 5 次。
$scene = (string) ($data['scene'] ?? 'oil');
if ($scene !== 'oil') {
$qyt->saveLog('3.10 发放结果通知 scene=' . $scene . '(非运单油费),已忽略 batch_no=' . $data['batch_no']);
return json(['code' => '100', 'msg' => 'Success']);
}
(new QueueService())->push('callback_queue', json_encode([
'header' => ['method' => 'qyt_waybill_result'],
'res' => $data,
], 320));
return json(['code' => '100', 'msg' => 'Success']);
}
}