一、依赖包
1. 关于实现思路流程:可参考以下大佬文章:
1.1 php依赖包网站上输入overtrue/wechat
1.2 进入文档官网
1.3 本文是基于php8.0.2版本的,laravel框架8.5版本,至于为何使用8.0.2版本,是由于在实现支付宝支付功能时,低版本php均会出现composer安装依赖问题,easywechat本文安装的是5.x版本。安装easywechat依赖:(在自己项目目录下运行即可)
composer require overtrue/wechat:~5.0 -vvv
1.4 创建配置文件
php artisan vendor:publish --provider="Overtrue\\LaravelWeChat\\ServiceProvider"
二、配置项目config
2.1 在config中创建easywechat.php(名字可自己定义):
<?php
return [
'easyWechat' => [
/**
* 账号基本信息,请从微信公众平台/开放平台获取
*/
'app_id' => '', // AppID
'secret' => '', // AppSecret
'token' => '', // Token
'aes_key' => '', // EncodingAESKey,兼容与安全模式下请一定要填写!!!
/**
* 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
* 使用自定义类名时,构造函数将会接收一个 `EasyWeChat\Kernel\Http\Response` 实例
*/
'response_type' => 'array',
],
/**
* 日志配置
*
* level: 日志级别, 可选为:
* debug/info/notice/warning/error/critical/alert/emergency
* path:日志文件位置(绝对路径!!!),要求可写权限
*/
'log' => [
'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod
'channels' => [
// 测试环境
'dev' => [
'driver' => 'single',
'path' => storage_path('logs/easywechat.log'),
'level' => 'debug',
],
// 生产环境
'prod' => [
'driver' => 'daily',
'path' => storage_path('logs/easywechat.log'),
'level' => 'info',
],
],
],
/**
* 接口请求相关配置,超时时间等,具体可用参数请参考:
* http://docs.guzzlephp.org/en/stable/request-config.html
*
* - retries: 重试次数,默认 1,指定当 http 请求失败时重试的次数。
* - retry_delay: 重试延迟间隔(单位:ms),默认 500
* - log_template: 指定 HTTP 日志模板,请参考:https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php
*/
'http' => [
'max_retries' => 1,
'retry_delay' => 500,
'timeout' => 5.0,
// 'base_uri' => 'https://api.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
],
/**
* OAuth 配置
*
* scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login
* callback:OAuth授权完成后的回调页地址
*/
'oauth' => [
'scopes' => ['snsapi_userinfo'],
'callback' => '/examples/oauth_callback.php',
],
];
2.2 关于微信公众号配置信息数据,刚开始可以用官方文档的接口测试号,直接申请即可,用于初步调试方便,后面调试通了,再用实际微信公众号的配置信息数据(需要申请),如何申请,直接百度即可。
接口测试号申请:
测试号管理:
注意点:在提交以上的【接口配置信息】时,填入的URL,需要返回echostr,才能提交配置信息成功,因为微信会向该接口发送请求,成功访问并且收到echostr返回,微信方才能确定该接口成功,3.1处有说明。微信官方文档里也写有:
三、微信公众号服务器验证
3.1 配置好config,以及测试号管理之后,按照所填入的url,创建该接口:
/**
* 微信公众号服务器验证
*/
public function notifyEasyWeChat(Request $request)
{
$data = $request->all();
$boolean = self::checkSignature($data);
Log::info('easywechat', $data);
Log::info('checkSignature', [$boolean]);
return $data['echostr'];
}
/**
* 验证
*/
function checkSignature($data)
{
$signature = $data["signature"];
$timestamp = $data["timestamp"];
$nonce = $data["nonce"];
$token = config('xxx.xxx.token');//config里自己定义的token,
$tmpArr = array($token, $timestamp, $nonce);
sort($tmpArr, SORT_STRING);
$tmpStr = implode($tmpArr);
$tmpStr = sha1($tmpStr);
if ($tmpStr == $signature) {
return true;
} else {
return false;
}
}
此处该接口必须定义为get请求,然后返回微信请求的echostr,方能验证接口成功,提交成功2.2处的接口配置信息。可以将其easywechat通过log日志打印出来,如下:
easywechat
{
"signature":"f56fcdd5053370b7a3803777856a08e87dade380",
"echostr":"8190732441967026149",
"timestamp":"1704330081",
"nonce":"784151826"
}
[2024-01-04 09:30:32] production.INFO: checkSignature [true]
四、微信公众号配置信息
4.1 开通后,然后生成的微信公众号配置信息如下:
4.2 上面的2、3、4、5、6、7都是必须的,测试阶段的话,上面的7,可以用明文与密文模式,方便调试,上面的3,必须把自己的服务器的ip填入,不然后续获取access_token,会报错。其他的直接按照要求生成,填入config即可,上面的6对应config的aes_key,上面的2对应的是secret。本人做的时候,都是后知后觉的,走了不少弯路。
五、生成微信二维码
5.1 书写生成微信登录二维码接口:(完成四之后)
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\BaseController;
use App\Http\Response\ApiResponse;
use EasyWeChat\Factory;
use Illuminate\Support\Facades\Cache;
class WeChatController extends BaseController
{
/**
* 生成微信登录二维码
*/
public function qrcodeCreate()
{
$config = config('easywechat.easyWechat');
$app = Factory::officialAccount($config);
$uuid = date('YmdHis') . rand(0000, 9999);
$scene_value = 'login-' . $uuid;//此处通过二维码带参数一起带过去,可自己定义
Cache::put($uuid, 1, 5 * 3600);//此处缓存,可去掉,本人自己的代码逻辑
$result = $app->qrcode->temporary($scene_value, 5 * 3600);
$url = $app->qrcode->url($result['ticket']);
$data = [
'qrcpde_url' => $url,
'uuid' => $uuid,
];
return ApiResponse::successData($data);
}
}
上述代码,使用的是easywechat第三方包依赖实现的,可以在easywechat文档中详见:
接口返回数据:
六、接收微信扫码点击关注公众号之后响应数据(解密)
6.1 定义路由,请求为post,url是第三点验证路由一样,差别在于一个get请求,一个post请求。
/**
* 微信公众号
*/
//微信公众号服务器验证
$api->get('wechat/easyNotify', [NotifyController::class, 'notifyEasyWechat']);
//微信公众号扫码登录回调
$api->post('wechat/easyNotify', [NotifyController::class, 'notifyEWXML']);
6.2 解密用得是openssl,由于微信官方文档提供过于复杂,以及在php7.2之后,一些函数已经是弃用了,也可以说,对于php8.0.2来说,官方提供的解密源代码已经过期了,以下是我参考:微信公众号之安全模式下消息加解密
本人非常感谢这位老哥的文档,解密中的OPENSSL_ZERO_PADDING解决了我一天半都解决不了的bug,在于解密未关注与关注的密文中,关注的密文解不了的问题。以下是本人的代码:
/**
* 微信公众号扫码登录回调
*/
public function notifyEWXML()
{
// 获取微信服务器推送的数据
$postData = file_get_contents("php://input");
//解密与解析xml
$data = self::xmlToArray($postData);
//业务操作
if ($data['Event'] === 'subscribe' || $data['Event'] === 'SCAN') {
$open_id = $data['FromUserName'];
$uuid = explode('-', $data['EventKey']);
$cache_uuid = 'login-' . $uuid[1];
Cache::put($cache_uuid, $open_id, 5 * 60);
$user = User::where('open_id', $open_id)->first();
if (empty($user)) {
$new_user = new User();
$new_user->open_id = $open_id;
$new_user->nickname = '微信用户';
$new_user->save();
}
//响应数据
event(new WeChatSubscribeEvent($open_id));//此处,是后续的功能,回复用户:登录成功
}
echo '';
}
/**
* 使用 simplexml_load_string 解析 XML 数据
* @param $postData --微信公众号响应的xml数据
*/
public static function xmlToArray($postData)
{
//将xml进行解析为数组格式
$orginArray = json_decode(json_encode(simplexml_load_string($postData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
Log::info('源数据:', $orginArray);
//获取微信公众号提供的消息加解密密钥
$encodingAESKey = config('easywechat.easyWechat.aes_key');
//由于微信公众号提供的43位,需要加一位,为4的倍数,微信方要求的
$encodingAESKey = base64_decode($encodingAESKey . '=');
//openssl_decrypt进行解密
$decryptedData = self::decryptWeChatResponse($orginArray['Encrypt'], $encodingAESKey);
//去掉额外的没用的字符串
$pattern = '/<xml>(.*?)<\/xml>/s';
$postArray = '';
if (preg_match($pattern, $decryptedData, $matches)) {
$xmlData = $matches[0];
//xml转数组
$postArray = json_decode(json_encode(simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
Log::info('解析xml后数据', [$postArray]);
return $postArray;
}
/**
* 微信公众号解密
* @param $encryptedData --加密的数据
* @param $encodingAESKey --微信公众号上的消息加解密密钥
*/
public static function decryptWeChatResponse($encryptedData, $encodingAESKey)
{
// 将 Base64 编码的加密数据解码
$decodedData = base64_decode($encryptedData);
// 获取 AES 密钥的前 32 个字符作为实际密钥
$encodingAESKey = substr($encodingAESKey, 0, 32);
// 使用 openssl_decrypt 进行解密
$decryptedData = openssl_decrypt(
$decodedData, // 待解密的数据
'aes-256-cbc', // 解密算法
$encodingAESKey, // 解密密钥
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, // 输出原始数据,不进行 base64 编码,此处很关键
substr($encodingAESKey, 0, 16) // 初始化向量(IV),长度为 16 字节
);
Log::info('解密后数据', [$decryptedData]);
// 返回解密后的数据
return $decryptedData;
}
6.3 解密关注响应的密文的关键:
6.4 解密后的格式如下:
EventKey中,正是携带的参数
6.5 如果在实现中碰到的问题,需要往composer.json添加的,直接添加即可,其他问题,需要自己解决了。本文,是本人实现后所记录的,其中很多细节无法复现了,也碰到了不少的问题。不过本人都能解决,各位大佬肯定也能。
七、响应用户消息
7.1 功能实现效果如下:
7.2 创建事件处理程序:
php artisan make:event WeChatSubscribeEvent
7.3 在生成的 WeChatSubscribeEvent
类中定义处理逻辑,例如:
namespace App\Events;
use Illuminate\Foundation\Events\Dispatchable;
class WeChatSubscribeEvent
{
use Dispatchable;
public $openid;
public function __construct($openid)
{
$this->openid = $openid;
}
}
7.4 注册事件监听器:
在 EventServiceProvider
中注册事件监听器:
// app/Providers/EventServiceProvider.php
protected $listen = [
'App\Events\WeChatSubscribeEvent' => [
'App\Listeners\WeChatSubscribeListener',
],
];
7.5 创建事件监听器:
创建一个事件监听器,用于处理关注事件:
php artisan make:listener WeChatSubscribeListener
在生成的 WeChatSubscribeListener
类中定义响应逻辑,例如:
// app/Listeners/WeChatSubscribeListener.php
namespace App\Listeners;
use App\Events\WeChatSubscribeEvent;
class WeChatSubscribeListener
{
public function handle(WeChatSubscribeEvent $event)
{
$openid = $event->openid;
// 在这里编写响应逻辑,例如发送“登录成功”的消息
// 使用 EasyWeChat 的 OfficialAccount 实例来发送消息
// 在这里编写响应逻辑,例如发送“登录成功”的消息
$openid = $event->openid;
$config = config('xxx.xxx');
$app = Factory::officialAccount($config);
$app->customer_service->message('登录成功!')->to($openid)->send();
}
}
7.6 触发事件:
// 在你的控制器或其他地方触发关注事件
event(new \App\Events\WeChatSubscribeEvent($openid));
放到微信响应的代码中即可。
八、总结
这是本人第一次写文章,比较生疏,后续还会有更新。本人刚从事php方向一年,之前用的框架是tp6,第一次接触laravel框架,又爱又恨,恨其依赖问题过于繁琐,麻烦,难以解决,不过,这也是没办法避免的。希望大家点点关注,给予小白一些写作动力,能关注一下php小白的平凡之旅!非常感谢,大家有问题可以私信,看到会积极响应的。