Selaa lähdekoodia

批发-增加订单打印日志(缓存)

ouyang 1 viikko sitten
vanhempi
commit
d7654c3a72

+ 46 - 0
app-ghs/controllers/FeieyunController.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace ghs\controllers;
+
+use common\components\feiePrintLogUtil;
+use common\components\feieyunCallbackUtil;
+use common\components\util;
+use Yii;
+
+/**
+ * 飞鹅云打印回调
+ */
+class FeieyunController extends BaseController
+{
+    /** 飞鹅回调无需登录 */
+    public $guestAccess = ['call-back'];
+
+    /**
+     * 飞鹅打印状态回调
+     * POST application/x-www-form-urlencoded
+     */
+    public function actionCallBack()
+    {
+        $post = Yii::$app->request->post();
+        if (empty($post)) {
+            $raw = file_get_contents('php://input');
+            if (!empty($raw)) {
+                parse_str($raw, $post);
+            }
+        }
+        Yii::info('飞鹅打印回调: ' . json_encode($post, JSON_UNESCAPED_UNICODE), __METHOD__);
+        if (!feieyunCallbackUtil::verify($post)) {
+            Yii::warning('飞鹅打印回调验签失败: ' . json_encode($post, JSON_UNESCAPED_UNICODE), __METHOD__);
+            util::stop('飞鹅打印回调验签失败');
+        }
+
+        $feieOrderId = trim((string)($post['orderId'] ?? ''));
+        $status = intval($post['status'] ?? 0);
+        $stime = intval($post['stime'] ?? 0);
+        if ($feieOrderId !== '') {
+            feiePrintLogUtil::updateByCallback($feieOrderId, $stime, $status, $post);
+        }
+        // 飞鹅要求 5 秒内返回 SUCCESS
+        util::stop('SUCCESS');
+    }
+}

+ 23 - 1
app-ghs/controllers/OrderController.php

@@ -23,7 +23,7 @@ use bizGhs\order\services\OrderService;
 use bizHd\saas\services\RegionService;
 use bizGhs\product\classes\ProductClass;
 use common\components\baiduAip;
-use common\components\dict;
+use common\components\feiePrintLogUtil;
 use common\components\dateUtil;
 use common\components\dirUtil;
 use common\components\freight;
@@ -2549,6 +2549,28 @@ class OrderController extends BaseController
         util::complete();
     }
 
+    /**
+     * 飞鹅云打印日志(Redis 缓存)
+     */
+    public function actionPrintLog()
+    {
+        $id = intval(Yii::$app->request->get('id', 0));
+        if ($id <= 0) {
+            util::fail('订单id不对');
+        }
+        $order = OrderClass::getById($id, true);
+        OrderClass::valid($order, $this->shopId);
+
+        $printSn = $this->shopExt->printSn ?? '';
+        $list = feiePrintLogUtil::getList($order->mainId, $order->id);
+
+        util::success([
+            'printSn' => (string)$printSn,
+            'orderSn' => (string)($order->orderSn ?? ''),
+            'list' => $list,
+        ]);
+    }
+
     //订单确认完成 ssh 20210809
     public function actionConfirmFinish()
     {

+ 1 - 0
app-ghs/web/feieyun/feieyun_verify_Rd1vH59DmcEl4Bpe.txt

@@ -0,0 +1 @@
+Rd1vH59DmcEl4Bpe

+ 9 - 0
app-ghs/web/feieyun/public.key

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2LDlvyuClqrnKW01FqYg
+valPy1/e09ZWlvjb5Nu+0T1PsGhKjF4WBb+D7x3Dy/Db5IHMcpG/Eps6ew6n/6rw
+v8Ctu+uZI33YNv9sqAMPjG2EN+WcqCrVrMGUmjITVpIkQEjTdkuismf+VL3x+eJo
+W1y/TaLb9vchReBc6IZowRu2yItC+tFbock5Nupsl5uOCKltm3s0VqtiHUrpgVeV
+8dVJHLhmLENnLgcTqrkZeKogFDT+fTOhzQPZVEqQgdat/6kcmD44lN4UI7EvVNfe
+amwRLgy4e/CpInD9cql+t5eiRLem0+rgPq9RLivM1pRt67crH0WGY1xXtAtzWO0M
+MwIDAQAB
+-----END PUBLIC KEY-----

+ 9 - 1
biz-ghs/order/classes/OrderClass.php

@@ -2232,7 +2232,15 @@ class OrderClass extends BaseClass
 
         if (!empty($printSn)) {
             $p = new printUtil($printSn);
-            $p->printMsg($content, $shop, $orderSn);
+            // onlinePrint 每次打印均写入飞鹅打印日志缓存
+            $logMeta = [
+                'writeLog' => true,
+                'mainId' => intval($orderInfo['mainId'] ?? 0),
+                'orderId' => intval($orderInfo['id'] ?? 0),
+                'orderSn' => (string)$orderSn,
+                'printSn' => (string)$printSn,
+            ];
+            $p->printMsg($content, $shop, $orderSn, $logMeta);
         }
 
     }

+ 155 - 0
common/components/feiePrintLogUtil.php

@@ -0,0 +1,155 @@
+<?php
+
+namespace common\components;
+
+use Yii;
+
+/**
+ * 飞鹅云打印日志(Redis 缓存,按 mainId + orderId 聚合)
+ */
+class feiePrintLogUtil
+{
+    /** 日志缓存 TTL:180 天 */
+    const CACHE_TTL = 15552000;
+
+    /** 待回调映射 TTL:180 天 */
+    const MAP_TTL = 15552000;
+
+    /**
+     * 订单打印日志 Redis Key
+     */
+    public static function logCacheKey($mainId, $orderId)
+    {
+        return 'feie_print_log:' . intval($mainId) . ':' . intval($orderId);
+    }
+
+    /**
+     * 飞鹅订单 ID 反查 Redis Key
+     */
+    public static function mapCacheKey($feieOrderId)
+    {
+        return 'feie_print_map:' . trim((string)$feieOrderId);
+    }
+
+    /**
+     * 打印提交成功后写入 pending 记录,并建立 feieOrderId 反查索引
+     */
+    public static function appendPending($mainId, $orderId, $orderSn, $printSn, $feieOrderId)
+    {
+        $mainId = intval($mainId);
+        $orderId = intval($orderId);
+        $feieOrderId = trim((string)$feieOrderId);
+        if ($mainId <= 0 || $orderId <= 0 || $feieOrderId === '') {
+            return false;
+        }
+
+        $mapKey = self::mapCacheKey($feieOrderId);
+        $mapData = json_encode([
+            'mainId' => $mainId,
+            'orderId' => $orderId,
+            'orderSn' => (string)$orderSn,
+            'printSn' => (string)$printSn,
+        ], JSON_UNESCAPED_UNICODE);
+        Yii::$app->redis->executeCommand('SETEX', [$mapKey, self::MAP_TTL, $mapData]);
+
+        $logKey = self::logCacheKey($mainId, $orderId);
+        $list = self::decodeList(Yii::$app->redis->executeCommand('GET', [$logKey]));
+        $list[] = [
+            'orderSn' => (string)$orderSn,
+            'printSn' => (string)$printSn,
+            'feieOrderId' => $feieOrderId,
+            'printTime' => 0,
+            'printTimeText' => '',
+            'status' => 0,
+        ];
+        Yii::$app->redis->executeCommand('SETEX', [$logKey, self::CACHE_TTL, json_encode($list, JSON_UNESCAPED_UNICODE)]);
+
+        return true;
+    }
+
+    /**
+     * 飞鹅回调更新打印时间与状态
+     */
+    public static function updateByCallback($feieOrderId, $stime, $status, $rawCallback = [])
+    {
+        $feieOrderId = trim((string)$feieOrderId);
+        if ($feieOrderId === '') {
+            return false;
+        }
+
+        $mapRaw = Yii::$app->redis->executeCommand('GET', [self::mapCacheKey($feieOrderId)]);
+        if (empty($mapRaw)) {
+            Yii::warning('飞鹅打印回调未找到映射: ' . $feieOrderId, __METHOD__);
+            return false;
+        }
+
+        $map = json_decode($mapRaw, true);
+        if (empty($map['mainId']) || empty($map['orderId'])) {
+            return false;
+        }
+
+        $logKey = self::logCacheKey($map['mainId'], $map['orderId']);
+        $list = self::decodeList(Yii::$app->redis->executeCommand('GET', [$logKey]));
+        $stime = intval($stime);
+        $updated = false;
+
+        foreach ($list as &$item) {
+            if (($item['feieOrderId'] ?? '') === $feieOrderId) {
+                $item['printTime'] = $stime;
+                $item['printTimeText'] = $stime > 0 ? date('Y-m-d H:i:s', $stime) : '';
+                $item['status'] = intval($status);
+                if (!empty($rawCallback)) {
+                    $item['callbackInfo'] = $rawCallback;
+                }
+                $updated = true;
+                break;
+            }
+        }
+        unset($item);
+
+        if (!$updated) {
+            $list[] = [
+                'orderSn' => (string)($map['orderSn'] ?? ''),
+                'printSn' => (string)($map['printSn'] ?? ''),
+                'feieOrderId' => $feieOrderId,
+                'printTime' => $stime,
+                'printTimeText' => $stime > 0 ? date('Y-m-d H:i:s', $stime) : '',
+                'status' => intval($status),
+                'callbackInfo' => $rawCallback,
+            ];
+        }
+
+        Yii::$app->redis->executeCommand('SETEX', [$logKey, self::CACHE_TTL, json_encode($list, JSON_UNESCAPED_UNICODE)]);
+        return true;
+    }
+
+    /**
+     * 读取订单打印日志,按 printTime 降序
+     */
+    public static function getList($mainId, $orderId)
+    {
+        $logKey = self::logCacheKey($mainId, $orderId);
+        $list = self::decodeList(Yii::$app->redis->executeCommand('GET', [$logKey]));
+        usort($list, function ($a, $b) {
+            $timeA = intval($a['printTime'] ?? 0);
+            $timeB = intval($b['printTime'] ?? 0);
+            if ($timeA === $timeB) {
+                return strcmp($b['feieOrderId'] ?? '', $a['feieOrderId'] ?? '');
+            }
+            return $timeB <=> $timeA;
+        });
+        return $list;
+    }
+
+    /**
+     * 解析 Redis 中的 JSON 数组
+     */
+    protected static function decodeList($raw)
+    {
+        if (empty($raw)) {
+            return [];
+        }
+        $list = json_decode($raw, true);
+        return is_array($list) ? $list : [];
+    }
+}

+ 85 - 0
common/components/feieyunCallbackUtil.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace common\components;
+
+use Yii;
+
+/**
+ * 飞鹅云打印回调验签(SHA256WithRSA)
+ * 文档:http://help.feieyun.com/#/home/doc/zh;nav=1-8
+ */
+class feieyunCallbackUtil
+{
+    /**
+     * 飞鹅公钥文件路径
+     */
+    public static function getPublicKeyPath()
+    {
+        return Yii::getAlias('@app/web/feieyun/public.key');
+    }
+
+    /**
+     * 组装待验签字符串:剔除 sign 与空值,按参数名 ASCII 升序拼接
+     */
+    public static function buildSignContent(array $params)
+    {
+        unset($params['sign']);
+        $filtered = [];
+        foreach ($params as $key => $value) {
+            if ($value === null || $value === '') {
+                continue;
+            }
+            $filtered[(string)$key] = (string)$value;
+        }
+        ksort($filtered, SORT_STRING);
+
+        $parts = [];
+        foreach ($filtered as $key => $value) {
+            $parts[] = $key . '=' . $value;
+        }
+        return implode('&', $parts);
+    }
+
+    /**
+     * 校验飞鹅回调签名
+     */
+    public static function verify(array $params)
+    {
+        $sign = trim((string)($params['sign'] ?? ''));
+        if ($sign === '') {
+            return false;
+        }
+
+        $publicKeyPath = self::getPublicKeyPath();
+        if (!is_file($publicKeyPath)) {
+            Yii::error('飞鹅公钥文件不存在: ' . $publicKeyPath, __METHOD__);
+            return false;
+        }
+
+        $publicKey = file_get_contents($publicKeyPath);
+        if ($publicKey === false || trim($publicKey) === '') {
+            Yii::error('飞鹅公钥文件读取失败: ' . $publicKeyPath, __METHOD__);
+            return false;
+        }
+
+        $signContent = self::buildSignContent($params);
+        $signature = base64_decode($sign, true);
+        if ($signature === false) {
+            Yii::warning('飞鹅回调 sign Base64 解码失败', __METHOD__);
+            return false;
+        }
+
+        $key = openssl_pkey_get_public($publicKey);
+        if ($key === false) {
+            Yii::error('飞鹅公钥解析失败: ' . openssl_error_string(), __METHOD__);
+            return false;
+        }
+
+        $result = openssl_verify($signContent, $signature, $key, OPENSSL_ALGO_SHA256);
+        if (PHP_VERSION_ID < 80000) {
+            openssl_free_key($key);
+        }
+
+        return $result === 1;
+    }
+}

+ 29 - 5
common/components/printUtil.php

@@ -60,8 +60,10 @@ class printUtil
 
     /**
      * 打印订单
+     *
+     * @param array|null $logMeta 打印日志上下文:mainId、orderId、orderSn、printSn、writeLog
      */
-    public function printMsg($content, $shop = null, $orderSn = '')
+    public function printMsg($content, $shop = null, $orderSn = '', $logMeta = null)
     {
         $contentLength = strlen($content);
         if ($contentLength > 5000) {
@@ -69,21 +71,30 @@ class printUtil
             $newContent = '';
             foreach ($contents as $line) {
                 if (strlen($newContent . $line . "<BR>") > 5000) {
-                    $this->doPrint($newContent, $shop, $orderSn);
+                    $this->doPrint($newContent, $shop, $orderSn, $logMeta);
                     $newContent = $line . "<BR>";
                 } else {
                     $newContent .= $line . "<BR>";
                 }
             }
             if (!empty($newContent)) {
-                $this->doPrint($newContent, $shop, $orderSn);
+                $this->doPrint($newContent, $shop, $orderSn, $logMeta);
             }
         } else {
-            $this->doPrint($content, $shop, $orderSn);
+            $this->doPrint($content, $shop, $orderSn, $logMeta);
         }
     }
 
-    private function doPrint($content, $shop = null, $orderSn = '')
+    /**
+     * 飞鹅打印回调地址
+     */
+    protected function getCallbackUrl()
+    {
+        $host = Yii::$app->params['ghsHost'] ?? '';
+        return rtrim($host, '/') . '/feieyun/call-back';
+    }
+
+    private function doPrint($content, $shop = null, $orderSn = '', $logMeta = null)
     {
         $time = time();         //请求时间
         $msgInfo = array(
@@ -95,6 +106,10 @@ class printUtil
             'content' => $content,
             'times' => $this->times//打印次数
         );
+        // 写入打印日志时需要带回调地址
+        if (!empty($logMeta['writeLog'])) {
+            $msgInfo['backurl'] = $this->getCallbackUrl();
+        }
         try {
             $client = new \HttpClient(self::IP, self::PORT);
             $client->useGzip(false); // 禁用 Gzip
@@ -111,6 +126,15 @@ class printUtil
             $result = $client->getContent();
             Yii::info('打印日志吧:' . $result);
             $arr = json_decode($result, true);
+            if (isset($arr['ret']) && intval($arr['ret']) === 0 && !empty($arr['data']) && !empty($logMeta['writeLog'])) {
+                feiePrintLogUtil::appendPending(
+                    $logMeta['mainId'] ?? 0,
+                    $logMeta['orderId'] ?? 0,
+                    $logMeta['orderSn'] ?? $orderSn,
+                    $logMeta['printSn'] ?? $this->sn,
+                    $arr['data']
+                );
+            }
             if (isset($arr['ret']) && $arr['ret'] == 1001) {
                 $msg = $arr['msg'] ?? '';
                 $sjName = $shop->merchantName ?? '';