Skip to main content

微信客服聊天的开发

实现的目标

前端只要有人点击在线客服,进入聊天窗口,就发送后台设置的自动回复内容和图片。

后台可以把小程序里的客服列表,拉取出来,为每个客服设置不同的自动回复内容和图片。

环境:我们属于第三方开发者,后台是绑定多个小程序的。

步骤:

1. 进入微信小程序后台“客服”模块,找到设置“消息推送的文字”链接,打开滚动到中间,有一个“消息推送”,点击启用。然后设置服务器URL 等信息。

这是我路由中的设置。id是小程序详情表的id。

            /**
* 小程序客服聊天
*/
Route::any('{id}/chat','WechatChatController@Chat'); //小程序客服聊天的消息推送
Route::any('{id}/uploadTempMedia','WechatChatController@uploadTempMedia'); //小程序图片

服务器接收要验证并返回“echostr”的信息。PHP的话要把接收到echostr信息强制转成字符串一下,不然不通过。

//微信客服收到的消息
public function Chat(Request $request,$id){
$wc_id = $id;

\Log::channel('api')->info('------------');
\Log::channel('api')->info('requst:'.json_encode($request->all()));
$FromUserName = $request->input("FromUserName");
$MsgType = $request->input("MsgType");
$Event = $request->input("Event");
//&& $MsgType=='event' && $Event =='user_enter_tempsession'
if ( !empty($FromUserName) )
{
//查找后台设置的客服
$kf_info = MerchantConsult::getOneKfInfo($wc_id);
\Log::channel('api')->info('查找客服配置:'.json_encode($kf_info));
if (!empty($kf_info))
{
\Log::channel('api')->info('~~~~~~');
MiniApp::send_text($wc_id,$FromUserName,$kf_info['msg']);
\Log::channel('api')->info('~~~~~22~');
$response = MiniApp::send_img($wc_id,$FromUserName,$kf_info['media_id']);
\Log::channel('api')->info('------------'.$response.json_encode($response));
}

}

$signature = $request->input("signature");
$timestamp = $request->input("timestamp");
$nonce = $request->input("nonce");
$echostr = $request->input("echostr");
return $this->checkSignature($signature,$timestamp,$nonce,$echostr);
}

//检验signature
private function checkSignature($signature,$timestamp,$nonce,$echostr)
{
$token = 'tyunai';
$tmpArr = array($token, $timestamp, $nonce);

sort($tmpArr, SORT_STRING);
$tmpStr = implode( $tmpArr );
$tmpStr = sha1( $tmpStr );
\Log::channel('api')->info(json_encode($tmpArr).' tmpStr: '.$tmpStr.' signature: '.$signature);
if ($tmpStr == $signature ) {
return (string)$echostr;
} else {
return false;
}
}

2. 小程序后台设置成功之后。就看我的MiniApp里的封装吧。

现在我只封装了这些

MiniApp.php文件

/**
* Created by PhpStorm.
* User: 清行
* Date: 2020/1/6
* Time: 16:58
*/

namespace App\Common\Wechat\MiniApp;


class MiniApp
{
use User,Message;

}

User.php

<?php
/**
* Created by PhpStorm.
* User: 清行
* Date: 2020/1/6
* Time: 17:00
*/

namespace App\Common\Wechat\MiniApp;


use App\Common\Wechat\ThirdParty\Common;
use App\Exceptions\ApiException;
use App\Models\WechatMiniApp;

trait User
{
//登录凭证校验
public static function getCode2Session($appid,$appSecret,$js_code)
{
$url = 'https://api.weixin.qq.com/sns/jscode2session?appid='.$appid.'&secret='.$appSecret.'&js_code='.$js_code.'&grant_type=authorization_code';
return Common::wxGet($url);
}

//获取小程序全局唯一后台接口调用凭据access_token
public static function getAccessToken($appid,$appSecret)
{
$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$appSecret;
$response = Common::wxGet($url);
if (!empty($response['access_token'])){
return $response['access_token'];
}else{
throw new ApiException([$response['errcode'],$response['errmsg']]);
}
}

//
public static function get_access_token($id)
{
$appBase = WechatMiniApp::getAppBase($id);
return self::getAccessToken($appBase['app_id'],$appBase['app_secret']);
}
}

Message.php

<?php
/**
* Created by PhpStorm.
* User: 清行
* Date: 2020/2/17
* Time: 18:22
*/

namespace App\Common\Wechat\MiniApp;


use App\Common\Wechat\ThirdParty\Common;

trait Message
{

//发送文字类的内容
public static function send_text($id,$touser,$text)
{
$access_token = self::get_access_token($id);
$url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token='.$access_token;
$params = [
'touser' => $touser ,
'msgtype' => 'text' ,
'text' =>['content' => $text],
];
$response = Common::wxPost($url,$params);
return $response;
}

//发送图片格式的内容
public static function send_img($id,$touser,$media_id)
{
$access_token = self::get_access_token($id);
$url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token='.$access_token;
$params = [
'touser' => $touser ,
'msgtype' => 'image' ,
'image' =>['media_id' => $media_id],
];
$response = Common::wxPost($url,$params);
return $response;
}

//把媒体文件上传到微信服务器
public static function uploadTempMedia($id,$path)
{
$access_token = self::get_access_token($id);
$url = 'https://api.weixin.qq.com/cgi-bin/media/upload?access_token='.$access_token.'&type=image';
return self::newHttpsPost($url,$path);
}

//发送图片
public static function newHttpsPost($url ='' , $path = '' ){
$curl = curl_init();
if (class_exists('\CURLFile')){
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
$data = array('media' => new \CURLFile($path));//
}
else
{
curl_setopt($curl,CURLOPT_SAFE_UPLOAD,false);
$data = array('media'=>'@'.$path);
}

curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, 1 );
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_USERAGENT,"TEST");
$result = curl_exec($curl);
$res=json_decode($result,true);
return $res;
}

//获取客服列表
public static function getKFList($id)
{
$access_token = self::get_access_token($id);
$url = 'https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token='.$access_token;
$response = Common::wxGet($url);
return $response;
}

}

这里还用到了我另外一个封装,那是我封装第三方post,get请求的类,也放出来吧

<?php
/**
* Created by PhpStorm.
* User: 清行
* Date: 2019/11/11
* Time: 15:11
*/

namespace App\Common\Wechat\ThirdParty;

use App\Common\Err\ApiErrDesc;
use App\Exceptions\ApiException;
use App\Common\Curl\Curl;
use Illuminate\Support\Facades\Log;
use SimpleSoftwareIO\QrCode\Facades\QrCode;

class Common
{
/***
* 微信的Get请求 参数会urlencode编码一次
* @param $url 请求地址
* @param array $params 请求参数
* @return mixed
*/
public static function wxGet($url,$params=array())
{
$response = self::getJosn($url,$params);
return self::responseAction($url,$params,$response,'GET');
}

/***
* 微信的post请求
* @param $url 请求地址
* @param $params 请求参数
* @return mixed
*/
public static function wxPost($url,$params)
{
$response = self::postJosn($url,$params);
return self::responseAction($url,$params,$response,'POST');
}

/**
* get请求
* @param $url 请求地址
* @return mixed
*/
public static function get($url)
{
$curl = new Curl();
$response = $curl->to($url)->asJson( true )->get();
return $response;
}

/**
* 微信简单的get请求不需要json成数组
* @param $url 请求地址
* @return mixed
*/
public static function wxGetBase($url,$params=array())
{
$curl = new Curl();
$response = $curl->to($url)->withData($params)->get();
return self::responseAction($url,$params,$response,'GET');
}


/***
* get请求 参数会改为json格式
* @param $url 请求地址
* @param $params 请求参数
* @return mixed
*/
public static function getJosn($url,$params=array())
{
$curl = new Curl();
$response = $curl->to($url)->withData($params)->asJson( true )->get();
return $response;
}

/***
* post请求 参数会改为json格式
* @param $url 请求地址
* @param $params 请求参数
* @return mixed
*/
public static function postJosn($url,$params)
{
$curl = new Curl();
$response = $curl->to($url)->withData( $params)->asJson( true )->post();
return $response;
}

/**
* 对请求的返回信息进行处理
* @param $url 请求地址
* @param $params 请求参数
* @return mixed
*/
public static function responseAction($url,$params,$response,$method='')
{
if (empty($response['errcode']))
{
Log::channel('wechat')->info($method.'请求成功:请求的URL是'.$url.PHP_EOL.'请求的参数是'.json_encode($params).PHP_EOL.'返回内容是'.json_encode($response));
}else{
Log::channel('wechat')->error($method.'请求失败:请求的URL是'.$url.PHP_EOL.'请求的参数是'.json_encode($params).PHP_EOL.'返回内容是'.json_encode($response));
//抛出异常
if (!empty($response['errcode']) && !empty($response['errmsg'])){
throw new ApiException([$response['errcode'],$response['errmsg']]);
}else{
throw new ApiException(ApiErrDesc::ERR_UNKNOWN);
}
}
return $response;
}


/**
* 生成二维码
* @param string $string 生成的二维码内容
* @param int $size 大小
* @return mixed
*/
public static function generateQrcode($string='',$size=150)
{
return QrCode::size($size)->generate($string);
}

}

我感觉我写的代码都是精华,干货。

3. 看完微信小程序接口的封装,就该看后台代码了。后台我用的Laravel-admin

<?php

namespace App\Admin\Controllers;

use App\Common\Wechat\MiniApp\MiniApp;
use App\Models\MerchantConsult;
use App\Traits\LaravelAdmin;
use Encore\Admin\Admin;
use Encore\Admin\Controllers\AdminController;
use Encore\Admin\Form;
use Encore\Admin\Grid;
use Encore\Admin\Show;

class MerchantConsultController extends AdminController
{
/**
* Title for current resource.
*
* @var string
*/
use LaravelAdmin;
protected $title = '客服列表';

/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
$grid = new Grid(new MerchantConsult());

$grid->column('id', __('Id'));
$this->showMerchantName($grid,'所属商户');
$grid->column('kf_nick', __('客服名称'));
$grid->column('img', __('客服二维码'));
$grid->column('msg', __('客服自动回复消息'));
$grid->column('status', __('是否启用'))->switch(config('adminCommon.switch_using'));
$grid->column('kf_wx', __('客服微信号'));
$grid->column('position_id', __('所在页面位置'))->using(config('adminCommon.kf_position_id'));

return $grid;
}

/**
* Make a show builder.
*
* @param mixed $id
* @return Show
*/
protected function detail($id)
{
$show = new Show(MerchantConsult::findOrFail($id));

$show->field('id', __('Id'));
$show->field('merchant_id', __('Merchant id'));
$show->field('kf_nick', __('客服名称'));
$show->field('img', __('客服二维码'))->image();
$show->field('msg', __('客服自动回复消息'));
$show->field('status', __('状态'))->using(config('adminCommon.using'));;
$show->field('kf_id', __('客服ID'));
$show->field('kf_wx', __('客服微信号'));
$show->field('kf_headimgurl', __('客服微信头像'))->image();
$show->field('kf_account', __('客服账号'));
$show->field('position_id', __('所在页面位置'))->using(config('adminCommon.kf_position_id'));

return $show;
}

/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{


$form = new Form(new MerchantConsult());
$this->showMerchantName($form,'所属属商户');
$form->select('kf_id','选择客服');
$form->image('img', __('客服二维码'))->move('images/merchant/store/consult')->uniqueName()->removable();
$form->text('msg', __('客服自动回复消息'));
$form->switch('status', __('是否启用'))->states(config('adminCommon.switch_using'))->default(0);
$form->radio('position_id', __('所在页面位置'))->options(config('adminCommon.kf_position_id'));


Admin::script('
//改变商户时
$(".merchant_id").on("change", function(){
getList();
});
getList();
function getList()
{
var merchant_id = $(".merchant_id").val();

$.ajax({
method: \'get\',
url: "/admin/ajax/getWCKFList",
data: {
_token:LA.token,
q:merchant_id,
},
success: function (data) {
$(".kf_id").html("");
$.each( data, function(i, row){
$(".kf_id").append("<option value=\'"+row["id"]+"\'>"+row["text"]+"<\/option>");
});
}
});
}

',false);

//保存前回调
$form->saving(function (Form $form) {
$this->getKfInfo($form);
});

//保存后回调
$form->saved(function (Form $form) {

if( is_file(storage_path('app/public/'.$form->model()->img))){
$filedate = MiniApp::uploadTempMedia($form->model()->merchant_id,storage_path('app/public/'.$form->model()->img));
if (!empty($filedate['media_id']))
{
MerchantConsult::updateMediaId($form->model()->id,$filedate['media_id']);
}
}
});

return $form;
}



//根据kf_id获取客服列表的单项所有信息
public function getKfInfo($form)
{

$kf_id = $form->kf_id;
if (empty($kf_id)) {
return $form;
}
$kf_list = MiniApp::getKFList($form->merchant_id);
if (!empty($kf_list['kf_list']))
{

foreach ($kf_list['kf_list'] as $row )
{
if($row['kf_id'] == $form->kf_id)
{
$form->model()->kf_nick = $row['kf_nick'] ;
$form->model()->kf_account = $row['kf_account'] ;
$form->model()->kf_headimgurl = $row['kf_headimgurl'] ;
$form->model()->kf_wx = $row['kf_wx'] ;
return $form;
}
}
}

return $form;
}
}

4. 结束。这就是我的所有代码了。

感觉我封装的还不错的话,给我推荐个好工作吧。