惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
Simon Willison's Weblog
Simon Willison's Weblog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Schneier on Security
Cyberwarzone
Cyberwarzone
NISL@THU
NISL@THU
The Last Watchdog
The Last Watchdog
Security Archives - TechRepublic
Security Archives - TechRepublic
The Hacker News
The Hacker News
Schneier on Security
Schneier on Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Help Net Security
Help Net Security
Scott Helme
Scott Helme
SecWiki News
SecWiki News
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
L
LINUX DO - 最新话题
L
Lohrmann on Cybersecurity
TaoSecurity Blog
TaoSecurity Blog
K
Kaspersky official blog
Attack and Defense Labs
Attack and Defense Labs
P
Privacy & Cybersecurity Law Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
L
LINUX DO - 热门话题
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Forbes - Security
Forbes - Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
T
Tor Project blog
P
Palo Alto Networks Blog
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
O
OpenAI News
T
The Exploit Database - CXSecurity.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
AI
AI
N
News and Events Feed by Topic
博客园 - Franky
T
Threatpost
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
V2EX - 技术
V2EX - 技术
B
Blog RSS Feed
Hacker News: Ask HN
Hacker News: Ask HN
D
Docker
博客园 - 三生石上(FineUI控件)

博客园 - 唯一客服系统开发笔记

企业微信怎么设置投诉页面 汽修行业AI知识库应用到抖店智能咨询 快手直播间弹幕评论抓取 知识库 AI Excel表格数据上传处理 扣子知识库 AI 智能体完整创建、对接实操教程 客服私有化部署 + 抖音私信接入说明 抖店飞鸽客服自动回复机器人插件使用 网站客服系统与电商客服自动化工具 唯一客服浏览器插件自动回复抖音评论私信 拼多多客服如何实现24小时全自动值守、大幅降本增效?这款自动化插件帮你轻松破局! 扣子 AI 智能体对接企业微信客服 抖音私信和评论网页端浏览器自动化插件 功能详解 批量扒完抖音评论还能发私信 | 本地开源工具 唯一客服企业微信侧边栏客户画像:让客服在聊天时直接查看客户详情、进群记录和跟进轨迹 自定义微信卡片链接生成工具:自由设置标题、描述与封面图 唯一客服系统接入自定义大模型:一行代码都不用改,写个 HTTP 接口就够了 唯一客服系统多智能体架构深度解析:一个后台,四种 AI 引擎随心切换 GoLiveChat:Golang独立部署海外英文在线客服系统全解析 抖音无痕引流神器!评论自动回复藏微表情,规避系统检测,自动引私域 抖音评论区全自动表情回复插件-引流推广小技巧,有人评论自动化回表情 企业微信官方机器人无法对接外部群/个人微信的解决方案 Why We Built GoLiveChat — A Self-Hosted Live Chat Platform That Puts You in Control 抖音智能客服自动化:评论与私信自动化回复功能 微信个人号群发:用 UI 自动化模拟人工操作,实现零封号批量发送 抖音私信接入扣子知识库机器人教程|企业号/个人号全自动AI回复落地指南 抖音私信自动化回复-抖音企业号聚合回复-抖音私信AI智能体对接 抖音私信自动化回复-多版本 DOM 适配-抖音对不同账号推了不同的私信页面版本 Gin + 本地 CDN Vue + SQLite:绝配!真正开箱即用的本地系统三驾马车 轴承行业官网必备:一键接入在线客服,牢牢锁住搜索流量 PC 微信 4.1.9.62 UIA 抓取消息方案总结 唯一客服系统二次开发基础:Golang + Gin + Gorm + WebSocket 快速上手教程 Golang客服系统为什么比PHP客服系统更稳定?高并发、长连接、私有化部署全解析 聊城变量网络科技有限公司_专注企业客服系统与自动化解决方案 90%的人区分不清网站客服系统和电商客服平台 Python小程序二手房源界面抓取方案 聊城企业建站别踩坑!我是变量网络创始人程序员老狼,给聊城企业做真正能获客的官网 私域临时任务众包管理工具-基于微信号的创建任务,领取任务,反馈任务完整实现 聊城变量网络科技:扎根鲁西,用技术赋能临清本土企业数字化转型 电竞护航小程序完整开发方案 电竞护航UniApp源码项目-聚焦核心下单业务 在线客服系统源码 唯一客服 SCRM源码 微信群活码功能 企业微信 SCRM 服务商代开发实战:参数配置全打通,源码可直接交付 深耕PHP十余年,做聊城本地靠谱的网站建设公司——聊城变量网络科技有限公司 企业微信群活码开发-唯一客服企微xscrm源码开发 聊城本地企业微信服务商|变量网络科技,专注企微代开发 & 自研 SCRM 定制 从零搭建 UniApp 电竞护航项目源码(无NPM依赖、纯净版架构 2026电竞护航系统完整开发方案(行业分析+数据库+UniApp前端架构) 聊城临清本土互联网科技企业-聊城变量网络科技有限公司 Dify报错not_chat_app?一文解决工作流与对话流的核心混淆 唯一客服企业微信 SCRM:客户备注搜索功能 企业微信电竞护航运营体系 企业微信+电竞护航系统落地方案(解决漏单、私域流失等核心痛点) 从SCRM表结构底层逻辑,看唯一客服如何破解私域运营痛点 唯一客服 SCRM:独立部署的Golang企业微信SCRM源码 基于 UI 自动化的 QQ 群消息自动转发工具 自研企业微信SCRM系统源码独立部署(Golang+Vue.js) 程序员老狼|个人简介 企业微信SCRM应用开发:回调事件数据获取 基于企业微信 API 实现渠道活码 企业微信渠道活码(联系我)创建指南 企业微信开放平台:第三方系统通讯录管理 企业微信客服 × Dify 独立对接 AI企业实操落地方案服务商|不玩虚的,聚焦技术落地、解决企业实际问题 程序员老狼 · OPC超级个体介绍 私域引流:企业微信客服分流个人微信 扣子API使用场景及核心操作指南(非流式响应重点,记住上下文历史记录) 想让AI搜到你?自建内容站才是长久办法 拼多多客服自动回复工具|告别手动值守,轻松应对海量咨询 美团点评客服自动回复神器|告别手动回复,轻松达标平台考核 为什么企业需要专业的在线客服系统 Websocket高性能即时通讯在线客服系统 微信消息定时自动发送:用Python脚本与cron任务实现办公自动化 在线客服系统源码实现 企业客服系统解决方案:一站式解决客户服务难题 降低运营成本:智能客服系统ROI分析 电商客服系统:提升店铺转化率的必备利器 智能客服机器人:7×24小时永不离线的AI服务专家 在线客服系统功能全解析:企业选型必看指南 客服系统多少钱?在线客服系统价格全对比 在线客服系统:企业数字化转型的必备工具 独立站网页即时聊天在线客服系统 选择独立开发的Golang在线客服系统,安全稳定,性能领先 赋能跨境与独立站——您的专属智能在线客服系统 同城跑腿派送系统源码 聊城临清网络科技公司哪家强? 聊城网络科技公司哪家强? 实测!我们公众号的客服系统,消息提醒到底有多“狠”? 在线客服系统实现复杂统计需求,直接SQL操作查询数据库,统计超过一分钟未回复消息数 我开发的客服系统,自动欢迎语以及对接AI与转人工模式思考
从零搭建企业微信 AI 客服:Hook 注入 + 扣子知识库实战
唯一客服系统开发笔记 · 2026-06-04 · via 博客园 - 唯一客服系统开发笔记

让你的企业微信员工号秒变 AI 智能客服,支持私聊和群聊自动回复。

原文地址


一、项目背景

企业微信已成为国内企业内外沟通的核心工具。但员工号需要人工回复客户消息,效率低、响应慢。有没有办法让一个普普通通的企业微信员工号,自动拥有 AI 大脑,7×24 小时智能回复客户?

答案是:企业微信 Hook 注入协议 + 扣子(Coze)知识库机器人

本项目完整实现了这一方案——使用 Go 语言构建了一套轻量级的 SCRM 服务,通过 Hook 协议与企业微信客户端通信,接入扣子知识库 API,实现私聊&群聊全自动回复。


二、整体架构

flowchart LR subgraph 企业微信客户端 A[企业微信<br/>注入版客户端] end subgraph Hook 协议层 B[本地 Hook 服务<br/>127.0.0.1:8061] end subgraph SCRM 服务 C[Go 后端<br/>Gin 框架 :8089] D[(SQLite/MySQL)] end subgraph AI 能力 E[扣子 Coze API<br/>api.coze.cn] end subgraph 用户端 F[客户私聊] G[客户群聊] end F --> A G --> A A <-->|WebSocket/HTTP| B B -->|回调通知| C C -->|读取配置| D C -->|调用 AI| E E -->|返回回复| C C -->|发送消息 API| B B -->|路由至| A A -->|回复| F A -->|@回复| G

核心链路

  1. 客户在企业微信中给员工号发消息
  2. 注入版客户端拦截消息,通过 Hook 协议回调到我们的 Go 服务
  3. Go 服务提取消息内容,调用扣子知识库 API
  4. 扣子返回 AI 生成的回复,Go 服务再通过 Hook 协议驱动客户端发送回复
  5. 一次完整的 AI 自动对话就这样完成了

三、Hook 协议封装

3.1 协议简述

企业微信注入版 Hook 在本地暴露了一个 HTTP 服务(默认为 http://127.0.0.1:8061),通过不同的命令码(type)来操作企业微信客户端:

命令码 路径 功能
11029 /cmd/11029 发送普通消息
11035 /cmd/11035 获取账号信息
11069 /cmd/11069 发送群@消息
... ... 登录、扫码、获取联系人等

3.2 Go 客户端封装

我们将 Hook 协议封装为一个 Go 客户端库 lib/wxworkhook/client.go,核心设计如下:

单例客户端

var (
	instance *Client
	once     sync.Once
)

// Client 企业微信Hook协议客户端
type Client struct {
	ClientId   string  // client_id
	Profile    Profile // 当前账号信息(调用 GetProfile 后填充)
	profileFet bool    // 是否已拉取过个人信息
}

// GetClient 获取单例客户端
func GetClient(clientId string) *Client {
	once.Do(func() {
		instance = &Client{
			ClientId: clientId,
		}
	})
	return instance
}

发送普通消息(私聊用):

// SendMsg 发送消息(通过 /cmd/11029)
// conversationID: 会话ID,单聊填用户ID,群聊填群ID
// content: 消息文本内容
func (c *Client) SendTxtMsg(conversationID, content string) (*CmdResponse, error) {
	req := SendMsgReq{
		Type:     11029,
		ClientId: c.ClientId,
		Data: SendMsgData{
			ConversationID: conversationID,
			Content:        content,
		},
	}

	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("序列化请求失败: %v", err)
	}

	url := DefaultBaseURL + "/cmd/11029"
	log.Printf("[企微Hook发送] 请求: %s, 参数: %s\n", url, string(body))

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
	if err != nil {
		return nil, fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Body.Close()

	var result CmdResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("解析响应失败: %v", err)
	}

	respBody, _ := json.Marshal(result)
	log.Printf("[企微Hook发送] 响应内容: %s\n", string(respBody))

	if result.ErrCode != 0 {
		return &result, fmt.Errorf("发送消息失败: %s", result.ErrMsg)
	}

	log.Printf("[企微Hook发送] 消息发送成功 -> conversation_id=%s\n", conversationID)
	return &result, nil
}

发送群@消息(群聊用):

// SendAtMsg 发送群@消息(通过 /cmd/11069)
// conversationID: 会话ID(群聊填群ID)
// content: 消息文本内容
// atList: @的用户ID列表
func (c *Client) SendAtMsg(conversationID, content string, atList []string) (*CmdResponse, error) {
	req := SendAtMsgReq{
		Type:     11069,
		ClientId: c.ClientId,
		Data: SendAtMsgData{
			ConversationID: conversationID,
			Content:        content,
			AtList:         atList,
		},
	}

	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("序列化请求失败: %v", err)
	}

	url := DefaultBaseURL + "/cmd/11069"
	log.Printf("[企微Hook群@] 请求: %s, 参数: %s\n", url, string(body))

	resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
	if err != nil {
		return nil, fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Body.Close()

	var result CmdResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("解析响应失败: %v", err)
	}

	respBody, _ := json.Marshal(result)
	log.Printf("[企微Hook群@] 响应内容: %s\n", string(respBody))

	if result.ErrCode != 0 {
		return &result, fmt.Errorf("发送群@消息失败: %s", result.ErrMsg)
	}

	log.Printf("[企微Hook群@] 消息发送成功 -> conversation_id=%s, at_list=%v\n", conversationID, atList)
	return &result, nil
}

3.3 关键细节:私聊 vs 群聊识别

企业微信 Hook 回调中的 conversation_id 字段有明确前缀:

  • S: 开头 → 私聊(Single chat)
  • R: 开头 → 群聊(Room chat)

注意:这并非官方 API 规范,而是注入版 Hook 的约定。不同 Hook 版本可能不同,需根据实际情况调整。


四、扣子 Coze API 接入

4.1 为什么选扣子?

  • 知识库原生支持:上传文档即可构建领域知识库,无需训练模型
  • 会话记忆:每个用户维护独立会话,上下文连续
  • 简单 API:创建会话 → 发送消息 → 轮询结果 → 获取回复,四步搞定
  • 免费额度:个人开发者白嫖友好

4.2 Go 封装实现

扣子 API v3 的核心流程:

sequenceDiagram participant S as SCRM服务 participant C as Coze API Note over S,C: 每个用户首次对话 S->>C: POST /v1/conversation/create C-->>S: conversation_id S->>S: 缓存 conversation_id Note over S,C: 每次发消息 S->>C: POST /v3/chat?conversation_id=xxx C-->>S: chat_id + conversation_id loop 轮询等待(最多30秒) S->>C: GET /v3/chat/retrieve C-->>S: status: in_progress/completed end S->>C: GET /v3/chat/message/list C-->>S: assistant 回复内容 S->>S: 提取 answer 消息返回

关键代码——轮询等待完成后获取 AI 回复:

func (c *Coze) waitForCompletion(chatID, conversationID string) error {
	url := "https://api.coze.cn/v3/chat/retrieve"
	maxRetries := 30
	interval := 1 * time.Second

	for i := 0; i < maxRetries; i++ {
		queryURL := fmt.Sprintf("%s?chat_id=%s&conversation_id=%s", url, chatID, conversationID)
		
		req, err := http.NewRequest("GET", queryURL, nil)
		if err != nil {
			return fmt.Errorf("create request failed: %w", err)
		}

		req.Header.Set("Authorization", "Bearer "+c.API_KEY)

		client := &http.Client{Timeout: 30 * time.Second}
		resp, err := client.Do(req)
		if err != nil {
			return fmt.Errorf("request failed: %w", err)
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("read response failed: %w", err)
		}

		var response ChatRetrieveResponse
		if err := json.Unmarshal(body, &response); err != nil {
			return fmt.Errorf("unmarshal response failed: %w", err)
		}

		if response.Code != 0 {
			return fmt.Errorf("API error: %s", response.Msg)
		}

		if response.Data.Status == "completed" {
			return nil
		}

		time.Sleep(interval)
	}

	return fmt.Errorf("wait for completion timeout")
}

func (c *Coze) getFinalResponse(chatID, conversationID string) (string, error) {
	url := "https://api.coze.cn/v3/chat/message/list"
	queryURL := fmt.Sprintf("%s?chat_id=%s&conversation_id=%s", url, chatID, conversationID)

	req, err := http.NewRequest("GET", queryURL, nil)
	if err != nil {
		return "", fmt.Errorf("create request failed: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.API_KEY)

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("read response failed: %w", err)
	}

	var response MessageListResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return "", fmt.Errorf("unmarshal response failed: %w", err)
	}

	if response.Code != 0 {
		return "", fmt.Errorf("API error: %s", response.Msg)
	}

	var assistantReplies []Message
	for _, msg := range response.Data {
		if msg.Role == "assistant" && msg.Type == "answer" {
			assistantReplies = append(assistantReplies, msg)
		}
	}

	if len(assistantReplies) > 0 {
		return assistantReplies[len(assistantReplies)-1].Content, nil
	}

	return "", nil
}

4.3 会话缓存

每个用户的会话 ID 使用 sync.RWMutex 保护的内存 map 缓存,避免重复创建会话:

type Coze struct {
	BOT_ID       string
	API_KEY      string
	conversation map[string]string
	mu           sync.RWMutex
}

这意味着同一位客户的所有对话共享同一个 Coze 会话上下文,AI 能记住之前的对话内容。


五、核心:回调处理与自动回复

这是整个系统的大脑——routers/wxwork_hook.go 中的 PostWxWorkHookCallback

// PostWxWorkHookCallback 企业微信Hook回调(通用回调入口)
func PostWxWorkHookCallback(c *gin.Context) {
	rawData, err := io.ReadAll(c.Request.Body)
	if err != nil {
		log.Printf("[企微Hook回调] 读取body失败: %v\n", err)
		c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "读取失败"})
		return
	}

	log.Printf("[企微Hook回调] Body: %s\n", string(rawData))

	payloadType := gjson.GetBytes(rawData, "payload.type").Int()

	// 普通消息:提取字段存入 LiveComment 表
	clientId := gjson.GetBytes(rawData, "client_id").String()
	content := gjson.GetBytes(rawData, "payload.data.content").String()
	sender := gjson.GetBytes(rawData, "payload.data.sender").String()
	senderName := gjson.GetBytes(rawData, "payload.data.sender_name").String()
	conversationID := gjson.GetBytes(rawData, "payload.data.conversation_id").String()

	// type 11035 = 账号信息回调
	if payloadType == 11035 {
		var p wxworkhook.Profile
		if err := json.Unmarshal([]byte(gjson.GetBytes(rawData, "payload.data").Raw), &p); err != nil {
			log.Printf("[企微Hook回调] 解析账号信息失败: %v\n", err)
		} else {
			client := wxworkhook.GetClient(clientId)
			client.SetProfile(p)
		}
		c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "ok"})
		return
	}

	comment := &models.LiveComment{
		Platform: sender,
		Nickname: senderName,
		Content:  content,
		To:       conversationID,
	}
	if err := models.AddOrUpdateLiveComment(comment); err != nil {
		log.Printf("[企微Hook回调] 存表失败: %v\n", err)
	} else {
		log.Printf("[企微Hook回调] 消息入库 id=%d sender_name=%s content=%s\n", comment.ID, senderName, content)
	}

	// 自动回复(跳过自己的消息,防止死循环)
	myUserId := models.FindSettingDefault("wxworkUserId", "")
	if conversationID != "" && content != "" && myUserId != "" && sender != myUserId {
		botID := models.FindSettingDefault("cozeBotId", "")
		apiKey := models.FindSettingDefault("cozeApiKey", "")
		if botID != "" && apiKey != "" {
			reply, err := coze.ChatCozeAPI(botID, apiKey, sender, content)
			if err != nil {
				log.Printf("[企微Hook回调] Coze调用失败: %v\n", err)
			} else if reply != "" {
				client := wxworkhook.GetClient(clientId)
				var sendErr error
				if strings.HasPrefix(conversationID, "R:") {
					// 群聊:发送群@消息
					_, sendErr = client.SendAtMsg(conversationID, reply, []string{sender})
				} else {
					// 私聊:发送普通文本消息
					_, sendErr = client.SendTxtMsg(conversationID, reply)
				}
				if sendErr != nil {
					log.Printf("[企微Hook回调] 自动回复失败: %v\n", sendErr)
				}
			}
		} else {
			log.Printf("[企微Hook回调] Coze配置未完成,跳过自动回复\n")
		}
	}

	c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "ok"})
}

回调流程拆解:

步骤 操作 说明
① 接收 读取 HTTP Body 企业微信 Hook 回调 POST JSON
② 分发 根据 payload.type 判断 11035→更新账号信息,其他→消息处理
③ 入库 写入 LiveComment 所有消息持久化存储,可追溯
④ 防回环 比对 sendermyUserId 跳过自己发出的消息,避免无限回复
⑤ AI 调用 coze.ChatCozeAPI() 以发送者身份调用扣子 API
⑥ 会话区分 检查 conversationID 前缀 R: → 群@消息;S: → 普通消息
⑦ 回复 SendTxtMsg / SendAtMsg 通过 Hook 协议驱动企微客户端发送

六、配置管理

系统使用 SQLite 存储运行时配置(Setting 表),通过 key-value 方式管理:

func FindSettingDefault(key string, def string) string {
	val := FindSetting(key).Value
	if val == "" {
		return def
	}
	return val
}

三个关键配置项

Key 说明 示例值
wxworkUserId 当前员工号 UserID 用于防止自我回复
cozeBotId 扣子机器人 Bot ID 从 Coze 控制台获取
cozeApiKey 扣子 API Key Bearer Token 认证

这些配置通过后台管理页面 /admin/setting 可视化配置。


七、技术栈总结

层级 技术选型 用途
Web 框架 Gin (Go) HTTP 路由、中间件、模板渲染
数据库 SQLite (GORM) 配置存储、消息记录
缓存 Redis 可选,热数据缓存
WebSocket gorilla/websocket 实时消息推送(直播评论屏等)
AI 引擎 扣子 Coze API v3 知识库问答、上下文会话
通信协议 企业微信 Hook 接收消息回调 + 驱动发消息
前端 原生 HTML + JS 管理后台 Dashboard
  • 轻量:单 Go 二进制,SQLite 无外部依赖,Windows/Linux/Mac 通吃
  • 低侵入:Hook 注入方式,不依赖企业微信官方 API 权限审批
  • 可扩展:模块化设计,Hook 客户端、Coze 引擎均可替换为其他实现
  • 完整闭环:消息入库 → AI 生成 → 自动回复→ 前端实时展示

如果你也有类似需求——让企业微信员工号变身智能客服,不妨参考这套方案。


发表于 2026 年 6 月 4 日 | 标签:Go, 企业微信, Coze, AI, Hook, SCRM