简介:这是一套面向企业私域运营场景的Java企业微信SCRM系统源码,适合具备一定Java开发基础、希望快速搭建客户管理与营销平台的开发者或技术团队。系统围绕运营中心、引流获客、客户中心、客情维系、社群运营、全能营销、企业风控、企业管理八大模块展开,覆盖活码引流、会话存档、朋友圈红包、多场景营销工具等能力,并全面对接企微开放API,二次封装降低重复踩坑成本,采用主流Java架构保证扩展性与灵活性。资源包共1542个文件,以877个java后端源码、195个vue前端页面、120个js脚本及99个xml配置为主,另含少量sql、yml、properties等部署与配置项,压缩包约9.57MB,目录结构清晰便于按模块查阅。目前已有1059人学习下载,可作为私域流量管理与营销系统二次开发的参考底稿。
1. 企业微信 SCRM 源码拆包:从会话存档到侧边栏,一套能跑起来的私域底座
很多团队做私域,前端页面堆得飞快,一到“消息怎么落库、客户怎么分给销售、离职怎么交接”就集体卡壳。这套 Java 企业微信 SCRM 系统源码,解决的就是这个卡壳点:它把企业微信的会话存档、客户联系、侧边栏、群活码这些能力,用 Java 后端串成了一条可运行的链路。适合谁?一是手里有企业微信管理员权限、想自建私域中台的团队;二是接外包需要快速交付 SCRM 项目的开发者;三是想研究企微回调加解密、会话拉取怎么落库的工程师。它不是 SaaS 账号,是一套能部署到自己服务器上的源码,数据在自己手里,二次开发不用看别人脸色。下面按“能跑起来”的顺序拆。
2. 环境与依赖:把项目从压缩包跑到登录页
2.1 技术栈与目录结构先摸清
拿到源码先别急着mvn spring-boot:run,花十分钟看目录,后面省两小时。常见做法是后端 Spring Boot 多模块,前端 Vue 或 React 单独一个目录,数据库 MySQL 8,缓存 Redis,对象存储放会话存档的媒体文件。典型结构长这样:
wecom-scrm/ ├── scrm-admin/ 后端启动模块,含 application.yml ├── scrm-common/ 工具类、企微加解密、常量 ├── scrm-system/ 客户、员工、标签等业务 ├── scrm-wecom/ 企微回调、会话存档、侧边栏接口 ├── scrm-ui/ 前端工程 ├── sql/ 初始化脚本 └── docker-compose.yml 中间件编排scrm-wecom是核心,企微所有回调都进这里。scrm-common里的加解密工具别乱改,企微回调的 AES 加解密对参数顺序敏感,改错一个字节就解不出来。
2.2 中间件与配置项
用 docker-compose 起 MySQL 和 Redis 最省事,注意 MySQL 字符集要utf8mb4,会话存档里的表情和特殊符号用utf8会直接报错。启动前必须改的配置集中在application.yml:
wecom: corp-id: ww******** # 企业ID,企微后台「我的企业」里拿 agent-id: 1000002 # 自建应用AgentId secret: ******************** # 自建应用Secret token: ******************** # 回调Token,自己设,和后台一致 aes-key: ******************** # 回调EncodingAESKey,43位 chat-secret: ************** # 会话存档专用Secret chat-private-key: /opt/keys/private_key.pem # 会话存档RSA私钥corp-id、agent-id、secret三个填错,登录页都进不去,因为拿不到access_token。chat-secret和chat-private-key是会话存档单独用的,和自建应用的 secret 不是一回事,这是新手最容易混的地方。
2.3 建库、导数据、起服务
# 1. 建库 mysql -uroot -p -e "CREATE DATABASE scrm DEFAULT CHARACTER SET utf8mb4;" # 2. 导入初始化脚本 mysql -uroot -p scrm < sql/scrm_init.sql # 3. 起中间件 docker-compose up -d mysql redis # 4. 编译后端 mvn clean package -DskipTests # 5. 启动 java -jar scrm-admin/target/scrm-admin.jar --spring.profiles.active=dev前端单独npm install && npm run dev,默认代理到后端 8080。起来后访问登录页,用初始化脚本里的管理员账号登录。如果登录报“获取 token 失败”,九成是corp-id或secret填错,去企微后台核对,别在代码里找问题。
提示:会话存档的私钥文件权限设成 600,且不要提交到 Git。私钥泄露等于聊天记录裸奔。
3. 企微回调与会话存档:消息怎么进来、怎么落库
3.1 回调 URL 的加解密链路
企微回调不是明文,是 AES 加密后 POST 过来的。源码里scrm-common的WxCpCryptUtil负责解密,流程是:企微用你的aes-key加密,你收到msg_signature、timestamp、nonce、echostr(验证时)或加密 XML(消息时),先验签再解密。验签逻辑:
// 伪代码,对应源码 WxCpCryptUtil.checkSignature String[] arr = {token, timestamp, nonce, encryptMsg}; Arrays.sort(arr); String sha1 = DigestUtils.sha1Hex(String.join("", arr)); if (!sha1.equals(msgSignature)) { throw new RuntimeException("签名校验失败"); }token必须和企微后台「接收事件服务器」里填的完全一致,包括大小写。签名失败最常见的原因是 URL 里带了多余参数,或者 Nginx 转发时把timestamp改了。解密后拿到明文 XML,再解析成业务对象。
3.2 会话存档的拉取与落库
会话存档不是回调推给你,是你主动调接口拉。源码里有个定时任务ChatArchiveTask,逻辑是:记录上次拉取的seq,每次从seq开始拉一批,拉完更新seq。核心调用:
// 拉取会话记录,seq 是游标,limit 单次最大1000 ChatArchiveResp resp = wecomService.getChatRecords(seq, 1000); for (ChatArchiveMsg msg : resp.getMsgList()) { // 媒体文件单独下载,文本直接入库 if ("text".equals(msg.getMsgType())) { chatMapper.insert(buildTextEntity(msg)); } else { String mediaId = msg.getMediaId(); // 下载媒体,存本地或OSS,路径写回记录 String path = mediaService.downloadAndSave(mediaId); chatMapper.insert(buildMediaEntity(msg, path)); } } // 更新游标,下次从这里继续 configService.updateSeq(resp.getNextSeq());seq是单调递增的,别用时间戳去查,会漏。媒体文件下载有频率限制,源码里加了Thread.sleep做限流,别删。落库表一般分chat_text和chat_media两张,媒体表存路径不存二进制,否则数据库膨胀得飞快。
3.3 客户联系与侧边栏接口
客户联系这块,源码实现了「客户列表同步」「打标签」「分配销售」。侧边栏是企微聊天窗口右侧的 H5,通过agentConfig注入身份,前端拿external_userid调后端查客户画像。关键接口:
// 侧边栏获取当前客户信息 @GetMapping("/sidebar/customer") public R<CustomerVO> getCustomer(@RequestParam String externalUserId) { // 先查本地库,没有再调企微接口补全 CustomerVO vo = customerService.getByExternalUserId(externalUserId); if (vo == null) { vo = wecomService.fetchCustomerDetail(externalUserId); customerService.save(vo); } return R.ok(vo); }external_userid是客户在企微里的唯一标识,但同一个客户在不同企业下 ID 不同,别拿它当全局主键。侧边栏的agentConfig签名要用当前页面的 URL,URL 变了签名就失效,前端路由切换后要重新签。
4. 避坑与排查:那些让你加班到凌晨的细节
4.1 回调验证一直失败
现象:企微后台点「保存」提示“回调验证失败”。原因:echostr解密后没原样返回,或者返回时多了引号。解决:解密后直接response.getWriter().write(plainEchostr),不要包 JSON。另外确认aes-key是 43 位,不是 44 位带等号的。
4.2 会话存档拉不到数据
现象:定时任务跑了,但chat_text表一直是空的。原因:会话存档功能没在企微后台开启,或者chat-secret用的是自建应用的 secret。解决:企微后台「安全与管理」里开启会话存档,生成专用 secret 和公私钥,私钥放服务器,公钥传企微。拉取接口返回errcode非 0 时,源码里只打了日志没抛异常,去日志里搜getChatRecords看真实错误码。
4.3 媒体文件下载 403
现象:文本能落库,图片和语音下载报 403。原因:媒体下载 URL 有时效,且需要access_token,源码里 token 缓存过期了没刷新。解决:检查AccessTokenService的过期时间,企微 token 有效期 7200 秒,源码里设了 7000 秒刷新,如果服务器时间不准会提前过期。装个 NTP 同步时间。
4.4 侧边栏白屏
现象:企微里打开侧边栏一片白。原因:H5 页面用了localhost或内网 IP,企微客户端访问不到。解决:侧边栏必须是公网可访问的 HTTPS 域名,且域名要在企微后台「应用管理」里配置为可信域名。本地调试用内网穿透工具映射一个临时域名,别直接写 IP。
4.5 客户分配后销售看不到
现象:后台把客户分配给销售了,销售登录后列表为空。原因:分配逻辑只写了customer表的owner_id,没同步follow_user关系表。解决:源码里CustomerAssignService有两步,先更新客户归属,再插跟进人关系,检查第二步是否被事务回滚了。看日志里有没有DuplicateKeyException。
5. 二次开发与验证:把源码变成自己的东西
5.1 加一个自定义标签同步
源码自带的标签同步是全量覆盖,实际业务里往往要保留本地标签。改法是重写TagSyncService的sync方法,先查本地已有标签,再和企微返回的做 diff,只增不删。验证方法:在企微后台加一个标签,手动触发同步,看tag表是否新增且旧标签还在。
5.2 会话存档的敏感词告警
会话存档落库后,可以接一个敏感词过滤。源码里预留了ChatMessageListener接口,实现它即可:
@Component public class SensitiveWordListener implements ChatMessageListener { @Override public void onMessage(ChatTextEntity entity) { // 简单示例:命中关键词就写告警表 for (String word : sensitiveWords) { if (entity.getContent().contains(word)) { alertMapper.insert(buildAlert(entity, word)); break; } } } }sensitiveWords从配置文件或数据库加载,别硬编码。验证:用测试账号发一条含关键词的消息,等定时任务拉取后看alert表有没有记录。注意会话存档有延迟,通常几分钟,别发完就查。
5.3 部署到服务器的检查清单
| 检查项 | 正确做法 | 常见错误 |
|---|---|---|
| 回调 URL | 公网 HTTPS,无多余参数 | 带?debug=1导致验签失败 |
| 可信域名 | 企微后台配置 | 用 IP 或 localhost |
| 私钥权限 | chmod 600 | 755 被其他用户读取 |
| Token 缓存 | Redis 共享 | 多实例各自缓存导致频繁刷新 |
| 媒体存储 | OSS 或本地磁盘挂载 | 存容器内,重启丢失 |
多实例部署时,access_token必须放 Redis 共享,否则两个实例互相刷新,企微会限流。源码里默认用本地缓存,上生产前改成 Redis 实现。
5.4 一个验证源码是否跑通的小技巧
不用等所有功能都配好,先跑通「回调验证」和「拉取一条会话」这两步。回调验证通过说明加解密和网络通了,拉取一条会话说明chat-secret和私钥对了。这两步过了,剩下的就是业务逻辑问题,不是环境问题。我一般会在ChatArchiveTask里临时加一行日志,打印seq和拉取条数,确认游标在动。
从那以后我每次拿到这类源码,都强制先跑通回调验证和一条会话拉取,再动业务代码。希望帮到你。
本文还有配套的精品资源,点击获取