Laravel + EasyWechat开发公众号,常用功能记录,入口代码优化,希望小伙伴提出更好的解决方案和思路。

使用Laravel + EasyWechat开发公众号、小程序效率不要太高,感谢大牛。使用前建议多看公众号官方文档

一、微信公众号常用功能

1.接收普通消息

  • 用户给公众号发各种类型的消息,根据实际业务做出消息回复。
  • 常用的有文本、图片类型,回复主要是文本、图文(<=8篇);

2. 接收事件推送

  • 关注事件
    记录用户来源、存储用户、回复消息(例如,欢迎关注XXXX);
  • 取关事件
    更改用户关注状态;
  • 扫描带参数二维码事件
    常用于一些运营的活动,做数据记录;
  • CLICK事件的菜单
    用户回复文字、图文等消息;
  • 点击菜单跳转链接时的事件推送
    可用于菜单点击量统计;

3. 自定义菜单

  • 通过接口设置、修改、查看公众号菜单;

二、代码优化之旅

EasyWechat安装后,文档示例以下代码。上述提到的接收普通消息、接收事件推送,可在serve方法中处理;

namespace App\Http\Controllers;

use Log;

class WeChatController extends Controller
{
    public function serve()
    {
        $app = app('wechat.official_account');
        $app->server->push(function($message){
            return "欢迎关注 overtrue!";
        });
        return $app->server->serve();
    }
}

1. 早期封装的代码

处理不同消息类型依据:$message变量不同的MsgType参数

之前所有的消息类型代码都放到一个控制器中。随着业务需求,消息类型不断增多,代码变的难以维护,谁看谁头大。

class WeChatController extends Controller
{
    public function serve()
    {
        $app = app('wechat.official_account.default');
        $app->server->push(function($message){
            switch ($message['MsgType']) {
                //普通消息 -> 文本
                case 'text':
                    $this->dealTextMsg($message);
                    break;

                //普通消息 -> 图片
                case 'image';
                    $this->dealImageMsg($message);
                    break;
                break;

                //处理事件
                case 'event':
                    $this->dealEventMsg($message);
                    break;
                //消息类型、类方法不断增多。。。。
            }
        });
        return $app->server->serve();
    }

    //处理普通消息 ->文本
    public function delTextMsg($message)
    {
        //业务相关
    }

    //处理普通消息 ->图片
    public function dealImageMsg($message)
    {
        //业务相关
    }

    //处理所有的事件
    public function delEventMsg($message) {
        //处理关注、取关等一系列事件,见过在这个方法中写有上百行代码!
    }
}

2. 优化思路、上代码

2.1优化思路

接收普通消息、接收事件消息2种类型,创建2个Services;
NormalMsgServices.php 用于处理普通消息;
EventMsgServices.php 用于处理事件;
实际开发中可能用不到这么多的方法,所以需要加一层判断,判断类方法是否存在。

Services类方法规则:
一、普通消息MsgType:

  • 文本:text
  • 图片:image
  • 语音消息:voice
    暂定命名规则:dealText、dealImage、dealVoice

二、事件的MsgType统一是event,可以根据Event来区分,例如:

  • 关注事件 Event = subscribe
  • 取关事件 Event = unsubscribe
  • 扫描带参数二维码,未关注用户事件 Event = subscribe && EventKey(事件key值)
  • 点击菜单跳转链接时的事件推送 Event = VIEW

暂定命名规则:dealSubscribe、dealUnsubscribe、dealView

2.1 上代码

ChatController控制器

namespace App\Http\Controllers;

class WeChatController extends Controller
{
    public function serve()
    {
        $app = app('wechat.official_account.default')
        $app->server->push(function($message){

            //不同的消息类型,使用可变函数(变量函数),定义对应Services类的方法;
            switch ($msgType = strtolower($message['MsgType'])) {
                case 'event': //事件消息方法
                    $function = 'deal' . ucfirst($message['Event']);//dealSubscribe、dealUnsubscribe、dealView
                break;

                default://普通消息方法
                    $function = 'deal' . $msgType; //dealText、dealImage、dealVoice
            }

            //根据MsgType类型,动态调用Services(普通、事件)
            $service = ($msgType === 'event' ? 'Event' : 'Normal');
            //仅处理需要的消息类型,加个判断类中是否有方法的判断。
            if (method_exists('App\Services\Wechat\\' . $service . 'MsgServices', $function)) {
                return app('App\Services\Wechat\\' . $service . 'MsgServices')->$function($message);
            }
        });
        return $app->server->serve();
    }
}

普通消息处理Services示例

<?php

namespace App\Services\Wechat;

/**
 * 功能:处理普通消息
 * 文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
 */
class NormalMsgServices
{
    public function dealText($aMessage)
    {
        //业务逻辑
    }
}

事件处理Services示例

<?php

namespace App\Services\Wechat;

/**
 * 功能:处理事件消息
 * 文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
 */
class EventMsgServices
{
    /**
     * 关注、扫描带参数二维码(用户未关注)
     */
    public function dealSubscribe()
    {
        return "欢迎关注XXX公众号";
    }
}

写的有点多,有点乱,各位小伙伴凑合看吧,如果有更好的方案可以留言。一起学习进步。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 17
$this->server->push(TextMessageHandler::class, Message::TEXT);   // 粉丝文本
$this->server->push(EventMessageHandler::class, Message::EVENT); // 粉丝事件
$this->server->push(ImageMessageHandler::class, Message::IMAGE); // 粉丝发图片
$this->server->push(VoiceMessageHandler::class, Message::VOICE); // 粉丝发语音
$this->server->push(VideoMessageHandler::class, Message::VIDEO); // 粉丝发视频
$this->server->push(LocationMessageHandler::class, Message::LOCATION); // 粉丝发坐标
$this->server->push(LinkMessageHandler::class, Message::LINK); // 粉丝发链接
$this->server->push(FileMessageHandler::class, Message::FILE); // 粉丝发文件
1年前 评论
赤三禾 (楼主) 1年前
    public function serve(Request $request)
    {
        $config = [
            'app_id' => env('XTSDYH_WXAPP_APP_ID'),
            'secret' => env('XTSDYH_WXAPP_SECRET'),

            'token' => 'easywechat',          // Token
            'aes_key' => '2OCyK9OLpUYdEhKPEwLhIwvX3GkEaWUoAjdWRoDhIpB',                    // EncodingAESKey,兼容与安全模式下请一定要填写!
//            'log' => [
//                'level' => 'debug',
//                'file' => '/tmp/easywechat.log',
//            ],
        ];
        $app = Factory::officialAccount($config);
        $app->server->push(function ($message) {
            switch ($message['MsgType']) {
                case 'event':
                    return '收到事件消息';
                    break;
                case 'text':
                    return '收到文字消息';
                    break;
                case 'image':
                    return '收到图片消息';
                    break;
                case 'voice':
                    return '收到语音消息';
                    break;
                case 'video':
                    return '收到视频消息';
                    break;
                case 'location':
                    return '收到坐标消息';
                    break;
                case 'link':
                    return '收到链接消息';
                    break;
                case 'file':
                    return '收到文件消息';
                // ... 其它消息
                default:
                    return '收到其它消息';
                    break;
            }
//            return "您好!欢迎使用 EasyWeChat!";
        });

        $response = $app->server->serve();
//        $response->send(); // Laravel 里请使用:return $response;
        return $response;
//        $this->logic->serve($request);
    }

file

请问一下,为啥我扫码关注公众号或者回复信息报异常,怎么回事?

1年前 评论
my38778570 (作者) 1年前
lddtime 1年前
赤三禾 (楼主) 1年前
my38778570 (作者) 1年前
赤三禾 (楼主) 1年前
my38778570 (作者) 1年前

配置公众号的时候日志报错

[2022-07-13T18:38:32.274379+08:00] EasyWeChat.EMERGENCY: Unable to create configured logger. Using emergency logger. {"exception":"[object] (InvalidArgumentException(code: 0): Log [] is not defined. at /www/wwwroot/xg-laravel/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php:161)
1年前 评论
my38778570 (作者) 1年前
MuYan 1年前

在入口的地方统一处理可能涉及到的消息类型,将所有的消息触发成laravel事件,通过监听事件灵活处理

1年前 评论

封装

<?php

namespace App\Http\Services;

use App\Enums\LogEnum;
use EasyWeChat\OfficialAccount\Application;
use Illuminate\Support\Str;
use Overtrue\LaravelWeChat\EasyWeChat;
use Psr\Http\Message\ResponseInterface;

class Wechat
{
    private Application $app;
    private string $openid;

    public function __construct()
    {
        $this->app = EasyWeChat::officialAccount();
    }

    public function getApp(): Application
    {
        return $this->app;
    }

    public function serve(): ResponseInterface
    {
        $server = $this->app->getServer();
        $server->with(function ($message, \Closure $next) {
            LogChannel::wechat()->info(LogEnum::INFO_WECHAT_SERVE, ['wechatMessage' => $message]);
            $this->openid = $message['FromUserName'];
            $method = $message['MsgType'];
            return match ($method) {
                'event' => $this->event($message),
                'text' => $this->text($message),
                default => $next($message),
            };
        });
        return $server->serve();
    }

    // 事件
    private function event($message): array|string|null
    {
        $event = $message['Event'];
        return match ($event) {
            'subscribe' => $this->eventSubscribe($message),
            'unsubscribe' => $this->eventUnSubscribe($message),
            'SCAN' => $this->eventScan($message),
            default => ''
        };
    }

    // 文本
    private function text($message): string|array|null
    {
        return '文本消息';
    }

    // 订阅
    private function eventSubscribe($message): string|array|null
    {
        // 带参数二维码订阅兼容
        $code = Str::replace('qrscene_', '', $message['EventKey']);
        return '欢迎订阅';
    }


    // 取消订阅
    private function eventUnSubscribe($message): string|array|null
    {
        return '取消订阅';
    }

    // 扫描带参二维码
    private function eventScan($message): string|array|null
    {
        $code = $message['EventKey'];
        return '扫码';
    }
}

使用

<?php

namespace App\Http\Controllers;

use App\Http\Services\Wechat;
use Psr\Http\Message\ResponseInterface;
use Throwable;

class WechatController extends Controller
{
    private Wechat $easyWechat;

    public function __construct()
    {
        parent::__construct();
        $this->easyWechat = new Wechat();
    }

    /**
     *
     * @return ResponseInterface|void
     */
    public function serve()
    {
        try {
            return $this->easyWechat->serve();
        } catch (Throwable $e) {
        }
    }
}
1年前 评论
赤三禾 (楼主) 1年前

我的优化方式

  • 1.抽离出回复、公众号菜单api,发送公众号模板消息 等,进行封装。
  • 2.对自动回复方法进行处理。

抽离出回复、公众号菜单api,发送公众号模板消息 等,进行封装。

<?php

namespace App\Services;

use EasyWeChat\Factory;
use EasyWeChat\Kernel\Messages\News;
use EasyWeChat\Kernel\Messages\NewsItem;
use EasyWeChat\Kernel\Messages\Text;

class WeChatReplayService
{
    protected $oApp;
    public function __construct()
    {
        $this->oApp = Factory::officialAccount(config('wechat.official_account.default'));
    }


    /**
     * 文本消息
     * @param $sOpenId
     * @param $sMsg
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     */
    public function text($sOpenId,$sMsg){
        $this->oApp->customer_service->message(new Text($sMsg))->to($sOpenId)->send();
    }

    /**
     * 创建图文单项
     * @param $sTitle
     * @param $sDescription
     * @param $sLink
     * @param $sCoverImg
     * @return NewsItem
     */
    public function imgTextItem($sTitle, $sDescription, $sLink,$sCoverImg){
        return  new NewsItem([
            'title' => $sTitle,
            'description' => $sDescription,
            'url' => $sLink,
            'image' =>  $sCoverImg
        ]);
    }

    /**
     * 图文消息
     * @param $sOpenId
     * @param $imgTextItem
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
     */
    public function imageText( $sOpenId, $imgTextItem){

        $items = [];
        if(is_object($imgTextItem)){
            $items[] = $imgTextItem;
        }
        if(is_array($imgTextItem)){
            $items = $imgTextItem;
        }
        $this->oApp->customer_service->message(new News($items))->to($sOpenId)->send();
    }
}

对自动回复方法进行处理。

    public function reply(){
        $oApp = Factory::officialAccount(config('wechat.official_account.default'));
        $server = $oApp->getServer();

        // 使用回调作为中间件
        $server->with(function($oMsg, \Closure $next) {
                // 你的自定义逻辑1
                return $next($oMsg);
            });
        // 创建独立的类作为中间件
        $server->with(MyCustomHandler::class);


        return $oApp->server->serve();
    }

自定义回复类

<?php


namespace App\WechatRePly;


class MyCustomHandler
{
    public function __invoke($oMsg, \Closure $next)
    {
        //  处理回复逻辑
        if ($oMsg->MsgType === 'text') {
            //...
        }

        return $next($oMsg);
    }
}
1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!