Browse Source

排查并修复 ensureYearRecords 在并发或重复调用时触发唯一键冲突的问题

shizhongqi 2 months ago
parent
commit
d5dfbb937b
2 changed files with 52 additions and 9 deletions
  1. 49 8
      biz-hd/birthday/classes/BirthdayGiftClass.php
  2. 3 1
      common/components/sms.php

+ 49 - 8
biz-hd/birthday/classes/BirthdayGiftClass.php

@@ -16,6 +16,7 @@ use common\components\stringUtil;
 use common\components\util;
 use PhpAmqpLib\Wire\AMQPTable;
 use Yii;
+use yii\db\IntegrityException;
 use bizHd\base\classes\BaseClass;
 
 class BirthdayGiftClass extends BaseClass
@@ -216,13 +217,23 @@ class BirthdayGiftClass extends BaseClass
         return md5(uniqid('bg', true) . mt_rand(1000, 9999) . microtime(true));
     }
 
+    public static function findYearRecord($shopId, $customId, $year = null)
+    {
+        $year = $year ?: intval(date('Y'));
+        return self::getByCondition([
+            'shopId' => intval($shopId),
+            'customId' => intval($customId),
+            'year' => intval($year),
+        ], true);
+    }
+
     public static function getOrCreateYearRecord($shop, $custom, $levelMap)
     {
         $year = intval(date('Y'));
-        $shopId = $shop->id;
+        $shopId = intval($shop->id);
         $mainId = $shop->mainId;
-        $customId = $custom->id;
-        $gift = self::getByCondition(['shopId' => $shopId, 'customId' => $customId, 'year' => $year], true);
+        $customId = intval($custom->id);
+        $gift = self::findYearRecord($shopId, $customId, $year);
         $member = intval($custom->member);
         $giftName = self::getGiftNameForMember($member, $levelMap);
         $hd = HdClass::getByCondition(['customId' => $customId, 'shopId' => $shopId], true);
@@ -247,7 +258,15 @@ class BirthdayGiftClass extends BaseClass
         if (empty($gift)) {
             $data['status'] = $status;
             $data['claimToken'] = self::generateClaimToken();
-            $gift = self::add($data, true);
+            try {
+                $gift = self::add($data, true);
+            } catch (IntegrityException $e) {
+                // 并发请求同时创建同一年度记录时,捕获唯一键冲突后重新读取
+                $gift = self::findYearRecord($shopId, $customId, $year);
+                if (empty($gift)) {
+                    throw $e;
+                }
+            }
         } else {
             if (intval($gift->status) < self::STATUS_PENDING_CLAIM) {
                 $gift->status = $status;
@@ -267,9 +286,29 @@ class BirthdayGiftClass extends BaseClass
 
     public static function ensureYearRecords($shop)
     {
-        list($customs, $levelMap) = self::getEligibleCustoms($shop->id, $shop->mainId);
+        $shopId = intval($shop->id);
+        $year = intval(date('Y'));
+        list($customs, $levelMap) = self::getEligibleCustoms($shopId, $shop->mainId);
+        if (empty($customs)) {
+            return true;
+        }
+        $existingRows = self::getAllByCondition(['shopId' => $shopId, 'year' => $year], null, 'customId');
+        $existingIds = [];
+        if (!empty($existingRows)) {
+            foreach ($existingRows as $row) {
+                $cid = intval($row['customId'] ?? 0);
+                if ($cid > 0) {
+                    $existingIds[$cid] = 1;
+                }
+            }
+        }
         foreach ($customs as $custom) {
+            $customId = intval($custom->id);
+            if (isset($existingIds[$customId])) {
+                continue;
+            }
             self::getOrCreateYearRecord($shop, $custom, $levelMap);
+            $existingIds[$customId] = 1;
         }
         return true;
     }
@@ -534,8 +573,11 @@ class BirthdayGiftClass extends BaseClass
             $gift->shortLink = $shortLink;
         }
         $msg = "{$shopName}:亲爱的会员您好,明天就是您生日啦!这边有份您的vip生日福利礼包{$giftDesc},请您24小时内点击下方链接免费领取{$shortLink}逾期无效、感谢您的配合!祝您生活愉快!天天开心!";
-        sms::merchantSend($mobile, $msg, $merchant);
-        return [true, ''];
+        $re = sms::merchantSend($mobile, $msg, $merchant);
+        if ($re == false) {
+            return [$re, '短信余额不足'];
+        }
+        return [$re, ''];
     }
 
     public static function batchNotifyTomorrow($shop)
@@ -545,7 +587,6 @@ class BirthdayGiftClass extends BaseClass
         $success = 0;
         $fail = 0;
         $messages = [];
-        $year = intval(date('Y'));
         foreach ($customs as $custom) {
             if (!self::matchesBirthdayOffset($custom, 1)) {
                 continue;

+ 3 - 1
common/components/sms.php

@@ -42,12 +42,14 @@ class sms
         $asset = MerchantAssetService::getBySjId($sjId, true);
         $remainSmsNum = $asset->remainSmsNum;
         if ($remainSmsNum <= 0) {
-            Yii::info($merchant['name'] . '短信余额不足');
+            Yii::info('商家id:' . $sjId . ' -- ' . $merchant['name'] . '短信余额不足');
             return false;
         }
         $asset->remainSmsNum = --$remainSmsNum;
         $asset->save();
         self::freeSend($params, $msg, $merchant);
+
+        return true;
     }
 
     //自由发短,无ip和数量限制 ssh 2019.12.24