【微信公众号】微信集成功能--接入多客服系统

21 篇文章 0 订阅
6 篇文章 0 订阅

目录

需求来源

实现思路

1、邀请指定人员成为客服角色;

2、用户在公众号发送信息,通过关键字触发事件:通知客服;

3、客服角色接入会话,与用户对话沟通交流解决疑问,完成对话;

代码实现(基于Laravel框架前后端混合)

路由文件

 控制器

模型 

 数据表(请根据实际业务定义表结构)

使用步骤

感谢阅读,欢迎交流 


需求来源

在工作时间,用户可以在微信公众号与客服对话,咨询有关公众号的各种问题与寻求帮助;

实现思路

1、邀请指定人员成为客服角色;

功能上线时必须有客服帐号,调用接口:添加客服帐号(获取客服基本信息 | 微信开放文档)、邀请绑定客服帐号(获取客服基本信息 | 微信开放文档

2、用户在公众号发送信息,通过关键字触发事件:通知客服;

用户在微信公众号发送信息,微信服务器推送文本事件的xml数据包给开发者,开发者通过判断文本是否为接入客服的关键词,是则通知客服帐号与转发给多客服系统;

3、客服角色接入会话,与用户对话沟通交流解决疑问,完成对话;

代码实现(基于Laravel框架前后端混合)

路由文件

<?php
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
Route::group(['prefix' => 'wechat/'], function (Router $router) {
    // 接收微信推送信息,完成验证消息真实性
    $router->get('receive_push', "WechatController@receivePush");
    // 接收微信推送信息,完成接收普通消息并回复
    $router->post('receive_push', "WechatController@receivePush");
    // 对话服务-基础支持-获取access_token
    $router->get('get_access_token', 'WechatController@getAccessToken');
    // 微信公众号集成功能
    $router->group(['prefix' => '/func/'], function(Router $router) {
        // 微信公众号集成功能--对话服务--发送消息--客服接口--邀请并绑定客服帐号
        $router->get('customservice_kf_invite_bind', 'WechatController@customserviceKfInviteBind');
    });
});

 控制器

<?php
namespace App\Http\Controllers\Wechat;
use App\Models\WechatKfAccount;
use App\Models\WechatUserSession;
use Illuminate\Http\Request;
class WechatController extends BaseController
{
    // todo 接收微信推送--明文模式
    public function receivePush(Request $request)
    {
        file_log("进入访问" . $request->getUri(), "wechat");
        try {
            switch ($_SERVER["REQUEST_METHOD"]) {
                case "GET":
                    file_log("验证消息真实性" . $request->getUri(), "wechat");
                    $params = $request->all();
                    $params['token'] = config("wechat_develop.Token");
                    // 校验签名是否正确
                    $isMatch = $this->verifySignature($params);
                    if (!$isMatch) {
                        exit(json_encode(["errcode" => -40001, "errmsg" => "signature sha1 error"], JSON_UNESCAPED_UNICODE));
                    }
                    echo $params["echostr"];
                    exit;
                    break;
                case "POST":
                    file_log("接收公众号发来的信息" . $request->getUri(), "wechat");
                    // 接收微信公众号传送的xml信息包
                    $this->postXml = isset($GLOBALS["HTTP_RAW_POST_DATA"]) ? $GLOBALS["HTTP_RAW_POST_DATA"] : file_get_contents("php://input");
                    file_log("公众号发来的xml数据包" . $this->postXml, "wechat");
                    // xml转换成array
                    $postArray = json_decode(json_encode(simplexml_load_string($this->postXml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
                    // 按照实际业务处理不同类型的信息,并返回xml包
                    $this->receive = $postArray;
                    @ob_clean();
                    echo $this->reply();
                    exit;
                    break;
                default:
                    file_log("【非验证微信服务器信息】或【非微信服务器发送的信息】" . $request->getUri(), "wechat");
                    exit;
            }
        } catch (\Exception $e) {
            exception_file_log($e, 'wechat');
            exit($e->getMessage() . $e->getTraceAsString());
        }
    }

    private function verifySignature($getParam)
    {
        $tmpArr = array($getParam["token"], $getParam["timestamp"], $getParam["nonce"]);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);
        return $tmpStr == $getParam['signature'];
    }

    private function reply()
    {
        $this->message = [
            "MsgType" => $this->receive["MsgType"],
            "CreateTime" => time(),
            "ToUserName" => $this->receive["FromUserName"],
            "FromUserName" => $this->receive["ToUserName"],
        ];
        switch ($this->receive["MsgType"]) {
            case "event":
                $this->event();
                break;
            case "text":
                $this->text();
                break;
            default:
                return $this->postXml;
                break;
        }
        $replyXml = self::arr2xml($this->message);
        file_log("开发者发送的信息:" . $replyXml, "wechat");
        return $replyXml;
    }

    public static function arr2xml($data)
    {
        return "<xml>" . self::_arr2xml($data) . "</xml>";
    }

    private static function _arr2xml($data, $xmlContent = '')
    {
        foreach ($data as $key => $val) {
            is_numeric($key) && $key = 'item';
            $xmlContent .= "<{$key}>";
            if (is_array($val) || is_object($val)) {
                $xmlContent .= self::_arr2xml($val);
            } elseif (is_string($val)) {
                $xmlContent .= '<![CDATA[' . preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $val) . ']]>';
            } else {
                $xmlContent .= $val;
            }
            $xmlContent .= "</{$key}>";
        }
        return $xmlContent;
    }

    # text类型数据回复
    private function text()
    {
        // 判断用户发送的消息文本
        switch ($this->receive['Content']) {
            // 接入客服
            case "客服":
                try {
                    $this->integrationKf();
                } catch (\Exception $e) {
                    exception_file_log($e, "wechat");
                    $this->message['MsgType'] = "text";
                    $this->message['Content'] = "目前暂未接入客服系统";
                }
                break;
            default:
                // 默认情况的回复信息
                $this->message["Content"] = '您可输入关键词语"客服"';
                break;
        }

    }

    # event类型
    private function event()
    {
        $this->message["MsgType"] = "text";
        $this->message["Content"] = "遇到未知推送事件";
        file_log("推送事件名称:" . strtolower($this->receive["Event"]), "wechat");
        switch (strtolower($this->receive["Event"])) {
            case "kf_create_session": // 客服会话创建时推送
                $this->message_custom_send([
                    'touser' => $this->message["ToUserName"],
                    'msgtype' => "text",
                    "text" => ["content" => "您好,请问有什么可以帮到您?"]
                ]);
                // 更新当前用户会话状态
                $currUserSession = WechatUserSession::get_one([
                    'where' => [
                        ['openid','=', $this->message['ToUserName']],
                        ['status','=',WechatUserSession::Waiting]
                    ],
                    'orderBy' => [['id','desc']],
                    'field' => ['id']
                ]);
                if ($currUserSession) {
                    $where = [['id','=',$currUserSession['id']]];
                    $data = ['status' => WechatUserSession::Going, 'kf_account' => $this->receive['KfAccount'], 'start_time' => time()];
                    WechatUserSession::update_by_where(['where' => $where, 'data' => $data]);
                }
                break;
            case "kf_close_session": // 客服会话关闭时推送
                $this->message_custom_send([
                    'touser' => $this->message["ToUserName"],
                    'msgtype' => "text",
                    "text" => ["content" => "当前会话已关闭,如需在线咨询,请回复“客服”"]
                ]);
                // 更新聊天会话的客服帐号与会话状态
                $currUserSession = WechatUserSession::get_one([
                    'where' => [
                        ['openid','=', $this->message['ToUserName']],
                        ['status','=',WechatUserSession::Going]
                    ],
                    'orderBy' => [['id','desc']],
                    'field' => ['id']
                ]);
                if ($currUserSession) {
                    $where = [['id','=',$currUserSession['id']]];
                    $data = ['status' => WechatUserSession::End, 'end_kf_account' => $this->receive['KfAccount'], 'end_time' => time()];
                    WechatUserSession::update_by_where(['where' => $where, 'data' => $data]);
                }
                break;
            default:
                break;
        }
    }

    # 接入客服系统
    private function integrationKf()
    {
        // 判断是否在9:00-18:00这个工作时间
        $hour = date('H');
        if ($hour < 9 || $hour >= 18) {
            $this->message['Content'] = "您好,在线客服工作时间9:00~18:00,请您在客服服务时间内进行咨询,非工作时间请您留言咨询问题或添加人工微信客服:xxx,万能的客服xx会为您解答所有疑问喔~。";
            return true;
        }
        // 1.判断当前用户是否存在未结束的客服会话
        $userSession = WechatUserSession::getUserSessionByStatus([WechatUserSession::Waiting, WechatUserSession::Going],$this->message['ToUserName']);
        // 1-1.会话不存在或者已过期的等待会话
        if (!$userSession || ($userSession['status'] == WechatUserSession::Waiting && $userSession['invalidtime'] < time())) {
            // 查询数据库所有有效的客服帐号, 发送通知
            $kfAccountList = WechatKfAccount::getKfAccountByStatus([WechatKfAccount::Agree]);
            if ($kfAccountList) {
                foreach ($kfAccountList as $kfAccount) {
                    $this->message_custom_send([
                        "touser" => $kfAccount["kf_openid"],
                        "msgtype" => "text",
                        "text" => ["content" => "客服大大,目前有找您咨询的客户,请您立马登录客服系统进行服务。"]
                    ]);
                } unset($kfAccountList,$kfAccount);
            }
            //插入用户会话记录
            WechatUserSession::insert(new WechatUserSession(), [
                'openid' => $this->message['ToUserName'],
                'invalidtime' => time() + 60*30,
            ]);
            // 统计等待接入的用户数量
            $waitUserSessionCount = WechatUserSession::getWaitUserSessionCount();
            $this->message_custom_send([
                "touser" => $this->message['ToUserName'],
                "msgtype" => "text",
                "text" => ["content" => "目前共有{$waitUserSessionCount}人在排队,您在第{$waitUserSessionCount}位请您耐心等待。"]
            ]);
            // 通知微信多客服端接收会话,等待客服接入
            $this->message['MsgType'] = "transfer_customer_service";
        } else {
            // 1-2.会话存在并且正在进行中的会话
            if (empty($userSession['kf_account'])) {
                // 通知用户再排队队列的位置
                $waitUserSessionList = WechatUserSession::getWaitUserSessionList();
                $nowPosition = 1;
                $waitUserSessionCount = count($waitUserSessionList);
                if ($waitUserSessionCount > 1) {
                    foreach($waitUserSessionList as $key => $value){
                        if($value['openid'] == $this->message['ToUserName']){
                            $nowPosition = $key + 1;
                        }
                    }
                }
                $this->message_custom_send([
                    "touser" => $this->message['ToUserName'],
                    "msgtype" => "text",
                    "text" => ["content" => "目前共有{$waitUserSessionCount}人在排队,您在第{$nowPosition}位,请您耐心等待。"]
                ]);
                // 通知微信多客服端接收会话,等待客服接入
                $this->message['MsgType'] = "transfer_customer_service";
            } else {
                $this->message['MsgType'] = "transfer_customer_service";
                $this->message['TransInfo']['KfAccount'] = $userSession['kf_account'];
            }
        }
    }

    /**
     * todo 微信公众号集成功能--对话服务--发送消息--客服接口--邀请并绑定客服帐号
     */
    public function customserviceKfInviteBind(Request $request)
    {
        try {
            check_result_isexists(!is_null($kf_account = $request->kf_account), '客服帐号不存在');
            check_result_isexists(!is_null($nickname = $request->nickname), '客服昵称不存在');
            check_result_isexists(!is_null($wx_account = $request->wx_account), '客服微信号不存在');
            // 判断添加的客服帐号是否已存在
            $kfAccount = WechatKfAccount::get_one(['where' => [['kf_account', '=', $kf_account]]]);
            if ($kfAccount) return return_info(500, '客服帐号已存在,请勿重复添加');
            // 判断添加的客服微信号是否已存在
            $wxAccount = WechatKfAccount::get_one(['where' =>[['wx_account', '=', $wx_account]]]);
            if ($wxAccount) return return_info(500, '客服微信号已存在,请勿重复添加');
            // 发起请求添加微信客服
            $this->customservice_kfaccount_add([
                "kf_account" => $kf_account,
                "nickname" => $nickname,
            ]);
            // 发起请求为微信客服帐号绑定微信号
            $this->customservice_kfaccount_invite_worker([
                "kf_account" => $kf_account,
                "invite_wx" => $wx_account
            ]);
            // 客服数据入库
            WechatKfAccount::insert(new WechatKfAccount(), [
                'nickname' => $nickname,
                'kf_account' => $kf_account,
                'wx_account' => $wx_account,
                'create_time' => time()
            ]);
        } catch (\Exception $e) {
            exception_file_log($e, "wechat");
            exit("【customserviceKfInviteBind】邀请并绑定客服帐号失败,请到wechat日志文件查看详情");
        }
        return return_info(200, "邀请绑定已发送");
    }

    /**
     * todo 客服接口-发消息
     */
    public function message_custom_send($data = [])
    {
        $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$this->getAccessToken()}";
        return $this->post_http_request($url, $data);
    }

    /**
     * todo 添加客服帐号
     */
    public function customservice_kfaccount_add($accountData = [])
    {
        $url = "https://api.weixin.qq.com/customservice/kfaccount/add?access_token={$this->getAccessToken()}";
        return $this->post_http_request($url, $accountData);
    }

    /**
     * todo 邀请绑定客服帐号
     */
    public function customservice_kfaccount_invite_worker($accountData = [])
    {
        $url = "https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token={$this->getAccessToken()}";
        return $this->post_http_request($url, $accountData);
    }

    /**
     * todo 发起post请求
     */
    public function post_http_request($url, $postData)
    {
        $resultArr = json_decode($this->http_url($url, json_encode($postData, JSON_UNESCAPED_UNICODE)), true);
        if (isset($resultArr["errcode"]) && $resultArr["errcode"] != 0) {
            file_log($resultArr, $this->logFile ?: "HttpRequest");
            throw new \Exception($resultArr['errmsg']);
        }
        return $resultArr;
    }

    /**
     * todo 自定义请求接口函数,$data为空时发起get请求,$data有值时发起post请求
     * @param $url 请求地址
     * @param null $data 请求参数
     * @return mixed
     */
    public function http_url($url, $data = null)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if (!empty($data)) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        $res = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new \Exception("error:" . curl_error($ch));
        }
        curl_close($ch);
        return $res;
    }
}

模型 

<?php
namespace App\Models;
class WechatUserSession extends Model
{
    protected $table = 'wechat_user_sessions';

    const Waiting = 0; // todo 会话等待中
    const Going = 1; // todo 会话执行中
    const End = 2; // todo 会话已结束

    /**
     * todo 根据会话状态获取会话数据
     * @param array $status
     * @param $openId
     * @param null $field
     * @return bool
     */
    public static function getUserSessionByStatus($status = [], $openId, $field = null)
    {
        $param = [];
        $param['whereIn'] = [['status', $status]];
        $param['where'] = [['openid', '=', $openId], ['invalidtime', '>', time()]];
        if ($field) $param['field'] = $field;
        return self::get_one($param);
    }

    /**
     * todo 根据会话状态获取等待接入的会话数量
     */
    public static function getWaitUserSessionCount()
    {
        $param = [];
        $param['whereIn'] = [['status', [self::Waiting]]];
        $param['where'] = [['invalidtime', '>', time()]];
        $param['field'] = 'id';
        $param['opera'] = 'count';
        return self::number_opera($param);
    }

    /**
     * todo 根据会话状态获取等待接入的会话列表
     */
    public static function getWaitUserSessionList($field = null)
    {
        $param = [];
        $param['whereIn'] = [['status', [self::Waiting]]];
        $param['where'] = [['invalidtime', '>', time()]];
        $field ? $param['field'] = $field : true;

        $list = self::get_all($param);
        return $list ? $list : [];
    }
}
<?php
namespace App\Models;
class WechatKfAccount extends Model
{
    public $timestamps = false;
    protected $table = 'wechat_kf_accounts';

    const Waiting = 0; // todo 邀请等待中
    const Agree = 1; // todo 邀请已同意
    const Reject = 2; // todo 邀请已拒绝

    /**
     * todo 根据客服帐号状态获取客服帐号数据
     * @param array $status
     * @param $openId
     * @param null $field
     * @return bool
     */
    public static function getKfAccountByStatus($status = [], $field = null)
    {
        $param = [];
        $param['whereIn'] = [['status', $status]];
        if ($field) $param['field'] = $field;
        return self::get_all($param);
    }
}

 数据表(请根据实际业务定义表结构)

CREATE TABLE `wechat_kf_accounts` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `nickname` varchar(50) DEFAULT '' COMMENT '客服昵称',
  `kf_account` varchar(50) DEFAULT '' COMMENT '客服帐号',
  `kf_openid` varchar(50) DEFAULT '' COMMENT '客服openid(需要手动更新,微信公众号不支持自动获取)',
  `wx_account` varchar(50) DEFAULT '' COMMENT '客服的微信号(发起客服绑定邀请时需要填入)',
  `status` tinyint(1) unsigned DEFAULT '0' COMMENT '邀请状态 0-邀请中 1-已接受 2-已拒绝',
  `create_time` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
  `update_time` int(10) unsigned DEFAULT '0' COMMENT '更新时间',
  `delete_time` int(10) unsigned DEFAULT '0' COMMENT '删除时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='微信客服帐号信息表';

CREATE TABLE `wechat_user_sessions` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `openid` varchar(50) DEFAULT '' COMMENT '用户openid',
  `invalidtime` int(10) unsigned DEFAULT '0' COMMENT '会话过期时间',
  `status` tinyint(1) unsigned DEFAULT '0' COMMENT '会话状态 0-未接入 1-已接入 2-已结束',
  `kf_account` varchar(50) DEFAULT '' COMMENT '开始对接会话的客服帐号',
  `end_kf_account` varchar(50) DEFAULT '' COMMENT '结束对接会话的客服帐号',
  `start_time` int(10) unsigned DEFAULT '0' COMMENT '客服开始接入会话时间',
  `end_time` int(10) unsigned DEFAULT '0' COMMENT '客服结束接入会话时间',
  `created_at` datetime DEFAULT NULL COMMENT '创建时间',
  `updated_at` datetime DEFAULT NULL COMMENT '更新时间',
  `delete_at` datetime DEFAULT NULL COMMENT '删除时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='微信客服会话记录表';

使用步骤

1、调用接口邀请帐号成为客服帐号:/wechat/func/customservice_kf_invite_bind?kf_account=xxx@公众号微信号&wx_account=微信号&nickname=昵称;

2、用户在微信公众号发送信息,推送信息给客服帐号;

3、客服帐号在微信公众平台客服功能web页面登录(微信公众平台客服功能登录),接入会话;

4、用户与客服会话直到结束会话;

感谢阅读,欢迎交流 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东小记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值