5.5 KiB
OneTalk DOM 二次卡片采集重构设计
目标与边界
二次采集以 DOM-only reader 替代 React/Fiber reader,并独立保存不带 kind 的命名补全字段。它不判断也不覆盖基础消息 order、inquiry、product 或其它 kind;补全可先于基础消息保存,后续读取或实时发布再由完整复合 identity 关联。
renderedCard 是 exact-shape、versioned 公共值,至少包含一个已批准字段,但绝不含 kind、模板 ID、DOM path、class、data-expinfo、HTML、style、React props、URL query/fragment 或动作参数:
type OneTalkRenderedCard = {
version: 1;
title?: string;
image?: string;
price?: string;
minimumOrder?: string;
purchaseQuantity?: string;
requirementText?: string;
reference?: string;
productCount?: { display: string; value: number | null };
status?: string;
total?: string;
deliveryDate?: string;
shippingAddress?: string;
};
字段是可选的,因不同模板本来显示不同内容;空对象、未知键、无安全字段或不符合模板映射的卡 fail closed。图片仍须为 approved host、HTTPS、无 query/fragment 的 URL。
数据流与所有权
DOM card + selected conversation
-> structure-based field reader in MAIN
-> generic named renderedCard observation
-> page bridge -> SW ledger -> rendered.card.observed -> server JSONB
-> base-message read/create/update by composite identity
-> OneTalkCenterMessage.renderedCard
MAIN 是 DOM 结构和可见节点读取的唯一 owner;shared contract 拥有 OneTalkRenderedCard exact shape、URL/text guards、fingerprint/clone/equality;server repository 拥有独立事实的 first-content-wins/conflict;read projection 是唯一将 base message 与 generic supplement 组合为 Mind 输出的 owner。
严格 identity
读取前后必须是同一 single selected conversation。卡和最近 wrapper 的 data-expinfo 都必须解码,messageId 必须合法且相等;任一已出现 cid/conversationCode/externalConversationId 为空、非法或冲突即拒绝。卡 metadata 缺 conversation ID 时,只能用读前后一致的 selected conversation 补足。任何 cardType/messageType 只可作为诊断与模板采样证据,永不进入 payload 或作为业务类型。
结构化命名字段
模板 ID 和路径仅可用于运行时调试或 fixture 采样,绝不能作为采集准入、业务类型或字段映射的门槛。reader 以恰好一个可见文本锚点选择内部字段解析器:订单=订单状态,询盘=详细需求,商品=最小订购量/Minimum order;任一类别锚点重复、零个或多个类别同时命中时 fail closed。该分发不产生或传输业务 kind。所有卡只从已验证图片 owner 取第一张安全图片为单值 image。询盘从可见 .description-container 读取保留换行的 requirementText,并将商品、采购数量与 ID 分别投影为 title/image、purchaseQuantity、reference。订单读取可见“订单”为 title;5 products 这类直接数量展示从同一局部容器的前置兄弟图片区取代表图,绝不扫描整卡而误取顶部箭头图标;再映射已审核的 productCount、status、total、deliveryDate、shippingAddress。其它未识别行省略。商品在同一 .dx-event-node owner 内以三段 normalized MOQ label/value/unit 锚定,读取其所属可见 TextView 序列中的价格、标题和安全图片;MOQ 公开为原样组合字符串(如 500 Pieces)。动作、促销/服务标签与数组字段不投影。
读取每个 DOM text/image 前都排除 template、hidden、aria-hidden=true、display:none、visibility:hidden 祖先。无法识别或不安全的单个字段省略;没有任何可投影字段才 fail closed。图片仍须为安全 URL,且不安全可选图片组不能阻断已验证的文本字段。
Contract、数据库与公开组合
OneTalkRenderedCardContent 保留 legacy rendered_inquiry/product/order 仅为已存数据的读取兼容,新生产 reader 只写无 kind 的 OneTalkRenderedCard。页面 bridge 解码、wire 与 ledger 的 observation content 只允许该 generic object,typed 历史内容不可重发。OneTalkCenterMessage.renderedCard?: OneTalkRenderedCard 同样无 kind,基础 content 原样保留。
repository 继续独立 insert/duplicate/conflict;基础消息先到或补全先到均按 [channelAccountId, conversationId, messageId] 关联。legacy typed supplement 保留旧 public projection;新 generic supplement 只附加顶层 renderedCard。本次不把 send.result 扩展为补全 transport。
数据库 migration 保留 0015 的四列删除,替换 0016 中的 temporary rendered_dom_card check:兼容 legacy typed kinds,或允许无 kind 且 version=1 的 generic object。迁移不得 DELETE、TRUNCATE、回填或重建表。
验证与回滚
fixture 必须覆盖未知模板 ID 仍可按已识别 DOM 结构采集、无模板 metadata 与 view-name="TextView" 的订单/商品结构、空结构拒绝、hidden nodes、identity 非法/冲突、unsafe images、DOM mutation 重读、generic supplement/base 先后顺序、legacy read compatibility 与 exact Center decoder。真实 Chrome 仅做无点击/无刷新验证,且须加载本次构建才能宣称 bridge/ACK/DB runtime 通过。
generic rows 出现后,回滚 schema check 前由用户在任务外处理 generic rows;本任务不自动删除 supplement。