MasSms.php 13.4 KB
<?php
/**
+-----------------------------------------------------------------------------------------------------------------------
 * 短信工厂类:移动云 MAS 短信
 * 适配移动云 MAS 系统的 http 请求发送普通短信功能的类
 * 需要配置 http 请求的接口的相关账号, 数据可以维护到 config/config.INI 中
 * ------------------------------------------------------------------
 * 接口参数说明:
 * @param string $ecName
 * @param string $apId
 * @param string $mobiles 逗号分隔手机号码
 * @param string $content
 * @param string $sign 下载的签名中有
 * @param string $addSerial 扩展码, 根据向移动公司申请的通道填写, 如果申请的精确匹配通道, 则填写空字符串(""), 否则添加移动公司允许的扩展码
 * @param string $Mac API 输入参数签名结果, 签名算法: 将 ecName,apId,secretKey,mobiles,content ,sign,addSerial 按照顺序拼接, 然后通过 md5(32 位小写)计算后得出的值
 * 以上数据需要字符集 utf8
 * POST 请求路径: http://112.35.1.155:1992/sms/norsubmit
 * ------------------------------------------------------------------
 * 使用范例:
 * require_once "util/mas.10086.class.PHP";
 * $mas = new Mas10086();
 * $response = $mas->sendSms('138 XXXX XXXX', 'Hello World!');
 * var_dump($response);
 * ------------------------------------------------------------------
 * version 1.0
 * 可以通过 http 接口, 发送 sms 普通短信, 接口适配 mas 的 HTTP2.1 版本
+-----------------------------------------------------------------------------------------------------------------------
 *
 * PHP version 7
 *
 * @category  App\Sms
 * @package   App\Sms
 * @author    Richer <yangzi1028@163.com>
 * @date      2021年8月19日10:04:55
 * @copyright 2020-2022 Richer (http://www.Richer.com/)
 * @license   http://www.Richer.com/ License
 * @link      http://www.Richer.com/
 */
namespace App\Sms;

use App\Models\System\SmsLog;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Overtrue\EasySms\EasySms;

/**
 * Class MasSms
 *
 * @category  App\Sms
 * @package   App\Sms
 * @author    Richer <yangzi1028@163.com>
 * @date      2021年8月19日10:04:55
 * @copyright 2020-2022 Richer (http://www.Richer.com/)
 * @license   http://www.Richer.com/ License
 * @link      http://www.Richer.com/
 */
class MasSms
{
    protected $message = '短信发送成功!';

    /**
     * 项目常量配置,
     */
    private $apId;// 接口账号用户名
    private $sign;// 签名编码。在云MAS平台『管理』→『接口管理』→『短信接入用户管理』获取
    private $addSerial;// 扩展码。依据申请开户的服务代码匹配类型而定,如为精确匹配,此项填写空字符串("");如为模糊匹配,此项可填写空字符串或自定义的扩展码,注:服务代码加扩展码总长度不能超过20位
    private $secretKey;// 用户密码
    private $ecName; // 企业名称
    private $norSmsUrl;// 普通短信发送 url
    private $tmpSmsUrl;// 模板短信发送 url
    private $mac; // 参数校验序列,生成方法:将ecName、apId、secretKey、templateId、mobiles、params、sign、addSerial按序拼接(无间隔符),通过MD5(32位小写)计算出的值

    /**
     * 初始化操作: 读取 config, 设定配置参数
     * 如果没有配置, 则使用默认
     */
    public function __construct()
    {
        $this->apId      = config('mas.AP_ID');
        $this->sign      = config('mas.SIGN');
        $this->addSerial = config('mas.ADD_SERIAL');
        $this->secretKey = config('mas.SECRET_KEY');
        $this->ecName    = config('mas.EC_NAME');
        $this->norSmsUrl = config('mas.NOR_SMS_URL');
        $this->tmpSmsUrl = config('mas.TMP_SMS_URL');
    }

    /**
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * 根据 MAS 的接口规范, 需要装配一组 Mac 字符串做验证
     * @param string $mobiles 逗号分隔得电话号码字符串
     * @param string $content
     * @return string
     */
    private function makeMacString(string $mobiles, string $content): string
    {
        $macstr = $this->ecName . $this->apId . $this->secretKey . $mobiles . $content . $this->sign . $this->addSerial;
        return $this->mac = strtolower(md5($macstr));
    }

    /**
     * 参数校验序列,生成方法:将ecName、apId、secretKey、templateId、mobiles、params、sign、addSerial按序拼接(无间隔符),通过MD5(32位小写)计算出的值
     *
     * @param string $mobiles 逗号分隔得电话号码字符串
     * @param string $templateId
     * @param string $params
     * @return string
     */
    private function makeTmpMacString(string $mobiles, string $templateId, string $params): string
    {
        $macstr = $this->ecName . $this->apId . $this->secretKey . $templateId. $mobiles . $params . $this->sign . $this->addSerial;
        return $this->mac = strtolower(md5($macstr));
    }

    /**
     * 发送消息
     *
     * @param $smslog
     * @param string $module 模块
     * @param Model $model 对象模型
     * @param string $target 发送目标
     * @param string $operate 操作
     *
     * @return array
     * @throws GuzzleException
     */
    public function pushNotification($smslog, $mobile, $module, $model, $target = 'user', $operate = 'warning')
    {
        // 本地环境以外的环境才进行短信消息推送
        if (request('test') === 'test' || (config('app.env') !== 'local1' && config('app.open_SMS'))) {
            $data = Template::getTemplate($module, $model, $target, $operate);
            if (!$data) {
                return false;
            }

            //发送确认消息给用户
            $params     = Arr::get($data, 'params');
            $template   = Arr::get($data, 'template');
            $content    = Arr::get($data, 'content');
            $type       = Arr::get($data, 'type');

            $smslog->sent_body = $params;
            $smslog->sent_at   = now()->toDateTimeString();
            $smslog->save();

            if ($template) {
                // 发送短信
                $result = $this->sendTmpSms($mobile, $template, $params);
            } else {
                // 发送短信
                $result = $this->sendSms($mobile, $content);
            }

            // 发送状态
            $code = Arr::get($result, 'code');
            $data = Arr::get($result, 'data');
//            $return['mobile'] = $mobile;
            if ($code == 0) {
                $smslog->result         = SmsLog::SEND_SUCCESS;
                $smslog->received_body  = $data;
                $smslog->received_at    = now()->toDateTimeString();
                $smslog->out_id         = Arr::get($data, 'msgGroup');
                $smslog->save();
                return true;
                $return['code'] = 0;
            } else {
                $smslog->result         = SmsLog::SEND_FAIL;
                $smslog->received_body  = $data;
                $smslog->received_at    = now()->toDateTimeString();
                $smslog->out_id         = Arr::get($data, 'msgGroup');
                $smslog->save();
                return false;

                $return['code'] = 1;
                $return['message'] = $this->message;
            }

            return $return;
        }
    }


    /**
     * 发送普通短信函数
     *
     * @param string|array $mobiles 手机号
     * @param string $content 短信文本
     * @return false|object
     * @throws GuzzleException
     */
    public function sendSms($mobiles, $content = '')
    {
        if (is_array($mobiles)) {
            if (count($mobiles) === 0) {
                $this->message = '发送方手机号为空!';
                return false;
            }
            $mobiles = implode(',', $mobiles);
        }
//        $content .= "\n\r".'[系统短信, 请勿回复]';
        // 去掉前后的逗号
        $mobiles = ltrim(rtrim($mobiles, ','), ',');

        // 生成参数校验序列
        $this->makeMacString($mobiles, $content);
        // 组合请求参数
        $data = [
            'ecName'    => $this->ecName,
            'apId'      => $this->apId,
            'mobiles'   => $mobiles,
            'content'   => $content,
            'sign'      => $this->sign,
            'addSerial' => $this->addSerial,
            'mac'       => $this->mac
        ];

        return $this->httpPost($this->norSmsUrl, $data);
    }

    /**
     * 发送模板短信
     *
     * @param string|array $mobiles 手机号
     * @param string $templateId 短信模板
     * @param string[] $params 模板变量。格式:[“param1”,“param2”],无变量模板填[""]。
     * @return false|object
     */
    public function sendTmpSms($mobiles, $templateId = '', $params = '')
    {
        Log::channel('sms')->info("=============================== MAS 短信 begin ===============================");

        if (is_array($mobiles)) {
            if (count($mobiles) === 0) {
                $this->message = '发送方手机号为空!';
                return false;
            }
            $mobiles = implode(',', $mobiles);
        }
//        $content .= "\n\r".'[系统短信, 请勿回复]';
        // 去掉前后的逗号
        $mobiles = ltrim(rtrim($mobiles, ','), ',');

        // 生成参数校验序列
        $this->makeTmpMacString($mobiles, $templateId, $params);
        // 组合请求参数
        $data = [
            'ecName'    => $this->ecName,
            'apId'      => $this->apId,
            'templateId'=> $templateId,
            'mobiles'   => $mobiles,
            'params'    => $params,
            'sign'      => $this->sign,
            'addSerial' => $this->addSerial,
            'mac'       => $this->mac
        ];
        return $this->httpPost($this->tmpSmsUrl, $data);
    }

    /**
     * @param $url
     * @param $data
     * @return false|JsonResponse|mixed
     * @throws GuzzleException
     */
    public function httpPost($url, $data)
    {
        // 生成Client时候添加 verify 为false 参数即可,这样强制关闭ssl的验证
        $client = new Client(['verify' => false]);
        $dataContent = base64_encode(json_encode($data));
        try {
            Log::channel('sms')->info("请求内容:".json_encode($data));
            $response =  $client->request('POST', $url, [
                'headers' => [
                    'Content-Type'  => 'application/json',
                ],
                'json' => $dataContent
            ]);
            Log::channel('sms')->info("响应内容:".json_encode($response));

            $status = $response->getStatusCode(); // 200
            $reason = $response->getReasonPhrase(); // OK
            Log::channel('sms')->info("响应 status:".$status);
            Log::channel('sms')->info("响应 reason:".$reason);

            if ($status == 200) {
                $content =  json_decode($response->getBody()->getContents());
                Log::channel('sms')->info("响应 content:".json_encode($content));
                $rspcod = $content->rspcod;
                $return  = [
                    'data' => obj_2_array($content),
                ];
                if ($rspcod === 'success') {
                    $return['code'] = 0;
//                    return $content;
                } else {
                    $return['code'] = 1001;
                    $return['message'] = $reason;
                    $this->message = $reason;
//                    return false;
                }
                return $return;
            } else {
                $return['code'] = 1001;
                $return['message'] = $reason;
                $this->message = $reason;
                return $return;
                return false;
            }
        } catch (\Exception $e) {
//            dump($e);
            if ($e->getMessage()) {
                $message = $e->getMessage();
//                dump($e->getMessage());
//                dump($e->getCode());
//                dd($e);
                switch ($e->getCode()) {
                    case 500:
                    case 404:
                        break;
                }
                $this->message = $message;
            } else {
                if ($e->hasResponse()) {
                    $exception = (string)$e->getResponse()->getBody();
                    $exception = json_decode($exception);
                    $json = new JsonResponse($exception, $e->getCode());
                    $data = $json->getData();
                    $this->message = $data->error_msg;
                } else {
                    dd(new JsonResponse($e->getMessage(), 503));

                    return new JsonResponse($e->getMessage(), 503);
                }

                return false;
            }
//            if ($throwable instanceof ClientException) {
//                //doing something
//                dd(1);
//                return;
//            }
//            if ($throwable instanceof ServerException) {
//                //doing something
//                dd(2);
//
//                return ;
//            }
            //doing something
            Log::channel('sms')->info('请求错误信息 1:' . $this->message);
            return false;
        }
    }
}