first commit

This commit is contained in:
Your Name
2026-09-08 11:40:15 +08:00
commit a5353f7eb5
9568 changed files with 1646214 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# 企业微信 员工↔客户 IM 功能部署说明
本文档对应「管理后台 IM」功能(`admin/src/views/chat`),负责在后端/后台视角下的**收/发**一对一消息。
- 发送:企业群发 `add_msg_template`(员工本人身份 + 手机端确认)
- 接收:会话内容存档(官方 C SDK + RSA 解密 + 计划任务拉取)
- 老回调接口 `QywxExternalContactCallbackController` 仍用于 **添加/编辑/删除外部联系人** 事件,不承载消息内容。
---
## 1. 数据表
执行一次:
```bash
mysql -u<user> -p<pass> <db> < 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)。
+97
View File
@@ -0,0 +1,97 @@
# 企业微信获客助手配置
管理端菜单:`一诊 / 企业微信获客助手`
该功能使用当前企业的内部自建应用配置,不使用服务商第三方应用,也不需要 SuiteID、suite_ticket、永久授权码或企业扫码安装。
系统直接复用 `server/.env` 已有配置:
```ini
[work_wechat]
CORP_ID = "当前企业 CorpID"
AGENT_ID = "内部自建应用 AgentID"
CUSTOMER_ACQUISITION_SECRET = "获客助手可调用应用的 Secret"
[app]
HOST = "https://公开访问域名"
```
兼容已有项目:没有 `CUSTOMER_ACQUISITION_SECRET` 时,会依次回退读取 `AGENT_SECRET``SECRET`。如果现有 `SECRET` 就是获客助手中配置的“可调用应用”Secret,无需重复配置。
企业微信管理后台还需完成三项外部配置:开通获客助手、将该内部应用设置为获客助手可调用应用、将接口服务器公网 IP 加入可信 IP。页面“验证获客助手 API”会通过只读列表接口检查这些条件。
## 官方 API 对接范围
按[企业微信获客链接管理文档](https://developer.work.weixin.qq.com/document/path/97297)完成以下五个接口:
- 获取获客链接列表 `list_link`
- 获取获客链接详情 `get`
- 创建获客链接 `create_link`
- 更新获客链接 `update_link`
- 删除获客链接 `delete_link`
“永久删除企业微信链接”会调用官方删除接口且无法恢复;“从本地移除”只退出当前分流池,不会修改企业微信后台。
获客成员来自后台管理员的 `work_wechat_userid`。管理员可管理全量;组长、医助等账号只返回 `DataScopeService` 当前角色与部门范围内的成员。同步远端链接时,非全量账号只导入 `range.user_list` 与其可见成员有交集的数据;企业微信部门 ID 尚未建立本地映射时按安全原则隐藏,不会越权放行。
`list_link` 只返回当前获客助手可调用应用通过 API 创建的官方链接。后台历史手工粘贴的 `work.weixin.qq.com/ca/...` 链接,以及其他应用创建的链接,不会出现在当前应用的同步列表中,也无法仅凭 URL 反查为官方 `link_id`。需要官方客户、统计和消息归因时,应在本页面使用“创建官方获客链接”。
链接分流只接受企业微信获客助手生成的链接:
```text
https://work.weixin.qq.com/ca/xxxxxxxx
```
“联系我”、客户群、自有网页或其他外部链接均会被拒绝;已有的非获客助手历史链接也不会参与随机分流。Secret 与 access_token 不会返回到浏览器,也不会写入接口错误日志。
如果需要为点击 IP 生成不可逆服务端哈希,可在 `[qywx_promotion]` 下额外设置独立的 `CREDENTIAL_KEY`
公开 JS 示例:
```html
<script src="https://你的域名/api/qywx-promotion/js/分流方案KEY" defer></script>
<a href="https://你的域名/api/qywx-promotion/go/分流方案KEY" data-wecom-promotion="分流方案KEY">添加企业微信</a>
```
## 公开浮窗
每个分流方案可选择是否由同一段公开 JS 自动挂载客服浮窗。关闭浮窗时,已有的
`data-wecom-promotion``.wecom-promotion-link[data-pool]`
`window.WecomPromotion[KEY].open()` 手动触发方式仍然可用。
浮窗配置保存在分流方案的 `widget_config_json` 中。当前配置版本为 `v=1`,支持:
- 模板:`bubble``pill``card``message``edge``bar`
- 位置:`bottom-right``bottom-left`
- 标题、副标题、按钮文案和 `#RRGGBB` 主题色
- 16-160 像素底部距离、移动端展示开关和浮窗总开关
公开脚本仅下发经过白名单校验的展示配置,不下发兜底链接或真实获客链接池。模板
由脚本内置,管理端文案通过 DOM `textContent` 写入,不接受自定义 HTML、CSS 或脚本。
损坏配置、未知版本和非法枚举会按关闭浮窗处理。
脚本会暴露以下运行时方法:
```js
window.WecomPromotion['分流方案KEY'].open()
window.WecomPromotion['分流方案KEY'].show()
window.WecomPromotion['分流方案KEY'].hide()
window.WecomPromotion['分流方案KEY'].destroy()
```
公开脚本缓存 60 秒,因此浮窗样式或开关更新最多延迟约 60 秒;方案运行状态仍会在
每次服务端跳转时即时校验。脚本会从自身 `src` 解析跳转接口域名,不会把公开请求的
Host 写入缓存内容。管理端安装代码优先使用 `[app] HOST`,请在生产环境配置唯一的
HTTPS 公开域名。
接入站点若启用了严格 CSP,需要允许脚本域名,并给安装 `<script>` 添加站点当前请求
`nonce`。公开脚本会把该 `nonce` 传给 Shadow DOM 内的动态样式:
```html
<script nonce="当前请求的 nonce" src="https://你的域名/api/qywx-promotion/js/分流方案KEY" defer></script>
```
点击来源只上报页面的 origin 与 pathname,不包含查询参数或 fragment。推广页路径中也
不应放置手机号、患者 ID、重置令牌等敏感信息。
随机分流在服务端完成。候选链接必须同时满足:方案启用、链接上线、处于有效时间段、未超过当日上限。权重越大,被选中的概率越高。