OpenClaw插件开发:为GLM-4.7-Flash扩展钉钉消息通道
本文介绍了如何在星图GPU平台上自动化部署【ollama】GLM-4.7-Flash镜像,并开发OpenClaw插件扩展钉钉消息通道。该解决方案可帮助企业快速搭建智能客服系统,实现通过钉钉接收和响应员工查询,提升内部沟通效率。
OpenClaw插件开发:为GLM-4.7-Flash扩展钉钉消息通道
1. 为什么需要开发钉钉插件
上周三凌晨2点,我被手机震动惊醒——团队在飞书群里紧急讨论一个线上问题。当我挣扎着打开电脑准备参与排查时,突然意识到:如果OpenClaw能通过钉钉通知我,就能直接用手机语音回复处理方案。这个深夜插曲促使我决定为GLM-4.7-Flash开发钉钉消息通道插件。
与飞书相比,钉钉在企业服务市场占有率更高。根据我的实践观察,钉钉的OAuth2.0授权流程更简洁,但消息加解密机制却比飞书复杂得多。开发过程中最让我意外的是:钉钉官方文档中至少有3种不同的加解密方案,而飞书只有标准AES模式。
2. 开发环境准备
2.1 基础工具链配置
我选择在搭载M2芯片的MacBook Pro上进行开发,这里分享几个关键配置细节:
# 使用nvm管理Node版本(钉钉SDK要求Node 18+)
nvm install 18.20.2
nvm use 18.20.2
# 初始化插件项目目录结构
mkdir dingtalk-channel && cd dingtalk-channel
npm init -y
mkdir -p src/{handlers,services,utils} test
特别注意:钉钉官方JS SDK与OpenClaw的TypeScript类型定义存在冲突。我通过以下方式解决:
// tsconfig.json 新增配置
{
"compilerOptions": {
"skipLibCheck": true,
"esModuleInterop": true
}
}
2.2 钉钉开发者账号准备
在钉钉开放平台创建应用时,有3个关键配置项常被忽略:
- IP白名单:必须添加部署服务器的公网IP,可通过
curl ifconfig.me获取 - 回调域名:需要填写OpenClaw网关的完整URL(如
https://your-domain.com/dingtalk/callback) - 权限范围:至少要勾选"消息通知"和"通讯录只读"权限
建议在开发阶段启用"测试企业"模式,可以绕过部分审核流程。我在首次提交时因为漏选"接收员工消息"权限,导致消息回调失败,浪费了两小时排查时间。
3. OAuth2.0接入实战
3.1 授权流程差异对比
与飞书的静默授权不同,钉钉采用显式授权页面。以下是核心代码片段:
// src/services/auth.service.ts
import { DingTalkClient } from '@dtfe/nodesdk';
export class AuthService {
private client: DingTalkClient;
constructor(private readonly config: DingTalkConfig) {
this.client = new DingTalkClient({
appKey: config.appKey,
appSecret: config.appSecret,
});
}
async getAuthUrl(state: string): Promise<string> {
return this.client.getAuthorizeUrl({
redirect_uri: this.config.redirectUri,
scope: 'openid',
state,
response_type: 'code',
});
}
}
实际开发中发现一个坑点:钉钉的redirect_uri必须与开放平台配置完全一致,包括末尾的/符号。我通过添加URL规范化处理解决了这个问题:
// src/utils/url.util.ts
export function normalizeUrl(url: string): string {
return url.endsWith('/') ? url : `${url}/`;
}
3.2 令牌管理策略
钉钉的access_token有效期只有2小时,比飞书的48小时短得多。我的解决方案是结合Redis实现自动刷新:
// src/services/token.service.ts
async refreshToken(): Promise<void> {
const { access_token, expires_in } = await this.client.getAccessToken();
await this.redis.set(
'dingtalk:access_token',
access_token,
'EX',
expires_in - 300 // 提前5分钟过期
);
}
在网关启动时注册定时任务:
// src/index.ts
const tokenService = new TokenService();
schedule.scheduleJob('*/30 * * * *', () => tokenService.refreshToken());
4. 消息加解密实现
4.1 加解密方案选择
钉钉提供三种加解密模式,经过测试我选择了最稳定的"加密模式3":
// src/utils/crypto.util.ts
import { createCipheriv, createDecipheriv } from 'crypto';
export class DingTalkCrypto {
private readonly aesKey: Buffer;
constructor(private readonly token: string, encodingAesKey: string) {
this.aesKey = Buffer.from(`${encodingAesKey}=`, 'base64');
}
decrypt(msg: string): string {
const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesKey.slice(0, 16));
let decrypted = decipher.update(msg, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
注意:钉钉的encodingAesKey需要补=字符才能正确解码,这个细节官方文档没有明确说明。
4.2 消息处理器实现
消息处理的核心是正确解析钉钉的事件格式。以下是我总结的消息类型处理矩阵:
| 消息类型 | 处理方式 | 响应要求 |
|---|---|---|
| 文本消息 | 直接转发给GLM-4.7-Flash | 必须5秒内响应 |
| 事件消息 | 本地处理不转发 | 返回success字符串 |
| 富文本消息 | 提取文字内容后转发 | 可异步响应 |
实现代码示例:
// src/handlers/message.handler.ts
async handleMessage(event: DingTalkEvent): Promise<Response> {
if (event.EventType === 'chat_message') {
const question = this.extractText(event.Message);
const answer = await this.glmService.query(question);
return { msgtype: 'text', text: { content: answer } };
}
return { code: 200, body: 'success' };
}
5. 插件打包与部署
5.1 依赖管理技巧
钉钉SDK的依赖项较多,我通过webpack实现按需打包:
// webpack.config.js
module.exports = {
externals: {
'@dtfe/nodesdk': 'commonjs2 @dtfe/nodesdk',
},
};
使用npm pack生成tgz包时,务必包含以下文件:
dingtalk-channel/
├── package.json
├── dist/
├── config/
│ └── default.json
└── assets/
└── icon.png
5.2 与飞书通道的共存配置
在openclaw.json中配置多通道时,要注意端口冲突问题:
{
"channels": {
"feishu": {
"enabled": true,
"port": 18790
},
"dingtalk": {
"enabled": true,
"port": 18791
}
}
}
启动时需要分别指定端口:
openclaw gateway --port 18790 --channel feishu
openclaw gateway --port 18791 --channel dingtalk
6. 调试与问题排查
开发过程中遇到最棘手的问题是消息签名验证失败。通过以下调试步骤最终解决:
- 使用
ngrok暴露本地服务,确保回调地址可访问 - 在代码中添加签名日志:
console.log('Computed sign:', sign, 'Received sign:', receivedSign); - 发现钉钉服务器时间与本地存在3分钟偏差,添加时间容差处理:
const isValid = Math.abs(timestamp - Date.now()) < 5 * 60 * 1000;
最终效果验证:插件成功部署后,GLM-4.7-Flash可以同时处理来自飞书和钉钉的消息查询。测试期间平均响应时间保持在1.2秒以内,完全满足企业即时通讯场景需求。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐

所有评论(0)