您现在的位置是:首页 > 技术教程 正文

php-基于laravel框架实现微信公众号扫码关注登录

admin 阅读: 2024-03-16
后台-插件-广告管理-内容页头部广告(手机)

一、依赖包 

1. 关于实现思路流程:可参考以下大佬文章:

laravel结合esaywechat组件实现微信授权登录

Laravel框架 微信授权登陆 代码封装

Laravel——微信授权登陆

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(名字可自己定义):

  1. <?php
  2. return [
  3. 'easyWechat' => [
  4. /**
  5. * 账号基本信息,请从微信公众平台/开放平台获取
  6. */
  7. 'app_id' => '', // AppID
  8. 'secret' => '', // AppSecret
  9. 'token' => '', // Token
  10. 'aes_key' => '', // EncodingAESKey,兼容与安全模式下请一定要填写!!!
  11. /**
  12. * 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
  13. * 使用自定义类名时,构造函数将会接收一个 `EasyWeChat\Kernel\Http\Response` 实例
  14. */
  15. 'response_type' => 'array',
  16. ],
  17. /**
  18. * 日志配置
  19. *
  20. * level: 日志级别, 可选为:
  21. * debug/info/notice/warning/error/critical/alert/emergency
  22. * path:日志文件位置(绝对路径!!!),要求可写权限
  23. */
  24. 'log' => [
  25. 'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod
  26. 'channels' => [
  27. // 测试环境
  28. 'dev' => [
  29. 'driver' => 'single',
  30. 'path' => storage_path('logs/easywechat.log'),
  31. 'level' => 'debug',
  32. ],
  33. // 生产环境
  34. 'prod' => [
  35. 'driver' => 'daily',
  36. 'path' => storage_path('logs/easywechat.log'),
  37. 'level' => 'info',
  38. ],
  39. ],
  40. ],
  41. /**
  42. * 接口请求相关配置,超时时间等,具体可用参数请参考:
  43. * http://docs.guzzlephp.org/en/stable/request-config.html
  44. *
  45. * - retries: 重试次数,默认 1,指定当 http 请求失败时重试的次数。
  46. * - retry_delay: 重试延迟间隔(单位:ms),默认 500
  47. * - log_template: 指定 HTTP 日志模板,请参考:https://github.com/guzzle/guzzle/blob/master/src/MessageFormatter.php
  48. */
  49. 'http' => [
  50. 'max_retries' => 1,
  51. 'retry_delay' => 500,
  52. 'timeout' => 5.0,
  53. // 'base_uri' => 'https://api.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
  54. ],
  55. /**
  56. * OAuth 配置
  57. *
  58. * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login
  59. * callback:OAuth授权完成后的回调页地址
  60. */
  61. 'oauth' => [
  62. 'scopes' => ['snsapi_userinfo'],
  63. 'callback' => '/examples/oauth_callback.php',
  64. ],
  65. ];

2.2 关于微信公众号配置信息数据,刚开始可以用官方文档的接口测试号,直接申请即可,用于初步调试方便,后面调试通了,再用实际微信公众号的配置信息数据(需要申请),如何申请,直接百度即可。
接口测试号申请:

微信官方文档-公众号

测试号管理:


注意点:在提交以上的【接口配置信息】时,填入的URL,需要返回echostr,才能提交配置信息成功,因为微信会向该接口发送请求,成功访问并且收到echostr返回,微信方才能确定该接口成功,3.1处有说明。微信官方文档里也写有:

三、微信公众号服务器验证

3.1 配置好config,以及测试号管理之后,按照所填入的url,创建该接口:
 

  1. /**
  2. * 微信公众号服务器验证
  3. */
  4. public function notifyEasyWeChat(Request $request)
  5. {
  6. $data = $request->all();
  7. $boolean = self::checkSignature($data);
  8. Log::info('easywechat', $data);
  9. Log::info('checkSignature', [$boolean]);
  10. return $data['echostr'];
  11. }
  12. /**
  13. * 验证
  14. */
  15. function checkSignature($data)
  16. {
  17. $signature = $data["signature"];
  18. $timestamp = $data["timestamp"];
  19. $nonce = $data["nonce"];
  20. $token = config('xxx.xxx.token');//config里自己定义的token,
  21. $tmpArr = array($token, $timestamp, $nonce);
  22. sort($tmpArr, SORT_STRING);
  23. $tmpStr = implode($tmpArr);
  24. $tmpStr = sha1($tmpStr);
  25. if ($tmpStr == $signature) {
  26. return true;
  27. } else {
  28. return false;
  29. }
  30. }

此处该接口必须定义为get请求,然后返回微信请求的echostr,方能验证接口成功,提交成功2.2处的接口配置信息。可以将其easywechat通过log日志打印出来,如下:

  1. easywechat
  2. {
  3. "signature":"f56fcdd5053370b7a3803777856a08e87dade380",
  4. "echostr":"8190732441967026149",
  5. "timestamp":"1704330081",
  6. "nonce":"784151826"
  7. }
  8. [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 书写生成微信登录二维码接口:(完成四之后)

  1. <?php
  2. namespace App\Http\Controllers\Auth;
  3. use App\Http\Controllers\BaseController;
  4. use App\Http\Response\ApiResponse;
  5. use EasyWeChat\Factory;
  6. use Illuminate\Support\Facades\Cache;
  7. class WeChatController extends BaseController
  8. {
  9. /**
  10. * 生成微信登录二维码
  11. */
  12. public function qrcodeCreate()
  13. {
  14. $config = config('easywechat.easyWechat');
  15. $app = Factory::officialAccount($config);
  16. $uuid = date('YmdHis') . rand(0000, 9999);
  17. $scene_value = 'login-' . $uuid;//此处通过二维码带参数一起带过去,可自己定义
  18. Cache::put($uuid, 1, 5 * 3600);//此处缓存,可去掉,本人自己的代码逻辑
  19. $result = $app->qrcode->temporary($scene_value, 5 * 3600);
  20. $url = $app->qrcode->url($result['ticket']);
  21. $data = [
  22. 'qrcpde_url' => $url,
  23. 'uuid' => $uuid,
  24. ];
  25. return ApiResponse::successData($data);
  26. }
  27. }

上述代码,使用的是easywechat第三方包依赖实现的,可以在easywechat文档中详见:

接口返回数据:

六、接收微信扫码点击关注公众号之后响应数据(解密)

6.1 定义路由,请求为post,url是第三点验证路由一样,差别在于一个get请求,一个post请求。

  1. /**
  2. * 微信公众号
  3. */
  4. //微信公众号服务器验证
  5. $api->get('wechat/easyNotify', [NotifyController::class, 'notifyEasyWechat']);
  6. //微信公众号扫码登录回调
  7. $api->post('wechat/easyNotify', [NotifyController::class, 'notifyEWXML']);

6.2 解密用得是openssl,由于微信官方文档提供过于复杂,以及在php7.2之后,一些函数已经是弃用了,也可以说,对于php8.0.2来说,官方提供的解密源代码已经过期了,以下是我参考:微信公众号之安全模式下消息加解密
本人非常感谢这位老哥的文档,解密中的OPENSSL_ZERO_PADDING解决了我一天半都解决不了的bug,在于解密未关注与关注的密文中,关注的密文解不了的问题。以下是本人的代码:
 

  1. /**
  2. * 微信公众号扫码登录回调
  3. */
  4. public function notifyEWXML()
  5. {
  6. // 获取微信服务器推送的数据
  7. $postData = file_get_contents("php://input");
  8. //解密与解析xml
  9. $data = self::xmlToArray($postData);
  10. //业务操作
  11. if ($data['Event'] === 'subscribe' || $data['Event'] === 'SCAN') {
  12. $open_id = $data['FromUserName'];
  13. $uuid = explode('-', $data['EventKey']);
  14. $cache_uuid = 'login-' . $uuid[1];
  15. Cache::put($cache_uuid, $open_id, 5 * 60);
  16. $user = User::where('open_id', $open_id)->first();
  17. if (empty($user)) {
  18. $new_user = new User();
  19. $new_user->open_id = $open_id;
  20. $new_user->nickname = '微信用户';
  21. $new_user->save();
  22. }
  23. //响应数据
  24. event(new WeChatSubscribeEvent($open_id));//此处,是后续的功能,回复用户:登录成功
  25. }
  26. echo '';
  27. }
  28. /**
  29. * 使用 simplexml_load_string 解析 XML 数据
  30. * @param $postData --微信公众号响应的xml数据
  31. */
  32. public static function xmlToArray($postData)
  33. {
  34. //将xml进行解析为数组格式
  35. $orginArray = json_decode(json_encode(simplexml_load_string($postData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
  36. Log::info('源数据:', $orginArray);
  37. //获取微信公众号提供的消息加解密密钥
  38. $encodingAESKey = config('easywechat.easyWechat.aes_key');
  39. //由于微信公众号提供的43位,需要加一位,为4的倍数,微信方要求的
  40. $encodingAESKey = base64_decode($encodingAESKey . '=');
  41. //openssl_decrypt进行解密
  42. $decryptedData = self::decryptWeChatResponse($orginArray['Encrypt'], $encodingAESKey);
  43. //去掉额外的没用的字符串
  44. $pattern = '/(.*?)<\/xml>/s';
  45. $postArray = '';
  46. if (preg_match($pattern, $decryptedData, $matches)) {
  47. $xmlData = $matches[0];
  48. //xml转数组
  49. $postArray = json_decode(json_encode(simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
  50. }
  51. Log::info('解析xml后数据', [$postArray]);
  52. return $postArray;
  53. }
  54. /**
  55. * 微信公众号解密
  56. * @param $encryptedData --加密的数据
  57. * @param $encodingAESKey --微信公众号上的消息加解密密钥
  58. */
  59. public static function decryptWeChatResponse($encryptedData, $encodingAESKey)
  60. {
  61. // 将 Base64 编码的加密数据解码
  62. $decodedData = base64_decode($encryptedData);
  63. // 获取 AES 密钥的前 32 个字符作为实际密钥
  64. $encodingAESKey = substr($encodingAESKey, 0, 32);
  65. // 使用 openssl_decrypt 进行解密
  66. $decryptedData = openssl_decrypt(
  67. $decodedData, // 待解密的数据
  68. 'aes-256-cbc', // 解密算法
  69. $encodingAESKey, // 解密密钥
  70. OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, // 输出原始数据,不进行 base64 编码,此处很关键
  71. substr($encodingAESKey, 0, 16) // 初始化向量(IV),长度为 16 字节
  72. );
  73. Log::info('解密后数据', [$decryptedData]);
  74. // 返回解密后的数据
  75. return $decryptedData;
  76. }

6.3 解密关注响应的密文的关键:

6.4 解密后的格式如下:

EventKey中,正是携带的参数

6.5 如果在实现中碰到的问题,需要往composer.json添加的,直接添加即可,其他问题,需要自己解决了。本文,是本人实现后所记录的,其中很多细节无法复现了,也碰到了不少的问题。不过本人都能解决,各位大佬肯定也能。

七、响应用户消息

7.1 功能实现效果如下:

7.2 创建事件处理程序:

php artisan make:event WeChatSubscribeEvent

7.3 在生成的 WeChatSubscribeEvent 类中定义处理逻辑,例如:

  1. namespace App\Events;
  2. use Illuminate\Foundation\Events\Dispatchable;
  3. class WeChatSubscribeEvent
  4. {
  5. use Dispatchable;
  6. public $openid;
  7. public function __construct($openid)
  8. {
  9. $this->openid = $openid;
  10. }
  11. }

7.4 注册事件监听器:

在 EventServiceProvider 中注册事件监听器:

  1. // app/Providers/EventServiceProvider.php
  2. protected $listen = [
  3. 'App\Events\WeChatSubscribeEvent' => [
  4. 'App\Listeners\WeChatSubscribeListener',
  5. ],
  6. ];

7.5 创建事件监听器:

创建一个事件监听器,用于处理关注事件:

php artisan make:listener WeChatSubscribeListener

在生成的 WeChatSubscribeListener 类中定义响应逻辑,例如:

  1. // app/Listeners/WeChatSubscribeListener.php
  2. namespace App\Listeners;
  3. use App\Events\WeChatSubscribeEvent;
  4. class WeChatSubscribeListener
  5. {
  6. public function handle(WeChatSubscribeEvent $event)
  7. {
  8. $openid = $event->openid;
  9. // 在这里编写响应逻辑,例如发送“登录成功”的消息
  10. // 使用 EasyWeChat 的 OfficialAccount 实例来发送消息
  11. // 在这里编写响应逻辑,例如发送“登录成功”的消息
  12. $openid = $event->openid;
  13. $config = config('xxx.xxx');
  14. $app = Factory::officialAccount($config);
  15. $app->customer_service->message('登录成功!')->to($openid)->send();
  16. }
  17. }

7.6 触发事件:

  1. // 在你的控制器或其他地方触发关注事件
  2. event(new \App\Events\WeChatSubscribeEvent($openid));

放到微信响应的代码中即可。

八、总结

        这是本人第一次写文章,比较生疏,后续还会有更新。本人刚从事php方向一年,之前用的框架是tp6,第一次接触laravel框架,又爱又恨,恨其依赖问题过于繁琐,麻烦,难以解决,不过,这也是没办法避免的。希望大家点点关注,给予小白一些写作动力,能关注一下php小白的平凡之旅!非常感谢,大家有问题可以私信,看到会积极响应的。

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索