ソースを参照

建立零售端限购缓存(相对批发是独立的)

shizhongqi 4 ヶ月 前
コミット
6f8fb2b953

+ 1 - 1
app-ghs/controllers/ProductController.php

@@ -1227,7 +1227,7 @@ class ProductController extends BaseController
         $connection = Yii::$app->db;
         $transaction = $connection->beginTransaction();
         try {
-            ProductClass::updateProduct($post, $this->shop);
+            ProductClass::updateProduct($post, $this->shop, 'ghs');
             $transaction->commit();
             util::complete();
         } catch (\Exception $e) {

+ 1 - 1
app-hd/controllers/ItemController.php

@@ -466,7 +466,7 @@ class ItemController extends BaseController
         util::complete('清除成功');
     }
 
-    //清空单个客户的已购 ssh 20250710
+    //清空单个客户的已购
     public function actionClearOneLimitBuy()
     {
         $get = Yii::$app->request->get();

+ 1 - 1
app-hd/controllers/ProductController.php

@@ -932,7 +932,7 @@ class ProductController extends BaseController
         $connection = Yii::$app->db;
         $transaction = $connection->beginTransaction();
         try {
-            ProductClass::updateProduct($post, $this->shop);
+            ProductClass::updateProduct($post, $this->shop, 'hd');
             $transaction->commit();
             util::complete();
         } catch (\Exception $e) {

+ 13 - 9
app-mall/controllers/OrderController.php

@@ -203,13 +203,13 @@ class OrderController extends BaseController
             util::fail('不是你的花店');
         }
         $post['hdName'] = $hd->name ?? '';
-        $customId = $hd->customId ?? 0;
-        $custom = CustomClass::getById($customId, true);
+        $hdCustomId = $hd->customId ?? 0;
+        $custom = CustomClass::getById($hdCustomId, true);
         if (empty($custom)) {
             util::fail('没有找到客户');
         }
         $customName = $custom->name ?? '';
-        $post['customId'] = $customId;
+        $post['customId'] = $hdCustomId;
         $post['customName'] = $customName;
         $post['customNamePy'] = stringUtil::py($customName);
 
@@ -238,7 +238,6 @@ class OrderController extends BaseController
                 if (!isset($currentInfo['mainId']) || $currentInfo['mainId'] != $this->mainId) {
                     util::fail('只能选择同一家的商品哦');
                 }
-                $currentName = $currentInfo['name'] ?? '';
                 $presell = $currentInfo['presell'] ?? 0;
                 $presellData[] = $presell;
                 if ($presell == 1) {
@@ -293,19 +292,24 @@ class OrderController extends BaseController
             foreach ($productList as $eleKey => $element) {
                 $level = 0;
                 $productId = $element['productId'];
-                $current = $productInfoList[$productId];
-                if (empty($current)) {
+                $product = $productInfoList[$productId];
+                if (empty($product)) {
                     util::fail('有商品信息没有找到');
                 }
-                $price = \bizGhs\product\classes\ProductClass::getFinalPrice($current, $level, $priceMap, $addPriceMap);
+                $price = \bizGhs\product\classes\ProductClass::getFinalPrice($product, $level, $priceMap, $addPriceMap);
                 $num = $element['num'] ?? 0;
                 $currentTotal = bcmul($price, $num, 2);
                 $modifyPrice = bcadd($modifyPrice, $currentTotal, 2);
                 $productList[$eleKey]['unitPrice'] = $price;
-                $weight = $current['weight'] ?? 0;
+                $weight = $product['weight'] ?? 0;
                 $currentWeight = bcmul($num, $weight, 2);
                 $totalWeight = bcadd($totalWeight, $currentWeight, 2);
                 $totalNum = bcadd($totalNum, $num);
+
+                if ($product['limitBuy'] > 0) {
+                    $product['productId'] = $productId;
+                    \bizHd\product\classes\ProductClass::handleLimitBuy($product, $hdCustomId, floatval($num));
+                }
             }
             $post['product'] = $productList;
 
@@ -418,7 +422,7 @@ class OrderController extends BaseController
             if ($hbId > 0) { // 不使用 $hbId =! 0,因为要用负数来表示红包已取消
                 $hb = HbClass::getById($hbId, true);
                 if (!empty($hb)) {
-                    if ($hb->customId != $customId) {
+                    if ($hb->customId != $hdCustomId) {
                         util::fail('不是你的红包');
                     }
                     if ($hb->amount != $post['hbAmount']) {

+ 19 - 4
biz-ghs/product/classes/ProductClass.php

@@ -1602,8 +1602,14 @@ class ProductClass extends BaseClass
         return bcmul($mergeNum, $weight, 2);
     }
 
-    //修改product
-    public static function updateProduct($data, $shop)
+    /**
+     * 修改product
+     * @param $data
+     * @param $shop
+     * @param $type 所属平台:ghs 或 hd
+     * @throws \Exception
+     */
+    public static function updateProduct($data, $shop, $type)
     {
         $id = $data['id'] ?? 0;
         $adminId = $data['adminId'] ?? 0;
@@ -1736,7 +1742,11 @@ class ProductClass extends BaseClass
             $upData['limitBuy'] = $data['limitBuy'];
             //取消限购
             if ($data['limitBuy'] == 0) {
-                self::cancelLimitBuy($id);
+                if ($type == 'ghs') {
+                    self::cancelLimitBuy($id);
+                } else {
+                    \bizHd\product\classes\ProductClass::cancelLimitBuy($id);
+                }
             }
         }
         if (isset($data['stockWarning'])) {
@@ -1932,7 +1942,11 @@ class ProductClass extends BaseClass
         if ($data['limitBuy'] > 0 && isset($data['limitBuyClearTime']) && $data['limitBuyClearTime'] != '') {
             $limitBuyClearTime = strtotime($data['limitBuyClearTime']);
             if ($limitBuyClearTime > 0) {
-                self::createLimitBuyCache($id, $limitBuyClearTime - time());
+                if ($type == 'ghs') {
+                    self::createLimitBuyCache($id, $limitBuyClearTime - time());
+                } else {
+                    \bizHd\product\classes\ProductClass::createLimitBuyCache($id, $limitBuyClearTime - time());
+                }
             }
         }
         
@@ -2696,6 +2710,7 @@ class ProductClass extends BaseClass
             ]);
             Yii::info('限购延迟消息已发送: ' . json_encode([
                 'type' => 'limit_buy_clear',
+                'ptType' => 'ghs',
                 'productId' => intval($productId),
                 'clearAt' => intval($clearAt),
                 'delayMs' => intval($seconds * 1000),

+ 152 - 4
biz-hd/product/classes/ProductClass.php

@@ -8,10 +8,14 @@ use common\components\imgUtil;
 use common\components\noticeUtil;
 use common\components\sms;
 use common\components\util;
+use PhpAmqpLib\Wire\AMQPTable;
 use Yii;
 
 class ProductClass extends BaseClass
 {
+    const LIMIT_BUY_KEY = 'hd_limit_buy:';
+    const CLEAR_MARK_KEY = 'hd_limit_buy_clear_at:';
+    const LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY = 'hd_limitBuyRollbackSnapshot'; //记录限购旧值快照缓存键
 
     public static $baseFile = '\bizHd\product\models\Product';
 
@@ -424,10 +428,53 @@ class ProductClass extends BaseClass
         return $list;
     }
 
+    /**
+     * 限购处理
+     * @param $product
+     * @param $customId 零售xhCustom表的id
+     * @param $num
+     */
+    public static function handleLimitBuy($product, $customId, $num)
+    {
+        $limitBuy = $product['limitBuy'];
+        if ($limitBuy <= 0) {
+            return;
+        }
+
+        $productId = $product['productId'];
+        $limitKey = self::LIMIT_BUY_KEY . $productId;
+        $has = Yii::$app->redis->executeCommand('HEXISTS', [$limitKey, $customId]);
+        if (!empty($has)) {
+            $hasNum = Yii::$app->redis->executeCommand('HGET', [$limitKey, $customId]);
+            $lastNum = bcadd($hasNum, $num);
+            if ($lastNum > $limitBuy) {
+                // 如果有特价,则超过的数量按原价卖;否则中断
+                if ($product['hjDiscountPrice'] > 0 && $product['discountPrice'] > 0 && $product['skDiscountPrice'] > 0) {
+                    return;
+                }
+
+                util::error(-1, '累计已超出限购数', ['productId'=>$productId, 'limitBuy'=>$limitBuy, 'exceed'=>$lastNum - $limitBuy]);
+            }
+            self::recordLimitBuyRollbackSnapshot($productId, $customId, true, $hasNum);
+            Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $lastNum]);
+        } else {
+            if ($num > $limitBuy) {
+                // 如果有特价,则超过的数量按原价卖;否则中断
+                if ($product['hjDiscountPrice'] > 0 && $product['discountPrice'] > 0 && $product['skDiscountPrice'] > 0) {
+                    return;
+                }
+
+                util::error(-1, '超出限购数', ['productId'=>$productId, 'limitBuy'=>$limitBuy, 'exceed'=>$num - $limitBuy]);
+            }
+            self::recordLimitBuyRollbackSnapshot($productId, $customId, false, 0);
+            Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $num]);
+        }
+    }
+
     //取消限购的缓存
     public static function cancelLimitBuy($productId)
     {
-        $limitKey = 'limit_buy_' . $productId;
+        $limitKey = self::LIMIT_BUY_KEY . $productId;
         $arr = Yii::$app->redis->executeCommand('HKEYS', [$limitKey]);
         if (!empty($arr)) {
             foreach ($arr as $field) {
@@ -437,10 +484,14 @@ class ProductClass extends BaseClass
         self::clearLimitBuyClearMark($productId);
     }
 
-    //$num -1 表示减全部,大于-1表示减值
+    /**
+     * @param $productId
+     * @param $customId
+     * @param int $num  -1 表示减全部,大于-1表示减值
+     */
     public static function baseClearLimitBuy($productId, $customId, $num = -1)
     {
-        $limitKey = 'limit_buy_' . $productId;
+        $limitKey = self::LIMIT_BUY_KEY . $productId;
         if ($num <= -1) {
             //清掉全部
             Yii::$app->redis->executeCommand('HDEL', [$limitKey, $customId]);
@@ -463,9 +514,106 @@ class ProductClass extends BaseClass
         if ($productId <= 0) {
             return false;
         }
-        $clearMarkKey = 'limit_buy_clear_at:' . $productId;
+        $clearMarkKey = self::CLEAR_MARK_KEY . $productId;
         Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]);
         return true;
     }
 
+    // 创建限购缓存(有过期时间)
+    public static function createLimitBuyCache($productId, $seconds = 0)
+    {
+        $limitKey = self::LIMIT_BUY_KEY . $productId;
+        $clearMarkKey = self::CLEAR_MARK_KEY . $productId;
+
+        $has = Yii::$app->redis->executeCommand('HEXISTS', [$limitKey, 0]);
+        if (!empty($has)) {
+            // 获取缓存超时时间
+            $expire = Yii::$app->redis->executeCommand('TTL', [$limitKey]);
+            if ($expire > 0) {
+                //return;
+            }
+            //删除缓存
+            Yii::$app->redis->executeCommand('HDEL', [$limitKey, 0]);
+        }
+        Yii::$app->redis->executeCommand('HSET', [$limitKey, 0, 0]);
+        if ($seconds > 0) {
+            $clearAt = time() + intval($seconds);
+            Yii::$app->redis->executeCommand('EXPIRE', [$limitKey, $seconds]);
+            Yii::$app->redis->executeCommand('SET', [$clearMarkKey, $clearAt]);
+
+            // 用 RabbitMQ 延迟消息在到期时清空订单项限购字段
+            $message = [
+                'type' => 'limit_buy_clear',
+                'productId' => $productId,
+                'clearAt' => $clearAt,
+            ];
+            $message = serialize($message);
+            $producer = Yii::$app->rabbitmq->getProducer('stockProducer');
+            $producer->publish($message, 'limitBuyDelayExchange', 'limitBuyDelayRoute', [
+                'delivery_mode' => 2,
+                'content_type' => 'application/octet-stream',
+                'application_headers' => new AMQPTable([
+                    'x-delay' => intval($seconds * 1000),
+                ]),
+            ]);
+            Yii::info('限购延迟消息已发送: ' . json_encode([
+                    'type' => 'limit_buy_clear',
+                    'ptType' => 'hd',
+                    'productId' => intval($productId),
+                    'clearAt' => intval($clearAt),
+                    'delayMs' => intval($seconds * 1000),
+                ], JSON_UNESCAPED_UNICODE), __METHOD__);
+        } else {
+            Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]);
+        }
+    }
+
+    /**
+     * 记录限购缓存写入前快照(同一次请求内同商品同客户只记录一次)
+     *
+     * @param int $productId
+     * @param int $customId 零售xhCustom表的id
+     * @param bool $hasOldValue
+     * @param string|int|float $oldValue
+     * @return void
+     */
+    public static function recordLimitBuyRollbackSnapshot($productId, $customId, $hasOldValue, $oldValue = 0)
+    {
+        $productId = intval($productId);
+        $customId = intval($customId);
+        if ($productId <= 0 || $customId <= 0) {
+            return;
+        }
+        $snapshotKey = $productId . '_' . $customId;
+        if (!isset(Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY]) || !is_array(Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY])) {
+            Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY] = [];
+        }
+        if (isset(Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY][$snapshotKey])) {
+            return;
+        }
+        Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY][$snapshotKey] = [
+            'productId' => $productId,
+            'customId' => $customId,
+            'hasOldValue' => $hasOldValue ? 1 : 0,
+            'oldValue' => strval($oldValue),
+        ];
+    }
+
+    /**
+     * 根据花材清空订单项限购值
+     *
+     * @param int $productId
+     * @return bool
+     */
+    public static function clearLimitBuyByProductId($productId)
+    {
+        $productId = intval($productId);
+        if ($productId <= 0) {
+            return false;
+        }
+
+        self::updateById($productId, ['limitBuy' => 0]);
+        return true;
+    }
+
 }

+ 23 - 8
common/components/rabbitmq/stockConsumer.php

@@ -7,6 +7,7 @@
 namespace common\components\rabbitmq;
 
 use bizGhs\product\classes\ProductClass;
+use bizHd\product\classes\ProductClass as hdProductClass;
 use common\components\noticeUtil;
 use mikemadisonweb\rabbitmq\components\ConsumerInterface;
 use PhpAmqpLib\Message\AMQPMessage;
@@ -78,7 +79,7 @@ class stockConsumer extends baseConsumer
      */
     private function clearOrderItemLimitBuy($data)
     {
-        $productId = intval($data['productId'] ?? 0);
+        $productId = intval($data['productId']);
         if ($productId <= 0) {
             noticeUtil::push('取消限购的消费者报错:limit_buy_clear 缺少 productId');
             return true;
@@ -95,15 +96,29 @@ class stockConsumer extends baseConsumer
             return true;
         }
 
-        $result = ProductClass::clearLimitBuyByProductId($productId);
-        if ($result) {
-            ProductClass::clearLimitBuyClearMark($productId);
+        $ptType = $data['ptType'];
+        if ($ptType == 'ghs') {
+            $result = ProductClass::clearLimitBuyByProductId($productId);
+            if ($result) {
+                ProductClass::clearLimitBuyClearMark($productId);
+            } else {
+                noticeUtil::push('限购字段清理失败: ' . json_encode([
+                        'productId' => $productId,
+                        'clearAt' => $clearAt,
+                    ], JSON_UNESCAPED_UNICODE));
+            }
         } else {
-            noticeUtil::push('限购字段清理失败: ' . json_encode([
-                'productId' => $productId,
-                'clearAt' => $clearAt,
-            ], JSON_UNESCAPED_UNICODE));
+            $result = hdProductClass::clearLimitBuyByProductId($productId);
+            if ($result) {
+                hdProductClass::clearLimitBuyClearMark($productId);
+            } else {
+                noticeUtil::push('限购字段清理失败: ' . json_encode([
+                        'productId' => $productId,
+                        'clearAt' => $clearAt,
+                    ], JSON_UNESCAPED_UNICODE));
+            }
         }
+
         return $result;
     }