Jelajahi Sumber

1. 限购缓存相关方法修改 2. 限制取消实现使用rabbitMQ延时消费队列

shizhongqi 4 bulan lalu
induk
melakukan
23482e4325

+ 30 - 16
biz-ghs/order/classes/OrderItemClass.php

@@ -23,6 +23,23 @@ class OrderItemClass extends BaseClass
 
     public static $baseFile = '\bizGhs\order\models\OrderItem';
 
+    /**
+     * 根据花材清空订单项限购值
+     *
+     * @param int $productId
+     * @return bool
+     */
+    public static function clearLimitBuyByProductId($productId)
+    {
+        $productId = intval($productId);
+        if ($productId <= 0) {
+            return false;
+        }
+
+        self::updateByCondition(['productId' => $productId], ['limitBuy' => 0]);
+        return true;
+    }
+
     //预订单删除花材项 ssh 20240123
     public static function delBookItem($order, $smallId, $params = [])
     {
@@ -306,7 +323,18 @@ class OrderItemClass extends BaseClass
         return $list;
     }
 
-    //添加修改花材 ssh 2021.1.22
+    /**
+     * 替换订单(orderSn)下所有商品项为指定的 product 列表,并根据参数处理附加商详(如每支鲜花 xj)、校验及扣减库存、更新限购等。
+     *
+     * 步骤说明:
+     * 1. 根据 customId 获取客户等级和属性,格式化 product 信息;
+     * 2. 先删除该订单下历史商品项,再批量插入新 product 项;
+     * 3. 若包含 xj(每支鲜花)信息,批量关联并校验单支库存、同步写入;
+     * 4. 处理每项商品的限购、本次扣减库存(如 stockMayChange 为 true),并记录出库流水。
+     * 5. 返回本次批量插入及处理后的汇总结果(带总重量)。
+     *
+     * 用于订单商品整体替换的核心业务方法。
+     */
     public static function replaceItem($orderSn, $product, $post = null, $stockMayChange = false, $moreParams = [])
     {
         $customId = $post['customId'] ?? 0;
@@ -373,23 +401,9 @@ class OrderItemClass extends BaseClass
             $weight = bcadd($w, $weight, 2);
 
             $limitBuy = $val['limitBuy'] ?? 0;
-            $limitKey = 'limit_buy_' . $productId;
             $currentNum = floatval($itemNum);
             if ($limitBuy > 0) {
-                $has = Yii::$app->redis->executeCommand('HEXISTS', [$limitKey, $customId]);
-                if (!empty($has)) {
-                    $hasNum = Yii::$app->redis->executeCommand('HGET', [$limitKey, $customId]);
-                    $lastNum = bcadd($hasNum, $currentNum);
-                    if ($lastNum > $limitBuy) {
-                        util::fail($name . ' 超出限购数');
-                    }
-                    Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $lastNum]);
-                } else {
-                    if ($currentNum > $limitBuy) {
-                        util::fail($name . ' 超出限购数');
-                    }
-                    Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $currentNum]);
-                }
+                ProductClass::setLimitBuyCache($productId, $customId, $limitBuy, $currentNum);
             }
 
             if ($stockMayChange == true) {

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

@@ -663,6 +663,23 @@ class ProductClass extends BaseClass
             $list[$k]['autoPrice'] = $autoPrice;
             $list[$k]['autoSkPrice'] = $skAutoPrice;
             $list[$k]['userPrice'] = 0;
+
+            // 限购缓存
+            if ($v['limitBuy'] > 0) {
+                $limitKey = 'limit_buy_' . $v['id'];
+                $has = Yii::$app->redis->executeCommand('HEXISTS', [$limitKey, 0]);
+                if (!empty($has)) {
+                    // 获取缓存超时时间
+                    $expire = Yii::$app->redis->executeCommand('TTL', [$limitKey]);
+                    if ($expire > 0) {
+                        $list[$k]['limitByuClearTime'] = date('Y-m-d H:i:s', time() + $expire);
+                    } else {
+                        $list[$k]['limitByuClearTime'] = '';
+                    }
+                } else {
+                    $list[$k]['limitByuClearTime'] = '';
+                }
+            }
         }
         return $list;
     }
@@ -1893,12 +1910,19 @@ class ProductClass extends BaseClass
         /***************产品模块*****************/
 
         self::updateById($id, $upData);
+
+        //创建限购缓存
+        if ($data['limitBuy'] > 0 && $data['limitBuyClearTime'] != '') {
+            $limitByuClearTime = strtotime($data['limitBuyClearTime']);
+            if ($limitByuClearTime > 0) {
+                self::createLimitBuyCache($id, $limitByuClearTime - time());
+            }
+        }
+        
         //不影响其它直营店花材的上下架
         unset($upData['status']);
 
         if (isset($shop->default) && $shop->default == 1 && isset($shop->dataSync) && $shop->dataSync == 1) {
-
-
             /***************产品模块*****************/
             $ptClassList = PtCpClassClass::getAllByCondition(['delStatus' => 0], null, '*', 'id');
             /***************产品模块*****************/
@@ -2091,10 +2115,8 @@ class ProductClass extends BaseClass
 
                 //直营店同步价格等重要因素
                 self::updateById($chainProduct->id, $params);
-
             }
         }
-
     }
 
     //变成自动改价 2021.4.14
@@ -2618,6 +2640,69 @@ class ProductClass extends BaseClass
         return $product['mainId'] ?? 0;
     }
 
+    // 创建限购缓存(有过期时间)
+    public static function createLimitBuyCache($productId, $seconds = 0)
+    {
+        $limitKey = 'limit_buy_' . $productId;
+        $clearMarkKey = 'limit_buy_clear_at:' . $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,
+            ];
+            $producer = Yii::$app->rabbitmq->getProducer('cancelLimitBuyProducer');
+            $producer->publish($message, 'limitBuyDelayExchange', 'limitBuyDelayRoute', [
+                'delivery_mode' => 2,
+                'content_type' => 'application/octet-stream',
+                'expiration' => (string)($seconds * 1000),
+            ]);
+        } else {
+            Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]);
+        }
+    }
+
+    // 设置限购缓存
+    public static function setLimitBuyCache($productId, $customId, $limitBuy, $num)
+    {
+        if ($limitBuy <= 0) {
+            return;
+        }
+        
+        $limitKey = 'limit_buy_' . $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) {
+                util::fail('超出限购数');
+            }
+            Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $lastNum]);
+        } else {
+            if ($num > $limitBuy) {
+                util::fail('超出限购数');
+            }
+            Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $num]);
+        }
+    }
+
     //取消限购的缓存 ssh 20240605
     public static function cancelLimitBuy($productId)
     {
@@ -2628,6 +2713,33 @@ class ProductClass extends BaseClass
                 self::baseClearLimitBuy($productId, $field);
             }
         }
+        self::clearLimitBuyClearMark($productId);
+    }
+
+    // 校验清空限购消息是否是当前有效版本
+    public static function checkLimitBuyClearMessage($productId, $clearAt)
+    {
+        $productId = intval($productId);
+        $clearAt = intval($clearAt);
+        if ($productId <= 0 || $clearAt <= 0) {
+            return false;
+        }
+
+        $clearMarkKey = 'limit_buy_clear_at:' . $productId;
+        $currentClearAt = intval(Yii::$app->redis->executeCommand('GET', [$clearMarkKey]));
+        return $currentClearAt > 0 && $currentClearAt == $clearAt;
+    }
+
+    // 清理限购清空标记
+    public static function clearLimitBuyClearMark($productId)
+    {
+        $productId = intval($productId);
+        if ($productId <= 0) {
+            return false;
+        }
+        $clearMarkKey = 'limit_buy_clear_at:' . $productId;
+        Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]);
+        return true;
     }
 
     //$num -1 表示减全部,大于-1表示减值

+ 3 - 4
biz-ghs/product/services/ProductService.php

@@ -127,11 +127,10 @@ class ProductService extends BaseService
     //组装分类花材数据
     public static function assembleData($classInfo, $itemInfoData, $showTodayDiscount = false)
     {
-        //今日特价
-        $tjItem = [];
-        $mjItem = [];
+        $tjItem = []; //特价
+        $mjItem = []; //满减
         $itemGroup = [];
-        $preSellItem = [];
+        $preSellItem = []; //预售
         if ($itemInfoData) {
             foreach ($itemInfoData as $v) {
                 $reachNum = $v['reachNum'] ?? 0;

+ 92 - 0
common/components/rabbitmq/cancelLimitBuyConsumer.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * 取消限购消费者
+ * 处理取消限购操作
+ */
+
+namespace common\components\rabbitmq;
+
+use bizGhs\order\classes\OrderItemClass;
+use bizGhs\product\classes\ProductClass;
+use common\components\noticeUtil;
+use mikemadisonweb\rabbitmq\components\ConsumerInterface;
+use PhpAmqpLib\Message\AMQPMessage;
+use Yii;
+
+class cancelLimitBuyConsumer implements ConsumerInterface
+{
+    /**
+     * 执行消费者逻辑
+     * 
+     * @param AMQPMessage $msg 消息对象
+     * @return string 消息处理结果
+     * 
+     * ConsumerInterface::MSG_ACK - 确认消息(标记为已处理)并从队列中删除
+     * ConsumerInterface::MSG_REJECT - 拒绝并从队列中删除消息
+     * ConsumerInterface::MSG_REQUEUE - 拒绝并重新入队消息
+     */
+    public function execute(AMQPMessage $msg)
+    {
+        try {
+            // 反序列化消息体
+            $data = unserialize($msg->body);
+            if (!is_array($data)) {
+                noticeUtil::push("取消限购的消费者报错:Invalid notify message format: {$msg->body}", '15280215347');
+                return ConsumerInterface::MSG_REJECT;
+            }
+            print_r($data);
+            // 根据操作类型分发处理
+            $type = $data['type'] ?? null;
+            switch ($type) {
+                case 'limit_buy_clear':
+                    $result = $this->clearOrderItemLimitBuy($data);
+                    break;
+                default:
+                    noticeUtil::push("取消限购的消费者报错,未知 type: {$type}");
+                    $result = false;
+            }
+            if ($result) {
+                return ConsumerInterface::MSG_ACK;
+            } else {
+                noticeUtil::push("取消限购的消费者报错:Stock message processing failed");
+                return ConsumerInterface::MSG_REQUEUE;
+            }
+        } catch (\Exception $e) {
+            noticeUtil::push("取消限购的消费者报错:" . $e->getMessage());
+            return ConsumerInterface::MSG_REQUEUE;
+        }
+    }
+
+    /**
+     * 清空订单项限购值
+     *
+     * @param array $data
+     * @return bool
+     */
+    private function clearOrderItemLimitBuy($data)
+    {
+        $productId = intval($data['productId'] ?? 0);
+        if ($productId <= 0) {
+            noticeUtil::push('取消限购的消费者报错:limit_buy_clear 缺少 productId', '15280215347');
+            return true;
+        }
+
+        $clearAt = intval($data['clearAt'] ?? 0);
+        if ($clearAt <= 0) {
+            noticeUtil::push('取消限购的消费者报错:limit_buy_clear 缺少 clearAt', '15280215347');
+            return true;
+        }
+
+        // 旧消息直接忽略,避免“先到期的旧消息”提前清空
+        if (!ProductClass::checkLimitBuyClearMessage($productId, $clearAt)) {
+            return true;
+        }
+
+        $result = OrderItemClass::clearLimitBuyByProductId($productId);
+        if ($result) {
+            ProductClass::clearLimitBuyClearMark($productId);
+        }
+        return $result;
+    }
+
+}

+ 21 - 0
common/components/rabbitmq/stockConsumer.php

@@ -6,6 +6,7 @@
 
 namespace common\components\rabbitmq;
 
+use bizGhs\order\classes\OrderItemClass;
 use common\components\noticeUtil;
 use mikemadisonweb\rabbitmq\components\ConsumerInterface;
 use PhpAmqpLib\Message\AMQPMessage;
@@ -41,6 +42,9 @@ class stockConsumer implements ConsumerInterface
                     echo '持久化OK---';
                     print_r($data);
                     break;
+                case 'limit_buy_clear':
+                    $result = $this->clearOrderItemLimitBuy($data);
+                    break;
                 default:
                     noticeUtil::push("库存的消费者报错,未知 type: {$type}");
                     $result = false;
@@ -57,4 +61,21 @@ class stockConsumer implements ConsumerInterface
         }
     }
 
+    /**
+     * 清空订单项限购值
+     *
+     * @param array $data
+     * @return bool
+     */
+    private function clearOrderItemLimitBuy($data)
+    {
+        $productId = intval($data['productId'] ?? 0);
+        if ($productId <= 0) {
+            noticeUtil::push('库存的消费者报错:limit_buy_clear 缺少 productId', '15280215347');
+            return false;
+        }
+
+        return OrderItemClass::clearLimitBuyByProductId($productId);
+    }
+
 }

+ 45 - 0
common/config/rabbitMQ.php

@@ -36,6 +36,16 @@ $rabbitMQ = [
             'name' => 'ptExchange',
             'type' => 'direct',
             'durable' => true,
+        ],
+        [
+            'name' => 'limitBuyDelayExchange',
+            'type' => 'direct',
+            'durable' => true,
+        ],
+        [
+            'name' => 'limitBuyExchange',
+            'type' => 'direct',
+            'durable' => true,
         ]
     ],
 
@@ -63,6 +73,20 @@ $rabbitMQ = [
             'passive' => false,
             'durable' => true,
         ],
+        [
+            'name' => 'limitBuyDelayQueue',
+            'passive' => false,
+            'durable' => true,
+            'arguments' => [
+                'x-dead-letter-exchange' => 'limitBuyExchange',
+                'x-dead-letter-routing-key' => 'limitBuyRoute',
+            ],
+        ],
+        [
+            'name' => 'limitBuyQueue',
+            'passive' => false,
+            'durable' => true,
+        ],
     ],
 
     /**
@@ -89,6 +113,16 @@ $rabbitMQ = [
             'queue' => 'ptQueue',
             'exchange' => 'ptExchange',
             'routing_keys' => ['ptRoute'],
+        ],
+        [
+            'queue' => 'limitBuyDelayQueue',
+            'exchange' => 'limitBuyDelayExchange',
+            'routing_keys' => ['limitBuyDelayRoute'],
+        ],
+        [
+            'queue' => 'limitBuyQueue',
+            'exchange' => 'limitBuyExchange',
+            'routing_keys' => ['limitBuyRoute'],
         ]
     ],
 
@@ -112,6 +146,10 @@ $rabbitMQ = [
             //跑腿下单生产者
             'name' => 'ptProducer',
         ],
+        [
+            //取消限购生产者
+            'name' => 'cancelLimitBuyProducer',
+        ],
     ],
 
     /**
@@ -146,6 +184,13 @@ $rabbitMQ = [
                 'ptQueue' => '\common\components\rabbitmq\ptConsumer',
             ]
         ],
+        [
+            //取消限购消费者
+            'name' => 'cancelLimitBuyConsumer',
+            'callbacks' => [
+                'limitBuyQueue' => '\common\components\rabbitmq\cancelLimitBuyConsumer',
+            ]
+        ]
     ],
 ];
 return $rabbitMQ;