# 企业微信 员工↔客户 IM 功能部署说明 本文档对应「管理后台 IM」功能(`admin/src/views/chat`),负责在后端/后台视角下的**收/发**一对一消息。 - 发送:企业群发 `add_msg_template`(员工本人身份 + 手机端确认) - 接收:会话内容存档(官方 C SDK + RSA 解密 + 计划任务拉取) - 老回调接口 `QywxExternalContactCallbackController` 仍用于 **添加/编辑/删除外部联系人** 事件,不承载消息内容。 --- ## 1. 数据表 执行一次: ```bash mysql -u -p < server/database/migrations/2026_04_20_create_qywx_msg_tables.sql ``` 新增 5 张:`qywx_msg_session`、`qywx_msg_archive`、`qywx_msg_archive_media`、`qywx_msg_send_task`、`qywx_msg_archive_cursor`。 ## 2. 配置(server/config/pay.php → wechat_work) ```php 'customer_contact_secret' => '客户联系 Secret', // 已有:发消息 / 客户列表 // 会话内容存档 'msgaudit_enabled' => true, // 开关 'msgaudit_secret' => '会话存档 Secret', // 独立应用 Secret 'msgaudit_public_key_ver' => 1, // 管理后台上传公钥返回的版本号 'msgaudit_private_key_path'=> '/data/qywx/priv.pem', // RSA 私钥文件路径(建议) 'msgaudit_sdk_lib_path' => '/data/qywx/libWeWorkFinanceSdk_C.so', // SDK 动态库 'msgaudit_timeout' => 60, 'msgaudit_limit' => 500, ``` > `msgaudit_enabled = false` 或 `msgaudit_sdk_lib_path` 留空时,后端不会崩溃——拉取命令会以 `noop` 方式返回,方便「先上线、再接 SDK」。 ## 3. C SDK & 私钥准备 1. 从企微管理后台 → 安全与合规 → 会话内容存档 → SDK 下载区,下载官方 C SDK。 2. Linux 部署 `libWeWorkFinanceSdk_C.so` 到 `/data/qywx/`,并确保 PHP-FPM 账户有可执行权限。 3. **安装 PHP FFI**:`php.ini` 开启 `ffi.enable=true`(preload 场景用 `preload`,CLI + FPM 用 `true`)。 4. 生成 RSA 密钥对:`openssl genrsa -out priv.pem 2048 && openssl rsa -in priv.pem -pubout -out pub.pem` 5. 企微后台上传 `pub.pem` → 记录返回的 `public_key_ver` → 回填到配置。 6. 私钥 `priv.pem` 放到 `msgaudit_private_key_path` 指向的位置,权限 600。 > 轮换公钥时,**保留历史 `public_key_ver → private_key` 映射**(SDK 会用旧消息的版本号去找旧私钥)。如需多版本,代码已预留:在 `WeComFinanceSdkClient::loadPrivateKeys()` 同一配置下,可额外传入 `msgaudit_private_keys` 数组形式支持多版本(自行扩展)。 ## 4. 计划任务 推荐 30 秒一次(企微限制:每分钟不超过 600 次)。 Linux crontab(每 30s 跑一次的 2 行写法): ``` * * * * * cd /path/to/server && /usr/bin/php think qywx:sync-msg-archive --download >/dev/null 2>&1 * * * * * cd /path/to/server && sleep 30 && /usr/bin/php think qywx:sync-msg-archive --download >/dev/null 2>&1 ``` 可选:把媒体下载拆开、并发运行: ``` * * * * * cd /path/to/server && /usr/bin/php think qywx:sync-msg-archive >/dev/null 2>&1 */2 * * * * cd /path/to/server && /usr/bin/php think qywx:sync-msg-archive --only-media --max-media=500 >/dev/null 2>&1 ``` 命令已做 `flock` 互斥(`runtime/qywx_msg_archive.lock`),并发多跑也不会重复写。 ## 5. 权限与菜单 后台菜单需新增一条,路径 `/chat`(或自定义),component 指向 `views/chat/index.vue`。 建议开启菜单权限控制:仅运营/客服组可见。API 路由前缀为 `qywx.message/*`,请在 `adminapi` 权限中心相应配置。 ## 6. 使用流程 1. 打开「IM」页面,系统自动拉取会话列表。 2. 左侧可按员工筛选;选中会话后中间展示消息流,8 秒轮询新消息。 3. 在底部输入框输入文本/添加附件(图片/视频/文件/链接/小程序)→ 点击「发送」。 4. 后端创建企微 `add_msg_template` 任务,**员工手机**会收到确认弹窗,确认后客户即可收到。 5. 已发送任务可在右侧「发送任务」Tab 查看送达详情。 ## 7. 故障排查 | 症状 | 排查 | | --- | --- | | 前端顶部提示「会话存档 SDK 不可用」| 查 `config/pay.php` msgaudit_* 配置 + `libWeWorkFinanceSdk_C.so` 路径 + `ffi.enable` + 日志 `runtime/log/`。调 GET `/adminapi/qywx.message/archive_status` 看诊断。 | | 消息拉取无数据 | `php think qywx:sync-msg-archive -vvv`;看 `qywx_msg_archive_cursor` 表 seq 是否推进;确认企微已开通会话存档 License 并勾选目标员工。 | | 媒体文件一直下载中 | `qywx_msg_archive_media.status` / `retry_count` / `last_error`;路径 `runtime/qywx_msg_media/` 写权限;SDK `GetMediaData` 超时调大 `msgaudit_timeout`。 | | 发送「消息内容不能为空」 | 文本 + 附件都为空;或附件上传失败导致没有 media_id,面板会红色报错。 | | 发送成功但客户未收到 | 员工手机端漏点确认;或该员工未启用企业群发能力;查 `qywx_msg_send_task.fail_list` & 右侧「送达详情」。 | | 需要撤回消息 | 企微规则:群发任务已提交后无法撤回单条;会话存档里会记录 `recall` 动作并在前端打「已撤回」标记。 | ## 8. 未来扩展点 - 多版本 RSA 私钥轮换:`WeComFinanceSdkClient::loadPrivateKeys()` 改造为读取 `msgaudit_private_keys` 数组,key 为 `public_key_ver`。 - WebSocket 推送新消息:在 `QywxMsgArchiveService::persistBatch()` 写入后发布到 Workerman / Swoole,前端改成订阅(当前是 8s 轮询)。 - 群聊发送:企微「客户群 SOP」接口,当前 UI 仅展示群会话消息不允许发送(`canSendMessage` 为 false)。