$v) { $shortCover = $v['cover'] ?? ''; $cover = imgUtil::getPrefix() . 'hhb_small.png'; if (!empty($shortCover)) { $parse = parse_url(self::groupImg($shortCover)); if (empty($parse['query'])) { $cover = self::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; } else { $cover = self::groupImg($shortCover); } } $list[$k]['cover'] = $cover; //今日特价、会员价 $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); if ($level == 0) { //零售做特价时显示的原价,不能删除 if (isset($v['skPrice'])) { $list[$k]['prePrice'] = $v['skPrice']; } } if (isset($v['discountPrice']) && $v['discountPrice'] > 0) { //做特价花材时,花材列表和改价列表显示的会员价和零售价,不能删除 $list[$k]['hjPrice'] = floatval($v['hjDiscountPrice']); $list[$k]['skPrice'] = floatval($v['skDiscountPrice']); } $list[$k]['price'] = $price; $cost = $v['cost'] ?? 0; if ($price > 0) { $profit = bcsub($price, $cost, 2); $mll = bcdiv($profit, $price, 3); $mll = bcmul($mll, 100); $mll = round($mll, 1); } else { $mll = 0; } $list[$k]['mll'] = $mll; } return $list; } public static function kdItemGroup($list, $level = 0) { //各个等级对应的price addPrice字段 $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $cover = imgUtil::getPrefix() . 'hhb_small.png'; if (!empty($shortCover)) { $parse = parse_url(self::groupImg($shortCover)); if (empty($parse['query'])) { $cover = self::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; } else { $cover = self::groupImg($shortCover); } } $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 1; $list[$k]['cover'] = $cover; $stockInfo = self::formatStock($v['stock'], $ratio); $list[$k]['bigNum'] = $stockInfo['bigNum']; $list[$k]['smallNum'] = $stockInfo['smallNum']; $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); $list[$k]['price'] = $price; $list[$k]['bigPrice'] = $price; $smallRatio = $v['smallRatio'] ?? 0; $smallPrice = bcdiv($price, $ratio, 2); $smallPrice = bcmul($smallPrice, $smallRatio, 2); $list[$k]['smallPrice'] = $smallPrice; } return $list; } //花店的花材转成批发店花材 ssh 20230308 public static function hdToggleGhs($list, $ghsShop) { $ghsMainId = $ghsShop->mainId ?? 0; $ptItemIds = array_column($list, 'itemId'); $ghsHasProduct = ProductClass::getAllByCondition(['mainId' => $ghsMainId, 'itemId' => ['in', $ptItemIds]], null, '*', 'itemId'); $ghsSjId = $ghsShop->sjId ?? 0; $ghsShopId = $ghsShop->id ?? 0; $defaultClassId = \bizGhs\item\classes\ItemClassClass::getDefaultClassId($ghsMainId); if (empty($defaultClassId)) { util::fail('没有找到花材的默认分类'); } foreach ($list as $k => $v) { $ptItemId = $v['itemId'] ?? 0; $hdProductId = $v['productId'] ?? 0; if (isset($ghsHasProduct[$ptItemId]) == false) { //不存在,则从平台新增 $ptItem = PtItemClass::getById($ptItemId); unset($ptItem['addTime']); unset($ptItem['updateTime']); $productData = $ptItem; $productData['sjId'] = $ghsSjId; $productData['shopId'] = $ghsShopId; $productData['mainId'] = $ghsMainId; $productData['itemId'] = $ptItemId; $productData['classId'] = $defaultClassId; unset($productData['id']); $productData['rank'] = ''; $cost = $v['cost'] ?? 0; $addPrice = $v['addPrice'] ?? 0; $productData['price'] = bcadd($cost, $addPrice, 2); $addProduct = ProductClass::addBaseData($productData); $ghsProductId = $addProduct->id ?? 0; } else { $ghsProductId = $ghsHasProduct[$ptItemId]['id'] ?? 0; } $list[$k]['productId'] = $ghsProductId; $list[$k]['hdProductId'] = $hdProductId; } return $list; } public static function addBaseData($data) { $respond = self::add($data, true); return $respond; } public static function adStockCheck($shop, $productList) { //阿东开5支老是会出现5扎情况跟踪 $mainId = $shop->mainId ?? 0; Yii::info("mainId:" . $mainId); if (getenv('YII_ENV') == 'production') { if ($mainId != 742 && $mainId != 50) { return false; } } else { if ($mainId != 644) { return false; } } Yii::info('productList:' . json_encode($productList)); if (!empty($productList)) { $adIds = []; foreach ($productList as $adItem) { if (isset($adItem['property']) && $adItem['property'] == 1 && $adItem['unitType'] == 0 && $adItem['num'] == 5) { $adIds[] = $adItem['productId']; Yii::info('productId:' . $adItem['productId']); } } if (!empty($adIds)) { $arr = self::getByIds($adIds); if (!empty($arr)) { foreach ($arr as $it) { Yii::info("num:" . $it['scanNum']); if (isset($it['scanNum']) && $it['scanNum'] > 0) { $name = $it['name'] ?? ''; if (getenv('YII_ENV') == 'production') { if ($mainId == 742) { sms::freeSend('13531700113,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); sms::freeSend('15220670540,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); sms::freeSend('13531704009,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); sms::freeSend('15766689130,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); sms::freeSend('15280215347,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); } if ($mainId == 50) { sms::freeSend('15280215347,' . $name, '刚刚,花材:{$var},5支被开成了5扎,请注意!!!'); } } else { noticeUtil::push('刚刚,花材:' . $name . ',5支被开成了5扎,请注意!!!', '15280215347'); } } } } } } } //花材的库存转换: 库存是小数,根据各个花材的unitNum, 计算出多少扎,多少支 public static function formatStock($stock, $unitNum) { $pn = true; //正数 if ($stock < 0) { $pn = false; $stock = abs($stock); } $bigNum = floor($stock); // 扎 $stockArr = explode('.', $stock); $smallNum = 0; //支 if (count($stockArr) === 2) { $smallNum = bcmul(($stock - $bigNum), $unitNum); } if (!$pn) { $bigNum *= -1; $smallNum *= -1; } return [ 'bigNum' => $bigNum, 'smallNum' => $smallNum, ]; } //通用花材列表数组组合 public static function changePriceGroup($list, $level = 0) { //各个等级对应的price addPrice字段 $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $cover = imgUtil::getPrefix() . 'hhb_small.png'; if (!empty($shortCover)) { $parse = parse_url(self::groupImg($shortCover)); if (empty($parse['query'])) { $cover = self::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; } else { $cover = self::groupImg($shortCover); } } $list[$k]['cover'] = $cover; $list[$k]['prePrice'] = $v['price'] ?? 0; //今日特价、会员价 $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); if ($level == 0) { //零售做特价时显示的原价,不能删除 if (isset($v['skPrice'])) { $list[$k]['prePrice'] = $v['skPrice']; } } if (isset($v['discountPrice']) && $v['discountPrice'] > 0) { //做特价花材时,花材列表和改价列表显示的会员价和零售价,不能删除 $list[$k]['hjPrice'] = floatval($v['hjDiscountPrice']); $list[$k]['skPrice'] = floatval($v['skDiscountPrice']); } $list[$k]['price'] = $price; $cost = $v['avCost'] ?? 0; $ml = 0; if ($price > 0) { $profit = bcsub($price, $cost, 2); $ml = $profit; $mll = bcdiv($profit, $price, 3); $mll = bcmul($mll, 100); $mll = round($mll, 1); } else { $mll = 0; } $list[$k]['mll'] = $mll; $list[$k]['ml'] = $ml; } return $list; } public static function breakItemGroup($list, $level = 0) { foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 20; $stockInfo = self::formatStock($v['stock'], $ratio); $list[$k]['bigNum'] = $stockInfo['bigNum']; $list[$k]['smallNum'] = $stockInfo['smallNum']; $list[$k]['cover'] = imgUtil::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; } return $list; } public static function partItemGroup($list, $level = 0) { $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 20; $stockInfo = self::formatStock($v['stock'], $ratio); $list[$k]['bigNum'] = $stockInfo['bigNum']; $list[$k]['smallNum'] = $stockInfo['smallNum']; $list[$k]['cover'] = imgUtil::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); $list[$k]['price'] = $price; $list[$k]['bigPrice'] = $price; $smallRatio = $v['smallRatio'] ?? 0; $smallPrice = bcdiv($price, $ratio, 2); $smallPrice = bcmul($smallPrice, $smallRatio, 2); $list[$k]['smallPrice'] = $smallPrice; } return $list; } public static function checkItemGroup($list, $level = 0) { foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $list[$k]['cover'] = imgUtil::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 1; $stockInfo = self::formatStock($v['stock'], $ratio); $list[$k]['bigNum'] = $stockInfo['bigNum']; $list[$k]['smallNum'] = $stockInfo['smallNum']; } return $list; } public static function cgItemGroup($list, $level = 0) { $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $list[$k]['cover'] = imgUtil::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 1; $stockInfo = self::formatStock($v['stock'], $ratio); $list[$k]['bigNum'] = $stockInfo['bigNum']; $list[$k]['smallNum'] = $stockInfo['smallNum']; $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); $list[$k]['price'] = $price; $list[$k]['bigPrice'] = $price; $smallRatio = $v['smallRatio'] ?? 0; $smallPrice = bcdiv($price, $ratio, 2); $smallPrice = bcmul($smallPrice, $smallRatio, 2); $list[$k]['smallPrice'] = $smallPrice; } return $list; } //通用花材列表数组组合 public static function changeStockGroup($list, $level = 0) { //各个等级对应的price addPrice字段 $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $cover = imgUtil::getPrefix() . 'hhb_small.png'; if (!empty($shortCover)) { $parse = parse_url(self::groupImg($shortCover)); if (empty($parse['query'])) { $cover = self::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; } else { $cover = self::groupImg($shortCover); } } $list[$k]['cover'] = $cover; $list[$k]['prePrice'] = $v['price'] ?? 0; //今日特价、会员价 $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); //零售做特价时的原价 if ($level == 0) { $currentAddPrice = $v['skMore'] ?? 0; $list[$k]['prePrice'] = bcadd($v['price'], $currentAddPrice, 2); } if (isset($v['discountPrice']) && $v['discountPrice'] > 0) { $currentAddPrice = $v['skMore'] ?? 0; $list[$k]['skPrice'] = bcadd($v['discountPrice'], $currentAddPrice, 2); } $list[$k]['price'] = $price; $cost = $v['cost'] ?? 0; $ml = 0; if ($price > 0) { $profit = bcsub($price, $cost, 2); $ml = $profit; $mll = bcdiv($profit, $price, 3); $mll = bcmul($mll, 100); $mll = round($mll, 1); } else { $mll = 0; } $list[$k]['mll'] = $mll; $list[$k]['ml'] = $ml; } return $list; } public static function stockOutGroup($list, $level = 0) { //各个等级对应的price addPrice字段 $priceMap = \bizGhs\custom\classes\CustomClass::$levelPriceKeyMap; $addPriceMap = \bizGhs\custom\classes\CustomClass::$levelAddPriceKeyMap; foreach ($list as $k => $v) { $shortCover = $v['cover'] ?? ''; $list[$k]['cover'] = imgUtil::groupImg($shortCover) . "?x-oss-process=image/resize,m_fill,h_100,w_100"; $price = \bizGhs\product\classes\ProductClass::getFinalPrice($v, $level, $priceMap, $addPriceMap); $list[$k]['price'] = $price; $list[$k]['bigPrice'] = $price; $smallRatio = $v['smallRatio'] ?? 0; $ratio = isset($v['ratio']) && $v['ratio'] > 0 ? $v['ratio'] : 20; $smallPrice = bcdiv($price, $ratio, 2); $smallPrice = bcmul($smallPrice, $smallRatio, 2); $list[$k]['smallPrice'] = $smallPrice; } return $list; } /** * 限购处理 * @param $product * @param $customId 零售xhCustom表的id * @param $num */ public static function handleLimitBuy($product, $customId, $num, $doCache = true) { $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]); self::recordLimitBuyRollbackSnapshot($productId, $customId, true, $hasNum); $lastNum = bcadd($hasNum, $num, 2); if ($lastNum > $limitBuy) { // 如果有特价,则超过的数量按原价卖;否则中断 if ($product['hjDiscountPrice'] > 0 && $product['discountPrice'] > 0 && $product['skDiscountPrice'] > 0) { Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $lastNum]); return; } util::error(-1, '累计已超出限购数', ['productId'=>$productId, 'limitBuy'=>$limitBuy, 'exceed'=>$lastNum - $limitBuy]); } if ($doCache) { Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $lastNum]); } } else { self::recordLimitBuyRollbackSnapshot($productId, $customId, false, 0); if ($num > $limitBuy) { // 如果有特价,则超过的数量按原价卖;否则中断 if ($product['hjDiscountPrice'] > 0 && $product['discountPrice'] > 0 && $product['skDiscountPrice'] > 0) { Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $num]); return; } util::error(-1, '超出限购数', ['productId'=>$productId, 'limitBuy'=>$limitBuy, 'exceed'=>$num - $limitBuy]); } if ($doCache) { Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $num]); } } } /** * 获取零售客户当前清单中各花材的限购情况(Redis 键为 hd_limit_buy:,与批发商 limit_buy_ 区分) * @param array $list 花材列表,项含 id 及 bigCount/smallCount 或 bigNum/smallNum * @param int|string $customId 零售端 xhCustom.id(花店客户) */ public static function getLimitBuyInfoByList($list, $customId) { if (empty($list) || !is_array($list)) { return []; } $buyNumMap = []; $ids = []; foreach ($list as $item) { $productId = $item['id'] ?? 0; if (empty($productId)) { continue; } $productId = intval($productId); $bigNum = $item['bigCount'] ?? ($item['bigNum'] ?? 0); $smallNum = $item['smallCount'] ?? ($item['smallNum'] ?? 0); if (isset($buyNumMap[$productId]) == false) { $buyNumMap[$productId] = [ 'bigNum' => 0, 'smallNum' => 0, ]; } $buyNumMap[$productId]['bigNum'] = bcadd($buyNumMap[$productId]['bigNum'], $bigNum, 2); $buyNumMap[$productId]['smallNum'] = bcadd($buyNumMap[$productId]['smallNum'], $smallNum, 2); $ids[] = $productId; } $ids = array_values(array_unique($ids)); if (empty($ids)) { return []; } $productData = \bizGhs\product\classes\ProductClass::getProductByIds($ids); $productData = array_column($productData, null, 'id'); $respond = []; foreach ($ids as $productId) { $product = $productData[$productId] ?? []; if (empty($product)) { continue; } $limitBuy = $product['limitBuy'] ?? 0; $isLimit = $limitBuy > 0 ? true : false; $hasBuyNum = 0; if ($isLimit) { $limitKey = self::LIMIT_BUY_KEY . $productId; $hasBuyNum = Yii::$app->redis->executeCommand('HGET', [$limitKey, $customId]); $hasBuyNum = $hasBuyNum == null ? 0 : $hasBuyNum; } $buyNum = $buyNumMap[$productId] ?? []; $ratio = $product['ratio'] ?? 1; $currentBuyNum = \bizGhs\product\classes\ProductClass::mergeItemNum($buyNum['bigNum'] ?? 0, $buyNum['smallNum'] ?? 0, $ratio); $totalBuyNum = bcadd($hasBuyNum, $currentBuyNum, 2); $specialPrice = ( ($product['hjDiscountPrice'] ?? 0) > 0 && ($product['discountPrice'] ?? 0) > 0 && ($product['skDiscountPrice'] ?? 0) > 0 ) ? true : false; $respond[] = [ 'id' => $productId, 'name' => $product['name'] ?? '', 'isLimit' => $isLimit, 'limitBuy' => (float)$limitBuy, 'specialPrice' => $specialPrice, 'hasBuyNum' => $isLimit ? (float)$hasBuyNum : 0, 'currentBuyNum' => (float)$currentBuyNum, 'reachLimitBuyNum' => ($isLimit && $totalBuyNum > $limitBuy) ? true : false, 'exceedNum' => $isLimit ? (float)bcsub($hasBuyNum, $limitBuy, 2) : 0, ]; } return $respond; } //取消限购的缓存 public static function cancelLimitBuy($productId) { $limitKey = self::LIMIT_BUY_KEY . $productId; $arr = Yii::$app->redis->executeCommand('HKEYS', [$limitKey]); if (!empty($arr)) { foreach ($arr as $field) { self::baseClearLimitBuy($productId, $field); } } self::clearLimitBuyClearMark($productId); self::clearLimitBuyClearLoopConfig($productId); } public static function getHasLimitBuyList($product) { $productId = $product->id; $limitBuy = $product->limitBuy ?? 0; $limitKey = self::LIMIT_BUY_KEY . $productId; $arr = Yii::$app->redis->executeCommand('HKEYS', [$limitKey]); $customList = []; if (!empty($arr)) { $numMap = []; $ids = []; foreach ($arr as $customId) { $num = Yii::$app->redis->executeCommand('HGET', [$limitKey, $customId]); $numMap[$customId] = $num; $ids[] = $customId; } $specialPrice = ($product['hjDiscountPrice'] > 0 && $product['discountPrice'] > 0 && $product['skDiscountPrice'] > 0); // 销售花材是否开启特价 $customList = CustomClass::getAllByCondition(['id' => ['in', $ids]], null, '*'); foreach ($customList as $key => $val) { $customId = $val['id']; $num = $numMap[$customId] ?? 0; $customList[$key]['hasLimitBuyNum'] = $num; $customList[$key]['reachLimitBuyNum'] = $num >= $limitBuy ? 1 : 0; $avatar = $val['avatar'] ?? ''; $smallAvatar = imgUtil::groupImg($avatar); $customList[$key]['smallAvatar'] = $smallAvatar . "?x-oss-process=image/resize,m_fill,h_80,w_80"; $customList[$key]['specialPrice'] = $specialPrice; } } return $customList; } /** * @param $productId * @param $customId * @param int $num -1 表示减全部,大于-1表示减值 */ public static function baseClearLimitBuy($productId, $customId, $num = -1) { $limitKey = self::LIMIT_BUY_KEY . $productId; if ($num <= -1) { //清掉全部 Yii::$app->redis->executeCommand('HDEL', [$limitKey, $customId]); } else { $hasNum = Yii::$app->redis->executeCommand('HGET', [$limitKey, $customId]); $remainNum = $hasNum > $num ? bcsub($hasNum, $num) : 0; $remainNum = floor($remainNum); if ($remainNum > 0) { Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $remainNum]); } else { Yii::$app->redis->executeCommand('HDEL', [$limitKey, $customId]); } } } // 只清空已购买记录,不关闭商品限购配置 public static function clearLimitBuyRecordByProductId($productId) { $productId = intval($productId); if ($productId <= 0) { return false; } $limitKey = self::LIMIT_BUY_KEY . $productId; Yii::$app->redis->executeCommand('DEL', [$limitKey]); return true; } // 清理限购清空标记 public static function clearLimitBuyClearMark($productId) { $productId = intval($productId); if ($productId <= 0) { return false; } $clearMarkKey = self::CLEAR_MARK_KEY . $productId; Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]); return true; } /** * 创建限购缓存(有过期时间) * @param $productId 花材ID * @param int $seconds 过期时间 * @param array $options ['clearAt' => 0, 'recurring' => 0, 'intervalDays' => 0, 'clearHour' => 0] 循环清空配置 * @return bool */ public static function createLimitBuyCache($productId, $seconds = 0, $options = []) { $limitKey = self::LIMIT_BUY_KEY . $productId; $clearMarkKey = self::CLEAR_MARK_KEY . $productId; $clearLoopKey = self::CLEAR_LOOP_KEY . $productId; $recurring = !empty($options['recurring']); $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 = intval($options['clearAt'] ?? 0); if ($clearAt <= 0) { $clearAt = time() + intval($seconds); } Yii::$app->redis->executeCommand('EXPIRE', [$limitKey, $seconds]); Yii::$app->redis->executeCommand('SET', [$clearMarkKey, $clearAt]); if ($recurring) { $intervalDays = intval($options['intervalDays'] ?? 0); $clearHour = intval($options['clearHour'] ?? -1); Yii::$app->redis->executeCommand('SET', [$clearLoopKey, json_encode([ 'intervalDays' => $intervalDays, 'clearHour' => $clearHour, ], JSON_UNESCAPED_UNICODE)]); } else { Yii::$app->redis->executeCommand('DEL', [$clearLoopKey]); } // 用 RabbitMQ 延迟消息在到期时清空订单项限购字段 $message = [ 'type' => 'limit_buy_clear', 'ptType' => 'hd', 'productId' => $productId, 'clearAt' => $clearAt, ]; if ($recurring) { $message['recurring'] = 1; $message['intervalDays'] = intval($options['intervalDays'] ?? 0); $message['clearHour'] = intval($options['clearHour'] ?? -1); } $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), 'recurring' => $recurring ? 1 : 0, ], JSON_UNESCAPED_UNICODE), __METHOD__); } else { Yii::$app->redis->executeCommand('DEL', [$clearMarkKey]); Yii::$app->redis->executeCommand('DEL', [$clearLoopKey]); } } // 创建循环清空限购缓存 public static function createRecurringLimitBuyCache($productId, $intervalDays, $clearHour, $clearAt = 0) { $productId = intval($productId); $intervalDays = intval($intervalDays); $clearHour = intval($clearHour); if ($productId <= 0 || $intervalDays <= 0 || $clearHour < 0 || $clearHour > 23) { return false; } if ($clearAt <= 0) { $clearAt = self::getNextLimitBuyClearAt($intervalDays, $clearHour); } $seconds = max(1, $clearAt - time()); self::createLimitBuyCache($productId, $seconds, [ 'recurring' => 1, 'intervalDays' => $intervalDays, 'clearHour' => $clearHour, 'clearAt' => $clearAt, ]); return true; } // 计算下一次循环清空时间 public static function getNextLimitBuyClearAt($intervalDays, $clearHour, $baseTime = 0) { $baseTime = $baseTime > 0 ? intval($baseTime) : time(); $targetDay = $baseTime + intval($intervalDays) * 86400; $clearAt = strtotime(date('Y-m-d', $targetDay) . ' ' . sprintf('%02d:00:00', intval($clearHour))); while ($clearAt <= $baseTime) { $clearAt = strtotime('+' . intval($intervalDays) . ' days', $clearAt); } return $clearAt; } // 获取循环清空限购配置 public static function getLimitBuyClearLoopConfigByProductId($productId) { $productId = intval($productId); if ($productId <= 0) { return []; } $clearLoopKey = self::CLEAR_LOOP_KEY . $productId; $config = Yii::$app->redis->executeCommand('GET', [$clearLoopKey]); if (empty($config)) { return []; } $config = json_decode($config, true); return is_array($config) ? $config : []; } // 获取限购清空配置,用于商品详情回显 public static function getLimitBuyClearInfo($productId) { $productId = intval($productId); $info = [ 'limitBuyClearType' => 0, // 0不清空 1循环清空 2定时清空 'limitBuyClearTypeName' => '不清空', 'limitBuyClearAt' => 0, 'limitBuyClearTime' => '', 'limitBuyNextClearAt' => 0, 'limitBuyNextClearTime' => '', 'limitBuyClearIntervalDays' => 0, 'cleartAt' => '', ]; if ($productId <= 0) { return $info; } $loopConfig = self::getLimitBuyClearLoopConfigByProductId($productId); if (!empty($loopConfig)) { $info['limitBuyClearType'] = 1; $info['limitBuyClearTypeName'] = '循环清空'; $info['limitBuyClearIntervalDays'] = intval($loopConfig['intervalDays'] ?? 0); $info['cleartAt'] = intval($loopConfig['clearHour'] ?? 0); return $info; } $clearMarkKey = self::CLEAR_MARK_KEY . $productId; $clearAt = intval(Yii::$app->redis->executeCommand('GET', [$clearMarkKey])); if ($clearAt <= 0) { return $info; } $info['limitBuyClearType'] = 2; $info['limitBuyClearTypeName'] = '定时清空'; $info['limitBuyClearAt'] = $clearAt; $info['limitBuyClearTime'] = date('Y-m-d H:i:s', $clearAt); $info['limitBuyNextClearAt'] = $clearAt; $info['limitBuyNextClearTime'] = $info['limitBuyClearTime']; return $info; } // 清理循环清空限购配置 public static function clearLimitBuyClearLoopConfig($productId) { $productId = intval($productId); if ($productId <= 0) { return false; } $clearLoopKey = self::CLEAR_LOOP_KEY . $productId; Yii::$app->redis->executeCommand('DEL', [$clearLoopKey]); return true; } /** * 记录限购缓存写入前快照(同一次请求内同商品同客户只记录一次) * * @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; } /** * 回滚当前请求中已记录的限购缓存变更 * * @return bool */ public static function rollbackLimitBuySnapshot() { $snapshotList = Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY] ?? []; if (empty($snapshotList) || !is_array($snapshotList)) { return true; } foreach ($snapshotList as $snapshot) { $productId = intval($snapshot['productId'] ?? 0); $customId = intval($snapshot['customId'] ?? 0); if ($productId <= 0 || $customId <= 0) { continue; } $limitKey = self::LIMIT_BUY_KEY . $productId; $hasOldValue = intval($snapshot['hasOldValue'] ?? 0); if ($hasOldValue == 1) { $oldValue = $snapshot['oldValue'] ?? '0'; Yii::$app->redis->executeCommand('HSET', [$limitKey, $customId, $oldValue]); } else { Yii::$app->redis->executeCommand('HDEL', [$limitKey, $customId]); } } self::clearLimitBuyRollbackSnapshot(); return true; } /** * 清理当前请求中记录的限购回滚快照 * * @return void */ public static function clearLimitBuyRollbackSnapshot() { unset(Yii::$app->params[self::LIMIT_BUY_ROLLBACK_SNAPSHOT_KEY]); } }