拓冰建站拓冰建站
首页 / 资讯中心 / 正文

企业微信SCRM开源系统LinkWeChat源码部署与二次开发实战

简介基于企业微信开发的LinkWeChat开源SCRM系统设计源码是一套面向企业私域流量管理与营销的综合解决方案。后端采用主流Java微服务架构前端使用Vue3覆盖客户、群聊、朋友圈、任务、红包、裂变等典型业务模块适合有一定Java基础的中高级开发者学习企业微信SCRM落地方式。压缩包共2000个文件以Java源文件1778个和XML配置212个为主辅以Properties参数文件、文本说明及Markdown文档整体体积27.64MBJava代码承载核心业务逻辑XML/Properties负责框架与运行配置目录结构清晰便于按模块检索。代码注释详尽模块划分明确可快速定位客户服务、群聊任务、素材管理、二维码与裂变等核心实现是进行企业私域运营系统二次开发或毕业设计的重要参考。目前已有861人学习下载。1. 从一套可编译源码看企业微信SCRM的真实边界LinkWeChat 作为开源的 SCRM 系统它解决的并不是“加好友、发朋友圈”这类表层需求而是把企业微信的客户联系、群运营、会话存档和营销触达能力封装成一套可自托管的业务中台。市面上多数 SaaS 版 SCRM 按账号数收费数据落在别人服务器上而 LinkWeChat 的价值在于源码在手你可以在自己的服务器上编译、部署、改造成符合业务形态的私域工具链。很多团队拿到这套源码后第一反应是找部署文档但真正卡住他们的往往是三个问题企业微信侧的应用配置参数和回调地址怎么和本地代码对应起来会话存档功能的公私钥到底怎么生成和配置以及二次开发时新增一个营销任务需要动哪些表、哪些接口。这三个问题恰恰是把这套系统从“能跑起来”推向“能用起来”的关键。这篇文章不会复述 LinkWeChat 官方文档而是从源码结构、部署链路、二次开发、典型业务实现四个层面把它作为一套企业微信服务端工程来拆解。适合正在选型或已经拉下来源码、准备做私有化部署的开发者和运维人员也适合想理解企业微信开放平台能力边界的架构师。2. 企业微信服务端开发的三个前置概念回调、Token、会话存档LinkWeChat 的代码本质上是企业微信开放平台的服务端实现。理解它之前必须先弄清楚企业微信回调、Token 机制和会话存档这三个基础概念因为源码里大量配置项和工具类都在围绕这三件事展开。2.1 回调 URL 与企业微信的签名校验机制企业微信的所有事件通知比如客户添加、群成员变更、消息接收都会由企业微信服务器向你在管理后台配置的回调 URL 发起 HTTP 请求。这个回调 URL 必须是一个公网可访问的 HTTPS 地址并且都要通过签名校验。企业微信的签名校验参数有四个msg_signature、timestamp、nonce、echostr。LinkWeChat 源码中WxCpCryptUtil和WxCpAesException这套工具类就是用来处理这些参数的。URL 上携带的 timestamp 和 nonce 用于参与签名计算而 echostr 是加密字符串需要解密后原样返回才能完成 URL 的合法性验证。// 核心校验逻辑对 token、timestamp、nonce、加密串做字典序排序后拼接再 SHA1 哈希 String sortStr sort(token, timestamp, nonce, echostr); String sha1 SecureUtil.sha1(sortStr); if (!sha1.equals(msgSignature)) { throw new WxCpAesException(签名校验失败); }这段代码的逻辑是企业微信所有回调事件的“门禁”。很多部署者会遇到回调验证失败的问题常见原因是管理后台配置的 Token 与源码application.yml里的wx.cp.token不一致。还有一类隐蔽问题如果服务器前面挂了 Nginx且 Nginx 层做了 SSL 终止那么echostr中包含的号在 URL 传递过程中可能被解码为空格导致验签失败。此时需要在 Nginx 配置中关闭对查询参数的解码或者改用 POST 方式验证。2.2 EncodingAESKey 的生成、轮换与配置位置EncodingAESKey 是企业微信回调消息的 AES 加密密钥长度为 43 位由大小写字母和数字组成。企业微信管理后台的“接收消息”设置页面可以手动生成也可以由系统随机生成。生成的密钥需要同时填写到管理后台和源码配置中源码中的配置位置在application.ymlwx: cp: corp-id: ww1234567890abcdef agent-id: 1000002 secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx token: yourRandomToken aes-key: your43CharEncodingAESKey注意secret和企业微信管理后台“企业应用”中的 Secret 要严格对应且不要和agent-id混淆。agent-id是应用的数字编号secret是应用级密钥二者关联在企业微信后台的应用详情页。轮换 EncodingAESKey 时需要先在代码里将新密钥写入配置待服务重新加载后再在企业微信后台点击“保存”否则新旧密钥切换期间会造成事件丢失。2.3 会话存档公钥、私钥与数据落地链路LinkWeChat 的会话存档模块是很多企业选型它的核心原因。会话存档开启后企业微信会在员工和客户同意的前提下将聊天记录加密后推送到企业指定的回调地址。这个模块涉及两组密钥一组是企业微信公钥用于加密消息一组是企业的私钥用于解密消息。公钥可以定期从企业微信管理后台的“管理工具-会话存档”页面下载是 PEM 格式。私钥由企业自己生成并妥善保存LinkWeChat 源码解密时调用的是 Java 的Cipher类具体实现位于wecom-cp模块的WxCpChatDataDecryptor中。需要指出的是会话存档的推送消息体量很大高峰期每秒可能上千条部署时建议把存档数据直接写入 Kafka 或 RocketMQ而不是通过 LinkWeChat 内置的同步接口逐条落库否则数据库连接池会先被打爆。3. 从源码到可运行LinkWeChat 的项目结构与最小部署方案LinkWeChat 是一个典型的前后端分离工程。前端是 Vue 2 生态后端是 Spring Cloud 微服务架构。拿到源码后不要急着想全部跑通先把最小链路搭起来MySQL、Redis、后端服务、前端页面。3.1 后端微服务模块划分与启动顺序源码的linkwe-chat目录下按业务域拆分了多个 maven 模块核心模块包括linkwe-chat: 企业微信 API 对接层处理回调、通讯录同步、外部联系人管理linkwe-core: 公共工具类、注解、常量定义linkwe-auth: 鉴权与用户体系linkwe-system: 系统管理、角色权限、菜单配置linkwe-common: 数据库实体类与 Mapper 接口从启动顺序上看linkwe-auth需要先启动因为它负责生成登录态 token后续所有请求都依赖它。接着是linkwe-chat它会向企业微信后台注册回调服务。最后是linkwe-system和其他业务模块。实际上 LinkWeChat 通过 Nacos 做服务注册发现这里如果本地没有部署 Nacos可以让所有服务直连配置中心关闭注册发现依赖但生产环境不建议这样操作。启动前必须确认 MySQL 版本和数据库字符集。linkwe_script目录下的 SQL 脚本是 UTF-8 编码如果库字符集设置成了 utf8mb4 但排序规则和表不一致启动时插入中文数据会报 “Incorrect string value” 错误。建议统一所有表的DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci。3.2 企业微信后台配置与本地代码的字段映射部署过程中最容易让人困惑的是企业微信后台的“可信域名”和“网页授权及 JS-SDK”配置。LinkWeChat 前端页面里的扫码登录、员工扫码添加客户这两个功能依赖企业微信网页授权。企业微信后台要求可信域名和重定向域名完全一致且必须 ICP 备案不能带端口号。在代码侧前端的wx.config签名由后端的getJsapiTicket接口生成。注意jsapi_ticket的有效期是 7200 秒LinkWeChat 源码中用 Redis 缓存了 ticketkey 格式是jsapi_ticket:{corpId}。如果企业微信后台更新了应用配置ticket 不会立即失效但签名算法中的noncestr和timestamp每次都要重新生成不能复用。此外企业微信的allow to cross corp参数决定了应用是否允许跨企业访问。LinkWeChat 中如果要对接上下游企业需要开启这个开关否则回调推送的外部联系人数据不完整。3.3 Linux 服务器部署从编译到 systemd 守护进程既然很多人在搜索“企业微信 linux”这里给出一套适合生产环境的部署顺序。以 CentOS 7 为例先安装 JDK 1.8 和 Maven 3.6然后执行# 拉取源码并编译跳过单元测试 git clone https://github.com/xxx/LinkWeChat.git cd LinkWeChat mvn clean install -DskipTests -Pprod # 启动核心服务假设打成 jar 包 nohup java -jar linkwe-auth/target/linkwe-auth.jar --spring.profiles.activeprod /data/logs/auth.log 21 -Pprod激活的是application-prod.yml这个文件里的数据库地址和 Redis 地址必须提前改好。使用nohup启动仅适合临时验证生产环境我一般会写成systemd服务让linkwe-chat在所有依赖模块启动后自动拉起并在进程意外退出时自动重启。[Unit] DescriptionLinkWeChat Chat Service Afternetwork.target mysqld.service redis.service [Service] ExecStart/usr/local/jdk/bin/java -Xms1g -Xmx2g -jar /opt/linkwechat/linkwe-chat.jar Restartalways RestartSec10 Userwww-data [Install] WantedBymulti-user.target需要重点说明的是Restartalways在服务因 OOM内存溢出被系统杀掉时非常有用但如果是因为数据库连接池耗尽导致的假死systemd 的自动重启并不会清理连接池这时需要在 JVM 参数中加上-XX:ExitOnOutOfMemoryError让 JVM 在堆内存溢出时主动退出再由 systemd 拉起来。3.4 配置项速查表部署前必改的参数部署过程中涉及的配置项较多下面这张表汇总了必须检查的参数以及它们在哪里配置配置项配置位置必须一致性企业 corpIdapplication.yml/ 企业微信后台-我的企业完全一致应用 secretlinkwe-chat.yml与企业微信后台应用详情一致Token 和 AES Keyapplication.yml与回调设置页面一致可信域名Nginx server_name 和企业微信后台一致且已备案数据库 JDBC 连接串application-prod.yml指向正确的库名Redis 地址与密码application-prod.yml与 Redis 实例一致如果只做本地跑通测试可以临时关闭微信回调验证但生产环境不要这么做。回调验证是防止恶意请求伪装企业微信服务器的第一道防线。4. 企业微信客户联系与群运营功能在 LinkWeChat 中的落地实现LinkWeChat 之所以被称为 SCRM 而不是简单的“通讯录同步工具”是因为它把企业微信的客户联系能力抽象成了可配置的营销和运营流程。这一章挑三个典型功能来看源码里的实现思路客户分群打标签、群发消息的定时任务、渠道活码的参数解析。4.1 客户分群与标签体系从企业微信标签到本地数据库的同步企业微信本身有标签能力但标签粒度粗、无法做自定义字段。LinkWeChat 的做法是定时拉取企业微信的客户详情把客户 ID、添加员工 ID、来源渠道存入本地customer表再把企业微信标签同步到tag表并在customer_tag_rel表中建立多对多关联。-- 查询客户时联表带上标签用于分群筛选 SELECT c.customer_name, c.mobile, t.tag_name FROM customer c LEFT JOIN customer_tag_rel r ON c.id r.customer_id LEFT JOIN tag t ON r.tag_id t.id WHERE t.tag_name IN (高意向, 已下单);这里有一个常见误区企业微信标签有“企业标签”和“个人标签”之分企业标签需要客户联系配置中的tag_id才能同步个人标签无法通过 API 获取。LinkWeChat 源码中同步的是企业标签所以在管理后台打标签时要确保员工使用的是企业标签而非个人标签否则同步后本地查不到。4.2 定时群发任务避开频控限制的调度设计企业微信对群发消息有严格频控每个客户每天最多接收一条群发消息企业每月对每个客户最多群发 4 次。LinkWeChat 的群发任务模块本身不关心频控它只负责把任务拆解成一个个发送请求真正的频控由企业微信服务端强制拦截。如果群发任务给 10000 个客户发送直接循环调 API 会触发错误码 45009接口调用频率限制。源码中的解决思路是分批提交每批 100 个客户每批之间休眠 2 秒。实际部署中这个等待时间要和企业的不同应用类型匹配自建应用与第三方应用额度不同建议调大休眠时间到 5 秒以上。// 群发任务分批发送伪代码 ListCustomer customers getTargetCustomers(); for (int i 0; i customers.size(); i 100) { ListCustomer subList customers.subList(i, Math.min(i 100, customers.size())); sendGroupMsg(subList); Thread.sleep(5000); // 稳过频控的保守间隔 }注意Thread.sleep在 Spring Boot 的异步任务中会占用线程资源任务量大时建议改用 Quartz 的DisallowConcurrentExecution注解保证前一批任务没结束前下一批不会并发执行否则接口频次会叠加。4.3 多渠道活码参数解析与统计归因渠道活码是 SCRM 拉新的核心工具。员工把活码打印成海报用户扫码后企业微信自动添加该员工活码参数决定了这个用户被归因到哪个渠道、哪场活动。LinkWeChat 中活码的实现原理是后端根据渠道 ID 生成一个二维码码值码值映射到channel_code表的记录。用户扫码后企业微信回调change_external_contact事件源码在handleAddExternalContact中解析state参数这个state就是渠道 ID 的加密串。// 前端生成活码请求 axios.post(/api/customer/addWay, { scene: 1, state: encodeURIComponent(channelId), user: [zhangsan, lisi], remark: 抖音广告-11月活动 })如果发现扫码添加后的客户渠道归因为空优先检查state参数是否包含特殊字符。企业微信要求state不超过 30 个字符如果渠道 ID 是 UUID建议先用短码映射表转换成 6 位数字再传给企业微信否则回调中state被截断会导致查不到记录。5. 二次开发实战给 LinkWeChat 增加一个“客户流失预警”功能系统部署起来之后真正的价值在于二次开发。这一章做一个完整的开发演示当客户被员工删除客户流失时LinkWeChat 自动生成预警记录并通知管理员。这个功能在企业微信回调中是有对应事件的可以完全基于 LinkWeChat 现有的事件处理框架实现。5.1 新增数据库表与实体类客户流失事件触发后除了要记录客户 ID 和员工 ID还需要记录客户昵称、流失时间、归属部门等信息便于后续统计。CREATE TABLE customer_loss ( id bigint(20) NOT NULL AUTO_INCREMENT, customer_id varchar(64) NOT NULL COMMENT 客户ID, user_id varchar(64) NOT NULL COMMENT 员工ID, dept_id bigint(20) DEFAULT NULL COMMENT 归属部门, loss_time datetime DEFAULT NULL COMMENT 流失时间, status tinyint(1) DEFAULT 0 COMMENT 是否已处理, PRIMARY KEY (id), KEY idx_customer_id (customer_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户流失记录表;对应的实体类放在linkwe-common模块中直接继承 BaseEntity 获取创建时间、更新时间字段。这里要注意LinkWeChat 的代码生成器是基于 MyBatis-Plus 的所以实体类需要加TableName(customer_loss)注解Mapper 接口继承BaseMapperCustomerLoss不需要手写 XML 就能执行大部分 CRUD。5.2 监听企业微信删除客户回调事件企业微信删除客户的事件类型是del_external_contact。在 LinkWeChat 的事件分发器中找到处理change_external_contact的入口方法在原有的add、edit分支后面追加del分支。// 事件处理客户删除事件 if (del_external_contact.equals(eventType)) { String customerId requestMap.get(ExternalUserID); String userId requestMap.get(UserID); customerLossService.record(customerId, userId); }这段新增逻辑执行后customer_loss表中就多了一条流失记录。但仅记录还不够需要主动通知管理员。通知方式可以选择企业微信群机器人。企业微信群机器人的 webhook 地址是固定的向该地址 POST 一段 JSON 即可推送消息。5.3 基于定时任务的消息汇总与告警单独一条流失事件不必立刻告警零散的流失很可能是正常删粉。更有业务价值的做法是配置一个定时任务每天上午 10 点汇总前一天的流失客户列表按部门发送到管理群。// Quartz 定时任务每日 10 点汇总流失客户 Component public class CustomerLossReportJob { Autowired private CustomerLossService lossService; Scheduled(cron 0 0 10 * * ?) public void sendDailyReport() { ListCustomerLoss lossList lossService.listYesterdayLoss(); String message buildReportMessage(lossList); sendToWechatGroup(message); } }调度框架的选择上LinkWeChat 本身已集成 QuartzScheduled注解虽然简单但不支持动态修改执行频率生产环境建议使用 Quartz 的 JobDetail CronTrigger 方式把执行时间配置到数据库表方便运营调整。这里使用Scheduled只是为了演示最小代码路径。5.4 二次开发时的权限拦截注意事项LinkWeChat 的接口默认都经过PreAuthorize权限校验。新增的 Controller 接口如果没有加对应的权限标识前端调用时会返回 403。快速解决方式是使用Anonymous注解跳过权限验证但仅限内部测试生产环境要通过权限菜单管理后台为对应角色绑定权限码。另一个容易踩的坑是跨模块的 Mapper 扫描。LinkWeChat 各微服务模块默认只扫描本模块包下的 Mapper 接口。如果新写的 Mapper 在linkwe-core模块而业务代码在linkwe-chat模块中引用运行时会出现Invalid bound statement错误。解决方法是必须在启动类上显示指定 Mapper 扫描包MapperScan({com.linkwechat.mapper, com.linkwechat.customer.mapper})6. 压测、验证与上线前必须检查的 5 个细节系统开发完成后不能直接上生产。这一章给出两个最能发现问题的验证手段以及上线前检查清单。6.1 用脚本模拟回调验证签名链路是否通企业微信回调通知是从企业微信服务器发起的本地开发环境很难收到真实回调。常见做法是使用内网穿透工具将本地端口暴露到公网然后通过日志确认签名校验是否能通过。更稳妥的方式是写一个模拟脚本构造加密的请求体直接打到本地服务curl -X POST http://localhost:8080/wx/cp/callback/simple \ -H Content-Type: application/xml \ -d xmlToUserName![CDATA[ww123]]/ToUserNameEncrypt![CDATA[模拟加密串]]/Encrypt/xml使用模拟请求时不能直接复制企业微信后台的 echostr因为 echostr 是一次性的且 URL 校验与 POST 事件推送加解密流程不同。建议把WxCpCryptUtil的encrypt方法单独写一个测试用例用同一个密钥先加密一段明文再调用回调接口验证解密结果这样可以隔离后端加解密逻辑的问题避免排查时不知道是签名错还是解密错。6.2 数据库慢查询与索引检查LinkWeChat 默认的定时同步任务会频繁读写customer和contact表随着数据量增长customer_id查询会越来越慢。上线前审视一下表索引是必要动作。-- 检查执行计划确认是否走了索引 EXPLAIN SELECT * FROM customer WHERE customer_id wmXXX AND user_id zhangsan;如果type列显示ALL而非ref或eq_ref说明缺索引。经常按user_id查询员工名下的客户可以在customer表上建立(user_id, customer_id)联合索引注意列顺序等值查询的列放前面范围查询的列放后面。6.3 上线前必查清单数据、密钥、日志最后这些检查项是我在多次部署中总结出的高性价比清单。每一条都避免过一次线上事故或调试通宵企业微信后台的“企业可信 IP”配置必须包含当前服务器的外网 IP否则调用 API 返回 60020 错误。如果服务器 IP 会变化建议使用固定 IP 或弹性公网 IP不要使用 NAT 网关的随机出口 IP 配置信任列表。application.yml中wx.cp.agent-id是字符串类型但企业微信后台显示的是数字配置时不用加引号YAML 会自行转换。如果把1000002和1000002混写会造成 agentId 匹配失败回调事件找不到对应的应用。服务器时间必须与 NTP 同步且时区设置为Asia/Shanghai。企业微信回调签名校验的时间戳只接受前后五分钟偏差服务器时间漂移会直接导致验签失败。这个问题的排查思路是看日志中timestamp参数和服务器当前时间相差多少差的不是几分钟而可能是时区问题差的恰好在边界值则是网络延迟。Redis 缓存使用默认的db0数据库没问题但要注意linkwe-chat模块的 Redis key 与其它模块的 Key 可能冲突。部署时建议在配置中给 LinkWeChat 相关 key 增加统一前缀比如lw:避免与存量系统共用 Redis 时互相覆盖。日志切割必须配置。logback-spring.xml中如果未设定日志文件大小上限会话存档模块会把磁盘快速写满。建议配置maxFileSize500MB和maxHistory7并在/data/logs/目录下按天归档。做完以上验证和检查系统才能进入稳定运行阶段。LinkWeChat 的源码质量在开源 SCRM 领域属于中上水平它的边界不在代码而在你对企业微信开放平台的熟悉程度。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门