WechatController.php 15.2 KB
<?php

namespace app\wx\controllers;

use Yii;
use yii\base\Exception;
use yii\helpers\Url;
use domain\user\UserRepository;
use common\helpers\AppErrorLog;
use common\helpers\Log as AppLog;
use common\helpers\WechatMessageHelper;
use domain\user\models\User as UserModel;
use common\exts\wechat\PHPSDK as WxPHPSDK;
use common\helpers\WxHelper;
use common\helpers\ImageManager;
use domain\system\message\SmsMessage;

/**
 * Class WechatController
 * @package app\wx\controllers
 * @author CM
 * @date 2019/12/11
 */
class WechatController extends BaseController
{
    /**
     * CSR验证,关闭后可以在不提交CSR验证码的情况下通过POST方式提交数据
     * @var bool
     */
    public $enableCsrfValidation = false;

    /**
     * 开发者用来接收微信消息和事件的入口函数
     * 对应公众号管理后台: 开发-基本设置页面填写的服务器地址
     */
    public function actionNotify()
    {
        $wechat = WxHelper::getWxPHPSDK();
        $wechat->valid();
        // 事件处理
        $this->handleEvent();
    }

    /**
     * 开发者用来接收微信消息和事件的入口函数
     * 对应公众号管理后台: 开发-基本设置页面填写的服务器地址
     */
    public function handleEvent()
    {
        $wechat = WxHelper::getWxPHPSDK();
        $revType = $wechat->getRev()->getRevType();
        $revFromOpenId = $wechat->getRev()->getRevFrom();
        switch($revType) {
            case WxPHPSDK::MSGTYPE_TEXT:  // 文本
                $keyword = trim($wechat->getRevContent());

                if (YII_ENV_TEST) {
                    // 关注公众号
                    if ($keyword == "welcome") {
                        $title = isset($this->wx->subscribe_title) ? $this->wx->subscribe_title : '欢迎关注极办公';
                        $description = isset($this->wx->subscribe_desc) ? $this->wx->subscribe_desc : $title;
                        $picUrl = $this->wx->subscribe_img ? ImageManager::getUrl($this->wx->subscribe_img) : false;
                        $message = array(array(
                            'title' => $title,
                            'description' => $description,
                            'picurl' => $picUrl,
                            'url' => WxHelper::getDomain(true)
                        ));
                        WechatMessageHelper::sendCustomerNewsMessage($revFromOpenId, $message, $wechat);
                        return "";
                    }

                    // 开启短信消息测试
                    SmsMessage::test($keyword);
                }
                break;

            case WxPHPSDK::MSGTYPE_EVENT:  // 事件
                $receiveEvent = $wechat->getRev()->getRevEvent();//获取接收事件推送
                $event = $receiveEvent["event"];
		        $revSceneId = $wechat->getRev()->getRevSceneId();
                AppLog::DEBUG("微信事件: event=" . $event);
                switch ($event){
                    case "subscribe": // 关注公众号
                    {
                        AppLog::DEBUG("微信关注事件: Openid=[" . $revFromOpenId . "]");

                        $title = isset($this->wx->subscribe_title) ? $this->wx->subscribe_title : '欢迎来到极办公';
                        $description = isset($this->wx->subscribe_desc) ? $this->wx->subscribe_desc : $title;
                        $picUrl = $this->wx->subscribe_img ? ImageManager::getUrl($this->wx->subscribe_img) : false;
                        $linkUrl = isset($this->wx->subscribe_url) ? $this->wx->subscribe_url : WxHelper::getDomain(true) . "/user";
                        $message = array(array(
                            'title'         => $title,
                            'description'   => $description,
                            'picurl'        => $picUrl,
                            'url'           => $linkUrl
                        ));

                        // 推送关注图文消息
                        WechatMessageHelper::sendCustomerNewsMessage($revFromOpenId, $message, $wechat);

                        // 关注后处理
                        $this->autoSaveFromWechat($wechat, $revFromOpenId,true);
                        break;
                    }
                    case "unsubscribe": // 取消关注
                    {
                        // 更新数据表中的关注状态
                        $userModel = UserRepository::findOneByOpenId($revFromOpenId);
                        if ($userModel) {
                            $userModel->subscribe = false;
                            // is_enable_withdraw 是否要关闭了?
                            if( false == $userModel->save()){
                                throw new Exception(implode("\r\n", $userModel->getFirstErrors()));
                            }
                            AppLog::DEBUG("取消关注: openid=" . $revFromOpenId);
                        }
                        break;
                    }
                    case "CLICK": // 点击微信菜单
                        $menuUrl = $receiveEvent["key"];
                        AppLog::DEBUG("微信点击菜单: menuUrl=[" . $menuUrl . "]");
                        break;
                    case "SCAN": // 扫描二维码
                        break;
                    case "LOCATION": // 获取位置
                        $location = $wechat->getRev()->getRevEventGeo();
                        // 这里最好判断一下是不是认证过的工程师才能更新地理位置
                        AppLog::DEBUG("获取工程师地理位置:" . $revFromOpenId.' X->'.$location['x'].' Y->'.$location['y']);
                        break;
                    case 'KEY_INDEX' :
                        break;
                    case 'KEY_GROWTH' :
                        break;
                    default:
                        break;
                }
                break;

            case WxPHPSDK::MSGTYPE_IMAGE:  // 图像
                break;
            case WxPHPSDK::MSGTYPE_LOCATION:
                //获取接收地理位置
                $geoArr=$wechat->getRev()->getRevGeo();
                AppLog::DEBUG("获取接收地理位置:" . $revFromOpenId.' geoArr->'.json_encode($geoArr));
                break;
            case "TEMPLATESENDJOBFINISH": // 模板消息发送结束
                break;

            default:
                break;
        }
    }

    /**
     * 桥接两个用户
     * @return \yii\web\Response
     */

    public function actionBridging()
    {
        $sn = $this->request->get('sn');
        $tourl = $this->request->get('tourl');
        $tourl = urldecode($tourl);

        //如果当前用户已经是注册用户,那么跳过本步骤。
        //利用session存储跳转信息
        $appUser = Yii::$app->getUser();
        if($appUser->isGuest){
            //AppLog::DEBUG("绑定用户");
            $session = Yii::$app->session;
            $session->set('oauth_return_url', urlencode($tourl));
            $session->set('sn', urlencode($sn));
            $redirectUri = Yii::$app->request->getHostInfo() . Url::to(['/wechat/login-proccess', 'dest' => $tourl]);
            $wechat = WxHelper::getWxPHPSDK();
            $OAuthUrl = $wechat->getOAuthRedirect($redirectUri, 'jiwork', 'snsapi_base');

            return $this->redirect($OAuthUrl);
        } else {
            AppLog::DEBUG(" actionBridging 直接跳转");
            return $this->redirect($tourl);
        }
    }

    /**注销登录
     * @return \yii\web\Response
     */
    public function actionLogout()
    {
        if(Yii::$app->getUser()->logout()){
            return $this->goHome();
        }
    }

    /**
     * 检测到微信用户未登录, 马上执行登录处理
     */
    public function actionLoginProccess()
    {
        AppLog::DEBUG("actionLoginProccess: 执行登录处理");

        /**
         * 如果用户点击了确认授权,
         * 那么返回的链接GET中就会有 code
         */
        if($code = Yii::$app->request->get('code')){

            $wechat = WxHelper::getWxPHPSDK();

            /*
             * $tokenResult的结果:
             * {
             *    "access_token":"OezXcEiiBSKSxW0eoylIeAQ5V9CwnswVLKYv6D2nT4StW1c8B7QwjUNokVDLAC2_yxF5nAkAUCf1ciogRmyJu2ERvGvsbLtMO0tbfkkHZ6u5QYXZNArn3k7n_7TIUrnuGWHY-zXyz0W_Kce3ls6Dzw",
             *    "expires_in":7200,
             *    "refresh_token":"OezXcEiiBSKSxW0eoylIeAQ5V9CwnswVLKYv6D2nT4StW1c8B7QwjUNokVDLAC2_-ao_OHlsaK1DBXCCl2OXQgAFvraBXWjxAikuV3jpvkX528za96t2XJ4r3Igs37tYO5JMXCiOQ2jSEVDT1J4wpg",
             *    "openid":"oicCewi22xTYCclqDOVEP-g-uawY",
             *    "scope":"snsapi_base"
            *  }
            */
            $redis = Yii::$app->redis;
            $storeKey = 'ACCESS_TOKEN_' . $code;
            if ($redis->exists($storeKey)) { // 已成功获取token结果
                $tokenResult = $redis->get($storeKey);
                $tokenResult = json_decode($tokenResult, true);
            } else {
                $tokenResult = $wechat->getOAuthAccessToken($code);//用户授权的 access token
                if($wechat->errCode){

                    AppLog::ERROR("{$wechat->errCode} {$wechat->errMsg} ".json_encode($tokenResult));
                    return;
                }

                if(false == $tokenResult){
                    AppLog::ERROR("get token result fail.");
                    return;
                }

                // 保存token结果
                $redis->set($storeKey, json_encode($tokenResult));
                $redis->expireat($storeKey, time() + 30); // 到期自动删除
            }

            $this->processWechatLogin($wechat, $tokenResult['openid']);
        }
    }

    /**
     * 处理微信用户逻辑
     * @param WxPHPSDK $wechat
     * @param array $tokenResult
     * @return bool|void
     */
    protected function processWechatLogin(WxPHPSDK $wechat, $openId)
    {
        $userEntity = $this->autoSaveFromWechat($wechat, $openId);
        if(!($userEntity instanceof UserModel)) {
            AppLog::debug("processWeChatLogin: 返回 UserEntity 失败");
        }
        /**
         * 登陆时间为7000秒,目前微信API的access token 的 expires_in 为 7200秒
         */
        if(Yii::$app->getUser()->login($userEntity, 7000)) {
            $session = Yii::$app->session;
            $returnUrl = urldecode($session->get('oauth_return_url'));
            $session->remove('oauth_return_url');
            if ($returnUrl) {
                return $this->redirect($returnUrl);
            }
            $jumpDestUrl = $this->request->get('dest');
            if ($jumpDestUrl) {
                // 判断URL访问路径
                $arr = parse_url($jumpDestUrl);
                AppLog::DEBUG("jumpDestUrl:".$jumpDestUrl);
                if (isset($arr['query']) && !empty($arr['query'])) {
                    $arr_query = self::convertUrlQuery($arr['query']);
                    if (isset($arr_query["jumppath"]) && isset($arr_query["jumpmodel"]) && !empty($arr_query["jumppath"]) && !empty($arr_query["jumpmodel"])) {
                        AppLog::DEBUG("processWechatLogin:arr_query(path=".$arr_query["jumppath"].",model=".$arr_query["jumpmodel"].")");
                        $locationUrl = Yii::$app->params["baseUrl"]."/".$arr_query["jumpmodel"]."#".$arr_query["jumppath"];
                        header("Location: $locationUrl");exit;
                    }
                }
                AppLog::DEBUG("processWechatLogin登录成功: 目标跳转=" . $jumpDestUrl);
                return $this->redirect($jumpDestUrl);
            }
            return $this->goHome();
        }
    }

    /**
     * 将字符串参数变为数组
     * @param $query
     * @return array array (size=3)
     * 'm' => string 'content' (length=7)
     * 'c' => string 'index' (length=5)
     * 'a' => string 'lists' (length=5)
     */
    function convertUrlQuery($query)
    {
        $queryParts = explode('&', $query);
        $params = array();
        foreach ($queryParts as $param) {
            $item = explode('=', $param);
            $params[$item[0]] = $item[1];
        }
        return $params;
    }

    private function saveProfile($userModel, $userinfoResult)
    {
        if(isset($userinfoResult['nickname'])){
            $userModel->nickname = $userinfoResult['nickname'];
            $userModel->gender = $userinfoResult['sex'];
            $userModel->province = $userinfoResult['province'];
            $userModel->city = $userinfoResult['city'];
            $userModel->country = $userinfoResult['country'];
            $userModel->headimgurl = $userinfoResult['headimgurl'];

            AppLog::DEBUG('User saveProfile User id : '.$userModel->id);

            if(false == $userModel->save()){
                throw new Exception(implode("\r\n", $userModel->getFirstErrors()));
            }
        }
    }

    /**
     * 公众号处理逻辑
     * @param WxPHPSDK $wechat
     * @param $openId
     */
    private function autoSaveFromWechat(WxPHPSDK $wechat, $openId,$isSubscribe = false)
    {
        $userinfoResult = $wechat->getUserInfo($openId);
        AppLog::DEBUG("autoSaveFromWechat: 用户[{$openId}]读取微信资料[" . json_encode($userinfoResult) . ']');

        if($wechat->errCode){
            AppLog::ERROR("{$wechat->errCode} {$wechat->errMsg} ".json_encode($userinfoResult));
            return false;
        }

        /**
         * 通过 openid 来查找用户,
         * 一个用户在一个公众号的 open id 是唯一的,无论是否关注过。
         * 如果存在,那么从数据库中拿出来。
         * 如果不存在,新建一个实例。
         */
        $userModel = UserRepository::findOneByOpenId($openId);

        /**
         * 保存 union id
         */
        $unionid = 0;
        if( isset( $userinfoResult['unionid'] ) ){
            $unionid = $userinfoResult['unionid'];
        }

        if( false == $userModel ){
            $userModel = new UserModel;
            // 保存openId
            $userModel->openid = $openId;
        }

        if( !empty($unionid) ) {
            $userModel->unionid = $unionid ;
        }

        // 如果已关注
        $userModel->subscribe = (bool)$userinfoResult['subscribe'];
        $userModel->last_login_at = time();
        $transaction = Yii::$app->db->beginTransaction();
        try{

            /**
             * 如果当前用户是第一次访问,新记录。
             */
            if( true == $userModel->isNewRecord ) {

                if(!$userModel->save()){
                    throw new Exception(implode("\r\n", $userModel->getFirstErrors()));
                }

                $newEngineerId = $userModel->attributes['id'];
            } else {
                if ( false == $userModel->save() ) {
                    throw new Exception(implode("\r\n", $userModel->getFirstErrors()));
                }
            }
            // 更新profile信息
            $this->saveProfile($userModel, $userinfoResult);
            $transaction->commit();
            return $userModel;

        } catch (Exception $exception) {
            AppErrorLog::error('autoSaveFromWechat exception openid '. $openId .' message:'.$exception->getMessage());
            $transaction->rollBack();
            return null;
        }
    }
}