news 2026/2/11 12:51:49

Chandra实战教程:为Chandra添加Webhook通知能力,对接飞书/钉钉/企业微信消息推送

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Chandra实战教程:为Chandra添加Webhook通知能力,对接飞书/钉钉/企业微信消息推送

Chandra实战教程:为Chandra添加Webhook通知能力,对接飞书/钉钉/企业微信消息推送

1. 为什么需要给Chandra加Webhook通知

你有没有遇到过这样的场景:团队正在用Chandra做内部AI知识问答,但每次模型出错、服务重启或用户提交了重要咨询请求时,没人能第一时间知道?或者你想把用户在Chandra里提出的高频问题自动同步到飞书群,让运营同事快速响应?又或者,希望当有人连续三次提问失败时,系统自动发一条告警消息到钉钉运维群?

这些需求,原生Chandra并不支持——它专注做好一件事:提供一个轻量、私有、流畅的本地聊天界面。但它本身不带通知能力,也不连接外部消息通道。

好消息是:Chandra基于Ollama运行,所有对话请求都通过标准API(/api/chat)流转,而Ollama的调用链路完全可控。这意味着,我们不需要修改Chandra前端,也不用动Ollama核心,只需在请求入口层加一层轻量级代理,就能实现对飞书、钉钉、企业微信等主流IM平台的Webhook全兼容通知。

这不是“魔改”,而是典型的“能力外挂”:保持原有系统纯净,用最小侵入方式扩展价值。本教程将手把手带你完成三件事:

  • 搭建一个可拦截并转发Chandra请求的中间服务
  • 配置飞书/钉钉/企业微信的Webhook地址与消息模板
  • 实现「用户提问触发→记录日志→条件判断→推送消息」的完整闭环

整个过程无需Python高级知识,不依赖Docker Compose编排,所有代码可直接复制运行,5分钟内即可看到第一条飞书通知弹出。

2. 理解Chandra的通信结构与拦截点

2.1 Chandra如何与Ollama协作

Chandra本质是一个静态Web应用(HTML+JS),它不处理模型推理,只负责把用户输入打包成JSON,发给后端代理接口。这个代理接口默认指向Ollama的/api/chat,路径通常是:

POST http://localhost:11434/api/chat

而Chandra前端实际调用的是镜像内置的反向代理服务(比如Nginx或Caddy),该服务把/api/chat请求转发给本地Ollama。关键在于:这个代理层是可替换、可增强的

我们不碰Ollama,也不改Chandra源码,而是把原代理服务替换成一个“智能中继”——它既能原样转发请求给Ollama,又能在转发前后执行自定义逻辑,比如记录日志、提取关键词、触发Webhook。

2.2 为什么选HTTP中间件而非修改前端

你可能会想:直接在Chandra的JS里加个fetch()调用飞书Webhook不就行了?
不行。原因有三:

  • 跨域限制:浏览器禁止前端JS直连飞书/钉钉Webhook(它们要求Content-Type: application/json且无CORS头),会报CORS policy blocked错误;
  • 密钥暴露风险:Webhook地址含secrettoken,若写在前端JS里,任何人打开开发者工具都能看到,等于公开告警通道;
  • 不可靠性:用户可能关掉页面、网络中断、JS加载失败,导致通知永远发不出。

真正可靠的方式,是把通知逻辑放在服务端——也就是和Ollama同机部署的代理层。这里既可访问本地Ollama,又能安全持有Webhook密钥,还能捕获每一次成功/失败的请求。

2.3 我们的方案架构图

用户浏览器 ↓ (HTTPS) Chandra前端(/chat.html) ↓ (AJAX POST /api/chat) [智能代理服务] ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←......# Chandra实战教程:为Chandra添加Webhook通知能力,对接飞书/钉钉/企业微信消息推送 ## 1. 为什么需要给Chandra加Webhook通知 你有没有遇到过这样的场景:团队正在用Chandra做内部AI知识问答,但每次模型出错、服务重启或用户提交了重要咨询请求时,没人能第一时间知道?或者你想把用户在Chandra里提出的高频问题自动同步到飞书群,让运营同事快速响应?又或者,希望当有人连续三次提问失败时,系统自动发一条告警消息到钉钉运维群? 这些需求,原生Chandra并不支持——它专注做好一件事:提供一个轻量、私有、流畅的本地聊天界面。但它本身不带通知能力,也不连接外部消息通道。 好消息是:Chandra基于Ollama运行,所有对话请求都通过标准API(`/api/chat`)流转,而Ollama的调用链路完全可控。这意味着,我们不需要修改Chandra前端,也不用动Ollama核心,只需在**请求入口层**加一层轻量级代理,就能实现对飞书、钉钉、企业微信等主流IM平台的Webhook全兼容通知。 这不是“魔改”,而是典型的“能力外挂”:保持原有系统纯净,用最小侵入方式扩展价值。本教程将手把手带你完成三件事: - 搭建一个可拦截并转发Chandra请求的中间服务 - 配置飞书/钉钉/企业微信的Webhook地址与消息模板 - 实现「用户提问触发→记录日志→条件判断→推送消息」的完整闭环 整个过程无需Python高级知识,不依赖Docker Compose编排,所有代码可直接复制运行,5分钟内即可看到第一条飞书通知弹出。 ## 2. 理解Chandra的通信结构与拦截点 ### 2.1 Chandra如何与Ollama协作 Chandra本质是一个静态Web应用(HTML+JS),它不处理模型推理,只负责把用户输入打包成JSON,发给后端代理接口。这个代理接口默认指向Ollama的`/api/chat`,路径通常是:

POST http://localhost:11434/api/chat

而Chandra前端实际调用的是镜像内置的反向代理服务(比如Nginx或Caddy),该服务把`/api/chat`请求转发给本地Ollama。关键在于:**这个代理层是可替换、可增强的**。 我们不碰Ollama,也不改Chandra源码,而是把原代理服务替换成一个“智能中继”——它既能原样转发请求给Ollama,又能在转发前后执行自定义逻辑,比如记录日志、提取关键词、触发Webhook。 ### 2.2 为什么选HTTP中间件而非修改前端 你可能会想:直接在Chandra的JS里加个`fetch()`调用飞书Webhook不就行了? 不行。原因有三: - **跨域限制**:浏览器禁止前端JS直连飞书/钉钉Webhook(它们要求`Content-Type: application/json`且无CORS头),会报`CORS policy blocked`错误; - **密钥暴露风险**:Webhook地址含`secret`或`token`,若写在前端JS里,任何人打开开发者工具都能看到,等于公开告警通道; - **不可靠性**:用户可能关掉页面、网络中断、JS加载失败,导致通知永远发不出。 真正可靠的方式,是把通知逻辑放在服务端——也就是和Ollama同机部署的代理层。这里既可访问本地Ollama,又能安全持有Webhook密钥,还能捕获每一次成功/失败的请求。 ### 2.3 我们的方案架构图

用户浏览器 ↓ (HTTPS) Chandra前端(/chat.html) ↓ (AJAX POST /api/chat) [智能代理服务] ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←...... ├─→ 记录请求时间、用户IP、提问内容、模型名(gemma:2b) ├─→ 判断是否需通知(如含“紧急”、“报错”、“help”等关键词) ├─→ 调用飞书/钉钉/企微Webhook API └─→ 原样转发请求给 http://localhost:11434/api/chat ↓ Ollama(本地推理) ↓ 智能代理服务 ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←............ ↓ (返回响应) Chandra前端(渲染AI回复)

这个架构干净、安全、可维护。接下来,我们用一个不到100行的Python脚本实现它。 ## 3. 搭建智能代理服务:5分钟完成部署 ### 3.1 准备工作:确认运行环境 Chandra镜像基于Linux容器,已预装Python 3.9+和pip。你无需额外安装Python——只需进入容器执行命令即可。 打开终端,进入Chandra容器(若使用CSDN星图平台,点击镜像右侧“终端”按钮): ```bash # 进入容器后,先确认Python版本 python3 --version # 应输出类似:Python 3.9.18 # 安装必要依赖(requests用于调用Webhook,flask作为轻量Web服务器) pip install flask requests

注意:所有操作都在容器内进行,不影响宿主机。如提示Permission denied,请在命令前加sudo,或联系平台管理员确认权限。

3.2 创建代理服务脚本

新建文件webhook-proxy.py

# webhook-proxy.py from flask import Flask, request, jsonify, Response import requests import json import time import logging # 配置日志(输出到控制台,方便调试) logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) app = Flask(__name__) # ====== 请在此处配置你的Webhook地址 ====== # 飞书Webhook示例(替换为你自己的地址) FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxx" # 钉钉Webhook示例(替换为你自己的地址,含access_token) DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=yyyyyy" # 企业微信Webhook示例(替换为你自己的key) WEWORK_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=zzzzzz" # Ollama服务地址(默认不变) OLLAMA_URL = "http://localhost:11434/api/chat" # ====== 通知触发条件:关键词匹配 ====== ALERT_KEYWORDS = ["紧急", "报错", "error", "help", "崩溃", "无法响应", "timeout"] def send_feishu_message(content): """发送消息到飞书群""" payload = { "msg_type": "text", "content": {"text": content} } try: resp = requests.post(FEISHU_WEBHOOK, json=payload, timeout=5) if resp.status_code == 200: logger.info(" 飞书通知发送成功") else: logger.warning(f" 飞书通知失败,状态码:{resp.status_code}") except Exception as e: logger.error(f" 飞书通知异常:{e}") def send_dingtalk_message(content): """发送消息到钉钉群""" payload = { "msgtype": "text", "text": {"content": content} } try: resp = requests.post(DINGTALK_WEBHOOK, json=payload, timeout=5) if resp.status_code == 200: logger.info(" 钉钉通知发送成功") else: logger.warning(f" 钉钉通知失败,状态码:{resp.status_code}") except Exception as e: logger.error(f" 钉钉通知异常:{e}") def send_wework_message(content): """发送消息到企业微信群""" payload = { "msgtype": "text", "text": {"content": content} } try: resp = requests.post(WEWORK_WEBHOOK, json=payload, timeout=5) if resp.status_code == 200: logger.info(" 企业微信通知发送成功") else: logger.warning(f" 企业微信通知失败,状态码:{resp.status_code}") except Exception as e: logger.error(f" 企业微信通知异常:{e}") @app.route('/api/chat', methods=['POST']) def proxy_chat(): # 1. 记录原始请求 start_time = time.time() try: req_data = request.get_json() user_message = req_data.get("messages", [{}])[-1].get("content", "未知内容") model_name = req_data.get("model", "unknown") logger.info(f"📩 收到提问:'{user_message}' | 模型:{model_name}") # 2. 判断是否触发通知(简单关键词匹配) should_alert = any(kw in user_message for kw in ALERT_KEYWORDS) if should_alert: alert_text = f"[Chandra告警] 用户提问触发关键词\n模型:{model_name}\n提问:{user_message}\n时间:{time.strftime('%H:%M:%S')}" # 同时发三端(可按需注释掉不需要的) send_feishu_message(alert_text) send_dingtalk_message(alert_text) send_wework_message(alert_text) except Exception as e: logger.error(f" 解析请求失败:{e}") # 3. 原样转发请求给Ollama try: ollama_resp = requests.post( OLLAMA_URL, json=req_data, stream=True, timeout=300 # Ollama推理可能较长,设为5分钟 ) # 4. 将Ollama响应流式返回给Chandra前端 def generate(): for chunk in ollama_resp.iter_content(chunk_size=1024): if chunk: yield chunk return Response(generate(), content_type=ollama_resp.headers.get('content-type')) except Exception as e: logger.error(f" 转发至Ollama失败:{e}") return jsonify({"error": "服务暂时不可用,请稍后重试"}), 502 if __name__ == '__main__': logger.info(" Webhook代理服务已启动,监听端口 5000") app.run(host='0.0.0.0', port=5000, debug=False)

3.3 获取并配置Webhook地址

  • 飞书:在飞书群 → 群设置 → 机器人 → 添加机器人 → 选择“自定义机器人” → 复制Webhook地址(形如https://open.feishu.cn/...
  • 钉钉:在钉钉群 → 群设置 → 智能助手 → 添加机器人 → 选择“自定义” → 复制Webhook地址(含access_token=参数)
  • 企业微信:在管理后台 → 应用管理 → 自建应用 → 创建「群机器人」→ 复制Key

将三个地址分别填入脚本中对应变量(FEISHU_WEBHOOKDINGTALK_WEBHOOKWEWORK_WEBHOOK),保存文件。

3.4 启动代理服务

在容器内执行:

# 后台启动,不阻塞终端 nohup python3 webhook-proxy.py > proxy.log 2>&1 & # 查看是否启动成功 ps aux | grep webhook-proxy # 应看到类似:python3 webhook-proxy.py # 查看实时日志(按 Ctrl+C 退出) tail -f proxy.log

此时,代理服务已在http://localhost:5000/api/chat监听。

4. 修改Chandra前端指向新代理

Chandra前端默认调用/api/chat,该路径由镜像内置的Nginx反向代理指向Ollama。我们需要把它重新指向我们的新代理。

进入容器,编辑Nginx配置:

# 编辑Nginx配置文件(路径因镜像而异,常见位置如下) nano /etc/nginx/conf.d/default.conf # 或 nano /usr/share/nginx/html/config.js

找到类似以下的代理配置(通常在location /api/chat块中):

location /api/chat { proxy_pass http://localhost:11434/api/chat; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }

将其改为:

location /api/chat { proxy_pass http://localhost:5000/api/chat; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 保持长连接,支持流式响应 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }

保存后重启Nginx:

nginx -s reload

验证:刷新Chandra页面,输入问题,观察proxy.log是否有收到提问日志。若有,说明代理已生效。

5. 实战测试:从提问到收到飞书通知

现在我们来一次完整验证。

5.1 测试关键词触发

在Chandra聊天框中输入:

紧急!我的模型突然无法响应了,怎么办?

按下回车。几秒后,你应该在飞书群中看到类似消息:

[Chandra告警] 用户提问触发关键词 模型:gemma:2b 提问:紧急!我的模型突然无法响应了,怎么办? 时间:14:22:07

同时,终端日志会显示:

2024-06-15 14:22:07 - INFO - 📩 收到提问:'紧急!我的模型突然无法响应了,怎么办?' | 模型:gemma:2b 2024-06-15 14:22:08 - INFO - 飞书通知发送成功 2024-06-15 14:22:08 - INFO - 钉钉通知发送成功 2024-06-15 14:22:08 - INFO - 企业微信通知发送成功

5.2 测试非关键词提问(不触发)

输入普通问题,例如:

你好,介绍一下你自己。

日志中只会显示收到提问,但不会出现飞书通知发送成功,证明条件判断准确。

5.3 扩展建议:让通知更智能

当前是关键词匹配,你还可以轻松升级:

  • 添加频率限制:同一IP 5分钟内只发1条,防刷屏
  • 区分通知等级error发钉钉+飞书,help只发飞书
  • 附带上下文:把用户历史对话片段也发过去,方便快速定位
  • 对接数据库:把每次提问存入SQLite,供后续分析高频问题

这些都只需在proxy_chat()函数中增加几行代码,完全不改变架构。

6. 总结:一条轻量路径,解锁无限可能

我们没有修改一行Chandra前端代码,没有动Ollama核心,甚至没重启整个镜像——只是加了一个5000端口的代理服务,就让原本“安静”的本地AI聊天工具,拥有了主动触达团队的能力。

这背后体现的是一种务实的工程思维:不追求大而全的重构,而专注小而准的增强。Webhook不是炫技,它是让AI真正融入工作流的“神经末梢”。

你学到的不仅是三行Webhook调用,更是一种可复用的方法论:

  • 找到系统通信的“咽喉点”(这里是/api/chat
  • 用轻量中间件做“流量镜像”(记录+转发+扩展)
  • 把密钥、逻辑、策略全部收束在可信服务端
  • 用最简代码,解决最痛的问题

下次当你想给任何本地AI工具加告警、审计、统计、审批能力时,这套模式依然适用。

现在,去你的飞书群里,看看那条刚刚弹出的告警消息吧——那是你亲手赋予Chandra的第一声“心跳”。

7. 常见问题与避坑指南

7.1 为什么改了Nginx配置后Chandra打不开?

  • 检查proxy_pass地址是否拼写错误(注意末尾斜杠:http://localhost:5000/api/chat不能写成.../chat/
  • 查看Nginx错误日志:tail -n 20 /var/log/nginx/error.log
  • 确认代理服务确实在运行:ps aux | grep webhook-proxy

7.2 发送Webhook时提示400或403错误?

  • 飞书/钉钉/企微Webhook地址是否复制完整?尤其注意飞书地址末尾是否有空格
  • 钉钉地址必须含access_token=,企微必须含key=,缺一不可
  • 检查代理服务日志中的具体错误信息(如Connection refused说明端口未监听)

7.3 想只对接其中一种IM,怎么关闭其他?

直接注释掉对应send_xxx_message()三行调用即可,例如只留飞书:

# send_dingtalk_message(alert_text) # ← 注释掉 # send_wework_message(alert_text) # ← 注释掉 send_feishu_message(alert_text) # ← 保留

7.4 如何让通知包含更多上下文(比如用户IP、时间戳)?

alert_text变量中直接拼接:

user_ip = request.headers.get('X-Real-IP', request.remote_addr) alert_text = f"[Chandra告警] {user_ip} 提问触发关键词\n模型:{model_name}\n提问:{user_message}\n时间:{time.strftime('%Y-%m-%d %H:%M:%S')}"

获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/9 6:46:15

GLM-4-9B-Chat-1M行业落地:构建私有化智能文档处理平台

GLM-4-9B-Chat-1M行业落地:构建私有化智能文档处理平台 1. 为什么企业需要“能读懂整本书”的AI助手? 你有没有遇到过这些场景: 法务同事花三天通读一份200页的并购协议,只为确认某一条款的风险点;研发团队每次接手…

作者头像 李华
网站建设 2026/2/10 9:17:58

HY-Motion 1.0镜像部署:预装PyTorch3D/SMPLH的Docker镜像使用指南

HY-Motion 1.0镜像部署:预装PyTorch3D/SMPLH的Docker镜像使用指南 1. 为什么你需要这个镜像 你是不是也遇到过这样的问题:想跑一个3D动作生成模型,光是环境配置就折腾一整天?PyTorch3D编译失败、SMPLH模型加载报错、CUDA版本不匹…

作者头像 李华
网站建设 2026/2/9 6:46:26

ChatGLM3-6B镜像免配置:预置transformers==4.40.2与tokenizer修复补丁

ChatGLM3-6B镜像免配置:预置transformers4.40.2与tokenizer修复补丁 1. 为什么你需要一个“开箱即用”的ChatGLM3-6B本地对话系统 你是不是也遇到过这些情况? 下载好ChatGLM3-6B模型,兴冲冲跑起代码,结果第一行就报错&#xff1…

作者头像 李华
网站建设 2026/2/9 7:20:59

SAM 3镜像部署实操:阿里云ECS+Docker Compose一键部署生产可用服务

SAM 3镜像部署实操:阿里云ECSDocker Compose一键部署生产可用服务 1. 为什么你需要一个开箱即用的SAM 3服务 你有没有遇到过这样的场景:手头有一批商品图,需要快速抠出主体;或者一段监控视频里要持续追踪某个移动物体&#xff1…

作者头像 李华
网站建设 2026/2/9 6:26:12

通义千问3-Reranker-0.6B入门必看:Apache 2.0商用免责条款深度解读

通义千问3-Reranker-0.6B入门必看:Apache 2.0商用免责条款深度解读 你是不是也遇到过这样的困惑:刚在项目里集成了一个效果惊艳的重排序模型,正准备上线,突然被法务叫住问“这个模型能商用吗?有没有法律风险&#xff…

作者头像 李华