thinkphp接入扣子(coze)智能体Ai客服核心代码

控制器Coze.php代码:

<?php

namespace app\home\controller;

use think\Controller;
use app\home\logic\CozeLogic;

class Coze extends Controller
{
    private $cozeLogic;

    /**
     * 析构函数
     */
    function __construct() 
    {
        parent::__construct();
        $this->cozeLogic = new CozeLogic;
    }

    public function kefu()
    {
        // 获取访问令牌access_token
        $tokenData = $this->cozeLogic->get_access_token();
        if (!empty($tokenData['code'])) {
            $users = session('users'); // 获取登录用户信息
            if (empty($users)) { // 未登录,以游客方式显示
                $users['users_id'] = 0;
                $users['head_pic'] = get_head_pic();
                $users['nickname'] = '游客(IP:'.clientIP().')';
            } else { // 已登录
                $nickname = empty($users['nickname']) ? $users['username'] : $users['nickname'];
                $users['nickname'] = $nickname.'(IP:'.clientIP().')';
            }
            // 用于设置对话框中的显示用户信息,包括对话框中的用户头像、用户名等信息。
            $coze_userInfo = [
                'url' => $users['head_pic'],
                'nickname' => $users['nickname'],
            ];
            if (!empty($users['users_id'])) {
                // 用户的 ID,也就是用户在你的网站或应用中的账号 ID。未指定 ID 时,Chat SDK 会根据用户使用的设备随机分配一个用户 ID。
                $coze_userInfo['id'] = strval($users['users_id']);
            }
            // 智能体配置
            $coze_options = [
                'config' => [
                    'botId' => "7469685534995152896", // 智能体 ID
                ],
                // 'componentProps' => [
                //     'title' => "智能客服",
                // ],
                'auth' => [
                    'type' => 'token',
                    'token' => $tokenData['access_token'],
                ],
                'userInfo' => $coze_userInfo,
                'ui' => [
                    'base' => [
                        // 'icon' => 'https://p6-flow-product-sign.byteimg.com/tos-cn-i-13w3uml6bg/78f519713ce46901120fb7695f257c9a.png~tplv-13w3uml6bg-resize:128:128.image?rk3s=2e2596fd&x-expires=1742003492&x-signature=JjvhuPmgyWwFzX7bLM92InFvnXQ%3D',
                        // 'lang' => 'zh-CN',
                        'zIndex' => 1000,
                    ],
                    'header' => [
                        'isShow' => true,
                        'isNeedClose' => true,
                    ],
                    'asstBtn' => [
                        'isNeed' => true, // 悬浮球的显示与隐藏
                    ],
                    'footer' => [
                        'isShow' => true,
                        'expressionText' => 'Powered by © {{name}} {{name1}} 版权所有',
                        'linkvars' => [
                            'name' => [
                                'text' => '2016-2024',
                            ],
                            'name1' => [
                                'text' => 'Coze',
                                'link' => 'https://www.coze.cn',
                            ],
                        ],
                    ],
                    'chatBot' => [
                        'title' => "智能客服",
                        'uploadable' => true,
                        'width' => 400,
                    ],
                ],
            ];
            $data = [
                'users' => $users,
                'coze_options' => $coze_options,
            ];
            $this->success('成功', null, $data);
        }
        $this->error($tokenData['msg']);
    }

    public function get_access_token()
    {
        $tokenData = $this->cozeLogic->get_access_token();
        if (!empty($tokenData['code'])) {
            $this->success('成功', null, ['access_token'=>$tokenData['access_token']]);
        }
        $this->error($tokenData['msg']);
    }
}

业务层CozeLogic.php代码:

<?php

namespace app\home\logic;

class CozeLogic
{
    public function get_access_token()
    {
        $apiUrl = "https://api.coze.cn/api/permission/oauth2/token";
        $postData = [
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', // 固定值
            'duration_seconds' => 86399, // 申请的 AccessToken 有效期,单位为秒,默认 900 秒,即 15 分钟。最大可设置为 86399 秒,即 24 小时。
        ];
        $headers = [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->get_jwt()
        ];
        $response = $this->httpRequest($apiUrl, 'POST', json_encode($postData, JSON_UNESCAPED_UNICODE), $headers, 300);
        $result = json_decode($response, true);
        if (!empty($result['access_token'])) {
            return [
                'code' => 1, 
                'access_token' => $result['access_token'],
            ];
        }

        return [
            'code' => 0, 
            'msg' => empty($result['error_message']) ? "获取访问令牌失败" : "获取访问令牌失败,错误码: {$result['error_code']},错误信息: {$result['error_message']}",
        ];
    }

    private function get_jwt()
    {
        // OAuth 应用的公钥指纹
        $public_key_id = 'OAuth应用的公钥指纹';
        // OAuth 应用的 ID,必须是字符串类型
        $app_id = '应用的ID';
        // Header 参数
        $header = [
            'alg' => 'RS256', // 固定为RS256
            'typ' => 'JWT', // 固定为 JWT
            'kid' => strval($public_key_id) // OAuth 应用的公钥指纹
        ];
        /**
         * 访问令牌的会话标识。目前仅限在会话隔离场景下使用,即将 session_name 指定为用户在业务侧的 UID,以此区分不同业务侧用户的对话历史。
         * 若未指定 session_name,不同用户的对话历史可能会掺杂在一起。
         */
        $session_name = cookie('coze_session_name');
        if (empty($session_name) && session('?users_id')) {
            $session_name = 'coze_' . strval(session('users_id')) . session('users.username'); // 根据业务改成等于用户的uid
        }
        if (empty($session_name)) {
            $session_name = session_id();
        }
        cookie('coze_session_name', $session_name);
        // Payload 参数
        $payload = [
            'iss' => strval($app_id), // OAuth 应用的 ID
            'aud' => 'api.coze.cn', //扣子 API 的Endpoint
            'iat' => time(), // JWT开始生效的时间,秒级时间戳
            'exp' => time() + 86400, // JWT过期时间,秒级时间戳
            'jti' => (string) $this->get_rand_str(48, 0, 0), // 随机字符串,防止重放攻击
            'session_name' => strval($session_name),  // 必须是字符串类型
        ];
        $encodedHeader = $this->base64url_encode(json_encode($header));
        $encodedPayload = $this->base64url_encode(json_encode($payload));
        $signature = '';
        $data = $encodedHeader . '.' . $encodedPayload;

        // 私钥,改成自己的私钥
        $private_key = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDi5cjkJd0ni5tq\nzochBDgyjyenp4OZSQtMZz/8jl9LS+vdg5cfSFub06s7g6BtRJI/AgFmGlfcz3Jg\ni0FeJ0Rx/6i4omKCV+CrhO4yg440JtYnvGmV8iXdQrjbtJSQmjgn8q5XhOu5iIxf\nc4lYWpwOEKKyltBLO6rRfAyU6UYo6/Dd30u/IXQ7Ycq2wdaX9dgkpnJSHiOCJ4ur\nvv6cl11li8OiBSQXOZ/S+MVclLf3dKUOOD43Lo9t0tZ5AQsTYwzAfSJuOpSi3Dq7\nXasaFYxez2ocaOnvXzCXtxeR2/ps5WLpMcOtVtqDGNbyTbyw74+BdYxzhVz92ZP0\nRdFEDU8fAgMBAAECggEAapOTlrLAXCRj1rn22AfS29DJSCNTmaspPvSapic71LLv\nMefvErcarKmymyxbLSgR572p6YtDLQlWCMowZKjeKgvz9PH1gpMDiZ1Rg3Gu2IlL\ne3OxRsjshy8hhLWgOQHvkbgYsyxQyx+8C9PJtjItbh/bRDa51TTMKkTx5fpixbIX\nUaGFf/cB+ubWV11kCneSO+NvMEP97nO1XgTgXanmcUfs4kQ07T9pthWc3tIhQ5E5\nAAVj05IzqlOjjDQIzqMwARTLfURmqCTs3CCDGwUAlC+QKn7IXKxOPYvxdFaAJ1gU\nyVpYAHa8DOrpeEZAwYbzHZ/icXUn2UiUBe6jkOEJVQKBgQDyF2P4S0N1HZU7NcBX\n6iD/JiGQFr7VRqWV997ypm+UQJgt6kbluQ68RarCuxdog6yfx25tyU+xHRle4s8u\nZb04XnIky8pxFWTvfOu739sbd6jnymwm6oDG/aKad6AVJyBLEnOJgOMUaaUqrOgZ\n0wW6e+z12YilbsOhJzF9P4KACwKBgQDv7u265sEvi9TbqVdjj686VtOpcpgZW993\nzosD3p5W+6Rkmuw8BNdMrBWQmKQgELPx47ozrsPvQ224eHbAvBFpBPMUzYMt5+QR\nrMwUXOjs5SMBSmhZU0r0naI3b5D4RaW5OgQVhIbMAKuIjTvydmWBI+VSL5gBiBJZ\nYoG136e1vQKBgQDeDEQ+hlH+Rr8NCvU77dyMCp54OdSZbG4OisasS6pGEaVDYAHd\nTQrN9xxz9gS3cSbRequ3+RCmRCl0FZawWNLgtAf/5gfuBxCIdU0HCoE+xVdgGxNt\n8mNhoVwWUCi9niCJmwfsFBK0M14yMVvF6/7yGB61NOyXyGuZEYjb93esFwKBgQDq\nfjZDq1D1mhEf2gI5oO0d9EwLWJ8EccEWw3sj/gf8dRY26R6mjgipu6mb10LYv9pO\nN0X9SvndtqXeW4WcWnIdlKPKGgzx6agCCkKuSBuxo0Qx97nCS2B/cCxhR52Et/T+\n8Y+aRBrF+IWtAHOtRT9xFlcCmtdR794XE+o3iW3MdQKBgD/Y8LRuMn3fET7urLJi\neluWAsSuJVS2qc8VPx7a7v288fUoS99t88GzzOQ7dXn+fwD1op9ylyC3jAgZInh6\nrwQa9fcA3JrQihgCFNsWfH5sZfP1uMfnlJ67k+70kUVZzyWeIrw48FEasV80EWbV\nLRqCfMEPknRNgGABLFOJKeOz\n-----END PRIVATE KEY-----";

        openssl_sign($data, $signature, $private_key, 'sha256WithRSAEncryption');
        $encodeSignature = $this->base64url_encode($signature);

        return $encodedHeader . '.' . $encodedPayload . '.' . $encodeSignature;
    }

    private function base64url_encode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    /**
     * 获取随机字符串
     * @param int $randLength 长度
     * @param int $addtime 是否加入当前时间戳
     * @param int $includenumber 是否包含数字
     * @return string
     */
    private function get_rand_str($randLength = 6, $addtime = 1, $includenumber = 0)
    {
        if (1 == $includenumber) {
            $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
        } else if (2 == $includenumber) {
            $chars = '123456789';
        } else if (3 == $includenumber) {
            $chars = 'ABCDEFGHJKLMNPQEST123456789';
        } else {
            $chars = 'abcdefghijklmnopqrstuvwxyz';
        }
        $len     = strlen($chars);
        $randStr = '';
        for ($i = 0; $i < $randLength; $i++) {
            $randStr .= $chars[rand(0, $len - 1)];
        }
        $tokenvalue = $randStr;
        if ($addtime) {
            $tokenvalue = $randStr . time();
        }
        return $tokenvalue;
    }

    /**
     * CURL请求
     *
     * @param $url 请求url地址
     * @param $method 请求方法 get post
     * @param null $postfields post数据数组
     * @param array $headers 请求header信息
     * @param bool|false $debug 调试开启 默认false
     * @return mixed
     */
    private function httpRequest($url, $method = "GET", $postfields = null, $headers = array(), $timeout = 30, $debug = false)
    {
        $method = strtoupper($method);
        $ci     = curl_init();
        /* Curl settings */
        // curl_setopt($ci, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); // 使用哪个版本
        curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
        curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */
        // curl_setopt($ci, CURLOPT_TIMEOUT, 7); /* 设置cURL允许执行的最长秒数 */
        curl_setopt($ci, CURLOPT_TIMEOUT, $timeout); /* 设置cURL允许执行的最长秒数 */
        curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
        switch ($method) {
            case "POST":
                curl_setopt($ci, CURLOPT_POST, true);
                if (!empty($postfields)) {
                    if (is_string($postfields) && preg_match('/^([\w\-]+=[\w\-]+(&[\w\-]+=[\w\-]+)*)$/', $postfields)) {
                        parse_str($postfields, $output);
                        $postfields = $output;
                    }
                    if (is_array($postfields)) {
                        $tmpdatastr = http_build_query($postfields);
                    } else {
                        $tmpdatastr = $postfields;
                    }
                    curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr);
                }
                break;
            default:
                curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */
                break;
        }

        $ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE;
        curl_setopt($ci, CURLOPT_URL, $url);
        if ($ssl) {
            curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
            curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在
            // curl_setopt($ci, CURLOPT_SSLVERSION, 4);   //因为之前的POODLE 病毒爆发,许多网站禁用了sslv3(nginx默认是禁用的,ssl_protocols 默认值为TLSv1 TLSv1.1 TLSv1.2;),最新使用sslv4
        }
        //curl_setopt($ci, CURLOPT_HEADER, true); /*启用时会将头文件的信息作为数据流输出*/
        if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
            curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1);
        }
        curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/
        curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ci, CURLINFO_HEADER_OUT, true);
        /*curl_setopt($ci, CURLOPT_COOKIE, $Cookiestr); * *COOKIE带过去** */
        $response    = curl_exec($ci);
        $requestinfo = curl_getinfo($ci);
        $http_code   = curl_getinfo($ci, CURLINFO_HTTP_CODE);
        if ($debug) {
            echo "=====post data======\r\n";
            var_dump($postfields);
            echo "=====info===== \r\n";
            print_r($requestinfo);
            echo "=====response=====\r\n";
            print_r($response);
        }
        curl_close($ci);
        return $response;
        //return array($http_code, $response,$requestinfo);
    }
}

web前端代码:

<!DOCTYPE html>
<html>
<head>
    <title>thinkphp接入扣子(coze)智能体Ai客服</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script>
</head>
<body>
    <script src="https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.1.0-beta.3/libs/cn/index.js"></script>
    <script>
        $(document).ready(function(){
            coze_kefu();
            function coze_kefu()
            {
                $.ajax({
                    type : 'get',
                    url : "{:url('home/Coze/kefu')}",
                    data : {},
                    dataType : 'json',
                    success : function(res){
                        if (res.code == 1) {
                            var options = res.data.coze_options;
                            var users = res.data.users;
                            const cozeWebSDK = new CozeWebSDK.WebChatClient({
                                config: options.config,
                                // componentProps: options.componentProps,
                                auth:  {
                                    // Authentication methods, the default type is 'unauth', which means no authentication is required; it is recommended to set it to 'token', indicating authentication through PAT (Personal Access Token) or OAuth
                                    type: options.auth.type,    
                                    // When the type is set to 'token', it is necessary to configure a PAT (Personal Access Token) or OAuth access token for authentication.
                                    token: options.auth.token,
                                    // When the access token expires, use a new token and set it as needed.
                                    onRefreshToken: async () => {
                                        $.ajax({
                                            type : 'get',
                                            url : "{:url('home/Coze/get_access_token')}",
                                            data : {},
                                            dataType : 'json',
                                            success : function(ref_res){
                                                if (ref_res.code == 1) {
                                                    if (ref_res.data && ref_res.data.access_token) {
                                                        return ref_res.data.access_token;
                                                    } else {
                                                        console.log('Response does not contain access_token');
                                                    }
                                                } else {
                                                    console.log(ref_res.msg);
                                                }
                                                return '';
                                            },
                                            error: function(e){
                                                console.log(e.responseText);
                                                return '';
                                            }
                                        });
                                    },
                                },
                                userInfo: options.userInfo,
                                ui: {
                                    base: options.ui.base,
                                    header: options.ui.header,
                                    asstBtn: options.ui.asstBtn,
                                    footer: options.ui.footer,
                                    chatBot: {
                                        title: options.ui.chatBot.title,
                                        uploadable: options.ui.chatBot.uploadable,
                                        width: options.ui.chatBot.width, // PC 端窗口的宽度,单位为 px,默认为 460。
                                        el: undefined,
                                        // el: document.getElementById('position_demo'),
                                        onHide: () => { // 当聊天框隐藏的时候,会回调该方法。
                                            console.log('onHide')
                                            // todo...
                                        },
                                        onShow: () => { // 当聊天框显示的时候,会回调该方法。
                                            console.log('onShow')
                                            // todo...
                                        },
                                        onBeforeShow: () => { // 聊天框显示前调用,如果返回了 false,则不会显示。支持异步函数。
                                            // return false;
                                            console.log('onBeforeShow')
                                            // todo...
                                        },
                                        onBeforeHide: () => { // 聊天框隐藏前调用,如果返回了 false,则不会隐藏。支持异步函数。
                                            // return false;
                                            console.log('onBeforeHide')
                                            // todo...
                                        },
                                    },
                                },
                            });
                        } else {
                            console.log(res.msg);
                        }
                    },
                    error: function(e){
                        console.log(e.responseText);
                    }
                });
            }
        });
    </script>
</body>
</html>

如果涉及到用户登录,还需要在退出逻辑加上清除代码:

session('coze_session_name', null);
cookie('coze_session_name', null);

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值