把「AI 拍照盘点 / 全屋扫拍比对 / 巡检 / 退租验收」的现场作业搬进企业微信工作台,让物业管家不带电脑也能干活;把待办、审批、告警推到人手上,不再靠电话和微信群催。
四条链路,共用一套鉴权与事件底座。先看全局结构,再逐条拆。
企微工作台点开即登录,不用记账号密码;部门树自动映射到楼栋,顺手把多楼栋数据权限也解决了。
盘点任务下发、低置信复核预警、派单、审批待审、合同到期,全部推到人手上,点开直达单据。
调拨、报废、理赔定损走企微审批,审批结果回调自动回写台账,不再自研一套审批引擎。
AI 拍照盘点、全屋扫拍、退租验收挂在企微工作台,调原生相机,弱网离线拍照后补传。
这些必须在写代码之前做完,缺一项后面全部跑不通。
| 操作位置 | 配置项 | 拿到什么 / 注意 |
|---|---|---|
| 我的企业 → 企业信息 | 记录 CorpID |
ww******** 或 18 位数字 |
| 应用管理 → 自建 → 创建应用 | 应用名称「家具资产管理」,上传 logo,设置可见范围 | 拿到 AgentID 和 Secret(Secret 只显示一次,务必存好) |
| 应用 → 网页授权及 JS-SDK | 填写可信域名,下载校验文件 | 把 WW_verify_xxxx.txt 放到域名根目录,必须可公网访问且 Content-Type: text/plain |
| 应用 → 接收消息 | 设置 API 接收 URL、Token、EncodingAESKey | 回调 URL 必须 HTTPS、公网可达;校验通过后才会保存成功 |
| 应用 → 工作台应用主页 | 填 H5 主页 URL(https://asset.xxx.com/mobile/) |
用户从工作台点击直接进这个页;企微会追加 code 参数 |
| 安全与管理 → 管理工具 → 通讯录同步 | 开启并记录通讯录同步 Secret |
与应用的 Secret 不是同一个,同步接口必须用它 |
| 安全与管理 → 企业可信 IP | 把服务器出口 IP 加白 | 若企业开启此策略而未加白,所有接口报 60020 |
| 应用管理 → 审批 → 创建模板 | 建「家具资产调拨申请」「报废申请」「理赔定损」三个模板 | 拿到 template_id 和每个控件的 ControlID |
wecom_config.config_value。生产更推荐托管在配置中心(Apollo/Nacos)或 K8s Secret,表里只留引用键。wecom_access_token_cache.access_token 同理。
目标是「从企微工作台点开应用 = 已登录」,全程无输入、无跳登录页。
code 原样透传给后端,不落地存储useridsys_user.wecom_userid 查本地账号:contact.auto_create_user:为 1 则自动建档(bind_type=2,角色取 contact.default_role_code),为 0 则返回「未授权,请联系管理员」last_login_time、last_login_channel=2state 里存的目标路由)| 接口 | 说明 |
|---|---|
| GET /cgi-bin/gettoken | 取 access_token,返回 {access_token, expires_in:7200}。注意 同一 Secret 多次获取可能互相顶掉,必须集中缓存,不要每个请求都取 |
| POST /cgi-bin/stable_token | 稳定版取 token,推荐:不限制调用频率,返回结果稳定,不用自己抢锁 |
| GET /cgi-bin/auth/getuserinfo | code → userid。新接口,老接口 /cgi-bin/user/getuserinfo 已废弃,别再用 |
| GET /cgi-bin/auth/getuserdetail | 用 user_ticket 换手机号、头像等详情。需 scope=snsapi_privateinfo,此时用户会看到一次授权确认页 |
| GET /cgi-bin/user/get | 按 userid 查通讯录详情(姓名、部门、岗位),用于自动建档补全字段 |
https://open.weixin.qq.com/connect/oauth2/authorize
?appid={CorpID} // 注意:企业微信网页授权用 CorpID,不是 AgentID
&redirect_uri={URLEncode(回调地址)}
&response_type=code
&scope=snsapi_base // 静默授权,只拿 userid;要手机号用 snsapi_privateinfo
&state={业务侧防重放串}
&agentid={AgentID} // 这个参数不能省,决定用哪个应用的授权
#wechat_redirect // 井号必须带上
code 只能用一次、5 分钟过期。前端若在多个接口里重复提交同一个 code,第二次必定失败。正确做法:code 只在登录接口消费,之后一律用会话票据。
history.pushState)不会重新走授权,会话过期时必须重新跳 oauth2/authorize,而不是直接刷当前页。
scope=snsapi_privateinfo 不是静默的,会弹授权页。手机号属于敏感信息,能不要就不要——用通讯录同步拿到的 mobile 就够了。
| 方式 | 接口 | 用途与注意 |
|---|---|---|
| 全量对账 | GET /cgi-bin/department/list POST /cgi-bin/user/list_id POST /cgi-bin/user/get |
首次初始化 + 每日凌晨兜底对账。推荐 list_id 拉全量 userid(支持游标翻页),再用 user/get 批量查详情(单次最多 100 个)。注意 老的 user/list 有 10000 条上限,大企业会漏人 |
| 事件增量 | 回调 event=change_contact | 成员/部门变动实时同步,秒级生效。ChangeType 取值:create_user / update_user / delete_user / create_party / update_party / delete_party / update_tag |
| 企微字段 | 落地位置 | 转换规则 |
|---|---|---|
| userid | sys_user.wecom_userid | 唯一键,账号主锚点 |
| name | sys_user.real_name | 直接覆盖 |
| mobile | sys_user.mobile | 直接覆盖 |
| position | sys_user.wecom_position | 直接覆盖 |
| department[0] | sys_user.wecom_dept_id | 取主部门 |
| department[0] | sys_role_building | 关键一步:部门 → wecom_department_map.building_id → 楼栋,自动生成数据权限,实现 PRD 六.3 多楼栋隔离 |
| avatar | sys_user.wecom_avatar | 直接覆盖 |
| status(1启用/2禁用/4未激活/5退出) | sys_user.user_status | 非 1 一律视为停用 |
wecom_config.contact.sync_disable_strategy 三选一:
1 只停用本地账号(默认,推荐)——保留历史单据的操作人归属,否则盘点记录会变成「无主」;
2 保留不动——适合人员流动大、账号要复用的场景;
3 一并删除——不推荐,会破坏 furniture_operation_log 等审计链。
bind_type=1),并把该账号的 username 保留,保证历史操作日志不会断链。
这张表是消息链路的需求正文,模板已在 wecom_message_template 里预置好 14 条,改文案不需要发版。
| 业务事件 | 接收角色 | 消息类型 | 待办 | 跳转目标 |
|---|---|---|---|---|
| 盘点任务下发 | 物业管家 | textcard | ✓ | /mobile/inventory/task?id= |
| 低置信识别预警 | 运营主管 | textcard | ✓ | /mobile/inventory/review?taskId= |
| 盘点结果待审核 | 运营主管 | textcard | ✓ | /inventory/audit?taskId= |
| 盘点审核结果回推 | 执行人 | textcard | — | /inventory/detail?taskId= |
| 调拨单待审批 | 运营主管 | textcard | ✓ | /transfer/detail?id= |
| 调拨审批结果 | 发起人 | textcard | — | /transfer/detail?id= |
| 报修派单 | 维修工程师 | textcard | ✓ | /repair/detail?id= |
| 报修完工确认 | 运营 / 物业 | textcard | — | /repair/detail?id= |
| 巡检任务到期 | 物业 / 运营 | textcard | ✓ | /inspect/plan?id= |
| 合同到期 · 退租提醒 | 招商 / 运营 | textcard | ✓ | /checkout/create?unitId= |
| 理赔定损待审核 | 运营主管 | textcard | ✓ | /claim/detail?id= |
| 资产报废待审批 | 总经理 / 运营 | textcard | ✓ | /scrap/detail?id= |
| 月度折旧计提完成 | 财务 / 总经理 | textcard | — | /finance/depreciation?month= |
| AI 识别任务异常 | 运营主管 | textcard | ✓ | /inventory/detail?taskId= |
POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
Content-Type: application/json
{
"touser": "zhangsan|lisi", // 多个用 | 分隔
"msgtype": "textcard",
"agentid": 1000002,
"textcard": {
"title": "资产调拨单待审批", // ≤128 字节
"description": "调拨单号:TF-20261016-0001\n资产组:305单元标准办公套\n迁移路径:A栋305 → B栋208",
"url": "https://asset.xxx.com/transfer/detail?id=128",
"btntxt": "去审批"
},
"enable_id_trans": 0, // 不开 userid 转 openid
"enable_duplicate_check": 1, // 企微侧去重,强烈建议开
"duplicate_check_interval": 1800 // 去重窗口,最小 1800 秒
}
返回 {"errcode":0,"errmsg":"ok","msgid":"..."},把 msgid 写回 wecom_message_log 作为送达凭据。
| 机制 | 做法 |
|---|---|
| 幂等去重 | 写入时构造 dedup_key = 模板编码 + 业务ID + 接收人 + 批次,靠 uk_dedup_key 唯一索引拦重复。同时开企微侧 enable_duplicate_check 做第二道防线。为什么需要 一次审批驳回后重新提交,很容易把同一条消息再推一遍 |
| 失败重试 | 发送失败按 send_status=2 落库,next_retry_time 做指数退避(60s → 5min → 30min),超过 message.max_retry(默认 3 次)置为 4 已放弃 并告警 |
| token 治理 | 集中缓存 + 提前 300s 刷新 + 刷新加分布式锁。同一 Secret 并发获取 token 会互相顶掉,这是最常见的线上事故来源 |
| 限流保护 | 企微各接口有调用频率上限,超限返回 45009。网关侧按接口维度做令牌桶,批量推送要分片 + 间隔 |
| 可观测 | 视图 v_wecom_message_stat 看送达率,v_wecom_message_pending_retry 看待重试。送达率低于阈值告警 |
| errcode | 含义 | 处置 |
|---|---|---|
| 40014 / 42001 | access_token 非法 / 已过期 | 清缓存强刷 token 后重试一次;连续出现要查是否多实例互相顶号 |
| 40001 | Secret 错误或凭证无效 | 配置问题,不重试,直接告警 |
| 45009 | 接口调用超过频率限制 | 退避重试,并检查限流配置 |
| 60011 / 301002 | 无权限操作指定成员 | 成员不在应用可见范围内,检查可见范围配置 |
| 60020 | 访问 IP 不在白名单 | 把服务器出口 IP 加入企业可信 IP |
| 81013 | userid 不存在 | 该成员已离职或非本企业,标记本地账号停用,不重试 |
| -1 | 系统繁忙 | 可重试 |
消息推了就推了,待办是「必须点掉」的——它会挂在企微「待办」列表里,完成前一直提醒。凡是需要「某人必须处理」的,都应该同时下待办,对应模板里的 todo_flag=1。
msgtype=todo,需应用具备待办权限wecom_todo_ref.todo_status=1,并按 todo_msgid 调企微接口同步关闭| 模式 | 做法 | 取舍 |
|---|---|---|
| A · 企微审批承载 推荐 |
审批单在企微里走,系统只负责提交申请、接收状态回调 |
优点:审批人、节点、加签、抄送、催办全部白送,不用自研;审批记录在企微留痕,领导习惯 代价:审批流一改就要同步改字段映射(配置化可缓解) |
| B · 系统内自研 + 企微通知 | 系统自己管审批状态机,企微只发待办提醒 |
优点:业务逻辑完全可控,字段怎么变都行 代价:审批引擎、催办、加签全要自己做,工作量翻倍 |
wecom_approval_template_ref.field_mapping 把业务字段填进对应控件sp_no(企微审批单号),写入 wecom_approval_ref,建立业务单据 ↔ 审批单双向映射wecom_callback_log → 5 秒内返回 → 异步消费sp_no 找业务单据,状态映射后回写业务状态字段;若通过,则执行台账变更(调拨:批量更新资产组所在房间)| 企微 Status | 含义 | 调拨单 transfer_status |
报废单 scrap_status |
要做什么 |
|---|---|---|---|---|
| 1 | 审批中 | 保持 1(待审核) | 保持 0(待审批) | 无需动作,等回调 |
| 2 | 已通过 | 2 审核通过 | 1 审批通过 | 关键 执行台账批量变更:更新资产组位置、写 furniture_asset_change_log、生成调拨单归档 |
| 3 | 已驳回 | 3 驳回 | 2 驳回 | 记录审批意见,回推发起人 |
| 4 | 已撤销(发起人主动) | 退回 1 | 退回 0 | 台账未变更,无需回滚 |
| 6 | 通过后撤销 | 需回滚 | 需回滚 | 最麻烦 此刻台账已经生效,必须走逆向回滚:资产组位置改回 source_unit_id,并补一条变更留痕说明「审批撤销回滚」 |
| 7 | 已删除 | 退回 1 | 退回 0 | 同撤销处理,关注是否已生效 |
wecom_callback_log → 立即返回空串 → 异步任务消费。同时用 event_key 唯一索引保证重推不会重复执行。
回调会丢(超时 3 次即弃),所以必须有一个补偿任务:
wecom_approval_ref 中 wecom_status=1(审批中)且 apply_time 超过 30 分钟的记录POST /cgi-bin/oa/getapprovaldetail(body: {"sp_no":"..."})拉最新状态sync_source=2 定时补偿查询| 页面 | 对应 PRD | 核心能力 |
|---|---|---|
| 工作台首页 | PRD 2.9 | 待办卡片(待盘点 / 待复核 / 待审批)、快捷入口、今日任务 |
| AI 拍照盘点 | PRD 2.2 模式一 | 调原生相机连拍 → 上传 → 识别进度 → 品类数量清单 → 人工增删 → 差异高亮 → 提交送审 |
| 低置信复核 | PRD 四.3 | 逐条确认 AI 判定,遮挡/堆叠场景人工兜底 |
| 全屋扫拍比对 | PRD 2.2 模式二 | 环绕拍摄抽帧 → 空间去重 → 与搬迁前快照比对 → 匹配/缺失/新增三分类 |
| 巡检任务 | PRD 2.5 | 计划列表 → 房间逐间巡检 → 破损上报 |
| 退租验收 | PRD 三.5 | 全屋盘点 → 破损识别 → 定损确认 → 移交单签署 |
| 报修工单 | PRD 2.5 | 接单、维修前照片、完工上传维修后照片、录配件成本 |
| 待办审批 | PRD 2.4 / 三.6 | 调拨审批预览、报废审批、差异确认 |
// 1) 取 jsapi_ticket(用应用的 access_token),有效期 7200s,必须缓存
GET /cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN
// 2) 拼接并 sha1
string1 = "jsapi_ticket=" + ticket
+ "&noncestr=" + nonceStr
+ "×tamp=" + timestamp
+ "&url=" + 当前页面完整URL(不含 # 及其后片段)
signature = sha1(string1)
// 3) 前端注入配置
wx.config({
beta: true, // 企业微信必须传 true
debug: false,
appId: CORPID, // 注意:企业微信传的是企业 CorpID,不是 AgentID
timestamp: timestamp,
nonceStr: nonceStr,
signature: signature,
jsApiList: ["chooseImage", "getLocalImgData", "uploadImage",
"previewImage", "getLocation", "scanQRCode"]
});
# 后面的部分)。SPA 应用里常见失败原因就是前端在 history.pushState 之后才调签名接口,拿到的 url 与实际不符。
wx.chooseImage({
count: 9,
sizeType: ["compressed"], // 必须压缩,原图 base64 会爆内存
sourceType: ["camera", "album"],
success: function(res){
var localIds = res.localIds;
wx.getLocalImgData({
localId: localIds[0],
success: function(r){
var base64 = r.localData;
// ⚠ 坑一:iOS 返回值带 "data:image/jpeg;base64," 前缀,Android 不带
base64 = base64.replace(/^data:image\/\w+;base64,/, "");
// ⚠ 坑二:别把 base64 塞进 JSON 字段传,走 multipart/form-data
上传到业务服务器(base64);
}
});
}
});
| 上传方式 | 说明 |
|---|---|
方式 Awx.uploadImage |
上传到企微临时素材,拿 serverId(3 天后过期),再由服务端调媒体下载接口取回。不推荐 多一跳、要处理过期、还要额外下载 |
方式 Bwx.getLocalImgData推荐 |
直接拿 base64 传自己的服务器,落对象存储,furniture_photo_file 记 URL。链路短、无过期问题、与现有归档设计一致 |
写字楼地下车库、空置楼层信号差是常态,必须支持离线拍摄后补传。
client_uuid(UUID v4),图片存 IndexedDB,同时写一条 upload_status=0 待上传 的本地队列记录online 事件 + 定时探测)后,逐张以 multipart 补传client_uuid 做幂等(uk_client_uuid 唯一索引),重复补传不会产生重复记录upload_status=1,失败置 2 并累计 retry_count,超过 5 次提示用户手动重试改动全部收在独立脚本 wecom_integration_ddl.sql 里,不修改家具资产库原有字段语义,可重复执行。
| 表名 | 归属链路 | 职责 |
|---|---|---|
| wecom_config | 配置 | 凭据与开关集中管理,敏感项加密标记 |
| wecom_access_token_cache | 配置 | token 缓存与过期时间,避免并发顶号 |
| wecom_department_map | 通讯录 | 部门 ↔ 楼栋映射,多楼栋数据权限的来源 |
| wecom_sync_log | 通讯录 | 同步批次日志,增量对账的可追溯依据 |
| wecom_message_template | 消息 | 14 条文案模板,运营改文案不用发版 |
| wecom_message_log | 消息 | 推送底账:幂等、重试、送达统计 |
| wecom_callback_log | 事件 | 回调落库 + 事件幂等 + 处理状态追踪 |
| wecom_approval_template_ref | 审批 | 业务类型 ↔ 审批模板与控件映射 |
| wecom_approval_ref | 审批 | 业务单据 ↔ sp_no 桥接与状态映射 |
| wecom_todo_ref | 审批 | 待办下发与关闭回收 |
| 表 | 新增字段 | 用途 |
|---|---|---|
| sys_user | wecom_userid(唯一) wecom_dept_id / wecom_position wecom_avatar bind_type / bind_time last_login_channel / sync_time |
免登身份锚点;password 同时放宽为可空(免登用户无密码) |
| furniture_photo_file | source_channel wecom_userid wecom_media_id retry_count |
区分 PC / 企微 H5 来源,支撑离线补传与重试统计 |
| furniture_inventory_task | exec_channel exec_wecom_userid |
盘点在哪执行、谁执行的,配合 PRD 2.7 盘点方式统计 |
| furniture_repair_order | source_channel report_wecom_userid |
区分租户自助报修与物业代报 |
| sys_integration_log | (仅扩展 system_code 注释) | 把企业微信纳入对接系统枚举,复用现有接口日志表 |
| 视图 | 用途 |
|---|---|
| v_wecom_user_bind | 用户绑定情况总览:谁绑了、绑到哪个部门、映射到哪栋楼 |
| v_wecom_message_pending_retry | 发送失败待重试清单,含挂起时长,直接可做告警数据源 |
| v_wecom_message_stat | 按模板统计送达率 / 重试率 / 放弃率 |
| v_wecom_approval_overview | 企微审批单总览,含状态中文映射与同步来源 |
| v_wecom_callback_health | 近 7 天回调健康度:验签失败数、处理失败数、重复事件数 |
MySQL 原生不支持 ADD COLUMN IF NOT EXISTS,脚本里内置了两个辅助过程,基于 information_schema 判存后再执行动态 DDL:
CALL sp_add_column_if_absent('sys_user', 'wecom_userid',
'VARCHAR(64) DEFAULT NULL COMMENT ''企微成员userid,唯一''');
CALL sp_add_index_if_absent('sys_user', 'uk_wecom_userid', '`wecom_userid`', 1);
因此 整份脚本可以重复执行,不用担心「字段已存在」报错中断后续语句。
msg_signature / timestamp / nonce / echostr,需解密 echostr 并原样返回明文,否则保存不了回调地址<Encrypt> 密文sha1(sort(Token, timestamp, nonce, Encrypt)),与 msg_signature 比对Base64Decode(EncodingAESKey + "="),IV 取密钥前 16 字节16字节随机串 + 4字节消息长度(网络字节序) + 消息体 + receiveidreceiveid 等于自己的 CorpID,防伪造请求WXBizMsgCrypt),不要手写 AES 填充逻辑——PKCS7 填充与去除的边界处理很容易出错。
| 故障场景 | 降级行为 |
|---|---|
| 企微接口不可用 | 消息进重试队列;审批降级为系统内自研审批流;业务完全不受影响 |
| 回调长时间无响应 | 定时补偿任务主动拉取状态,业务链路自愈 |
| token 获取持续失败 | 告警 + 暂停推送,避免无效重试打爆队列 |
| 企微应用被停用 | wecom_config.base.enable=0 一键关闭对接,系统回落到站内通知 + PC 端作业 |
核心原则:企业微信是「入口与触达层」,不是业务依赖。任何时刻把 base.enable 置 0,系统都应能凭 PC 端完整跑完家具资产管理全流程。
| 阶段 | 内容 | 出口条件 |
|---|---|---|
| P0 | 配置与连通性 | 回调地址校验通过;token 能取到;测试消息能发到自己 |
| P1 | 免登 + 通讯录同步 | 指定测试账号免登成功;部门 → 楼栋映射正确;数据权限生效 |
| P2 | H5 工作台 + 拍照盘点 | 真机(iOS + Android 各一台)走通完整盘点流程;重点验 base64 前缀兼容 |
| P3 | 消息推送 + 待办 | 14 条模板各触发一次;断网重试验证;待办能正常关闭 |
| P4 | 审批流 | 调拨走通「提交→通过→台账变更」;单独验 Status=6 通过后撤销的回滚 |
| P5 | 灰度与全量 | 先开一栋楼,跑满一个巡检周期无异常再全量 |
| 风险 | 等级 | 应对 |
|---|---|---|
| 审批「通过后撤销」未做回滚,台账错位 | 高 | 单独实现回滚用例并纳入回归测试(见 7.4 第 8 条) |
| 回调 5 秒超时导致事件丢失 | 高 | 落库即响应 + 异步消费 + 定时补偿三重保障 |
| token 并发获取互相顶号 | 中 | 用稳定版接口 stable_token,或加分布式锁集中刷新 |
| 通讯录同步覆盖掉本地手工配置的角色 | 中 | 同步只更新企微来源字段,不触碰本地分配的角色;bind_type=3 的本地账号跳过同步 |
| iOS / Android base64 前缀差异导致上传失败 | 中 | 统一正则剔除前缀;双端真机必须都测 |
| 企微可见范围配置错误,导致消息发不出去(60011) | 低 | 上线前逐个接收角色核对应用可见范围 |
| 审批模板字段变更后代码未同步 | 低 | 字段映射走 wecom_approval_template_ref.field_mapping 配置,改配置不发版 |
| 文件 | 说明 |
|---|---|
| furniture_asset_init.sql | 家具资产库 v1 建库脚本(14 张表) |
| furniture_asset_init_v2.sql | 家具资产库 v2(32 张表,含存储过程与迁移脚本) |
| furniture_asset_prototype.html | PC 端单文件原型(10 个模块,含 AI 盘点与扫拍比对流程) |
| wecom_integration_ddl.sql | 本方案配套:企微对接数据库改造脚本(10 新表 + 5 视图 + 20 处列改造) |
| wecom_integration_design.html | 本方案文档 |
| furniture_asset_wecom_mobile.html | 企微工作台移动端原型(可点击演示) |