
简介这是一套基于企业微信深度集成的开源SCRM系统设计源码面向Java后端开发者、企业数字化运营团队及私域流量系统学习者聚焦客户关系管理、营销自动化与社群裂变等核心场景。资源包共2000个文件含1778个Java业务逻辑与服务实现类如WeCustomerServiceImpl、WeFissionServiceImpl等、212个XML配置文件支撑MyBatis映射与Spring配置、5个文本说明文件及少量Properties、Markdown文档整体压缩包仅27.64MB轻量易部署。已有862人学习下载反映出其在企业微信生态开发中的实践参考价值。代码采用清晰的微服务分层结构注释完备涵盖客户管理、朋友圈任务、素材库、活码、群管理、红包营销、裂变活动等完整SCRM功能模块前端Vue3与后端Java协同设计是研究企业微信API落地、私域SaaS架构演进与SCRM工程化实践的优质学习样本。1. 为什么企业微信生态里LinkWeChat 是少有的能真正跑通「私域SCRM开源」闭环的源码项目你不是没试过——下载过几十个标着“企业微信 SCRM”的 GitHub 仓库解压后发现要么是空壳前端页面配个 mock API要么是只支持单租户、连客户标签都存不进数据库更别说自动打标、会话存档对接、群活码分流这些真实业务刚需。而 LinkWeChat 不同它不是 Demo是已在中小 SaaS 团队生产环境稳定运行超 2 年的完整 SCRM 系统源码核心模块全部开源非“开源但关键模块闭源”且明确适配企业微信最新 4.x 接口规范含 2024 年新增的「客户联系-会话存档合规开关」「群聊消息审计回调」「外部联系人变更事件推送」等。它解决的不是“能不能调接口”而是“怎么让销售每天多聊 30 个客户、怎么让运营活动 ROI 可归因、怎么在不碰敏感词的前提下做自动化培育”。适合三类人想快速搭建私域中台的技术负责人、需要二次开发定制 SCRM 的 ISV 合作伙伴、以及正在用企业微信但被封闭生态卡住手脚的运营/销售团队。这不是又一个玩具项目而是你能在 Ubuntu 22.04 或 CentOS 7 上用 3 小时部署、当天上线、下周就跑通客户流转的生产级基座。2. 搭建 LinkWeChat从源码拉取到后台可登录的最小可行路径LinkWeChat 的源码结构清晰采用 Spring Boot MyBatis Plus Vue3 Element Plus 技术栈前后端分离数据库默认 MySQL 8.0。它不依赖 Docker Compose 一键启停很多开源项目卡在这一步而是提供可调试、可分步验证的手动部署流程——这对排查企业微信回调失败、证书校验异常等高频问题至关重要。2.1 拉取源码与初始化数据库项目托管在 GitHub注意非 Gitee 或国内镜像因部分 Webhook 配置需直连 GitHub Actions 验证推荐使用 SSH 方式克隆以避免 token 权限问题git clone gitgithub.com:linkwechat/linkwechat.git cd linkwechat提示不要用git clone https://...企业微信回调服务器若需 HTTPS 证书双向认证SSH 克隆能规避 Git HTTP 代理导致的证书链中断问题。进入linkwechat/sql目录执行建库脚本注意脚本内已预置utf8mb4_unicode_ci排序规则避免 emoji 客户昵称入库乱码-- 在 MySQL 中执行确保已创建 database linkwechat source /path/to/linkwechat/sql/linkwechat.sql;该 SQL 脚本共创建 47 张表核心包括customer客户主表含 unionid、external_userid、avatar 等字段chat_session会话存档表按天分区含 message_id、content、msg_type、sender_useridgroup_chat群聊表含 group_id、group_name、notice、member_counttag_relation标签关系表支持客户-标签、群-标签、员工-标签三级绑定2.2 配置企业微信可信域名与 API 权限LinkWeChat 必须通过企业微信「管理后台 → 应用管理 → 自建应用」完成授权不能用“第三方应用”模式后者权限受限无法获取会话存档。关键配置项如下配置项值说明可信域名your-domain.com必须备案且 HTTPS不可用 localhost 或 IP建议提前申请 Lets Encrypt 泛域名证书接口调用凭证corp_id,secret从「自建应用详情页」复制勿混淆「应用 Secret」与「客户联系 Secret」客户联系 Secret单独配置在application.yml中weixin.customer.contact.secret用于获取客户列表、发送消息与应用 Secret 分离会话存档 Secretweixin.chat.archive.secret必须在「管理后台 → 客户联系 → 会话存档」中开启并获取未开启则 chat_session 表始终为空注意企业微信对“可信域名”校验极严——若你填api.your-domain.com则所有回调地址如/api/callback/chat必须以该域名开头且 Nginx 反向代理时proxy_set_header Host $host必须保留否则回调签名验证失败。2.3 修改 application.yml 并启动后端服务编辑linkwechat/linkwechat-server/src/main/resources/application.yml重点修改以下段落spring: datasource: url: jdbc:mysql://127.0.0.1:3306/linkwechat?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseserverTimezoneGMT%2B8 username: root password: your_mysql_password weixin: corp-id: wwxxxxxxxxxxxxxx # 企业 ID12位字母数字组合 app: agent-id: 1000001 # 自建应用 AgentId整数 secret: xxxxxxxxxxxxxxxxxx # 应用 Secret customer: contact: secret: yyyyyyyyyyyyyyy # 客户联系 Secret chat: archive: secret: zzzzzzzzzzzzzzz # 会话存档 Secret enable: true # 必须设为 true否则不拉取存档消息启动命令JDK 17cd linkwechat-server mvn clean package -Dmaven.test.skiptrue java -jar target/linkwechat-server.jar服务默认监听8080端口启动成功后访问http://localhost:8080/actuator/health返回{status:UP}即表示基础服务就绪。2.4 编译并部署前端页面前端位于linkwechat/linkwechat-web使用 pnpm推荐比 npm/yarn 更快处理 Vue3 依赖cd linkwechat-web pnpm install # 修改 .env.production 中的 API 地址 echo VUE_APP_BASE_APIhttps://your-domain.com/api .env.production pnpm build生成的dist/目录需部署到 Nginx不可直接用file://打开 index.html因企业微信 JS-SDK 需 HTTPS 页面注入server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; location / { root /var/www/linkwechat/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }重启 Nginx 后访问https://your-domain.com输入默认账号admin/admin123即可登录后台。3. 核心功能落地客户自动打标、群活码分流、会话存档解析三步实操LinkWeChat 的价值不在“有功能”而在“功能可配置、可审计、可回溯”。下面三个高频场景每一步都对应真实业务断点且代码路径明确方便你二次开发。3.1 客户自动打标基于关键词触发的实时标签引擎客户添加员工后系统需根据聊天内容自动打标如客户发“报价单”打标“意向客户”发“安装教程”打标“售后咨询”。LinkWeChat 使用内存DB 双写策略避免高并发下标签漏打。实现路径在后台「营销中心 → 关键词管理」添加规则关键词报价单标签ID101匹配方式包含生效范围全部客户源码入口linkwechat-server/src/main/java/com/linkwechat/robot/service/impl/WeComRobotMsgServiceImpl.java中handleTextMsg()方法关键逻辑对每条客户发送的文本消息遍历启用的关键词规则调用String.contains()匹配非正则避免性能抖动命中后插入tag_relation表// WeComRobotMsgServiceImpl.java 片段 public void handleTextMsg(RobotMsg robotMsg) { String content robotMsg.getContent().trim(); ListKeywordRule rules keywordRuleService.listActiveRules(); // 从缓存读非实时 DB 查询 for (KeywordRule rule : rules) { if (content.contains(rule.getKeyword())) { tagRelationService.bindTagToCustomer(robotMsg.getExternalUserId(), rule.getTagId()); break; // 仅打第一个匹配标签避免冗余 } } }参数说明keywordRuleService.listActiveRules()默认缓存 5 分钟可通过spring.cache.redis.time-to-live调整bindTagToCustomer()内部做幂等判断INSERT IGNORE防止重复打标。3.2 群活码分流按员工负载动态分配新客户企业微信活码扫码后需将客户平均分配给销售而非固定分配。LinkWeChat 实现“轮询权重在线状态”三重分流轮询基础策略sales[0]→sales[1]→sales[2]→sales[0]...权重销售 A 设置权重 2B 设置权重 1则分配比例为 2:1在线状态仅分配给last_login_time now()-30min的员工配置位置后台「客户联系 → 活码管理 → 创建活码 → 分流设置」源码路径linkwechat-server/src/main/java/com/linkwechat/robot/service/impl/GroupCodeServiceImpl.java中getAssignEmployeeId()方法public Long getAssignEmployeeId(Long groupId) { ListEmployee onlineEmployees employeeService.listOnlineEmployees(groupId); // 查在线员工 if (onlineEmployees.isEmpty()) { return employeeService.getLeastLoadedEmployee(groupId); // 退化为按历史接待量最少 } // 加权轮询维护一个 position 指针每次 1取模总权重和 int totalWeight onlineEmployees.stream().mapToInt(Employee::getWeight).sum(); int pos (int) (Math.random() * totalWeight); // 随机起点防热点 int cursor 0; for (Employee emp : onlineEmployees) { cursor emp.getWeight(); if (pos cursor) return emp.getId(); } return onlineEmployees.get(0).getId(); }血泪经验企业微信活码 URL 有效期默认 7 天LinkWeChat 在GroupCodeController.refreshQrCode()中自动续期但需确保cron任务0 0 * * *正常运行否则活码过期后客户扫码跳转 404。3.3 会话存档解析从原始 JSON 提取结构化字段企业微信会话存档返回的是加密 JSONLinkWeChat 提供ChatArchiveDecryptor工具类解密并清洗解密密钥由weixin.chat.archive.secret生成 AES-256 密钥字段提取content消息正文、msgtypetext/image/link 等、sender_userid发送人、receiver_userid接收人、create_time毫秒时间戳源码路径linkwechat-server/src/main/java/com/linkwechat/robot/util/ChatArchiveDecryptor.javapublic ChatMessage decrypt(String encryptedData, String iv) { byte[] keyBytes DigestUtils.md5DigestAsHex( weixinProperties.getChat().getArchive().getSecret().getBytes() ).substring(0, 32).getBytes(StandardCharsets.UTF_8); SecretKeySpec keySpec new SecretKeySpec(keyBytes, AES); IvParameterSpec ivSpec new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8)); Cipher cipher Cipher.getInstance(AES/CBC/PKCS5Padding); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] decrypted cipher.doFinal(Base64.getDecoder().decode(encryptedData)); String jsonStr new String(decrypted, StandardCharsets.UTF_8); return JSONObject.parseObject(jsonStr, ChatMessage.class); }注意ChatMessage类中content字段已做 XSS 过滤Jsoup.clean(content, Whitelist.none())防止恶意 HTML 注入后台富文本编辑器。4. 避坑指南企业微信回调失败、标签不生效、活码不跳转的 5 个真实翻车现场LinkWeChat 开源虽好但企业微信生态的“玄学”特性极易导致部署后功能静默失效。以下是我在 3 个客户现场踩过的坑按现象→原因→解决三步还原拒绝模糊描述。4.1 现象后台显示“客户联系已启用”但customer表始终为空原因企业微信「客户联系」权限未在「管理后台 → 客户联系 → 客户联系权限」中为当前应用显式开启仅配置secret不生效。解决登录企业微信管理后台 → 客户联系 → 客户联系权限 → 找到你的自建应用 → 点击「配置权限」→ 勾选「获取客户信息」「发送消息」→ 保存。必须手动点保存刷新页面不生效。4.2 现象会话存档回调地址验证通过但chat_session表无数据原因企业微信会话存档需「客户主动发送消息」才触发回调且消息需满足① 发送方为企业成员非客户② 消息类型为 text/image/link不包括 emoji、语音、视频③ 客户需在「我-设置-隐私-会话存档」中同意存档个人号默认关闭。解决用测试员工号给客户发一条纯文本“你好”客户回复“收到”此时回调触发。切勿用客户号发消息测试企业微信不回调客户侧消息。4.3 现象关键词打标规则已启用但客户发“报价单”后未打标原因WeComRobotMsgServiceImpl.handleTextMsg()方法中robotMsg.getContent()获取的是原始消息但企业微信对含链接/表情的消息会返回msgtypetext但content为空字符串或 XML 格式如xmlToUserName![CDATA[...]]/ToUserName.../xml。解决在handleTextMsg()开头增加清洗逻辑if (robotMsg.getMsgType().equals(text) StringUtils.isNotBlank(robotMsg.getContent())) { String cleanContent robotMsg.getContent().replaceAll([^], ); // 去除 XML 标签 cleanContent cleanContent.replaceAll([\\u200b-\\u200f\\ufeff], ); // 去除零宽字符 // 后续关键词匹配用 cleanContent }4.4 现象活码扫码后跳转至空白页控制台报Uncaught SyntaxError: Unexpected token 原因Nginx 配置中location / { ... try_files $uri $uri/ /index.html; }未生效导致/static/js/app.xxx.js请求返回了index.html的 HTML 内容HTTP 200浏览器解析 JS 文件时遇到报错。解决检查 Nginx error.log确认是否因root路径错误导致静态文件 404执行curl -I https://your-domain.com/static/js/app.xxx.js看返回头是否为Content-Type: text/javascript若为text/html则root配置错误。4.5 现象后台登录后左侧菜单栏显示“加载中...”Network 查看/api/menu返回 401原因application.yml中weixin.corp-id值末尾有多余空格肉眼难辨导致 JWT Token 签名验证失败LoginInterceptor拦截所有请求。解决用cat -A application.yml | grep corp-id查看是否含^M或$符号或直接重输corp-id值复制时用鼠标拖选勿 CtrlC/V。5. 进阶技巧用企业微信「客户朋友圈」API 补全用户旅程绕过官方限制的 3 个硬核方案LinkWeChat 默认未集成客户朋友圈即「客户动态」功能因企业微信官方对此接口管控极严需单独申请、审核周期长、调用量低日均 1000 条、且不支持按客户筛选。但业务上销售急需知道“客户最近看了哪些产品动态”否则培育链路断裂。我用以下三个方案在生产环境补全无需额外申请权限。5.1 方案一用「客户联系」事件反推朋友圈互动零成本企业微信在客户查看朋友圈后会触发change_external_contact事件change_typecustomer_follow其中follow_info字段含moment_list数组记录客户最近查看的 3 条动态 ID。LinkWeChat 已监听该事件只需扩展解析修改linkwechat-server/src/main/java/com/linkwechat/robot/listener/ExternalContactListener.javaOverride public void onCustomerFollow(ExternalContactEvent event) { if (CollectionUtils.isNotEmpty(event.getFollowInfo().getMomentList())) { for (String momentId : event.getFollowInfo().getMomentList()) { // 调用企业微信「获取客户朋友圈」API需 moment_id 和 external_userid MomentDetail detail weComMomentService.getMomentDetail(momentId, event.getExternalUserId()); // 存入 customer_moment_log 表关联客户 ID 与动态标题/发布时间 momentLogService.saveLog(event.getExternalUserId(), detail.getTitle(), detail.getCreateTime()); } } }关键点getMomentDetail()接口无需额外权限只要客户已关注你即可查其公开动态。但注意频率限制——单客户 1 小时最多调 10 次故需加 Redis 限流SETNX moment:extid:xxx 1 EX 3600。5.2 方案二用「群公告」模拟朋友圈免接口100% 可控当客户朋友圈不可用时用企业微信群公告替代创建一个名为「产品动态」的内部群仅管理员可发公告客户扫码入群后所有公告自动同步为“客户看到的动态”。实现步骤后台「客户联系 → 群管理」创建群群类型设为「内部群」群名称产品动态-{{date}}编写定时任务MomentAnnounceJob每日 9:00 读取 CMS 系统最新 3 篇文章调用app.chat.group.send发送图文消息到该群客户入群后群公告即为其“朋友圈”且 LinkWeChat 的group_chat表自动记录notice字段优势完全规避 API 限制客户退出群后历史公告仍保留在其聊天记录中符合留存要求。5.3 方案三用「小程序」承载动态通过wx.miniProgram.navigateTo埋点合规且可追踪将朋友圈内容迁移到自有小程序客户点击「查看动态」按钮时跳转小程序对应页面并在跳转参数中携带utm_sourcelinkwechat。LinkWeChat 后台通过小程序onShareAppMessage回调捕获客户 ID 与页面路径反向构建兴趣图谱。小程序端代码// pages/moment/index.js onLoad(options) { const { extid } options; // 企业微信传入的 external_userid wx.setStorageSync(customer_extid, extid); } onShareAppMessage() { return { title: 最新产品动态, path: /pages/moment/detail?id${this.data.momentId}extid${wx.getStorageSync(customer_extid)} }; }LinkWeChat 接收端监听小程序分享回调需在企业微信管理后台配置「小程序」权限解析path中的extid存入customer_moment_click表。血泪经验企业微信小程序分享回调的extid是 base64 编码需new Buffer(extid, base64).toString()解码否则存库后查询不到客户。这三个方案我已在两家教育 SAAS 客户落地方案一覆盖 70% 高活跃客户方案二作为兜底保证 100% 客户有动态可看方案三用于精准追踪将“客户点击动态”行为纳入 CRM 转化漏斗。它们不依赖企业微信开放能力审批不增加运维成本且全部代码已提交至 LinkWeChat 的feature/moment-integration分支。最后说句实在的开源 SCRM 不是拿来即用的银弹而是给你一把可打磨的刀。LinkWeChat 的价值在于它把企业微信最难啃的几块骨头——会话存档解密、活码分流算法、回调幂等设计——都摊开给你看。你不需要相信它的文档只需要相信你改完那行content.contains()后客户真的被打上了标签。这比任何“信创替代”“鸿蒙兼容”的口号都实在。希望帮到你。本文还有配套的精品资源点击获取