Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Session、Message 与通知

oak-general-business 里有两套容易混淆的“消息”:

  • 一套是围绕 Session / SessionMessage 的会话消息,更像客服、聊天或对象会话;
  • 另一套是围绕 Message / Notification 的系统消息,更像站内信、模板通知或跨渠道推送。

把这两套能力拆开理解,是读懂这一章的关键。

主要对象

这部分实际涉及的实体不少:

  • Session:一条会话,绑定到某个 entity/entityId
  • SessionMessage:会话里的具体消息;
  • Message:系统级消息;
  • Notification:消息在具体渠道上的发送记录;
  • MessageType:业务消息类型;
  • MessageTypeTemplate:消息类型和微信模板的映射;
  • MessageTypeSmsTemplate:消息类型和短信模板的映射。

从职责上可以先这样记:

  • Session / SessionMessage 负责“人与对象之间的会话过程”;
  • Message / Notification 负责“系统要发出去的一条通知”。

组件

这部分已经带了不少现成组件:

  • src/components/session/list
  • src/components/session/cell
  • src/components/session/header
  • src/components/session/forMessage
  • src/components/session/messageNumber
  • src/components/session/sessionMessage
  • src/components/message/list
  • src/components/message/detail
  • src/components/message/cell
  • src/components/message/simpleList
  • src/components/my/message
  • src/components/sessionMessage/list
  • src/components/sessionMessage/upsert
  • src/components/sessionMessage/cell
  • src/components/messageTypeTemplate/list
  • src/components/messageTypeSmsTemplate/list
  • src/components/messageTypeSmsTemplate/tab

也就是说,这部分不仅有实体和后台逻辑,连前端展示层都已经准备了不少基础积木。

aspect / endpoint / feature

这部分直接暴露的后端 aspect 主要是:

  • createSession

它定义在 src/aspects/session.ts,用于按当前应用类型和消息来源创建或获取会话,并在需要时级联创建 sessionMessage

这一章没有独立的 session endpointmessage endpoint,但它会通过微信相关 endpoint 间接进入。最典型的入口就是 src/endpoints/wechat.ts,公众号或小程序消息回调最终会调用 createSession(...)

此外,这一章还会和 features.template 发生关系。features.template 并不直接发送消息,但会负责:

  • 获取消息类型列表;
  • 同步微信模板;
  • 同步短信模板。

所以消息类型和模板映射虽然在数据层属于“消息域”,但真正的同步入口在 Template feature 和对应 aspect 里。

后台规则

这一章的核心后台逻辑主要分布在几组 trigger / checker 文件里:

  • src/triggers/session.ts
  • src/triggers/sessionMessage.ts
  • src/triggers/message.ts
  • src/triggers/notification.ts
  • src/checkers/message.ts

默认行为包括:

  • 创建 session 时,通知订阅了 session 列表变化的前端事件;
  • 创建 sessionMessage 时,更新关联 session.lmts
  • 创建 sessionMessage 后,在提交事务后再做消息推送。

Message / Notification 这一支的规则是:

  • 创建 message 时,会先根据 userSystem、消息权重、限制渠道等信息自动拆出 notification
  • 创建 notification 后,在事务提交成功之后才真正发送;
  • notification 成功或失败后,会继续反向更新 message.iState
  • medium 权重消息在其它渠道都失败时,还会尝试补发短信;
  • 非 root 场景下查询 message 时,checkers/message.ts 会自动把结果收窄到当前 systemId 可见的数据。

这也是 Oak 的一个典型用法:真正需要推送外部消息的逻辑,放在提交之后,而不是直接塞进前端组件里。

注入点

这一章没有单独的 session feature,它的注入方式主要是:

  • 后端通过 ogb0Triggers 注入会话和消息的自动维护规则;
  • 前端组件直接围绕实体数据树工作;
  • 模板同步能力通过 features.template 注入。

这说明它更像一组已经接到 Oak 运行时上的基础设施,而不是一套必须从某个总入口手动初始化的业务模块。

项目中如何接入

Session 这章在项目里最常见的接法有三种:

  • 做客服/私信后台时,直接复用 src/components/session/*src/components/sessionMessage/*
  • 做微信消息接入时,让 wechat endpoint 自动调用 createSession(...) 生成或更新会话
  • 做系统通知中心时,直接创建 message 记录,让 message / notification 触发链自动拆渠道并发送

换句话说,这一章通常不是“项目自己设计会话模型”,而是“项目只决定从哪里进入、展示在哪个页面”。

session/list 常用参数

oak-general-business/src/components/session/list/index.ts 这一组组件其实比看起来更灵活。项目里最常用的参数有:

  • entityFilter
  • entityFilterSubStr
  • entityDisplay
  • entityProjection
  • sessionId
  • dialog
  • onItemClick

其中有两种非常不同的工作模式:

  1. 不传 entityFilter 这时组件会默认按 当前 userId 查自己的会话列表,并订阅 ${DATA_SUBSCRIBER_KEYS.sessionList}-u-${userId} 这类数据事件;
  2. entityFilter 这时它会按业务对象维度查会话,并根据 entityFilterSubStr 订阅对应的数据事件。

entityDisplay + entityProjection 这组参数也很关键。它们决定“列表上显示的是用户名字,还是某个业务对象名字”。如果你是在做“对象会话列表”而不是“我的私信列表”,通常就应该一起传这两个参数,让组件把会话里关联对象名称补出来。

还有两个很实用的行为:

  • 如果父层传了 sessionId,组件会默认把这一条设成选中态;
  • 如果传了 onItemClick,点击会话后不会直接跳 /session/sessionMessage,而是把控制权交给父组件。

这意味着 session/list 既可以做独立页左侧会话栏,也可以做弹窗里的嵌入式会话选择器。

session/forMessage 的真实职责

这组组件在原文里还没展开,但它非常适合做“消息页顶部的当前会话头”。oak-general-business/src/components/session/forMessage/index.ts 当前最关键的参数是:

  • sessionId
  • isEntity
  • entityDisplay
  • entityProjection

它的真实行为是:

  • 如果传了 sessionId,就直接从 cache 里把这条 session 及其用户 / 关联对象信息读出来;
  • 如果 sessionId 发生变化,会自动重新取当前会话;
  • isEntity = true 时,标题优先按会话里的用户信息显示;
  • isEntity = false 时,则会调用你传入的 entityDisplay([session]) 来决定显示名。

也就是说,这个组件的价值不是“再包一个 header”,而是把“当前聊天对象叫什么”这件事统一收口了。

my/message 的真实职责

如果只是做个人中心里的“未读消息入口”,通常没必要直接挂整页消息列表,my/message 就够了。这个组件的真实行为很集中:

  • 进入时直接按当前登录用户 userId 统计 message.visitState === 'unvisited'
  • 维护一个未读数量 count
  • 点击后默认跳 /message/list

也就是说,它更像一个“消息角标入口组件”,适合放在:

  • 我的页面
  • 个人中心快捷入口区
  • TabBar 上方的消息入口卡片

而真正的消息列表页、消息详情页,再分别交给 message/listmessage/detail

真实项目里的启动注册

这一块最值得直接参考 haina-busitaicang 的例程:

  • haina-busi/src/routines/messageType.ts
  • taicang/src/routines/registMessageType.ts

它们都会先执行:

registerMessageType(Object.keys(MessageTypes));

haina-busi/src/routines/start.ts 还继续注册了:

registerMessageHandler('wish', wishHandler);
registerNotificationHandler('wish', wishNotificationHandler);

这说明公共包把“消息对象、会话对象、通知链”准备好了,但真正有哪些消息类型、由谁来发送、由谁来处理,仍然应该由项目层在启动时注入。

组件适合落在哪里

taicang 的现有页面看:

  • message/list 适合放在管理台消息列表页
  • message/detail 适合放在消息详情页
  • 会话相关组件更适合放在客服页、公众号消息页、系统通知页

项目层通常只做路由、筛选条件和业务跳转,不建议重写消息列表本身。

如果再按组合关系细一点拆,会更接近真实项目:

  • 左侧会话栏:session/list
  • 顶部当前会话信息:session/forMessage
  • 中间消息流:sessionMessage/list
  • 底部发送框:sessionMessage/upsert
  • 个人中心未读入口:my/message

这样拼出来的聊天页,基本就是公共包当前已经准备好的标准组合。

使用示例

1. 在微信回调里自动创建或更新会话

oak-general-business/src/endpoints/wechat.ts 会在收到公众号或小程序消息时调用:

await createSession(
  {
    data,
    type: 'wechatPublic',
    entity: 'application',
    entityId: applicationId,
  },
  context
);

这一步会自动查找已有 session,必要时补出新的 sessionMessageextraFile

2. 客服页直接复用会话组件族

如果项目要做一个标准的聊天后台,最推荐的组合是:

  • src/components/session/list
  • src/components/session/sessionMessage
  • src/components/sessionMessage/list
  • src/components/sessionMessage/upsert

这样会话列表、消息列表、消息发送框、图片上传和数据订阅都能直接复用,不需要项目层自己重新拼一套。

3. 创建系统消息,让通知链自动工作

如果你要给用户发系统通知,项目层更推荐直接创建 message,而不是自己手工落 notification

await context.operate('message', {
  id: await generateNewIdAsync(),
  action: 'create',
  data: {
    id: await generateNewIdAsync(),
    entity: 'order',
    entityId: orderId,
    userId,
    type: 'orderPaid',
    weight: 'high',
    title: '订单已支付',
    content: '你的订单已经支付成功',
    channels: ['wechatPublic', 'email'],
  },
}, {});

后续的 notification 创建、提交后发送、状态回写,都会由 triggers/message.tstriggers/notification.ts 自动完成。

使用建议

最简单的判断标准是:

  • 需要围绕某个对象做持续会话,用 Session / SessionMessage
  • 需要给用户发系统通知或跨渠道消息,用 Message / Notification
  • 需要做模板映射,就把业务消息类型维护在 MessageType 一侧,再绑定微信模板或短信模板。

这样拆开以后,后续对微信、短信等具体渠道的理解也会清晰很多。