关键词:确定性选词 · 注册表驱动 · 内容类型正交 · 分享动作捕获 · 3s 去重 · clientId 幂等 · 统计口径对齐 · 离屏 canvas 封面
目标读者:前端 / 后端 / 产品


一、为什么不能套用既有模型

对应提交:45a5615 每日情话分享记录、77ff972 每日心语首页入口、3791be7 分享类型跟随 Tab + fab-home 悬浮入口、b9115f6 分享统计与记录明细页。目标只有一句:把「一次分享动作」变成一条可统计、可同步的数据,且不新增一套数据模型。

前五期覆盖的是用户主动写入的数据(情绪打卡、生理期)。内容分享有两个本质差异:

  1. 内容不是用户写的,由内容池按日产出 → 需要确定性选词,保证「你送出的」与「对方看到的」是同一句;
  2. 分享不是数据写入,微信只给 onShareAppMessage / onShareTimeline 两个钩子,不回传是否送达 → 只能记录「发起了分享」,不能宣称成功。

因此三条目标:内容可预测可扩展;数据与 mood/period 同构(本地优先 + 云端同步);游客态与登录态表现一致。

二、分层全景

内容层  heart-registry + pools/* + content-types
           ↓ pickDaily(type, date)
展现层  love(日历/卡片/统计)  love-share(落地页)  index(首页预览)  fab-home
动作层  onShareAppMessage / onShareTimeline → share-log.js → 3s 去重 → Api 门面
数据层  LocalAdapter.share()(游客,域 em_share) | RemoteAdapter.share()(登录,/sync/push)
服务端  ShareController + ShareService + ShareRecord + SyncService.applyShare
消费层  share-records(分页明细)  love 统计卡(total/today/friend/timeline/byType)

核心原则:新增一种数据,只在注册表加类型、适配器加 share 分支、服务端加文档与同步分支,幂等/合并/导出/清空全部复用。

三、内容层:确定性选词

utils/heart-registry.js 是情感类型(love / friendship / family)的唯一事实来源,含 theme / salt / emojis / pool。新增类型只需追加一条 + 新增池文件,页面零改动——Tab 由 registry.list() 渲染,分布由注册表顺序推导。

function pickDaily(key, date) {
  const type = get(key);
  const pool = type.pool || [];
  if (!pool.length) return { type, pool, index: 0, day: 1, note: null };
  const idx = (dayOfYear(date) + (type.salt || 0)) % pool.length;
  return { type, pool, index: idx, day: idx + 1, note: pool[idx] };
}

idx = (年内第几天 + salt) % 池长:salt(0/7/13)错开三种类型的排布;池长 ≥30 时连续 30 天不重样。纯函数无副作用,因此首页预览与进入后所见必然一致(index.js:210-218)。

content-types.js 定义 text / emoji / article / image / video / link,与情感类型正交:后者决定强调色相,前者决定背景基调。v1 只启用前两个,其余 enabled:false 不进选择器,但旧链接携带 ct=image 仍可渲染——向后兼容通道。

四、动作层:捕获与去重

  onShareAppMessage() {
    const { typeKey, picked, note, title } = this._sharePayload();
    // 记录分享(本地先行 / 云端同步由 Api 路由决定);失败不影响分享流程
    shareLog.recordShare({
      type: typeKey, note, noteId: note.id, date: this.data.date,
      mediaType: note.mediaType, channel: 'friend'
    }).catch(() => {});
  • 3 秒去重:键取 (type, noteId, channel)(share-log.js:11-27),连点两次或取消后重触发只入库一次;换渠道算两次。markShared() 在去重判断之前执行,保证「已分享」标记立即点亮。
  • 类型跟随 Tab:早期在浮层让用户二次选择,导致「看到友情、分享出去是爱情」的口径分歧。现由 _sharePayload() 从 this.data.typeKey 推导(love.js:232-242),好友转发、朋友圈、胶囊菜单三条路径共用同一份 payload。

五、数据层:本地域与幂等

本地 em_share 域三条要点(local-adapter.js:317-366):

  • 立即落盘:setDomain(..., true) 跳过 500ms 防抖,与凭证、待同步记录同级;
  • FIFO 上限 500:分享是高频动作,防止本地膨胀;
  • 软删墓碑:remove 只置 deleted=true,保留记录以便合并时向云端传播删除。

校验前后端一致:type 必填、文案与表情不可同时为空、noteText ≤ 300 字。

登录后写入不直连 REST,而是推 /sync/push(module:'share',remote-adapter.js:213-227),失败进 pendingOps 队列联网重放——断网也能记下。clientId 端侧生成(毫秒时间戳 + 4 位随机),既是记录 id 又是服务端幂等键,游客期上云后保持不变,杜绝重复。

六、服务端

索引(ShareRecord.java:18-23):uk_user_client(userId+clientId 唯一,幂等的物理保证)、idx_user_updated(增量拉取命脉)、idx_user_date。contentDate 用 YYYY-MM-DD 字符串避免时区串天;noteText/noteEmojis 存快照,内容池改版后历史仍可追溯。

接口:写操作一律走 /sync/push,REST 只读。

方法路径说明
GET/api/v1/shares分页列表,支持 type / channel 筛选(pageSize 上限收口 100)
GET/api/v1/shares/stats总数 / 今日 / 好友 / 朋友圈 / 按类型分布
GET/api/v1/shares/{clientId}详情

统计口径必须前后端一致:「今日次数」按 createdAt 落在本地语义日期当天算(ShareService.java:67-75);本地adapter 用同口径聚合,故登录前后数字连续。前端对 todayCount == null(旧版服务端缺字段)显示「—」而非 0(love.js:294)。

同步仲裁(SyncService.java:200-235):updatedAt 早于服务端抛 ConflictException;remove 落墓碑以支持多端删除传播;批量中单条缺 type 只拒收该条,不中断整批。

导出/导入/清空:导出含 share 域;导入为可选域(旧文件无 share 不报错,按 clientId 去重);清空支持 scope=share,同样是软删。

七、消费层

  • 统计卡:四数 + 一行类型分布,数据源统一走 shareLog 门面,页面不感知游客/登录差异。
  • 分页并发安全:用自增 reqId 丢弃过期响应(share-records.js:50-56),避免「触底加载」与「返回重拉首页」并发导致列表错乱;浏览往日心语后再分享时,补「某日的心语」来源标签消除时间错位困惑。
  • 事件驱动刷新:写入后 emit('share:changed'),love 与 share-records 订阅刷新,并在 onUnload 解绑。

落地页与封面:配色由 share-theme.js 双层融合——内容类型定基调层(渐变明度/冷暖/文字色),情感类型定强调层(徽标/CTA/描边色相),冲突时基调优先;未知组合回落 default,永不白屏,文字对比度 ≥ 4.5:1。封面用离屏 canvas 绘制 5:4,createOffscreenCanvas 缺失或绘制异常返回空串、回落微信默认截图——封面失败绝不阻断分享。

八、风险与应对

风险应对
微信不回传结果语义定为「记录动作」,不宣称成功
钩子多次触发3s 去重窗口
本地膨胀FIFO 上限 500
上云重复clientId 端侧生成 + 唯一复合索引
跨时区串日日期字符串 + 统一 DateUtil.ZONE
内容池改版存文案/表情快照
旧服务端缺字段null → 显示「—」
封面绘制失败回落微信默认截图
脏数据拖垮整批单条拒收,其余继续

九、验收清单

确定性(同日同类型恒定、三类型错开)|游客态落盘不丢|3 秒内重复只记一次、换渠道记两次|类型与 Tab 一致|登录合并不重复|断网进队列、联网补齐|删除多端同步|分页并发不错乱|导出含 share、导入旧文件不报错。

十、后续方向

内容池服务端下发 + 版本号(快照保证历史不受影响);统计改聚合管道 + 短缓存;channel 扩展 save-image / copy-link 并与 image/link 内容类型打通;落地页匿名回流埋点(不采集身份);canvas 封面模板化。

小结

三条工程原则:语义诚实(拿不到结果只记动作)、单一事实来源(分享类型由 Tab 推导而非二次选择)、口径一致(前后端同一套校验与统计规则,登录前后体验无缝)。


Logo

一站式 AI 云服务平台

更多推荐