- APPS_API 换 box_app 专用反查接口(不再被 isPublished=1 过滤卡住) - 缓存 key bump share_apps_index → v2(旧缓存只有上架应用,残留 1h) - shareCard.js 首次入库(此前为未跟踪文件);文档同步数据流与缓存说明
14 KiB
分享图片获取逻辑(box_app → 小程序 web_view)
适用项目:CRMEB 小程序(crmeb)+ box_app 应用管理平台 功能:小程序 web_view 内点"转发给好友"时,分享卡片使用 box_app 后台配置的分享图片
一、数据流全景
[box_app 管理后台]
新建/编辑应用 → 上传"分享图片"
→ 存入 App.shareImageUrl = https://mp.yizhezhe.com/upload/picture/xxx.png
[box_app API]
GET /api/mp/apps?identifier=xxx&isPublished=1 (单个应用,太阳码场景)
GET /api/mp/public/share-images (分享图反查表:所有配了图的应用,
**不过滤上架状态**,2026-09-22 起)
→ 返回 { url, shareImageUrl } 精简结构
[小程序宿主页(两个,见第三节)]
pages/annex/web_view/index.vue 容器页:扫码 / JumpPath / 分享落地
pages/index/index.vue 首页壳:兜爸心选应用宫格 location.href 进入的 H5
① 获取 shareImageUrl(网络图)→ 内存 + 本地缓存(mixins/shareCard.js)
② 用户点 ··· 转发给好友 → 卡片图 = shareImageUrl(网络图,微信支持)
③ 用户点 ··· 分享到朋友圈 → getImageInfo 转本地路径 → 卡片图 = 本地路径
二、box_app 侧配置(已实现)
后端(backend/app/models/app.py + services/app_serv.py)
App模型新增字段:shareImageUrl: str | None(微信分享卡片图,公网 https URL)AppCreate/AppUpdate/AppOut均含shareImageUrl,查询/写入透传(find / find_all / find_one / create / update 共 5 处)- 上传接口:
POST /api/upload(返回公网 URL)
管理后台前端(frontend/src/pages/ProductManagement.jsx)
- 新建/编辑应用表单新增 "分享图片 (Share Image)" 上传项
- 支持上传
/api/upload、编辑回填、单图(maxCount=1) - extra 提示:建议 5:4 比例,公网可访问
三、小程序端获取逻辑(两个宿主页共用 mixins/shareCard.js)
宿主页有两个,分享卡片由谁产出取决于用户是从哪进来的:
pages/annex/web_view/index.vue—— 容器页:太阳码扫码 / 分享卡片落地 / JumpPath / 客服 / 登录跳转;pages/index/index.vue—— 首页壳:兜爸心选首页的应用宫格用location.href换 H5(见mama-box/js/app.jsjumpApp),小程序页面栈不变,所以从这里进的应用(数胎动、孕检日历…)分享永远发生在首页壳。反查 / 缓存 / 朋友圈本地图转换统一抽到
mixins/shareCard.js,两页行为一致。
四种获取路径(按优先级)
| 优先级 | 场景 | 方法 | 说明 |
|---|---|---|---|
| ① 首选 | 太阳码扫码 | _resolveAppScene |
调 mp/apps?identifier=xxx 时一次请求同时拿 H5 url + shareImageUrl,identifier 严格匹配 |
| ② 兜底 | 容器页点击应用(JumpPath 直传 URL) | _fetchShareImage |
按 URL 前缀反查 mp/apps 列表,命中写入本地缓存 |
| ③ 覆盖 | H5 postMessage 自定义 | handleMessage 'share' |
H5 bridge.share({imageUrl}) 传了图才覆盖,为空保留 box_app 预取值 |
| ④ 同步 | 首页壳(应用宫格进入任意 H5) | _loadShareApps + resolveShareCard |
首页壳 onLoad 预热全量应用表(内存 + 1h 本地缓存),onShareAppMessage(options) 里用 options.webViewUrl 同步匹配出图(首次转发就有图);每次分享都重解析并检测应用切换,避免沿用上一个应用的图 |
① 太阳码扫码(_resolveAppScene)
扫码 → scene=app=testapp
→ GET https://mp.yizhezhe.com/api/mp/apps?identifier=testapp&isPublished=1&pageSize=1
→ items[0] = { url, shareImageUrl, ... }
→ 同一响应取 app.url(加载 H5)+ app.shareImageUrl(分享图)
→ 严格匹配 items.find(it => it.identifier === identifier),避免张冠李戴
② 点击应用(_fetchShareImage,在 _finalizeUrl 中异步触发)
点应用 → JumpPath 传完整 H5 URL(如 https://mp.yizhezhe.com/app_03/)
→ 1. 先同步查:内存应用表(若已预热)→ 单应用缓存 web_view_share_img:{urlBase}(24h TTL)
→ 2. 未命中 → GET /api/mp/public/share-images(拉分享图反查表,**含未上架应用**,1h 本地缓存)
→ 3. URL 前缀匹配:urlBase === appBase || urlBase.startsWith(appBase + '/')
→ 4. 命中 → this._applyShareImage(shareImageUrl) + 写入单应用缓存
页面加载时执行,用户点转发时图已就绪。_shareFetched 标志保证页面内只做一次异步兜底。
③ H5 postMessage 覆盖(handleMessage)
H5 bridge.share({ title, imageUrl }) → postMessage → handleMessage 'share'
→ if (data.imageUrl) this.shareImageUrl = data.imageUrl // 有值才覆盖
→ 为空则保留 box_app 预取的分享图片
④ 首页壳同步匹配(pages/index/index.vue,2026-09 补)
症状:从兜爸心选首页的应用宫格进入的 H5(如数胎动),点右上角 ··· 转发没有后台配置的分享图。
原因:宫格用 location.href 换 H5,小程序页面栈仍是首页壳 pages/index/index;而反查逻辑此前
只写在容器页,首页壳的 shareImageUrl 只能靠 H5 postMessage,数胎动这类 H5 没有 bridge.share
→ 卡片图为空 → 微信回退默认截图。
onLoad → _loadShareApps() // 预热全量应用表:内存 shareApps + 本地缓存 share_apps_index(1h)
点 ··· → onShareAppMessage(options)
→ target = options.webViewUrl || this.shareHref || this.webUrl // 当前 web-view 真实地址
→ this.resolveShareCard(target) // 见下:先做应用切换检测,再按当前地址反查
→ imageUrl = this.shareImageUrl || ''
_lookupShareImage 是同步的:先内存应用表,再单应用缓存。因此首次转发就能出图,
不依赖 postMessage 的投递时机。后台改分享图后,最迟 1 小时(列表缓存 TTL)生效。
朋友圈图仍走 _prepareTimelineImage(getImageInfo 转本地路径)。
⚠️ 跨应用残留(2026-09 修):症状是「A 应用转发能出图,切到 B 应用转发还是 A 的图」。 根因是首页壳不随 H5 换页重新 onLoad(宫格用
location.href),而当时分享图取到后就一直 留在shareImageUrl,并且解析被if (!this.shareImageUrl)短路掉了 —— 于是上一个应用的图钉住不换。修法(
resolveShareCard/syncShareCardTarget,两个宿主页共用):
shareImageBase记录当前图属于哪个 H5 应用;分享前发现target换了应用 → 清空shareImageUrl/shareTimelineImage/shareTitle/shareHref;- 每次都按当前
target重新反查,不再因为已有图就跳过;- 当前图若来自本应用 H5 的 postMessage(
shareImageSource === 'h5')才保留优先, 否则 box_app 配置图覆盖;- 当前应用没有配置图 → 宁可留空回退微信默认截图,也不沿用上一个应用的图。
局限与正解:
onShareTimeline拿不到webViewUrl,宿主无法自己判断当前应用。 正解是 H5 主动上报当前位置:换页/路由切换时发wx.miniProgram.postMessage({ data: { action: 'location', href: location.href } }), 宿主webviewHost.handleHostMessage收到后更新shareHref并调resolveShareCard(href), 朋友圈分享即拿到正确的落地页与分享图(共享库内置wxAuth.reportLocation()/wxAuth.watchLocation())。 H5 侧完整接入说明见box_app/docs/h5-auth-standard.md第四节。 若 H5 未接入该上报,朋友圈分享只能以最后一次好友转发解析出的图为准。
四、分享路径(深链)—— 2026-09 修复
症状:在 web-view 的 H5 里点「分享给微信好友」,接收者点开卡片却落到小程序首页, 而不是发送者当时所在的那个 H5 应用/页面。
根因
- web-view 里的 H5 无法用代码拉起分享面板,卡片只能由承载
<web-view>的宿主小程序页产出。 - H5 应用是从 mama-box(首页 web-view)的应用宫格用
location.href换页进入的 (mama-box/js/app.js→jumpApp),小程序页面栈仍是首页pages/index/index。 app_03的bridge.share()曾在 postMessage 后调wx.miniProgram.navigateBack({delta:1})试图触发投递 —— 这会把 web-view 页弹掉(栈里只有 tabBar 根页时还会失败), 转发随即被别的页面接管;首页回调又写死path: '/pages/index/index', 于是接收者永远进首页。onShareAppMessage忽略了微信回调参数options.webViewUrl(当前 web-view 真实地址, 含 H5 的 SPA 路由),H5 的 postMessagehref又因投递延迟不可靠 → 深链丢失。
修复
| 端 | 改动 |
|---|---|
小程序首页 pages/index/index.vue |
onShareAppMessage(options) 用 options.webViewUrl(兜底 shareHref / webUrl)拼 path: '/pages/annex/web_view/index?url=...';onMessage 支持 action:'share'/'setTitle';补 onShareTimeline + uni.showShareMenu;onLoad 支持 ?url= 落地(朋友圈转发经此转交容器) |
小程序容器 pages/annex/web_view/index.vue |
onShareAppMessage(options) 同样优先 options.webViewUrl;onShareTimeline 兜底 shareHref || url |
两页 stripAuthParams |
按 key 过滤(不再用会吃掉 #hash 的正则),剥 query 的 openid/unionid/t/_v 与 hash 的 openid/unionid/phone/nickname/avatar |
H5 app_03/frontend/src/app/services/bridge.ts |
删除 navigateBack;share() 只发 postMessage 并返回是否在小程序内,页内提示用户点右上角「···」 |
接收者身份:分享链接一律剥掉发送者身份,接收者经
pages/annex/web_view容器 按自己的 openid 重新注入(容器_applyIdentity,query 传参)。
五、好友转发 vs 朋友圈(微信平台差异)
| 场景 | 图片要求 | 实现 |
|---|---|---|
转发给好友 onShareAppMessage |
支持 https 网络图 | imageUrl: this.shareImageUrl(box_app 网络 URL 直接用) |
分享到朋友圈 onShareTimeline |
仅本地路径/代码包路径,网络图静默忽略 | _prepareTimelineImage 用 uni.getImageInfo 转本地临时路径 shareTimelineImage |
⚠️
onShareTimeline没有回调参数,拿不到webViewUrl,只能兜底shareHref || url; 它也不能自定义path(只重开当前页 + query),所以首页用?url=把目标转交 web_view 容器。
六、关键前置条件(易踩坑)
- box_app 已配置分享图:管理后台 → 应用 → 上传"分享图片"
- 图片必须公网 https 可访问(微信服务器要抓取)
- mp.yizhezhe.com 必须在微信后台合法域名白名单:
request合法域名(拉取 mp/apps 接口;首页壳预热应用表也用它)downloadFile合法域名(朋友圈转本地图 getImageInfo)- ⚠️ 未配置时:好友图可能正常(网络图),但朋友圈图转换失败回退默认截图,
_fetchShareImagefail 分支会提示
- 分享图有缓存,改图不是立刻生效:反查表缓存
share_apps_index_v21h,单应用分享图缓存web_view_share_img:*24h。 真机验证新图时可先清缓存(开发者工具 → 清除数据缓存 / 重新编译)或等 TTL 过期。
反查表与上架状态解耦(2026-09-22):旧数据源
/api/mp/apps?isPublished=1只回上架应用, 导致 mama-box(未建上架记录)和 laixin(下架、经 mama-box 底 tab/来信卡的硬编码兜底 URL 进入) 拿不到自定义转发图。现改用专用接口GET /api/mp/public/share-images(box_appmp_router.py): 返回所有配置了分享图的应用(含下架),只含url + shareImageUrl两字段,按 url 长度倒序 (长前缀优先命中,防撞车)。给「不上宫格但要可进入」的应用配图 = 后台建/编辑记录、传分享图、 保持下架;宫格展示仍由/api/mp/apps?isPublished=1决定,互不影响。缓存 key 随之 bump 为share_apps_index_v2(旧缓存里只有上架应用,不 bump 会残留 1h)。
七、相关代码位置
| 位置 | 文件 |
|---|---|
| box_app 模型字段 | box_app/backend/app/models/app.py |
| box_app 服务透传 | box_app/backend/app/services/app_serv.py |
| box_app 表单上传 | box_app/frontend/src/pages/ProductManagement.jsx |
| 分享图公共逻辑(两个宿主页共用) | crmeb/mixins/shareCard.js → resolveShareCard / syncShareCardTarget / _loadShareApps / _lookupShareImage / _fetchShareImage / _applyShareImage / _prepareTimelineImage |
| 宿主页其它公共能力(消息分发 / 授权面板 / 授权回传 / openid+unionid) | crmeb/mixins/webviewHost.js → handleHostMessage / consumeAuthResult / submitHostAuth |
| 小程序扫码取图 | crmeb/pages/annex/web_view/index.vue → _resolveAppScene |
| 小程序容器页(onLoad 兜底反查 / 转发回调) | 同上 → _fetchShareImage(mixin)/ onShareAppMessage / onShareTimeline / @message="handleHostMessage" |
| 小程序首页壳(应用宫格进入的 H5) | crmeb/pages/index/index.vue → onLoad 预热应用表 + onShareAppMessage 同步匹配(带 spid)/ onShareTimeline / @message="handleHostMessage" |
| 首页分享兜底(mama-box 等无 App 记录时) | 首页壳 getShare() → CRMEB 后台「分享设置」的 title / img |
| 分享链接净化 | 两页的 stripAuthParams / dropKeyValuePairs |
| H5 传图 / 触发分享 | app_03/frontend/src/app/services/bridge.ts → share({title,href,imageUrl}) |
授权/消息这一侧的完整链路(两条:独立授权页 与 宿主页内嵌面板)见
box_app/docs/h5-miniprogram-auth.md第 6 节。