| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- <?php
- namespace app\crontab;
- use think\console\Command;
- use think\console\Input;
- use think\console\Output;
- use think\Db;
- use think\Cache;
- use app\common\logic\Complex;
- use think\Env;
- /**
- * 聚合渠道游戏通知预警定时器脚本
- *
- * - 定时任务:每半小时执行一次
- * - 预警时间段:8~24 点 之间执行
- * - 每天第一次触发时候必通知一次
- * - 单次预警间隔3小时
- */
- class PolyChannelSmsWarn extends Command {
- protected function configure() {
- $this->setName('PolyChannelSmsWarn')->setDescription('聚合渠道游戏短信预警脚本');
- }
- protected function execute(Input $input, Output $output) {
- $hour = date('H');
- // 只在 8~24 点 之间执行
- if ( $hour < 8 || $hour > 24 ) {
- $output->writeln(date('Y-m-d H:i:s')." 非预警时间段(8~24点),脚本结束\r\n");
- return;
- }
- $list = Db::table('cy_polychannel_game_frozen')->alias('pgf')
- ->join('cy_polychannel_deposit pcd', 'pgf.channel_id=pcd.channel_id', 'LEFT')
- ->join('nw_complex_channel cc', 'pgf.channel_id=cc.id', 'LEFT')
- ->where(['pgf.status' => 1])
- // ->where([
- // // 'pgf.game_id' => ['in', [891,892]],
- // 'pgf.channel_id' => 45
- // ])
- ->group('pgf.channel_id')
- ->field('pgf.channel_id, pcd.mobile, pcd.total_advance, pcd.warn_amount, pcd.over_limit, cc.name as channel_name')
- ->select();
- $channelNames = array_column($list, 'channel_name');
- $output->writeln(date('Y-m-d H:i:s')." start - sum=".count($list)." - 渠道:".implode(',', $channelNames));
- if ( !empty($list) ) {
- $complexLogic = new Complex;
- $redis = Cache::store('redis')->handler();
- $noticeExpire = 60*60*3; // 单次预警间隔三小时
- foreach ( $list as $row ) {
- try {
- if ( empty($row['mobile']) ) continue;
- if ( !is_object($redis) ) continue;
- $totalWarnAmount = bcadd($row['total_advance'] - $row['warn_amount'] + $row['over_limit'], '0', 2); // 剩余预警额度(总预付款-预警金额+可超额度)
- $channelTotalMoney = $complexLogic->getChannelGameTotalAmount($row['channel_id']); // 渠道分成总流水
- if ( $totalWarnAmount > $channelTotalMoney ) continue;
- // 记录第一次执行
- $intKey = "wlsh:polyChannelSmsWarn:{$row['channel_id']}";
- $initNum = $redis->get($intKey);
- if($initNum === false){
- $initNum = 0;
- $redis->set($intKey, 1, strtotime('tomorrow') - time());
- }
- // $initNum = 0;
- $dayKey = "wlsh:polyChannelSmsWarn:{$row['channel_id']}:".date('md');
- $notify_day_count = (int)$redis->get($dayKey);
- // 8~24 三小时发送一次; 第一次立即发送;
- if ($notify_day_count <= 0 || $initNum == 0) {
- $remainAmount = bcadd($row['total_advance'] - $channelTotalMoney, '0',2); // 预付款剩余额度
- $output->writeln("## {$row['channel_name']}-渠道, 剩余预警额度: 渠道分成总流水=".$channelTotalMoney.", 预警剩余金额:{$row['warn_amount']}, 剩余额度=".$remainAmount);
- // $template = "\n## {$row['channel_name']}-渠道, 预付款余额不足,预警剩余金额:{$row['warn_amount']}, 剩余额度: {$remainAmount} 元;\n请多加关注,并通知相关渠道!\n时间:".date('Y-m-d H:i:s')."\n";
- // $this->send_message_dd($template);
- // $output->writeln(date('H:i:s')." {$row['channel_id']}_DD 钉钉发送完成");
- $template = "**{$row['channel_name']}-渠道**, 预付款余额不足,预警剩余金额:{$row['warn_amount']}, 剩余额度: **{$remainAmount} 元**;\n请多加关注,并通知相关渠道!\n时间:".date('Y-m-d H:i:s')."\n";
- $this->send_message_feishu($template);
- $output->writeln(date('H:i:s')." {$row['channel_id']}_feishu 飞书发送完成");
- $params = ['name' => $row['channel_name'], 'money' => $remainAmount];
- $mobileRes = $this->send_message_mobile($row['mobile'], $params);
- $output->writeln(date('H:i:s')." {$row['channel_id']}_mobild= {$row['mobile']} 短信发送完成:{$mobileRes}");
- $redis->incrby($dayKey, 1);
- $redis->expire($dayKey, $noticeExpire);
- }
- } catch (\Exception $e) {
- $output->writeln(date('H:i:s')." {$row["channel_id"]}_{$row["channel_name"]} 处理异常: ".$e->getMessage());
- continue;
- }
- }
- }
- $output->writeln(date('Y-m-d H:i:s')." end\r\n\n");
- }
- private function send_message_dd($template){
- $ddurl = Env::get('dingtalk.notice_qdyfk_url');
- curlDD($template, $ddurl,true);
- }
- private function send_message_feishu($template){
- $feishuUrl = Env::get('feishu.notice_url', '');
- // 使用 interactive 类型支持 Markdown 格式
- $result = sendFeishuMessage($feishuUrl, $template, 'interactive', '⚠️ 祈盟-渠道预付款警告');
- if (!$result['success']) {
- throw new \RuntimeException("飞书发送失败: " . $result['error']);
- }
- return true;
- }
- private function send_message_mobile($mobile, $params){
- // return $this->sendMobileNotifyBatch($mobile, $params);
- $mobiles = explode(',', $mobile);
- foreach($mobiles as $m){
- $this->sendMobileNotify($m, $params);
- }
- return "OK";
- }
- // ## 阿里云短信 ##
- // 批量发送
- private function sendMobileNotifyBatch($mobiles, $params){
- $accessKeyId = 'LTAI5tMnwbRaFFNegZABs1i9';
- $accessKeySecret = '1AFchDeuPoytXzcCD8HUU4gsMizwn3';
- /**
- * {
- * "PhoneNumberJson": "[\"13121776520\", \"13057807309\"]",
- * "SignNameJson": "[\"祈盟\", \"祈盟\"]",
- * "TemplateCode": "SMS_495865300",
- * "TemplateParamJson": "[{\"name\":\"xxx\", \"money\":\"666\"}, {\"name\":\"xxx\", \"money\":\"666\"}]",
- * "SourceIp": "183.158.7.39"
- * }
- */
- $mobilesList = explode(',', $mobiles);
- $phoneNumberJson = [];
- $signNameJson = [];
- $templateParamJson = [];
- foreach ($mobilesList as $v){
- $phoneNumberJson[] = $v;
- $signNameJson[] = "祈盟";
- $templateParamJson[] = $params;
- }
- $paramsData = [
- 'PhoneNumberJson' => json_encode($phoneNumberJson),
- 'SignNameJson' => json_encode($signNameJson),
- 'TemplateCode' => 'SMS_495865300',
- 'TemplateParamJson' => json_encode($templateParamJson, JSON_UNESCAPED_UNICODE),
- ];
- // var_dump(json_encode($paramsData));
- $paramsData = array_merge($paramsData, [
- "RegionId" => "cn-hangzhou",
- "Action" => "SendSms",
- "Version" => "2017-05-25",
- ]);
- $paramsData = array_merge([
- "SignatureMethod" => "HMAC-SHA1",
- "SignatureNonce" => uniqid(mt_rand(0,0xffff), true),
- "SignatureVersion" => "1.0",
- "AccessKeyId" => $accessKeyId,
- "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
- "Format" => "JSON",
- ], $paramsData);
- ksort($paramsData);
- $sortedQueryStringTmp = "";
- foreach ($paramsData as $key => $value) {
- $sortedQueryStringTmp .= "&" . $this->encode($key) . "=" . $this->encode($value);
- }
- $stringToSign = "POST&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1));
- $sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&",true));
- $signature = $this->encode($sign);
- $result = $this->fetchContent("http://dysmsapi.aliyuncs.com/", "POST", "Signature={$signature}{$sortedQueryStringTmp}");
- $result = json_decode($result, true);
- // dump($result);
- return $result['Code'];
- }
- // 单条发送
- private function sendMobileNotify($mobile, $params){
- $accessKeyId = 'LTAI5tMnwbRaFFNegZABs1i9';
- $accessKeySecret = '1AFchDeuPoytXzcCD8HUU4gsMizwn3';
- $paramsData = [
- 'PhoneNumbers' => $mobile,
- 'SignName' => '祈盟',
- 'TemplateCode' => 'SMS_495865300',
- 'TemplateParam' => json_encode($params, JSON_UNESCAPED_UNICODE),
- ];
- $paramsData = array_merge($paramsData, [
- "RegionId" => "cn-hangzhou",
- "Action" => "SendSms",
- "Version" => "2017-05-25",
- ]);
- $paramsData = array_merge([
- "SignatureMethod" => "HMAC-SHA1",
- "SignatureNonce" => uniqid(mt_rand(0,0xffff), true),
- "SignatureVersion" => "1.0",
- "AccessKeyId" => $accessKeyId,
- "Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
- "Format" => "JSON",
- ], $paramsData);
- ksort($paramsData);
- $sortedQueryStringTmp = "";
- foreach ($paramsData as $key => $value) {
- $sortedQueryStringTmp .= "&" . $this->encode($key) . "=" . $this->encode($value);
- }
- $stringToSign = "POST&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1));
- $sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&",true));
- $signature = $this->encode($sign);
- $this->fetchContent("http://dysmsapi.aliyuncs.com/", "POST", "Signature={$signature}{$sortedQueryStringTmp}");
- }
- private function encode($str)
- {
- $res = urlencode($str);
- $res = preg_replace("/\+/", "%20", $res);
- $res = preg_replace("/\*/", "%2A", $res);
- $res = preg_replace("/%7E/", "~", $res);
- return $res;
- }
- private function fetchContent($url, $method, $body) {
- $ch = curl_init();
- if($method == 'POST') {
- curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
- curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
- } else {
- $url .= '?'.$body;
- }
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_TIMEOUT, 5);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array(
- "x-sdk-client" => "php/2.0.0"
- ));
- if(substr($url, 0,5) == 'https') {
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
- }
- $rtn = curl_exec($ch);
- if($rtn === false) {
- // 大多由设置等原因引起,一般无法保障后续逻辑正常执行,
- // 所以这里触发的是E_USER_ERROR,会终止脚本执行,无法被try...catch捕获,需要用户排查环境、网络等故障
- throw new \RuntimeException("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch));
- }
- curl_close($ch);
- return $rtn;
- }
- }
|