Просмотр исходного кода

Merge branch 'master' into dev

shizhongqi 2 месяцев назад
Родитель
Сommit
6b1d6c7644
2 измененных файлов с 83 добавлено и 34 удалено
  1. 56 33
      biz-ghs/product/classes/ProductClass.php
  2. 27 1
      common/components/util.php

+ 56 - 33
biz-ghs/product/classes/ProductClass.php

@@ -931,29 +931,42 @@ class ProductClass extends BaseClass
 
     /**
      * 库存变动统一入口:用数据库行锁串行化同一花材的读-算-写,避免并发覆盖库存。
+     * 出现并发时,会自动重试3次。
+     * @param $productId
+     * @param callable $callback
+     * @return mixed
+     * @throws \Exception
      */
     private static function runStockMutation($productId, callable $callback)
     {
         $db = Yii::$app->db;
         $currentTransaction = $db->getTransaction();
         $ownTransaction = empty($currentTransaction) || !$currentTransaction->isActive;
-        $transaction = $ownTransaction ? $db->beginTransaction() : $currentTransaction;
-        try {
-            $productData = self::getLockById($productId);
-            if (!$productData) {
-                util::fail("花材不存在");
-            }
-            $respond = call_user_func($callback, $productData);
-            if ($ownTransaction) {
-                $transaction->commit();
-            }
-            return $respond;
-        } catch (\Exception $exception) {
-            if ($ownTransaction && !empty($transaction) && $transaction->isActive) {
-                $transaction->rollBack();
+        $mutation = function () use ($db, $currentTransaction, $ownTransaction, $productId, $callback) {
+            $transaction = $ownTransaction ? $db->beginTransaction() : $currentTransaction;
+            try {
+                $productData = self::getLockById($productId);
+                if (!$productData) {
+                    util::fail("花材不存在");
+                }
+                $respond = call_user_func($callback, $productData);
+                if ($ownTransaction) {
+                    $transaction->commit();
+                }
+                return $respond;
+            } catch (\Exception $exception) {
+                if ($ownTransaction && !empty($transaction) && $transaction->isActive) {
+                    $transaction->rollBack();
+                }
+                throw $exception;
             }
-            throw $exception;
+        };
+
+        if (!$ownTransaction) {
+            return call_user_func($mutation);
         }
+
+        return util::runWithDbConcurrencyRetry($mutation);
     }
 
     //加库存,允许负库存,负数数量(盘点时)
@@ -961,7 +974,7 @@ class ProductClass extends BaseClass
     {
         //小单位库存不再考虑 ssh 20230303
         //$itemNumPiece = 0;
-        return self::runStockMutation($productId, function ($productData) use ($itemNumBundle, $itemNumPiece) {
+        $callback = function ($productData) use ($itemNumBundle, $itemNumPiece) {
             $oldStock = $productData->stock;
             $ratio = $productData->ratio;
             //转成小数,加库存
@@ -975,7 +988,9 @@ class ProductClass extends BaseClass
             $productData->save();
 
             return ['oldStock' => $oldStock, 'newStock' => $newStock, 'itemNum' => $itemNum];
-        });
+        };
+
+        return self::runStockMutation($productId, $callback);
     }
 
     //加路上库存
@@ -1002,7 +1017,7 @@ class ProductClass extends BaseClass
     //加库存
     public static function addStockByItemNum($productId, $itemNum, $params = [])
     {
-        return self::runStockMutation($productId, function ($productData) use ($itemNum, $params) {
+        $callback = function ($productData) use ($itemNum, $params) {
             $oldStock = $productData->stock;
             $newItemCost = 0;
             $newItemStock = 0;
@@ -1050,7 +1065,9 @@ class ProductClass extends BaseClass
 
             return ['oldStock' => $oldStock, 'newStock' => $newStock, 'itemNum' => $itemNum,
                 'avCost' => $avCost, 'totalCost' => $newItemCost, 'totalStock' => $newItemStock, 'changeCost' => $changeCost, 'unitCost' => $unitCost];
-        });
+        };
+
+        return self::runStockMutation($productId, $callback);
     }
 
     //减库存,允许负库存,负数数量(盘点时)
@@ -1060,7 +1077,7 @@ class ProductClass extends BaseClass
     {
         //小单位库存不再考虑 ssh 20230303
         //$itemNumPiece = 0;
-        return self::runStockMutation($productId, function ($productData) use ($itemNumBundle, $itemNumPiece, $checkStock, $addSold, $params) {
+        $callback = function ($productData) use ($productId, $itemNumBundle, $itemNumPiece, $checkStock, $addSold, $params) {
             $oldStock = $productData->stock;
             $ratio = $productData->ratio;
             //转成小数,更新库存
@@ -1130,14 +1147,16 @@ class ProductClass extends BaseClass
 
             return ['oldStock' => $oldStock, 'newStock' => $newStock, 'itemNum' => $itemNum,
                 'avCost' => $avCost, 'totalCost' => $newItemCost, 'totalStock' => $newItemStock, 'changeCost' => $changeCost, 'unitCost' => $unitCost];
-        });
+        };
+
+        return self::runStockMutation($productId, $callback);
     }
 
 
     //根据数量减库存
     public static function decreaseStockByItemNum($productId, $itemNum, $checkStock = false)
     {
-        return self::runStockMutation($productId, function ($productData) use ($itemNum, $checkStock) {
+        $callback = function ($productData) use ($itemNum, $checkStock) {
             $oldStock = $productData->stock;
             $newStock = bcsub($oldStock, $itemNum, 2);
             if ($checkStock && $newStock < 0) {
@@ -1152,7 +1171,9 @@ class ProductClass extends BaseClass
             $productData->save();
 
             return ['oldStock' => $oldStock, 'newStock' => $newStock, 'itemNum' => $itemNum];
-        });
+        };
+
+        return self::runStockMutation($productId, $callback);
     }
 
     //减少在路上的库存
@@ -1253,7 +1274,7 @@ class ProductClass extends BaseClass
             $diffItemIds = array_diff($itemIds, $productDataItemIds);
             if ($diffItemIds) {
 
-                $oldProductInfo = ProductClass::getAllByCondition(['id' => ['in', $oldProductIds]], null, '*', 'itemId');
+                $oldProductInfo = self::getAllByCondition(['id' => ['in', $oldProductIds]], null, '*', 'itemId');
 
                 //新增到product表,price = itemPrice (出库时的售价), stock=0
                 foreach ($diffItemIds as $v) {
@@ -1335,7 +1356,7 @@ class ProductClass extends BaseClass
     //修改花材商品的库存: 统一方法
     public static function updateStockById($id, $stock, $adminId = 0)
     {
-        return self::runStockMutation($id, function ($productData) use ($stock, $adminId) {
+        $callback = function ($productData) use ($stock, $adminId) {
             $productData->stock = $stock;
             //如果有库存需要保持上架状态
             if ($stock > 0) {
@@ -1345,28 +1366,30 @@ class ProductClass extends BaseClass
                 $productData->adminId = $adminId;
             }
             return $productData->save();
-        });
+        };
+
+        return self::runStockMutation($id, $callback);
     }
 
     //订单退回库存
     public static function backStockByOrderItemInfo($orderItemInfo)
     {
         foreach ($orderItemInfo as $v) {
-            ProductClass::addStockByItemNum($v['productId'], $v['itemNum']);
+            self::addStockByItemNum($v['productId'], $v['itemNum']);
         }
     }
 
     //获取当前门店下商品信息 lqh 2021.1.28
     public static function getProductData($id, $mainId, $obj = false)
     {
-        return ProductClass::getByCondition(['id' => $id, 'mainId' => $mainId], $obj);
+        return self::getByCondition(['id' => $id, 'mainId' => $mainId], $obj);
     }
 
     //单个修改价格 lqh 2021.1.28
     public static function changeSinglePrice($shop, $ptItemId, $price, $skPrice = null, $hjPrice = null, $params = [])
     {
         $mainId = $shop->mainId ?? 0;
-        $productInfo = ProductClass::getByCondition(['itemId' => $ptItemId, 'mainId' => $mainId, 'delStatus' => 0], true);
+        $productInfo = self::getByCondition(['itemId' => $ptItemId, 'mainId' => $mainId, 'delStatus' => 0], true);
         if (empty($productInfo)) {
             return false;
         }
@@ -1676,7 +1699,7 @@ class ProductClass extends BaseClass
                 }
                 //增加盘点记录 2021.7.27
                 $ratio = $product->ratio ?? 0;
-                $formNum = ProductClass::formatStock($data['stock'], $ratio);
+                $formNum = self::formatStock($data['stock'], $ratio);
                 $ghsItemInfo = [['productId' => $product->id, 'bigNum' => $formNum['bigNum'], 'smallNum' => $formNum['smallNum'],]];
                 $pdData = [];
                 $pdData['itemInfo'] = $ghsItemInfo;
@@ -2024,7 +2047,7 @@ class ProductClass extends BaseClass
                 $staffName = $staff->name ?? '';
                 $changeParams2 = ['staffId' => $staffId, 'staffName' => $staffName];
 
-                $chainProduct = ProductClass::getByCondition(['mainId' => $chainMainId, 'itemId' => $ptItemId], true);
+                $chainProduct = self::getByCondition(['mainId' => $chainMainId, 'itemId' => $ptItemId], true);
                 if (empty($chainProduct)) {
                     continue;
                 }
@@ -2303,7 +2326,7 @@ class ProductClass extends BaseClass
                 $productData['status'] = 2;
                 //被归到默认分类,排序值要小,这样在花材列表搜索时,默认分类才不会被排在前面
                 $productData['inTurn'] = -1;
-                $addProduct = ProductClass::addBaseData($productData);
+                $addProduct = self::addBaseData($productData);
                 $hdProductId = $addProduct->id ?? 0;
 
                 //通知新花材需要分类
@@ -2727,7 +2750,7 @@ class ProductClass extends BaseClass
                     continue;
                 }
                 $ids = array_unique(array_filter($ids));
-                $productList = ProductClass::getAllByCondition(['id' => ['in', $ids]], null, '*', null, true);
+                $productList = self::getAllByCondition(['id' => ['in', $ids]], null, '*', null, true);
                 if (!empty($productList)) {
                     WxMessageClass::stockWarningInform($shop, $productList);
                 }

+ 27 - 1
common/components/util.php

@@ -335,7 +335,6 @@ class util
      */
     public static function getLock($key, $expire = 10)
     {
-
         $redis = Yii::$app->redis;
         return $redis->setex($key, $expire, time());
     }
@@ -442,6 +441,33 @@ class util
         return true;
     }
 
+    public static function isDbConcurrencyException($exception)
+    {
+        $message = $exception->getMessage();
+        return strpos($message, 'Deadlock found') !== false
+            || strpos($message, 'Serialization failure') !== false
+            || strpos($message, 'Lock wait timeout exceeded') !== false
+            || strpos($message, 'SQLSTATE[40001]') !== false
+            || strpos($message, 'SQLSTATE[HY000]: General error: 1205') !== false;
+    }
+
+    public static function runWithDbConcurrencyRetry(callable $callback, $maxRetry = 3)
+    {
+        $attempt = 0;
+        while (true) {
+            try {
+                return call_user_func($callback);
+            } catch (\Exception $exception) {
+                if (!self::isDbConcurrencyException($exception) || $attempt >= $maxRetry) {
+                    throw $exception;
+                }
+                $attempt++;
+                Yii::warning('数据库并发冲突,准备重试第' . $attempt . '次:' . $exception->getMessage());
+                usleep(mt_rand(50000, 300000));
+            }
+        }
+    }
+
     /**
      * 用 mainId 验证操作权限
      * @param array $arr 模型数据数组