公众号端基本配置
image.png
公众号端设置
image.png
域名要备案,文件下载上传至tp6的public目录下
image.png
composer require overtrue/wechat -vvv
微信相关路由配置
//微信
Route::group('wechat', function () use($version){
Route::get('',"{$version}.Wechat/index");//微信首页
Route::any('me',"{$version}.Wechat/me");//微信首页
Route::any('oauth_callback',"{$version}.Wechat/oauth_callback");//微信首页
Route::get('test',"{$version}.Wechat/test");//微信首页
Route::get('menu',"{$version}.Wechat/menuList");//微信菜单列表
Route::post('menu',"{$version}.Wechat/menuAdd");//微信菜单添加
Route::get('resource',"{$version}.Wechat/resource");//资源
});
config/wechat.php配置文件
return [
/**
* 账号基本信息,请从微信公众平台/开放平台获取
*/
'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''), // AppID
'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', ''), // AppSecret
'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''), // Token
'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_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' => ROOT_PATH.'logs/'.date('Y-m-d').'/easywechat.log',
'level' => 'debug',
],
// 生产环境
'prod' => [
'driver' => 'daily',
'path' => ROOT_PATH.'logs/'.date('Y-m-d').'/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' => '/api/wechat/oauth_callback',
],
];
// +----------------------------------------------------------------------
// | 微信逻辑【业务层】
// +----------------------------------------------------------------------
// | Author: DragonersLi<865196792@qq.com>
// +----------------------------------------------------------------------
// | Date: 2020/07/23
// +----------------------------------------------------------------------
namespace app\common\logic;
use EasyWeChat\OpenPlatform\Server\Guard;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Messages\Text;
use EasyWeChat\Kernel\Messages\Image;
use EasyWeChat\Kernel\Messages\Video;
use EasyWeChat\Kernel\Messages\Voice;
use EasyWeChat\Kernel\Messages\News;
use EasyWeChat\Kernel\Messages\NewsItem;
use EasyWeChat\Kernel\Messages\Article;
use app\common\service\RedisService;
use app\common\model\UserWeixin;
use app\common\model\Keyword;
use app\common\model\Notice;
use think\facade\Log;
class WechatLogic extends BaseLogic
{
public function initialize()
{
parent::initialize();
}
public function __construct()
{
$this->app = Factory::officialAccount(config('wechat'));
}
/**
* 微信首页
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function index(){
try{
$this->app->server->push(function ($message){
$user_openid = $message['FromUserName'];
switch ($message['MsgType']) {
case 'event':
$message['user'] = $this->app->user->get($user_openid);
return $result = $this->event($message);//处理点击事件
break;
case 'text':
return $this->text($message['Content']);//处理被动回复文本
break;
case 'image':
return $image = new Image($mediaId = '0b69zmxmX9VQ1WOyimslclMr3f2YbnNgrMkjS2KxWZM');
break;
case 'voice':
return $voice = new Voice($mediaId = 'Rwqz1xLYDNwFvTJ254fBstYU1rY3aKiN-z7aqEpiWic');
break;
case 'video':
return $video = new Video($mediaId = 'Rwqz1xLYDNwFvTJ254fBsmzRm-HXa-ilLRc028v-Ar0', [
'title' => $title = '标题',
'description' => '简介...',
]);
break;
case 'location'://收到坐标消息,微信目前不支持回复坐标消息
return $text = new Text("您好!openid:{$message['FromUserName']},{$message['MsgType']},收到坐标消息,微信目前不支持回复坐标消息");
break;
case 'link'://收到链接消息,微信目前不支持回复链接消息
return $text = new Text("您好!openid:{$message['FromUserName']},{$message['MsgType']},收到链接消息,微信目前不支持回复链接消息");
break;
case 'file':
return $text = new Text("您好!openid:{$message['FromUserName']},{$message['MsgType']},收到文件消息");
// ... 其它消息
default:
return '收到其它消息';
break;
}
});
}catch(\Exception $e){
dlog('wechat',$e->getMessage());
}
$response = $this->app->server->serve()->send();
}
/*
* 菜单列表
*/
public function menuList()
{
$list = $this->app->menu->list();//读取(查询)已设置菜单
$current = $this->app->menu->current();//获取当前菜单
return [
'list'=>$list,
'current'=>$current
];
}
/*
* 菜单创建
*/
public function menuAdd(){
$buttons = [
[
"type" => "view",
"name" => "下载APP",
"url" => config('system.api.url')."/download.html"
],
[
"type" => "view",
"name" => "个人中心",
"url" => config('system.api.url')."/h5"
]
];
return $this->app->menu->create($buttons);
}
/*
* $type 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news)
* $offset 从全部素材的该偏移位置开始返回,可选,默认 0,0 表示从第一个素材 返回
* $count 返回素材的数量,可选,默认 20, 取值在 1 到 20 之间
*/
public function resource($type = 'image'){
$user = $this->app->user->list($nextOpenId = null); // $nextOpenId 可选
$stats = $this->app->material->stats();//获取各类素材总数
//$item = $this->app->material->get($mediaId='');//获取单个素材
$resource = $this->app->material->list($type, $offset= 0, $count= 20);
// $this->app->material->delete($mediaId='');//删除素材
return [
'count'=>$stats,
'list'=>$resource,
'user'=>$user
];
}
/**
* 授权页面
* @return mixed
*/
public function me(){
if (!cookie('wechat_user')) {// 未登录
cookie('target_url',config('system.api.url').'/api/wechat/me');
return $this->app->oauth->redirect()->send();
}
$url = config('system.api.url').'/h5';
header('location:'. $url);
}
/**
* 回调页面
*/
public function oauth_callback(){
$user = $this->app->oauth->user(); // 获取 OAuth 授权结果用户信息
cookie('wechat_user',json_encode($user));
dlog('wechat_user',$user);
RedisService::redis()->set('weixin_user:'.$user->id,json_encode([
'openid'=>$user->id,
'nickname'=>$user->nickname,
'avatar'=>$user->avatar
]));
$url = cookie('target_url') ? cookie('target_url') : config('system.api.url').'/h5';
header('location:'. $url);
}
/**
* 回复事件
* @param array $message
* @return Image|string
*/
private function event($message = []){
$user_openid = $message['user']['openid'];
switch ($message['Event']) {
case "subscribe"://关注微信用户表新增记录
return '欢迎关注派金花公众号,有疑问请联系在线客服!' ;
break;
case "unsubscribe"://取消关注时执行的操作,要把记录该用户从记录微信用户信息的表中删掉
$msg = "{$user_openid}:取消关注!清除微信信息:";
$msg.= UserWeixin::destroy(function($query) use($user_openid){
$query->where(['wxOpenid'=>$user_openid]);
}) ? 'success' : 'failed';
dlog('wechat',$msg);//取消关注时执行的操作,要把记录该用户从记录微信用户信息的表中删掉
break;
case "CLICK":
switch ($message['EventKey']) {
case "V1000":
break;
case "V1001":
default:
return $image = new Image($mediaId = 'Rwqz1xLYDNwFvTJ254fBspGO-4KAaoUILJIKRzwUBrE');
break;
}
break;
case "SCAN":
case "LOCATION":
case "VIEW":
default:
break;
}
}
/**
* 回复文本
* @param string $content
* @return Image|News|Text|Video|Voice
*/
private function text($content = ''){
$result = config('wechatreply.keywords');
if(empty($result[$content])){ //关键词匹配失败
$other = config('wechatreply.other');
if(empty($other[$content])){//其它关键词也匹配失败,则返回关键词列表
$content = "输入序号快捷获取回复!\n";
foreach($result as $k=>$v){
$content .= "【{$k}】{$v['title']}\n";
}
return $text = new Text($content);
}else{ //匹配其它关键词
$result = $other;
}
}
switch ($result[$content]['type']){
case 'image'://图片
return $image = new Image($result[$content]['content']);
break;
case 'video'://视频
return $video = new Video($mediaId = $result[$content]['content'], [
'title' => $result[$content]['title'],
'description' => '视频描述...',
// 'thumb_media_id'=>'Rwqz1xLYDNwFvTJ254fBsiuqJw5ZV9dZF2jNu34_bPU' // 封面资源 ID
]);
break;
case 'voice'://声音
return $voice = new Voice($mediaId = $result[$content]['content']);
break;
case 'news'://资讯
$data = Notice::where(['id'=>$result[$content]['content']])->field('id,title,content')->find();
return $news = new News([
new NewsItem([
'title' => $result[$content]['title'],
'description' =>$result[$content]['content'],
'url' =>$result[$content]['id'],
//'image' => 'https://www.easywechat.com/img/logo-black.svg',
// ...
]),
]);
break;
case 'text'://文本
default:
$result[$content]['content'] = htmlspecialchars_decode($result[$content]['content']);
$result[$content]['content'] = str_replace('
','',$result[$content]['content']);
$result[$content]['content'] = str_replace('
',"\r",$result[$content]['content']);$result[$content]['content'] = str_replace('
',"\r",$result[$content]['content']);
return $text = new Text($result[$content]['content']);
break;
}
}
/**
* 发送模板消息[静态方法方便调用]
* @param array $data
* @return mixed
*/
public static function tpl($data = []){
// $data['openid'] = 'o9soys5-em4VBQD78uy4_dyZx_Pg';
// $data['type'] = 'test';
// $data['url'] = config('system.api.url').'/h5';
// $data['data'] =[
// 'first'=>'hello,world',
// 'keyword1' => '220.30元',
// 'keyword2' => '荣事达旗舰店',
// 'keyword3' => '2014年7月21日 18:36',
// ];
// $this->tpl($data);
$result = Factory::officialAccount(config('wechat'))->template_message->send([
'touser'=>$data['openid'],
'template_id'=>config("system.wechat_tpl.{$data['type']}"),
'url' => $data['url'],
'data' => $data['data'],
]);
if(!$result['errcode']){//发送成功
return ['code'=>1,'msg'=>$result['errmsg'],'data'=>['msgid'=>$result['msgid']]];
}else{//发送失败记录日志
dlog('wechat_tpl',['data'=>$data,'result'=>$result]);
return ['code'=>0,'msg'=>$result['errmsg'],'data'=>['result'=>$result]];
}
}
}
开通模版消息,配置模版ID
//微信模板ID
'wechat_tpl'=>[
'test'=>'tGy9UXpXz97cv7SygOnVR6S-kqOZtnEDblTnN5QwkJs',//测试
'register'=>'pxIIRpFWFZu-02103RmVIxCmqS-LdyDJIpPcNbwVupo',//注册模板ID
],
完全按官网demo接入,菜单资源等接口都能正常运作,自动回复却提示:该公众号暂时无法提供服务,请稍后再试
tail -f tp6.xxx.com.log查看日志,看到是post请求,返回错误404 Not Found!
排查结果:路由请求方式由get改成any解决!
223.166.222.120 - - [22/Aug/2020:14:12:37 +0800] "POST /api/wechat?signature=acd16cbad48ae6bad1df96b27e302d979eb454cd×tamp=1598076757&nonce=1560311832&openid=o9soys5-em4VBQD78uy4_dyZx_Pg&encrypt_type=aes&msg_signature=26a0a9c431a2a56fca7e5e0db794e4a98e7b4617 HTTP/1.1" 404 21487 "-" "Mozilla/4.0"
laraval下请求方式错误返回405 Method Not Allowed
"The POST method is not supported for this route. Supported methods: GET, HEAD."