11 KiB
企业微信推广自动化部署与验收
此实现覆盖推广渠道欢迎语、企业标签、客户备注/描述及原客户同步补偿。企微获客链接接口不提供欢迎语和标签配置字段,因此这些设置不会显示在企微链接详情的“配置”入口,而是在客户添加回调后通过客户联系接口执行;它不会覆盖企业微信后台欢迎语设置,也不意味着企业已开通接口权限。官方能力及限制见 核验报告。
部署顺序
- 新环境先执行
server/sql/1.9.20260831/add_wecom_promotion_automation.sql;已建四张自动化表的环境再执行server/sql/1.9.20260901/upgrade_qywx_promotion_automation_runtime.sql,补登记分钟补偿和素材刷新任务。默认表前缀为zyt_,实际前缀不同须由部署人员调整。本开发任务没有执行业务数据库迁移。 - 部署 PHP 代码。为 PHP-FPM、CLI worker 使用同一项目目录
server/runtime/qywx_promotion_private/,授予应用运行用户读写权限。目录必须位于 Web 根目录之外,禁止静态文件映射;保留源文件,不能随意清理该目录。 - 配置客户联系“可调用应用”,优先使用创建获客链接的同一自建应用。代码默认使用
qywx_customer_acquisition.secret,无该值才使用pay.wechat_work.customer_contact_secret。需要明确覆盖时设置WECHAT_WORK_PROMOTION_CONTACT_SECRET。不会使用external_pay_secret。新接入不要依赖已受官方限制的客户联系系统应用 Secret。检查应用可见范围、可信 IP、外部联系人变更事件订阅;欢迎语回调必须来自可调用应用的相应配置。 - 建议设置至少32字符的随机
WECHAT_WORK_PROMOTION_ENCRYPTION_KEY,所有 API/CLI 节点保持一致。未设置时会自动在项目私有目录生成welcome.key(0600)。多节点使用共享源文件目录和一致密钥;更换密钥前先处理/过期并清空待处理欢迎码。 - 在启用渠道欢迎语之前,启动并监控下面的常驻 worker,并确保系统
crontab调度进程正常运行。回调会立即尝试欢迎语和标签;常驻 worker 负责欢迎码窗口内的快速重试,分钟任务负责其余补偿和素材预热。
若旧版脚本在 welcome_cipher 字段的 COMMENT= 处报 MySQL 1064,请重新打开已修正的 SQL 文件并完整重跑。字段注释必须使用 COMMENT '内容'(不带等号);表级的 COMMENT='内容' 是合法语法,无须修改。脚本中的四张表都使用 CREATE TABLE IF NOT EXISTS,已成功创建的表会保留,不需要删表。四张表全部创建成功后再启动 worker。
必须运行的进程
欢迎码只有20秒有效。验签回调入库后会立即尝试欢迎语,并为正式客户立即添加标签;普通客户同步、备注、范围更新和素材上传等慢动作不进入回调。独立常驻进程继续秒级消费明确可重试的欢迎语任务。
cd /path/to/server
php think qywx:work-promotion-automation
使用 Supervisor 或 systemd 保持常驻、自动重启。建议同一数据库启动2个欢迎语worker,以免单个HTTP请求阻塞其他事件;任务租约保证同一任务不重复领取。高并发须按实际20秒延迟指标增加进程。--once 仅用于受控诊断/部署测试,不能替代守护进程。
例如 Supervisor 配置(路径和用户替换为实际值):
[program:qywx-promotion-welcome]
command=/usr/bin/php /path/to/server/think qywx:work-promotion-automation
directory=/path/to/server
numprocs=2
process_name=%(program_name)s_%(process_num)02d
user=www-data
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=35
stdout_logfile=/var/log/qywx-promotion-welcome.log
stderr_logfile=/var/log/qywx-promotion-welcome-error.log
迁移会把每分钟补偿和每5分钟素材预热登记到 zyt_dev_crontab;部署仍须保证 php think crontab 被系统每分钟触发。若部署不使用内置任务表,可改用以下系统 cron:
* * * * * cd /path/to/server && /usr/bin/flock -n /tmp/qywx-promotion-retry.lock /usr/bin/php think qywx:retry-promotion-automation
*/5 * * * * cd /path/to/server && /usr/bin/flock -n /tmp/qywx-promotion-media.lock /usr/bin/php think qywx:refresh-promotion-media
现有 qywx:sync-promotion-ranges、qywx:retry-customer-acquisition-events 也须保留。新推广配置事件优先在回调中添加标签;失败标签以及备注、描述、成员记账、范围更新、客户同步由补偿任务执行,处理延迟通常不超过一分钟。不要把这些慢速动作塞进欢迎语worker。方案日上限是回调后的统计控制,不是企微并发建联的硬性上限。
配置与素材契约
QywxPromotionContactApiService::tagOptions(): array→{tag_groups:[{group_id,group_name,tag:[{id,name}]}]}。- 方案客户标签为单选;接口仍使用
tag_ids: []/[id],服务端拒绝多个标签。旧多选配置会显示重新选择提示,不自动截断。自定义标签通过POST firstvisit.wecomPromotion/createTag({name})调用企微externalcontact/add_corp_tag,固定保存到“推广渠道”分组,名称最多30字符;同组同名复用,创建成功后自动选中。创建立即写入企微标签库,取消方案编辑不会删除该标签;本次调整不新增数据库迁移。 QywxPromotionMediaService::upload($file, string $type, int $adminId): array→{asset_id,name,type}。仅接受 ThinkPHP 已验证的UploadedFile;不接受服务器路径或网络下载地址。上传时立即预热临时素材。validateConfig(array $config, int $adminId, array $existingConfig = []): array→ 保留其他配置字段、规范化附件后的完整配置。调用方必须先校验方案编辑权;第三参数只传数据库读取的旧配置,不能传用户提供的“白名单”。其他管理员只能保留已获授权的旧方案资产,不能新引入别人的资产。validateAttachments(array $attachments, int $adminId, array $allowedAssetIds = []): array为底层契约;不要直接向HTTP客户端暴露第三参数。- 素材 ID 是随机48位十六进制字符串。图片 JPG/PNG≤10MB、视频MP4≤10MB、文件≤20MB,且大于5字节。普通文件限定PDF、Office、文本、CSV、ZIP、JPG/PNG、MP4;拒绝可执行文件、HTML/SVG等格式。后端使用实际MIME和扩展名,不信任浏览器Content-Type。
- 附件支持
image/video/file.{asset_id}、miniprogram.{title,appid,page,pic_asset_id}、link.{title,url,desc,picurl?}。不接受前端任意media_id。本实现不开放image.pic_url:官方只支持uploadimg生成的URL,普通CDN地址不能替代。 - 欢迎语文本最多4000 UTF-8字节(产品配置还可额外限制1200字符),最多9附件;链接标题128字节、描述512字节,小程序标题64字节。小程序需已关联企业;本地无法代替企微验证关联与页面可达性。
- 源文件与媒体记录持久保留;缓存media_id有效3天,在到期前1小时进入刷新候选。发送只使用剩余至少5分钟的缓存。过期素材不会在欢迎语关键路径上传,不会静默丢掉附件只发文字;会记录准备失败,直至20秒期限结束。
- 临时素材与凭证指纹绑定。更换企业/应用Secret后,先运行
qywx:refresh-promotion-media并检查失败数,再恢复渠道曝光。
回调、幂等与失败策略
- 入口仅在 EasyWeChat 已验签/解密的
change_external_contactlistener 中调用enqueueVerifiedEvent($event, true);不得把此服务直接作为未验签HTTP接口。 - 优先用
State=zyt_pool:{id},无State时才使用事件实际存在的LinkId/LinkID。必须存在正常的推广方案、非删除成员关系、官方有效链接、新配置记录。仅有可猜测的State不是授权。普通客户事件、无新配置旧方案、迁移尚未安装时保留旧同步路径;不会让所有客户突然依赖新worker。 - 正式客户add可执行全部动作;半客户add_half只处理欢迎语,不提前打标签、改备注或落正式客户表。事件重复通过唯一事件键去重;同一WelcomeCode在half/add之间通过唯一摘要索引只分配一次消费权。
- 队列持久化失败会抛专用异常,listener不吞掉,HTTP返回500供企微重投。即时发送结果按分动作持久化;明确可重试的失败由持久任务补偿,事件重投不会重复发送已完成动作。
- 每条任务保存配置快照;分时欢迎语按事件时间和Asia/Shanghai选择,未命中用基础欢迎语。
default和none均不发送本系统欢迎语,不能抑制企业微信管理端或其他应用发送。 - 欢迎码以AES-256-GCM短存;动作进入sent/skipped/expired/uncertain/failed后清除密文。既有客户事件raw字段现在移除WelcomeCode;审计中无明文code/token、上游请求或Guzzle异常链。历史已有raw数据需要另外评估清理,本次未改历史记录。
- 发送前先持久化running。网络超时、无法解析响应、HTTP失败,或进程在发送后记录成功前崩溃,均记为uncertain,清理密文并停止自动重发,避免重复推送;需通过企微实际聊天结果人工核对。
- 只有明确的企微拒绝响应允许在剩余窗口重试;41096可重试,41051记为已使用并停止。token明确失效可刷新一次。过期或缺少code有独立原因,不会当成成功发送。
- 标签仅增添配置标签,不删除人工标签;备注与描述只写明确启用的字段。变量替换只支持白名单,名字接口失败使用本地员工名或“客户顾问”、客户用“您”。备注截断到20字符;欢迎语字节截断有审计原因。
- 元数据每个动作单独记录状态。失败最多10次、指数间隔后终态failed;成功动作不重放。最终failed不是成功,应安排告警/人工修复。范围动作仅触发现有范围任务,其最终应用状态仍以原范围同步表为准。
监控与验收
zyt_qywx_promotion_automation_task.actions_json 保存分动作状态、次数、错误码、原因和重试时间;zyt_qywx_promotion_automation_action_log 保存每次状态转移。监控欢迎语入队延迟、expired/uncertain/failed 数量、待处理最早事件时间、素材 last_error 和worker存活。一次没有异常输出不等于客户端已收到消息。
安全离线验证(不会初始化现有数据库、HTTP全部Mock):
cd server
php tests/QywxPromotionContactApiServiceTest.php
php tests/QywxPromotionMediaServiceTest.php
php tests/QywxPromotionAutomationServiceTest.php
php tests/QywxPromotionCodeCipherTest.php
生产验收仍需在授权的企业测试客户/员工上验证真实权限、半客户/正式客户回调、每类附件实际接收、昵称模板、默认欢迎语互斥、worker故障和跨节点存储。此开发没有发出任何真实企业微信写请求。