Browse Source

Merge branch 'dev' of http://git.huaml.com/zhh/huahuibao into dev

shish 2 months ago
parent
commit
1a2c3f5cae

+ 2 - 0
app-hd/controllers/BirthdayGiftController.php

@@ -57,4 +57,6 @@ class BirthdayGiftController extends BaseController
         BirthdayGiftClass::manualPrint($this->shopId, $id);
         util::complete('打印指令已发送');
     }
+
+
 }

+ 10 - 0
app-mall/controllers/BirthdayGiftController.php

@@ -28,6 +28,16 @@ class BirthdayGiftController extends BaseController
         if (empty($post)) {
             $post = Yii::$app->request->get();
         }
+        if (BirthdayGiftClass::isSmsClaimPush($post)) {
+            Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
+            try {
+                BirthdayGiftClass::claimBySmsPush($post);
+                return ['clcode' => '000000'];
+            } catch (\Throwable $e) {
+                Yii::error('birthday gift sms claim failed: ' . $e->getMessage() . ' params=' . json_encode($post, JSON_UNESCAPED_UNICODE), __METHOD__);
+                return ['clcode' => '111111'];
+            }
+        }
         $token = trim($post['token'] ?? '');
         $account = intval($post['account'] ?? 0);
         $pickupTime = trim($post['pickupTime'] ?? '');

+ 160 - 22
biz-hd/birthday/classes/BirthdayGiftClass.php

@@ -10,7 +10,7 @@ use bizHd\shop\classes\ShopClass;
 use bizHd\shop\classes\ShopExtClass;
 use common\components\orderSn;
 use common\components\printUtil;
-use common\components\sms;
+use common\components\ChuanglanSmsApi;
 use common\components\stringUtil;
 use common\components\util;
 use PhpAmqpLib\Wire\AMQPTable;
@@ -506,14 +506,6 @@ class BirthdayGiftClass extends BaseClass
         return $shopName == '首店' ? $sjName : trim($sjName . ' ' . $shopName);
     }
 
-    public static function buildShortLink($shopId, $token)
-    {
-        $page = 'pages/home/recent'; // TODO 测试用
-        //$page = 'pages/member/birthdayGift?account=' . $shopId . '&token=' . urlencode($token); // 正式 TODO
-        $ret = \bizMall\wx\classes\WxMiniClass::getShortLink($page);
-        return $ret['link'] ?? '';
-    }
-
     public static function scheduleExpireDelay($giftId, $expireAt)
     {
         $giftId = intval($giftId);
@@ -567,16 +559,13 @@ class BirthdayGiftClass extends BaseClass
         }
         $shopName = self::getShopDisplayName($shop);
         $giftDesc = $gift->giftName ?: '生日礼品';
-        $shortLink = $gift->shortLink;
-        if (empty($shortLink)) {
-            $shortLink = self::buildShortLink($shop->id, $gift->claimToken);
-            Yii::info("短链地址:" . $shortLink);
-            $gift->shortLink = $shortLink;
-        }
-        $msg = "{$shopName}:亲爱的会员,明天是您生日,这边有份生日福利礼包{$giftDesc},请24小时内点击下方链接领取{$shortLink}。祝您生活愉快!天天开心!";
-        $re = sms::freeSend($mobile, $msg, ['name' => '253云通讯']);
-        if ($re == false) {
-            return [$re, '短信余额不足'];
+        $msg = "{$shopName}:亲爱的会员您好,明天就是您生日啦!为您准备了专属生日礼品:{$giftDesc}。请您回复领取时间,如(5月20日10点),24小时内回复有效。不领取请忽略短信,祝您生活愉快!";
+        $sms = new ChuanglanSmsApi();
+        $sms->useAccount('marketing');
+        $re = $sms->sendSMS($mobile, $msg);
+        Yii::info('生日礼品短信返回:' . json_encode($re));
+        if (!$re) {
+            return [false, '短信发送失败'];
         }
         return [$re, ''];
     }
@@ -657,17 +646,166 @@ class BirthdayGiftClass extends BaseClass
         ];
     }
 
-    public static function claimByToken($shopId, $token, $pickupTime)
+    public static function isSmsClaimPush($params)
     {
-        if (empty($pickupTime)) {
-            util::fail('请选择领取时间');
+        return is_array($params) && !empty($params['mobile']) && array_key_exists('msg', $params);
+    }
+
+    public static function claimBySmsPush($params)
+    {
+        $mobile = preg_replace('/\D/', '', trim($params['mobile'] ?? ''));
+        $content = self::normalizeSmsReply($params['msg'] ?? '');
+        if (empty($mobile) || empty($content)) {
+            util::fail('参数错误');
+        }
+        if (self::isGiveUpReply($content)) {
+            Yii::info('birthday gift sms claim ignored give up reply: mobile=' . $mobile . ' content=' . $content, __METHOD__);
+            return true;
+        }
+        $extend = trim($params['extend'] ?? '');
+        $gift = self::findGiftBySmsReply($mobile, $extend);
+        $pickupTime = self::parsePickupTime($content, $params['moTime'] ?? '');
+        return self::claimGift($gift, $pickupTime, false, true);
+    }
+
+    public static function normalizeSmsReply($content)
+    {
+        $content = trim((string)$content);
+        if ($content !== '' && strpos($content, '%') !== false) {
+            $content = urldecode($content);
+        }
+        return trim($content);
+    }
+
+    public static function isGiveUpReply($content)
+    {
+        return preg_match('/不领|不要|不用|放弃|取消|算了/u', $content) === 1;
+    }
+
+    public static function findGiftBySmsReply($mobile, $extend = '')
+    {
+        $gift = null;
+        $giftId = intval($extend);
+        if ($giftId > 0) {
+            $gift = self::getById($giftId, true);
+            if (!empty($gift)) {
+                $custom = CustomClass::getById($gift->customId, true);
+                $customMobile = preg_replace('/\D/', '', trim($custom->mobile ?? ''));
+                if ($customMobile != $mobile) {
+                    util::fail('手机号与领取记录不匹配');
+                }
+                return $gift;
+            }
+        }
+
+        $customs = CustomClass::getAllByCondition([
+            'mobile' => $mobile,
+            'delStatus' => 0,
+        ], null, 'id', null, false);
+        $customIds = [];
+        foreach ($customs as $custom) {
+            $customIds[] = intval($custom['id'] ?? 0);
+        }
+        $customIds = array_filter($customIds);
+        if (empty($customIds)) {
+            util::fail('未找到会员信息');
+        }
+        $gifts = self::getAllByCondition([
+            'customId' => ['in', $customIds],
+            'status' => self::STATUS_PENDING_CLAIM,
+            'smsSent' => 1,
+            'expireTime>=' => date('Y-m-d H:i:s'),
+        ], 'notifyTime DESC,id DESC', '*', null, true);
+        if (empty($gifts)) {
+            util::fail('未找到可领取的生日礼品');
+        }
+        return $gifts[0];
+    }
+
+    public static function parsePickupTime($content, $moTime = '')
+    {
+        $baseTime = self::parseMoTime($moTime);
+        $date = null;
+        $hour = null;
+        $minute = 0;
+        $period = '';
+        $text = preg_replace('/\s+/u', '', $content);
+
+        if (preg_match('/(\d{1,2})月(\d{1,2})(?:日|号)?.*?(上午|中午|下午|晚上|傍晚)?(\d{1,2})(?:[::点时](\d{1,2})?分?)?/u', $text, $match)) {
+            $year = intval(date('Y', $baseTime));
+            $month = intval($match[1]);
+            $day = intval($match[2]);
+            if (!checkdate($month, $day, $year)) {
+                return '';
+            }
+            $date = sprintf('%04d-%02d-%02d', $year, $month, $day);
+            $period = $match[3] ?? '';
+            $hour = intval($match[4]);
+            $minute = intval($match[5] ?? 0);
+        } elseif (preg_match('/(\d{1,2})[-\/.](\d{1,2}).*?(上午|中午|下午|晚上|傍晚)?(\d{1,2})(?:[::点时](\d{1,2})?分?)?/u', $text, $match)) {
+            $year = intval(date('Y', $baseTime));
+            $month = intval($match[1]);
+            $day = intval($match[2]);
+            if (!checkdate($month, $day, $year)) {
+                return '';
+            }
+            $date = sprintf('%04d-%02d-%02d', $year, $month, $day);
+            $period = $match[3] ?? '';
+            $hour = intval($match[4]);
+            $minute = intval($match[5] ?? 0);
+        } elseif (preg_match('/(今天|明天|后天).*?(上午|中午|下午|晚上|傍晚)?(\d{1,2})(?:[::点时](\d{1,2})?分?)?/u', $text, $match)) {
+            $offsetMap = ['今天' => 0, '明天' => 1, '后天' => 2];
+            $date = date('Y-m-d', strtotime('+' . $offsetMap[$match[1]] . ' days', $baseTime));
+            $period = $match[2] ?? '';
+            $hour = intval($match[3]);
+            $minute = intval($match[4] ?? 0);
+        }
+
+        if ($date === null || $hour === null || $hour > 23 || $minute > 59) {
+            return '';
+        }
+        if (in_array($period, ['下午', '晚上', '傍晚']) && $hour < 12) {
+            $hour += 12;
+        } elseif ($period == '中午' && $hour < 11) {
+            $hour += 12;
+        }
+        return $date . sprintf(' %02d:%02d:00', $hour, $minute);
+    }
+
+    public static function parseMoTime($moTime)
+    {
+        $moTime = trim((string)$moTime);
+        if (preg_match('/^\d{10}$/', $moTime)) {
+            $date = \DateTime::createFromFormat('ymdHi', $moTime);
+            if ($date !== false) {
+                return $date->getTimestamp();
+            }
         }
+        return time();
+    }
+
+    public static function claimByToken($shopId, $token, $pickupTime)
+    {
         $gift = self::getByCondition(['shopId' => $shopId, 'claimToken' => $token], true);
         if (empty($gift)) {
             util::fail('链接无效');
         }
+        return self::claimGift($gift, $pickupTime, true, false);
+    }
+
+    public static function claimGift($gift, $pickupTime, $requirePickupTime = true, $idempotent = false)
+    {
+        if (empty($gift)) {
+            util::fail('记录不存在');
+        }
+        if ($requirePickupTime && empty($pickupTime)) {
+            util::fail('请选择领取时间');
+        }
         $status = intval($gift->status);
         if ($status == self::STATUS_COLLECTED) {
+            if ($idempotent) {
+                return $gift;
+            }
             util::fail('已领取,请勿重复操作');
         }
         if ($status == self::STATUS_EXPIRED) {

+ 82 - 10
common/components/ChuanglanSmsApi.php

@@ -16,16 +16,38 @@ class ChuanglanSmsApi {
 
 	//参数的配置 请登录zz.253.com 获取以下API信息 ↓↓↓↓↓↓↓
 	const API_SEND_URL='http://smsbj1.253.com/msg/send/json'; //创蓝发送短信接口URL
-	
 	const API_VARIABLE_URL = 'http://smsbj1.253.com/msg/variable/json';//创蓝变量短信接口URL
-	
 	const API_BALANCE_QUERY_URL= 'http://XXX/msg/balance/json';//创蓝短信余额查询接口URL
 	
 	const API_ACCOUNT= 'N2890342'; // 创蓝API账号
-	
 	const API_PASSWORD= 'abc178c7';// 创蓝API密码
 	//参数的配置 请登录zz.253.com 获取以上API信息 ↑↑↑↑↑↑↑
 
+	private $account = self::API_ACCOUNT;
+	private $password = self::API_PASSWORD;
+
+	public function __construct($accountKey = 'default')
+	{
+		$this->useAccount($accountKey);
+	}
+
+	/**
+	 * 切换短信账号
+	 *
+	 * .env 配置示例:
+	 * SMS_ACCOUNTS={"default":{"account":"Nxxxx","password":"xxxx"},"marketing":{"account":"Nyyyy","password":"yyyy"}}
+	 *
+	 * @param string $accountKey SMS_ACCOUNTS 中的账号标识
+	 * @return $this
+	 */
+	public function useAccount($accountKey = 'default')
+	{
+		$account = self::getAccountConfig($accountKey);
+		$this->account = $account['account'];
+		$this->password = $account['password'];
+
+		return $this;
+	}
 
 	/**
 	 * 发送短信
@@ -38,8 +60,8 @@ class ChuanglanSmsApi {
 		
 		//创蓝接口参数
 		$postArr = array (
-			'account'  =>  self::API_ACCOUNT,
-			'password' => self::API_PASSWORD,
+			'account'  =>  $this->account,
+			'password' => $this->password,
 			'msg' => urlencode($msg),
 			'phone' => $mobile,
 			'report' => $needstatus,
@@ -53,17 +75,21 @@ class ChuanglanSmsApi {
 	 *
 	 * @param string $msg 			短信内容
 	 * @param string $params 	最多不能超过1000个参数组
+	 * @param string $extend 	上行回复匹配用扩展码
 	 */
-	public function sendVariableSMS( $msg, $params) {
+	public function sendVariableSMS( $msg, $params, $extend = '') {
 		
 		//创蓝接口参数
 		$postArr = array (
-			'account'  =>  self::API_ACCOUNT,
-			'password' => self::API_PASSWORD,
+			'account'  =>  $this->account,
+			'password' => $this->password,
 			'msg' => $msg,
 			'params' => $params,
 			'report' => 'true'
         	);
+		if ($extend !== '') {
+			$postArr['extend'] = (string)$extend;
+		}
 
 		$result = $this->curlPost( self::API_VARIABLE_URL, $postArr);
 		return $result;
@@ -78,13 +104,59 @@ class ChuanglanSmsApi {
 		
 		//查询参数
 		$postArr = array ( 
-		        'account'  =>  self::API_ACCOUNT,
-			'password' => self::API_PASSWORD,
+		        'account'  =>  $this->account,
+			'password' => $this->password,
 		);
 		$result = $this->curlPost(self::API_BALANCE_QUERY_URL, $postArr);
 		return $result;
 	}
 
+	private static function getAccountConfig($accountKey)
+	{
+		$accounts = self::env('SMS_ACCOUNTS');
+		if (!empty($accounts)) {
+			$accounts = json_decode($accounts, true);
+			if (!is_array($accounts)) {
+				util::fail('短信账号配置格式错误');
+			}
+
+			if (!empty($accounts[$accountKey]) && is_array($accounts[$accountKey])) {
+				return self::validateAccountConfig($accounts[$accountKey]);
+			}
+
+			if ($accountKey !== 'default') {
+				util::fail('短信账号不存在');
+			}
+		}
+
+		$account = self::env('CHUANGLAN_SMS_ACCOUNT', self::API_ACCOUNT);
+		$password = self::env('CHUANGLAN_SMS_PASSWORD', self::API_PASSWORD);
+
+		return self::validateAccountConfig([
+			'account' => $account,
+			'password' => $password,
+		]);
+	}
+
+	private static function validateAccountConfig($account)
+	{
+		if (empty($account['account']) || empty($account['password'])) {
+			util::fail('短信账号配置不完整');
+		}
+
+		return $account;
+	}
+
+	private static function env($key, $default = null)
+	{
+		$value = getenv($key);
+		if ($value !== false) {
+			return $value;
+		}
+
+		return '';
+	}
+
 	/**
 	 * 通过CURL发送HTTP请求
 	 * @param string $url  //请求URL

+ 5 - 2
common/components/sms.php

@@ -53,7 +53,7 @@ class sms
     }
 
     //自由发短,无ip和数量限制 ssh 2019.12.24
-    public static function freeSend($params, $msg, $merchant = null)
+    public static function freeSend($params, $msg, $merchant = null, $extend = '')
     {
         $ptStyle = dict::getDict('ptStyle', 'hd');
         if (isset(Yii::$app->params['ptStyle'])) {
@@ -64,16 +64,19 @@ class sms
         $sign = isset($merchant) == true ? "【{$merchant['name']}】" : "【{$name}】";
         $msg = $sign . $msg;
         $sms = new ChuanglanSmsApi();
-        $result = $sms->sendVariableSMS($msg, $params);
+        $result = $sms->sendVariableSMS($msg, $params, $extend);
         if (!is_null(json_decode($result))) {
             $output = json_decode($result, true);
             if (isset($output['code']) && $output['code'] == '0') {
                 Yii::info("短信发送成功,参数:{$params} 返回:" . $result);
+                return true;
             } else {
                 Yii::info("短信发送失败,参数:{$params} 返回:" . $result);
+                return false;
             }
         } else {
             Yii::info("短信发送失败,参数:{$params} 返回:" . $result);
+            return false;
         }
     }