关于企微群聊天工具功能的开发---PHP+JS+CSS+layui (手把手教学)

文章目录

前言

公司要求开发企微群聊天工具。首先一个客户一个群,其余群成员都是公司销售、设计师、工长、售后等人员。要求开发一个群聊天工具,工长点击进来以后就可以看到群内客户的合同信息,可以上传每天的施工进度等等。

实现思路:获取当前群聊id,将群聊id和客户合同id进行关联。有了合同id,什么就都有了。

本人是后端人员,但是公司要让我前后端都搞。我也没办法,只能用几年前的layui和最基本的js。

准备工作
  1. 登录到企业微信管理后台官网
  2. 应用管理-应用-自建-创建应用
  3. 复制保存好应用的AgentId和Secret。
  4. 配置到聊天工具栏
  5. 配置应用最开始进入时候加载的页面
  6. 配置企业可信IP、启用JS-SDK工具
  7. 找到我的企业-企业ID
  8. 客户与上下游-点击API-配置可调用接口的应用-选择自己刚才的创建应用。让它有调用客户信息的权限。
    准备工作完成!接下来就是代码示例!
PHP代码示例
php 复制代码
function getToken(){
	//获取access_token
   	$url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" . $company_id . "&corpsecret=" . $secret;
   	$result = sendCurlRequest($url);

    if ($result['errcode'] == 0) {
        return ajax_return(200, '获取成功', $result);
    }
    return ajax_return(500, 'token获取失败');
}
//这个方法很重要  验签!
function getJsApiTicket(){
	$access_token = $_REQUEST['access_token'];
    //获取企业的jsapi_ticket--这个没用到
//    $url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=".$access_token;
    //获取应用内的jsapi_ticket
    $url = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=" . $access_token . "&type=agent_config";
    $result = sendCurlRequest($url);

    if ($result['errcode'] == 0) {

        $result['company_id'] = $company_id;
        $result['agentid'] = $agentid;
        $result['timestamp'] = $time;
        $result['nonceStr'] = $nonceStr;
        $str = "jsapi_ticket=" . $result['ticket'] . "&noncestr=" . $nonceStr . "&timestamp=" . $time . "&url=" . $sign_url;
        $result['signature'] = sha1($str);
        $result['url'] = $sign_url;
        return ajax_return(200, '获取成功', $result);
    }
    return ajax_return(500, 'jsapi_ticket获取失败', $result);
}

//返回json
function ajax_return($code, $msg, $data = [])
{
    exit(json_encode(
        [
            'code' => $code,
            'msg' => $msg,
            'data' => $data
        ]
    ));
}

//发送curl请求
function sendCurlRequest($url, $data = array(), $method = 'GET')
{
    $ch = curl_init();//1.初始化
    curl_setopt($ch, CURLOPT_URL, $url);//2.请求地址
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);//3.请求方式
    //4.参数如下
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    if ($method == "POST") {//5.post方式的时候添加数据
        $data = json_encode($data);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $output = curl_exec($ch);
    curl_close($ch);
    return json_decode($output, true);
}
//生成随机16位的字符串
function generateRandomString($length = 16)
{
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';

    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }

    return $randomString;
}
前端代码示例 主要是js
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Document</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/layui/2.5.7/css/layui.css" media="all">
</head>
<body>

<div class="layui-container" style="padding: 10px">
    <div class="layui-row">
        <div class="layui-col-xs12">
            <div id="search" style="display: none">
                <div class="layui-input-inline" style="display: flex;justify-content: space-between;">
                    <input type="text" id="searchInput" name="title" required lay-verify="required"
                           placeholder="请输入合同号" autocomplete="off" class="layui-input">
                    <button class="layui-btn" id="searchBtn" style="margin-left: 10px;">搜索</button>
                </div>
            </div>

            <div id="content" style="display: none">
                <div class="layui-input-inline" style="display: flex;justify-content: space-between;margin-top: 10px">
                    <button class="layui-btn" id="contractDetails">合同详情</button>
                    <button class="layui-btn" id="broadcast">施工播报</button>
                    <button class="layui-btn" id="acceptLog">验收日志</button>
                </div>
                <div class="layui-input-inline" style="display: flex;justify-content: space-between;margin-top: 10px">
                    <button class="layui-btn" id="complaint">客诉整改</button>
                    <button class="layui-btn" id="satisfaction">满意度评价</button>
                    <button class="layui-btn" id="change">变更管理</button>
                </div>
            </div>

            <input type="hidden" id="access_token" name="access_token" class="layui-input">
            <input type="hidden" id="group_id" name="group_id" value="" class="layui-input">
        </div>
    </div>
    <div id="myTable">
        <table class="layui-table" id="simple" lay-filter="simple"></table>
    </div>

    <script type="text/html" id="barDemo">
        <div class="layui-btn-container">
            <button class="layui-btn layui-btn-sm" lay-event="bind">绑定</button>
        </div>
    </script>
</div>


</body>
<script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/layui/2.5.7/layui.js"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
<script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js" referrerpolicy="origin"></script>
<script>

    //定义参数
    let access_token = "";

    $(document).ready(function () {
        let group_id = window.sessionStorage.getItem('group_id')
        if(!group_id){
            //获取token
            getAccessToken();
        }else{
            checkBandContract(group_id)
        }
    })

    // 获取AccessToken,这通常需要企业微信的CorpID和Secret
    function getAccessToken() {
        $.ajax({
            url: "http://",
            type: "GET",
            dataType: 'json',
            contentType: 'application/json',
            success: function (res) {
                $('#access_token').val(res.data.access_token)
                access_token = res.data.access_token
                console.log('access_token:',access_token)
                //获取企业的jsapi_ticket
                getJsApiTicket(access_token);
            },
            error: function (e) {
                console.log(e)
            }
        })

    }

    //获取ticket以及群id
    function getJsApiTicket(access_token){
        $.ajax({
            url: "http://getJsApiTicket", //对应php代码中的getJsApiTicket方法
            type: "GET",
            dataType: 'json',
            contentType: 'application/json',
            data: {
                access_token:access_token,
            },
            success: function (res) {

                wx.agentConfig({
                    beta:true,
                    debug:true,
                    corpid: res.data.company_id, // 必填,企业微信的corpid,必须与当前登录的企业一致
                    agentid: res.data.agentid, // 必填,企业微信的应用id (e.g. 1000247)
                    timestamp: res.data.timestamp, // 必填,生成签名的时间戳
                    nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
                    signature: res.data.signature, // 必填,签名,见附录-JS-SDK使用权限签名算法
                    jsApiList: ['getCurExternalChat'], //必填,传入需要使用的接口名称
                    success: function (res) {

                        wx.invoke('getCurExternalChat', { }, function (res) {
                            if (res.err_msg === 'getCurExternalChat:ok') {
                                let group_id = res.chatId //返回当前外部群的群聊ID
                                //判断当前群聊是否绑定了合同
                                checkBandContract(group_id)
                                //存入到session
                                window.sessionStorage.setItem('group_id',res.chatId)
                            } else {
                                //错误处理
                                console.log(888888)
                            }
                        })
                        // 回调
                    },
                    fail: function (res) {
                        if (res.err_msg.indexOf('function not exist') > -1) {
                            alert('版本过低请升级')
                        }
                        alert('获取企微群id失败')
                    },
                })

            },
            error: function (e) {
                console.log(e)
            }

        })
    }

    //判断当前群聊是否绑定了合同
    function checkBandContract(group_id){
        $.ajax({
            url: "http://",
            type: "GET",
            dataType: 'json',
            data:{
                group_id:group_id
            },
            contentType: 'application/json',
            success: function (res) {
                if(res.data == 1){
                    //未绑定
                    $('#search').css('display','block')
                }else if(res.data == 2){
                    //已绑定
                    $('#content').css('display','block')
                }else{
                    layer.msg('系统出错!请联系管理员')
                }
            },
            error: function (e) {
                console.log(e)
            }
        })
    }

    layui.use(['form', 'layer','table'], function () {
        let layer = layui.layer;
        let table = layui.table;

        //搜索点击事件
        $('#searchBtn').on('click', function () {
            let searchText = $('#searchInput').val();

            $.ajax({
                url: "http",
                type: "GET",
                dataType: 'json',
                data:{
                    sn:searchText
                },
                contentType: 'application/json',
                success: function (res) {
                    if(res.code === 200){
                        // getTableData(res.data)
                        table.render({
                            elem: '#simple',
                            cellMinWidth: 80,  // 全局定义常规单元格的最小宽度,layui2.2.1新增
                            cols: [[
                                // 表头,对应数据格式,此示例只设置3格
                                // 若不填写width列宽将自动分配
                                {field: 'cid', title: 'ID', width: 80, fixed: 'left'},
                                {field: 'sn', title: 'SN', width: 120},
                                {field: 'customer_name', title: '姓名', width: 80},
                                {field: 'address', title: '地址', width: 220},
                                {title: '操作', toolbar: "#barDemo"}
                            ]],
                            // 默认不开启分页(即false),若要开启,设置:
                            page: true,
                            // 若不想通过url获取数据也可用data设置赋值已知数据
                            data: res.data,
                            toolbar:'#barDemo'
                        });
                        console.log('tableData:',res)
                    }else{
                        layer.msg(res.msg);
                    }
                },
                error: function (e) {
                    console.log(e)
                }
            })
        });

        //工具列点击事件
        table.on('tool(simple)', function (obj) {
            let event = obj.event;
            if (event === 'bind') {
                let id = obj.data.cid;
                let group_id = window.sessionStorage.getItem('group_id')
                console.log('group_id:',group_id)
                if (id === '' || id === 'undefined') {
                    layer.msg('id不能为空');
                    return;
                }
                if (group_id === '' || group_id === 'undefined') {
                    layer.msg('group_id不能为空');
                    return;
                }

                layer.confirm('绑定后不可更改,确定绑定吗?', {
                    btn: ['确定', '取消'] //按钮
                }, function () {
                    $.post('http', {
                        id: id,
                        group_id: group_id
                    }, function (data) {
                        if (data.code == 200) {
                            layer.msg(data.msg);
                            setTimeout(function () {
                                $('#myTable').css('display','none')
                                $('#search').css('display','none')
                                $('#content').css('display','block')
                            }, '1000');
                        } else {
                            layer.msg(data.msg);
                        }
                    }, 'JSON');
                });
            }
        });

        //合同详情
        $('#contractDetails').on('click', function () {
            let group_id = window.sessionStorage.getItem('group_id')

            $.ajax({
                url: "http:",
                type: "GET",
                dataType: 'json',
                data:{
                    group_id:group_id
                },
                contentType: 'application/json',
                success: function (res) {
                    if(res.code === 200){
                        console.log('res===',res)
                        window.location.href = "";
                    }else{
                        console.log('res=====',res)
                        layer.msg(res.msg);
                    }
                },
                error: function (e) {
                    console.log(e)
                }
            })
        });

        //施工播报

        //验收日志
        
        //客诉整改
        
        //变更管理
        

    });



</script>
</html>
踩的小坑&笔记
  1. html文件必须在企微环境下运行,不然报错 "wx.invoke is not a function"。(这个很恶心,搞了大半天才知道。)
  2. 企微错误码查询工具
  3. 官方文档的提示:请确保在服务上线前一定全局缓存access_token和jsapi_ticket ,两者有效期均为7200秒(以返回结果中的expires_in为准),否则一旦上线触发频率限制,服务将不再可用。
  4. 官方文档中的:常见错误以及解决方法
  5. 客户端调试工具 这个很重要!!!
  6. 当点击页面中某个按钮后,企微会自动登录然后跳转到首页。这时候需要修改企微登录的逻辑。(这是我当前项目中的逻辑,不适用于每一个人!
  7. 一定要先执行wx.agentConfig验签通过后才可以调用wx.invoke.
  8. 要注意,获取群列表的时候,只可以获取当前应用下的群列表!
  9. 在不同应用中获取的同一个群的id也是不同的!(不太确定,记得在文档中看到这句话了)。
最终达成的效果

每一个都是一个自建应用。在手机上显示的话,就会显示到聊天窗口的上面。

总结

其实这玩意就是企微内嵌了一个浏览器。

还是等于是页面开发。

主要就是验签的那一步,其实也没啥。

就是一开始不知道在企微还得下载一个安装包才可以进行调试。

能够进行调试以后,就一马平川了。

相关推荐
dsyyyyy11013 小时前
JavaScript变量
开发语言·javascript·ecmascript
kyriewen4 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
胡志辉的博客5 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖5 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
dog2505 小时前
网络长尾延时的重尾本质
开发语言·网络·php
懂懂tty5 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js
小二·6 小时前
Next.js 15 全栈开发实战
开发语言·javascript·ecmascript
Rain5097 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
拾年2758 小时前
从零手写 Ajax:用原生 XHR 搭建前后端交互全流程
前端·javascript·ajax
拉勾科研工作室9 小时前
区块链工程毕业论文题目【249个】
开发语言·javascript