没有不值得去解决的问题,也没有不值得去学习的技术!

Refactoring WeChat Message Encryption in Yii 2.0: Adjust Class Naming to PSR-4 and Use the Official Yii Autoloader

It is important to note that the file PKCS7Encoder.php contains 2 classes, which need to be split into two files: PKCS7Encoder.php and Prpcrypt.php. The base directory name is in lowercase letters separated by hyphens.

1. A previous colleague’s implementation downloaded the WeChat sample code, added it to the current application’s /vender/wx_biz_msg_crypt directory, and then used require in the init() method. See Figure 1.

A previous colleague's implementation downloaded the WeChat sample code, added it to the current application's /vender/wx_biz_msg_crypt directory, and then used require in the init() method
Figure 1
PHP
<?php
 
namespace frontend\controllers;
 
use Yii;
use frontend\models\http\wx_auth\AccessToken as HttpWxAuthAccessToken;
use WXBizMsgCrypt;
use yii\filters\AccessControl;
use yii\helpers\Json;
use yii\rest\Controller;
use yii\web\NotFoundHttpException;
use yii\web\ServerErrorHttpException;
 
class WxMsgEventController extends Controller
{
    public $redis;
 
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['logout', 'signup'],
                'rules' => [
                    [
                        'actions' => ['signup'],
                        'allow' => true,
                        'roles' => ['?'],
                    ],
                    [
                        'actions' => ['logout'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
 
    public function init()
    {
        parent::init();
        require __DIR__ . '/../vendor/wx_biz_msg_crypt/wxBizMsgCrypt.php';
        $this->redis = Yii::$app->redis;
 
    }
}
 

2. Searching GitHub for “WeChat encryption and decryption” yielded no official WeChat Git package, confirming that it cannot be installed via Composer.

3. Refer to Working with Third-Party Code, Using Downloaded Libraries: https://www.yiiframework.com/doc/guide/2.0/zh-cn/tutorial-yii-integration#using-downloaded-libs.

4. Reference: https://github.com/PizzaLiu/PHP-FIG/blob/master/PSR-4-autoloader-cn.md. Example: \Aura\Web\Response\Status. I decided to adjust the class library naming convention to comply with the PSR-4 standard, and then use Yii’s official autoloader to automatically load these classes. I renamed the file names to match their class names respectively, keeping the letter case consistent.

5. Edit the file: /frontend/vendor/src/wx-biz-msg-crypt/WXBizMsgCrypt.php, declare the namespace: wx\biz\msg\crypt, and delete all include_once statements. Apply the same process to the other files.

PHP
<?php
namespace wx\biz\msg\crypt;
 
/**
 * 对公众平台发送给公众账号的消息加解密示例代码.
 *
 * @copyright Copyright (c) 1998-2014 Tencent Inc.
 */
 
/**
 * 1.第三方回复加密消息给公众平台;
 * 2.第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
 */
class WXBizMsgCrypt
{
}

6. It is important to note that the file PKCS7Encoder.php contains 2 classes, which need to be split into two files: PKCS7Encoder.php and Prpcrypt.php. The base directory name is in lowercase letters separated by hyphens. See Figure 2.

It is important to note that the file PKCS7Encoder.php contains 2 classes, which need to be split into two files: PKCS7Encoder.php and Prpcrypt.php. The base directory name is in lowercase letters separated by hyphens.
Figure 2

7. Declare a root path alias for the root namespace by editing the application configuration file: /frontend/config/main.php. The namespace prefix is in lowercase letters separated by forward slashes.

PHP
return [
    'aliases' => [
        '@wx/biz/msg/crypt' => '@frontend/vendor/wx-biz-msg-crypt/src',
    ],
]

8. Edit the previous colleague’s implementation file again to adjust the references: use wx\biz\msg\crypt\WXBizMsgCrypt;, and then remove the require statement from the init() method.

PHP
<?php
 
 
namespace frontend\controllers;
 
use Yii;
use frontend\models\http\wx_auth\AccessToken as HttpWxAuthAccessToken;
use wx\biz\msg\crypt\WXBizMsgCrypt;
use yii\filters\AccessControl;
use yii\helpers\Json;
use yii\rest\Controller;
use yii\web\NotFoundHttpException;
use yii\web\ServerErrorHttpException;
 
class WxMsgEventController extends Controller
{
    public $redis;
 
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'only' => ['logout', 'signup'],
                'rules' => [
                    [
                        'actions' => ['signup'],
                        'allow' => true,
                        'roles' => ['?'],
                    ],
                    [
                        'actions' => ['logout'],
                        'allow' => true,
                        'roles' => ['@'],
                    ],
                ],
            ],
        ];
    }
 
    public function init()
    {
        parent::init();
        $this->redis = Yii::$app->redis;
 
    }
 
    /**
     * 获取验证票据(接收微信服务器推送)
     */
    public function actionTicket()
    {
        //try {
            $request = Yii::$app->request;
            $requestRawBody = $request->getRawBody();
            $requestQueryParams = $request->getQueryParams();
            file_put_contents(Yii::getAlias('@runtime') . '/componentVerifyTicketQueryParams.txt', print_r($requestQueryParams, true), LOCK_EX);
            file_put_contents(Yii::getAlias('@runtime') . '/componentVerifyTicketRawBody.txt', print_r($requestRawBody, true), LOCK_EX);
 
            $appId = Yii::$app->params['wxOpen']['component']['appId'];
            $appSecret = Yii::$app->params['wxOpen']['component']['appSecret'];
            $token = Yii::$app->params['wxOpen']['component']['token'];
            $encodingAesKey = Yii::$app->params['wxOpen']['component']['symmetricKey'];
 
            $msg = '';
            $pc = new WXBizMsgCrypt($token, $encodingAesKey, $appId);
            // 解密 xml 数据
            $errCode = $pc->decryptMsg($requestQueryParams['msg_signature'], $requestQueryParams['timestamp'], $requestQueryParams['nonce'], $requestRawBody, $msg);
            file_put_contents(Yii::getAlias('@runtime') . '/componentVerifyTicketDecrypt.txt', print_r($errCode, true), LOCK_EX);
            // 解密成功
            if ($errCode == 0) {
                $ticketData = $this->xmlToArray($msg);
                if (isset($ticketData['ComponentVerifyTicket'])) {
                    $redisCommandkeyPrefix = Yii::$app->params['redisCommand']['keyPrefix'];
                    $componentVerifyTicketKey = $redisCommandkeyPrefix . $ticketData['AppId'] . ':component_verify_ticket';
                    $this->redis->set($componentVerifyTicketKey, $ticketData['ComponentVerifyTicket']);
                    // 获取第三方平台component_access_token
                    $this->getComponentAccessToken($appId, $appSecret);
                }
            }
            file_put_contents(Yii::getAlias('@runtime') . '/componentVerifyTicketMsgXml.txt', print_r($msg, true), LOCK_EX);
            /*
        } catch (\Throwable $th) {
            file_put_contents(Yii::getAlias('@runtime') . '/componentVerifyTicketError.txt', print_r($th->getMessage(), true), LOCK_EX);
        }
            */
        exit('success');
    }
 

9. Change the encoding of all files to UTF-8. Rerun the program and check the decrypted data file: componentVerifyTicketMsgXml.txt. The result meets expectations. See Figure 3.

Change the encoding of all files to UTF-8. Rerun the program and check the decrypted data file: componentVerifyTicketMsgXml.txt. The result meets expectations
Figure 3
XML
<xml><AppId><![CDATA[wxd98c58b273d21bdf]]></AppId>
<CreateTime>1625124291</CreateTime>
<InfoType><![CDATA[component_verify_ticket]]></InfoType>
<ComponentVerifyTicket><![CDATA[ticket@@@PaPM2hcjK8DPPkW_ZzbaR4DCQtirExq932-BBnxhQg19QWJtIKm3Io0TXsWMqcdv-18WnjkWSiSLNPSoXmqGSA]]></ComponentVerifyTicket>
</xml>

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系