职贝云数AI新零售门户

标题: 手把手教你部署Openclaw,打通企业微信与您的智能助手聊天 [打印本页]

作者: OZQ    时间: 8 小时前
标题: 手把手教你部署Openclaw,打通企业微信与您的智能助手聊天
还在愁企业微信没有专属智能聊天小助手?教你疾速部署小龙虾 Openclaw,轻松完成企业微信与智能助手的无缝对接,日常办公咨询、音讯回复全搞定,全程步骤明晰,新手也能跟着做!后期预备

一台云服务器(某云平台即可),系统选择 Linux,确保服务器可正常联网并能停止端口和面板配置;

企业微信管理员权限,可进入运用管理停止新增运用、配置服务器等操作;

MiniMax 账号(用于获取智能接口密钥),官网注册即可,新用户自带 15 元体验额度,足够后期测试运用。
核心部署步骤:先搞定 Openclaw 小龙虾基础环境

步骤 1:云服务器基础配置,开放必备端口

购买 Linux 云服务器后,首先进入服务器防火墙设置,开放 18789 端口(小龙虾 Openclaw 的核心服务端口)和3000 端口(后续企业微信音讯转发接口端口),两个端口缺一不可,需确保为入站规则开放,避免后续衔接失败。步骤 2:安装 1panel 面板,部署 Openclaw 核心程序

在云服务器上安装 1panel 管理面板(安装命令可参考 1panel 官方文档,Linux 系统一键安装即可),安装完成后经过服务器 IP + 面板端口进入管理后台;

打开 1panel 的运用商店,搜索Openclaw,选择最新版本(当前为 2026.2.9)停止一键安装,面板会自动完成容器化部署,无需手动配置基础环境。
步骤 3:配置 MiniMax 密钥,启动 Openclaw 容器

登录 MiniMax 官网,在个人中心找到接口密钥,复制备用;

前往 1panel,进入 Openclaw 的容器管理页面,找到容器挂载的目录,在环境变量配置项中粘贴复制的 MiniMax 接口密钥;

保存配置后启动 Openclaw 容器,等待启动完成后,在阅读器输入http://[你的服务器IP]:18789,即可进入 Openclaw 的聊天和管理界面,基础的小龙虾智能助手就可以用啦!
步骤 4:验证 Openclaw 部署,完成核心衔接

进入 Openclaw 容器目录,找到openclaw.json文件,打开后查找并复制外面的token值;

回到http://[你的服务器IP]:18789的管理界面,点击Overview选项,在 token 输入框中粘贴复制的内容,点击衔接;

若界面Status显示为Connected,阐明 Openclaw 小龙虾部署成功,此时可直接在该界面与小龙虾停止智能聊天测试。
关键对接步骤:打通企业微信,完成音讯互通

部署好基础的 Openclaw 后,接上去我们将其与企业微信对接,让企业微信里的同事都能经过专属小助手和小龙虾聊天,核心是配置音讯中转服务,完成企业微信与 Openclaw 的音讯双向转发。步骤 5:企业微信创建专属小龙虾小助手,配置服务器回调

登录企业微信管理后台,进入运用管理,点击创建运用,运用称号填写 “小龙虾小助手”,上传图标后完成创建;

进入刚创建的小龙虾小助手运用概况页,找到Api 接收音讯服务器配置;

配置项中填写服务器地址:http://[你的服务器IP]:3000/wework/callback,其他项暂时默许,点击保存;

保存后会随机生成Token和EncodingAESKey,务必记录上去;同时在企业微信后台找到企业的corpId、该运用的agentId和secret,全部备用,后续配置会频繁用到。

配置IP 白名单:将云服务器的公网 IP 添加到运用的 IP 白名单中,否则企业微信无法向服务器推送音讯。
步骤 6:创建音讯转发文件,配置对接参数

前往云服务器,进入 Openclaw 容器的openclaw/data/workspace/目录;

新建文件wework-openclaw.js,该文件是企业微信与 Openclaw 的音讯中转核心,需在文件中填写以下配置参数,将之前记录的信息对应交换:
openclaw: { wsUrl: 'ws://127.0.0.1:18789/', token: '[你的openclaw Token]', sessionKey: 'agent:main:wechat' },wechat: { corpId: '[corpId]', agentId: '[agentId]', secret: '[secret]', token: '[token]', aesKey: '[aesKey]' }, const express = require('express');const WebSocket = require('ws');const crypto = require('crypto');const axios = require('axios');const app = express();let ws = null, wsHandshakeDone = false, accessToken = '', tokenExpireTime = 0;let runHandlers = {};function wechatDecrypt(text) {  try {    const aesKey = Buffer.from(CONFIG.wechat.aesKey + '=', 'base64');    const encrypted = Buffer.from(text, 'base64');    const decipher = crypto.createDecipheriv('aes-256-cbc', aesKey, encrypted.slice(0, 16));    decipher.setAutoPadding(false);    let decrypted = decipher.update(encrypted.slice(16)) + decipher.final('binary');    const pad = decrypted.charCodeAt(decrypted.length - 1);    if (pad < 1 || pad > 32) pad = 0;    decrypted = decrypted.slice(0, decrypted.length - pad);    const len = decrypted.charCodeAt(0) | (decrypted.charCodeAt(1) << 8) | (decrypted.charCodeAt(2) << 16) | (decrypted.charCodeAt(3) << 24);    return decrypted.slice(4, 4 + len).toString();  } catch (e) { return null; }}function parseWeChatXML(xml) {  const msgType = xml.match(/<MsgType[^>]*><!\[CDATA\[([^\]]+)\]\]><\/MsgType>/);  const fromUser = xml.match(/<FromUserName[^>]*><!\[CDATA\[([^\]]+)\]\]><\/FromUserName>/);  const content = xml.match(/<Content[^>]*><!\[CDATA\[([^\]]+)\]\]><\/Content>/);  return { msgType: msgType?.[1]?.trim()||'', fromUser: fromUser?.[1]?.trim()||'', content: content?.[1]?.trim()||'' };}function connectOpenClaw() {  console.log('🔌 衔接 OpenClaw...');  ws = new WebSocket(CONFIG.openclaw.wsUrl);  ws.on('open', () => {    console.log('✅ 已衔接');  });  ws.on('message', (data) => {    try {      const raw = data.toString();      if (raw.includes('res') || raw.includes('event')) {        console.log('📥 WS音讯:', raw.slice(0, 150));      }      const msg = JSON.parse(raw);      // 认证      if (msg.event === 'connect.challenge') {        ws.send(JSON.stringify({ type: 'req', method: 'connect', id: '1', params: { minProtocol: 1, maxProtocol: 3, client: { id: 'cli', displayName: 'WeChat', version: '1.0.0', platform: 'node', mode: 'cli' }, auth: { token: CONFIG.openclaw.token }, scopes: ['operator.admin'] } }) + '\n');      }      else if (msg.type === 'res' && msg.id === '1' && msg.ok) {        console.log('✅ 认证成功');        wsHandshakeDone = true;      }      // 流式音讯 - 打印一切 agent 事情      else if (msg.type === 'event' && msg.event === 'agent') {        console.log('📥 Agent事情:', JSON.stringify(msg.payload).slice(0, 200));        if (runHandlers[msg.payload?.runId]) {        // 支持多种格式的文本提取        const text = msg.payload?.data?.text || msg.payload?.data?.delta || '';        if (text) {          runHandlers[msg.payload.runId].reply += text;          console.log('📝 累积文本:', text.slice(0, 50));        }        if (msg.payload?.stream === 'stop' || msg.payload?.data?.phase === 'end') {          const h = runHandlers[msg.payload.runId];          console.log('📬 完成,回复:', h?.reply?.slice(0, 100));          if (h) { h.resolve(h.reply); delete runHandlers[msg.payload.runId]; }        }        }      }      // 呼应 - 保存 runId,不删除 id版本的 handler      else if (msg.type === 'res' && msg.id) {        console.log('📥 收到呼应, msg.id:', msg.id, ', 能否有handler:', !!runHandlers[msg.id], ', payload:', JSON.stringify(msg.payload).slice(0, 100));        if (runHandlers[msg.id]) {          clearTimeout(runHandlers[msg.id].timeout);          const runId = msg.payload?.runId;          if (runId) {            runHandlers[runId] = runHandlers[msg.id];  // 用 runId 关联 handler            console.log('✅ 已关联 runId:', runId);          }          // 不要删除 id 版本的 handler,由于后续能够还会用到        }      }    } catch (e) {}  });  ws.on('error', (err) => console.error('WS 错误:', err.message));  ws.on('close', () => { console.log('🔌 断开,3秒后重连'); wsHandshakeDone = false; setTimeout(connectOpenClaw, 3000); });}function sendToOpenClaw(question) {  return new Promise((resolve) => {    if (!wsHandshakeDone) return resolve('OpenClaw 未衔接');    const id = crypto.randomUUID();    let reply = '';    const timeout = setTimeout(() => {      delete runHandlers[id];      resolve(reply || '请稍后再试');    }, 15000);    runHandlers[id] = { resolve, reply, timeout };    // 用 id 作为 idempotencyKey,这样呼应会用 id 作为回复 id    ws.send(JSON.stringify({ type: 'req', method: 'chat.send', id, params: { sessionKey: CONFIG.openclaw.sessionKey, message: question, idempotencyKey: id } }) + '\n');    console.log('📤 已发送, request id:', id);  });}async function getAccessToken() {  if (Date.now() < tokenExpireTime) return accessToken;  const res = await axios.get(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${CONFIG.wechat.corpId}&corpsecret=${CONFIG.wechat.secret}`);  if (res.data.errcode !== 0) throw new Error(res.data.errmsg);  accessToken = res.data.access_token;  tokenExpireTime = Date.now() + (res.data.expires_in - 60) * 1000;  return accessToken;}async function sendToWeChat(userId, content) {  const token = await getAccessToken();  const res = await axios.post(`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${token}`, { touser: userId, msgtype: 'text', agentid: CONFIG.wechat.agentId, text: { content }, safe: 0 }, { timeout: 5000 });  if (res.data.errcode !== 0) throw new Error(res.data.errmsg);  console.log('✅ 已回复\n');}app.use(express.raw({ type: 'text/xml' }));app.use(express.raw({ type: 'application/xml' }));app.get('/wework/callback', (req, res) => res.send('success'));app.post('/wework/callback', async (req, res) => {  console.log('📨 收到回调, body:', req.body?.toString?.()?.slice(0, 200));  try {    const bodyStr = req.body?.toString?.() || '';    if (!bodyStr.includes('<Encrypt>')) return res.send('success');    const match = bodyStr.match(/<Encrypt[^>]*><!\[CDATA\[([^\]]+)\]\]><\/Encrypt>/);    if (!match) return res.send('success');    const xml = wechatDecrypt(match[1]);    if (!xml) return res.send('success');    const msg = parseWeChatXML(xml);    if (!msg || msg.msgType !== 'text' || !msg.content) return res.send('success');    console.log(`📩 ${msg.fromUser}: ${msg.content}`);    const reply = await sendToOpenClaw(msg.content);    console.log(`💬 OpenClaw回复: ${reply}`);    await sendToWeChat(msg.fromUser, reply);    res.send('success');  } catch (err) { console.error('错误:', err.message); res.send('success'); }});console.log('🚀 企业微信 → OpenClaw 中转服务\n');connectOpenClaw();app.listen(CONFIG.server.port, () => console.log(`✅ 运转: http://localhost:${CONFIG.server.port}`));

保存文件,确保参数无拼写错误,否则会导致音讯转发失败。同时需求在容器里安装相关的依赖。
步骤 7:安装 Node.js,启动中转服务并测试

进入 Openclaw 容器外部,安装Node.js v20 及以上版本(可经过官网下载安装包或包管理器一键安装),安装完成后经过node -v验证版本;

在openclaw/data/workspace/目录下,执行命令node wework-openclaw.js启动音讯中转服务;

启动后,打开企业微信,向 “小龙虾小助手” 发送恣意音讯,若能收到 Openclaw 的智能回复,阐明中转服务测试成功;若未收到,检查参数配置和端口能否开放。
步骤 8:设置中转服务自启动,完成容器联动

为了避免服务器或容重视启后中转服务失效,我们需求将中转服务设置为随 Openclaw 容器一同启动的系统服务,与日俱增。
在openclaw/data/workspace/目录下,新建文件wework-service.sh,编写服务启动脚本,核心内容为启动 wework-openclaw.js(脚本需确保可执行权限,可经过chmod +x wework-service.sh设置);
#!/bin/bash# wework-service.sh - 企业微信中转服务 watchdog# 自动启动并保活SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"NODE_SCRIPT="$SCRIPT_DIR/wework-openclaw.js"LOG_FILE="$SCRIPT_DIR/wework-service.log"log(){    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"}log "🚀 启动企业微信中转服务 watchdog"
while true; do      if[!-f "$NODE_SCRIPT"];    then      log "❌ 脚本不存在: $NODE_SCRIPT"      sleep 10      continue     fi      log "▶ 启动服务..."      node "$NODE_SCRIPT" >> "$LOG_FILE" 2 >& 1      log "⚠️ 进程加入,5秒后重启..."      sleep 5done
前往 1panel 的 Openclaw 容器编排页面,修正容器配置:

添加中转服务启动命令:
sh -c "node dist/index.js gateway --bind lan --port 18789 &             sleep 5 &&             /home/node/.openclaw/workspace/wework-service.sh"
添加中转服务端口的窗口映射:
      - ${HOST_IP}:${PANEL_APP_PORT_3000:-3000}:3000

保存容器配置后重启 Openclaw 容器,重启完成后再次在企业微信测试音讯发送,确认回复正常。
最终验证

一切步骤完成后,做两个简单验证:
直接访问http://[服务器IP]:18789,小龙虾聊天界面正常运用;

企业微信向 “小龙虾小助手” 发送音讯,秒级收到智能回复,服务器后台无报错。假如有成绩可以查看日志文件来排查。
至此,小龙虾 Openclaw 就成功部署并与企业微信完成对接啦!后续可根据实践需求,在 MiniMax 后台充值额度、调整 Openclaw 的智能回复配置,也可以在企业微信中修正小助手的权限,设置可运用的部门和人员。整个部署过程核心在于端口开放、参数准确配置和服务联动,只需跟着步骤一步步来,新手也能轻松搞定,赶紧给本人的企业微信安排上专属智能小助手吧!




欢迎光临 职贝云数AI新零售门户 (https://www.taojin168.com/cloud/) Powered by Discuz! X3.5