社群聊天模块设计方案(内部)
状态:草案 v0.1 · 2026-09-28
范围:TraderX 主推 App 的社交 / 社群聊天能力(客户端 React + React Native,服务端 Rust)
所属仓库:TraderX_BackEnd(crates/social-*)产品侧该做什么、不做什么,见《社交聊天该做什么》;本文只谈怎么实现。
不做通用 IM。这个模块只解决两件 Telegram 做不到的事:让发言者身上带着可验证的仓位和战绩,让看到信号到下单之间没有跳转。下面是开源选型、三条架构决策、crate 分层、消息管线和数据模型。
1. 背景与目标
TraderX 的定位是基于事件和社交双驱动的 AI 交易平台,覆盖永续合约、预测市场和 Meme 币三条腿。社交机制中「发现」(看到别人买所以想买)为重,「执行」(跟单 / clan 自动跟)为辅;事件与社交的耦合中「社交信号因事件归因被打分」为重,「事件流因社交图谱排序」为辅。
社群聊天模块的目标不是再造一个通用 IM,而是做出 Telegram / Discord 天然给不了的两样东西:
- 可信:发言者身上带着可验证的真实仓位和历史战绩。
- 可执行:从看到信号到下单之间零跳转。
模块成败的核心指标是来自社交入口的成交占比,而不是聊天 DAU。
2. 功能优先级
2.1 第一梯队(首发必做,构成差异化)
| 功能 | 说明 | 依赖 |
|---|---|---|
| 发言带持仓徽章 | 讨论某市场时,消息旁显示发言者在该市场的真实仓位(方向、规模区间、浮盈亏)。金额可隐藏,但「有无仓位、方向」默认公开。存发送时刻的快照,防止先喊单后平仓。 | 统一身份、统一账本 |
| 交易卡片 + 一键跟 | 消息中的 ticker、合约地址、Polymarket 链接、HyperLiquid 交易对自动渲染为带实时价格与一键下单的卡片,三条腿全覆盖。成交走 builder fee 等协议原生费用。 | MarketMapping、三条腿交易通道 |
| 事件房间 | 每个事件(议息、选举、热点发币)自动生成房间,聚合相关永续、预测市场、meme 币。这是「事件 + 社交」定位最具体的形态。 | Event 生命周期、MarketMapping |
| 喊单打分(先采集) | 每条带方向的发言是一次有时间戳的喊单,追踪 1h / 24h / 7d 表现,沉淀信誉分,群内排序按信誉而非活跃度。评分 UI 可晚上线,原始信号必须从第一天全量留存。 | Attribution、原始信号全量留存 |
2.2 第二梯队(上线后迭代)
Clan 成交流(成员开启后成交自动推送到 clan 频道,配 clan 排行榜与 clan 自动跟);AI 成员,定位为「事实核查员」而非「喊单员」,负责群聊摘要(「过去一小时谁有真实仓位在讨论什么」)以及贴出合约地址时的自动风险检查(貔貅盘、持币集中度、开发者钱包动向、流动性锁定);仓位门槛群(按持币或 30 天已实现 PnL 准入)。
2.3 早期不投入
语音房、画布、复杂话题串、表情包生态、文件分享等通用 IM 功能。付费带单群需谨慎:涉及代收费分账可能触及托管属性与投资建议合规问题,与极致非托管路线冲突。
2.4 前提条件(非功能,但必须随首发上线)
限流与反垃圾。公开交易社群上线第一天就会遭遇机器人刷假币与钓鱼链接。
2.5 分发策略补充
用户的社群本来就在 TG 上。可同步做一个 TG Bot 桥接:在 TG 群贴出的地址与市场由 bot 渲染成带 TraderX 一键交易链接的卡片,并显示已绑定用户的仓位徽章。App 内的事件房间与 clan 承接迁移过来的高价值用户。
3. 开源参考项目
没有单一开源项目能同时满足 React + React Native + Rust 且贴合交易社群场景,因此按层选取参考。
| 层 | 参考项目 | 许可证 | 借鉴重点 |
|---|---|---|---|
| Rust 服务端 · 实时内核 | block/buzz | Apache 2.0(可复制,保留版权声明) | 连接生命周期、三级订阅扇出、事件管线、Redis 多节点扇出、哈希链审计 |
| Rust 服务端 · 社群能力 | stoatchat/stoatchat(原 Revolt) | AGPL-3.0(只读,参考重写) | 角色权限模型、限流、推送守护进程、链接预览代理、定时清理 |
| React Native 客户端 | mattermost/mattermost-mobile | Apache 2.0 | 离线存储、WebSocket 重连、推送、长消息列表性能 |
| React Web 客户端 | Buzz desktop/(React)+ Mattermost webapp | Apache 2.0 | 订阅状态管理、消息渲染 |
| RN 备选 | Rocket.Chat React Native | MIT | 第二参考 |
3.1 Buzz 评估结论
适合作为参考实现,不适合直接 fork 作为社群后端。
契合点:Rust/Axum + Postgres + Redis 与 TraderX 栈完全同构;crate 分层干净(buzz-core 零 I/O,子系统互不调用,由 relay 统一编排);幂等写入对应去重键;签名事件模型天然适合喊单存证;agent 作为频道成员的模式可直接用于 AI 分析员。
不匹配点:限流仅有测试桩,未实际生效;审核只停留在 VISION 文档;推送通知未实现;移动端(Flutter)仍在开发;面向几十到几百人的团队频道,历史查询单 filter 上限 500 条,在线状态只在单节点扇出;协议要求客户端对每条消息做 Schnorr 签名,与 Privy 托管的用户主密钥冲突;Postgres FTS 使用 simple 分词,中文不可用;git 托管、语音会议、画布等模块体量大且与需求无关,fork 后维护成本高。
3.2 Stoat 要点
后端纯 Rust,按服务拆分:delta(REST)、bonfire(WebSocket)、services/autumn(文件)、services/january(代理 / 链接预览)、daemons/pushd(推送)、daemons/crond(定时清理),core 层有 config、database、files、models、permissions、presence、result、coalesced。存储为 MongoDB + RabbitMQ,与 TraderX 的 Postgres 路线不同,数据层不照搬。
许可证注意:整体 AGPL-3.0,但个别 crate 可能自带许可证。依赖前逐个核实 Cargo.toml 的 license 字段,未确认前一律按 AGPL 处理。
3.3 XMTP(不采用为主干)
LibXMTP 为 Rust 核心、有官方 React Native SDK、身份绑定钱包地址。但端到端加密使服务端无法做归因打分、反垃圾和持仓徽章校验,MLS 群组也不面向数千人公开房间。仅在未来做「按钱包地址私信交易员」时考虑。
4. 架构决策
决策一:协议用「Nostr 形状」,不用「Nostr 身份」
线协议沿用 Buzz 的帧结构与 kind 整数分发:
| 方向 | 帧 | 用途 |
|---|---|---|
| C → S | [「EVENT」, <msg>] | 发送消息 |
| C → S | [「REQ」, <sub_id>, <filter>...] | 订阅 |
| C → S | [「CLOSE」, <sub_id>] | 取消订阅 |
| S → C | [「EVENT」, <sub_id>, <msg>] | 推送匹配消息 |
| S → C | [「EOSE」, <sub_id>] | 历史消息结束 |
| S → C | [「OK」, <msg_id>, bool, reason] | 发送结果 |
| S → C | [「CLOSED」, <sub_id>, reason] | 订阅被关闭 |
认证改用 TraderX session,由服务端标记发送者,不要求客户端逐条签名。「喊单可验证」由服务端哈希链存证保证。新功能 = 新 kind,老客户端不受影响。
决策二:多 crate,单进程部署
代码按 crate 分层,编译为单一二进制部署。两人团队无法维护 Stoat 式微服务集群;待压力出现再将 social-gateway 单独拆出。
决策三:不引入额外中间件
队列用 Redis Streams 替代 RabbitMQ,存储统一 Postgres,媒体用 S3。
5. Crate 结构
| Crate | 来源 | 方式 | 关键改动 |
|---|---|---|---|
social-core | buzz-core | 改造复用 | 保留零 I/O、kind 注册表、filter 匹配;去掉 Schnorr 验签;消息 ID 改 UUIDv7,另存内容哈希用于去重 |
social-gateway | buzz-relay 连接层,参考 Stoat bonfire | 改造复用 | 保留连接信号量、30s 心跳、慢客户端背压、三级订阅索引、订阅前鉴权;auth 换 TraderX session |
social-pipeline | buzz-relay 事件管线 | 改造复用 | 见第 6 节 |
social-db | buzz-db | 改造复用 | 保留月分区、成员软删除、幂等写入;改用 sqlx::query! 编译期校验 |
social-pubsub | buzz-pubsub | 改造复用 | 保留 Redis 扇出与 typing ZSET;补上在线状态跨节点扇出 |
social-audit | buzz-audit | 直接复用 | 改为喊单存证链,按 clan 或全局维护链头 |
social-permissions | Stoat core/permissions | 参考重写 | 位掩码权限、角色等级、频道覆盖;新增持仓 / PnL 条件型门槛 |
social-ratelimit | Stoat 限流 crate | 参考重写 | Redis 令牌桶,按用户 / 频道 / IP 分档(新账号、认证交易员、clan 主、AI agent) |
social-push | Stoat daemons/pushd | 参考重写 | Redis Streams 消费;APNs / FCM;在线不推;按提及、私信、clan 警报分类 |
social-unfurl | Stoat services/january + Buzz SSRF 校验 | 参考重写 | 核心是交易卡片解析器,通用链接预览为附带功能 |
social-media | buzz-media | 改造复用(低优先级) | sha256 寻址 S3,首发仅图片 |
social-agent | buzz-acp 思路 | 参考重写(第二阶段) | 每频道一个队列、同时只处理一个请求;群聊摘要、合约风险检查 |
social-cron | Stoat daemons/crond | 参考重写 | 分区创建、过期清理、信号表现回填(1h / 24h / 7d) |
social-signal | — | 完全自研 | 喊单提取、归因打分、信誉分 |
5.1 Buzz 中值得原样保留的机制
三级订阅索引:DashMap 按 (channel_id, kind) O(1) 查找,其次频道通配索引,最后全局订阅线性扫描;频道消息不投递给全局订阅,这是安全边界。
订阅前鉴权:REQ 先校验频道权限再注册订阅,避免非成员在注册与校验之间收到私有频道消息。
先实时后历史:客户端重建频道状态时,必须先(或重叠地)打开实时订阅,再拉历史分页,否则中间空档会静默丢消息。服务端发送终止性 CLOSED 后,客户端必须同步释放本地订阅状态。
慢客户端背压:发送使用 try_send,缓冲区连续 3 次满即断开。
ephemeral 与持久化分离:在线状态 SET EX 带 TTL(心跳间隔的 3 倍),输入中提示用 ZSET 维护 5 秒窗口,二者均不入库。
6. 消息管线(social-pipeline)
同步段(决定是否接受)
1. RATE LIMIT 令牌桶;新账号发地址 / 链接额外限额
2. AUTH session 有效,服务端确定 sender_id
3. PERMISSION 频道权限位 + 条件门槛(持仓 / PnL)
4. SPAM CHECK 钓鱼域名黑名单、重复内容指纹
5. ENRICH ① 解析 ticker / 合约地址 / Polymarket 链接 → MarketMapping → cards
② 读统一账本,为涉及市场打持仓快照 position_snapshot
6. DB INSERT ON CONFLICT DO NOTHING
7. REDIS PUBLISH
8. FAN-OUT → 返回 OK
异步段(fire-and-forget,失败不影响发送)
9. SIGNAL EXTRACT 带方向的发言写入 raw_signals,关联 event_id ← 归因链入口
10. AUDIT CHAIN 喊单存证(含持仓快照)
11. PUSH 提及 / 私信 / clan 警报
12. SEARCH INDEX (第二阶段)
13. AGENT TRIGGER (第二阶段)
持仓快照在第 5 步生成并随消息入库,第 10 步进入哈希链,保证事后不可篡改。
7. 数据模型草案
-- 频道:clan / 事件房间 / 私信 / 普通群统一建模
CREATE TABLE channels (
id uuid PRIMARY KEY,
kind smallint NOT NULL, -- 1 clan / 2 event_room / 3 dm / 4 group
clan_id uuid NULL,
event_id uuid NULL,
visibility smallint NOT NULL,
gate_rule jsonb NULL, -- {"min_position_usd": ..., "min_30d_pnl": ...}
default_perms bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE channel_members (
channel_id uuid NOT NULL,
user_id uuid NOT NULL,
role_id uuid NULL,
joined_at timestamptz NOT NULL,
removed_at timestamptz NULL, -- 软删除
muted_until timestamptz NULL,
PRIMARY KEY (channel_id, user_id)
);
CREATE TABLE roles ( -- 参考 Stoat
id uuid PRIMARY KEY,
scope_id uuid NOT NULL, -- clan 或频道
name text NOT NULL,
perms bigint NOT NULL,
rank int NOT NULL
);
CREATE TABLE channel_role_overrides (
channel_id uuid NOT NULL,
role_id uuid NOT NULL,
allow bigint NOT NULL,
deny bigint NOT NULL,
PRIMARY KEY (channel_id, role_id)
);
-- 消息:按月分区
CREATE TABLE messages (
id uuid NOT NULL, -- UUIDv7
channel_id uuid NOT NULL,
sender_id uuid NOT NULL,
kind int NOT NULL,
content text NOT NULL,
tags jsonb NULL,
reply_to uuid NULL,
thread_root uuid NULL,
cards jsonb NULL,
position_snapshot jsonb NULL,
content_hash bytea NOT NULL,
created_at timestamptz NOT NULL,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
CREATE TABLE reactions (message_id uuid, user_id uuid, emoji text, created_at timestamptz);
CREATE TABLE mentions (message_id uuid, user_id uuid, created_at timestamptz); -- 索引 (user_id, created_at)
CREATE TABLE read_markers (channel_id uuid, user_id uuid, last_read_id uuid); -- 未读数
-- 与第一阶段模型对接
CREATE TABLE raw_signals (
id uuid PRIMARY KEY,
message_id uuid NOT NULL,
user_id uuid NOT NULL,
market_id uuid NOT NULL, -- MarketMapping
event_id uuid NULL, -- Event
direction smallint NOT NULL,
price_at_call numeric NOT NULL,
created_at timestamptz NOT NULL,
perf_1h numeric NULL, -- social-cron 回填
perf_24h numeric NULL,
perf_7d numeric NULL
);
CREATE TABLE push_tokens (user_id uuid, platform smallint, token text, updated_at timestamptz);
CREATE TABLE notification_settings (user_id uuid, scope_id uuid, level smallint);
CREATE TABLE audit_chain (
seq bigint NOT NULL,
scope_id uuid NOT NULL,
message_id uuid NOT NULL,
prev_hash bytea NOT NULL,
hash bytea NOT NULL,
created_at timestamptz NOT NULL,
PRIMARY KEY (scope_id, seq)
);
事件房间无需单独建表:事件进入 active 状态时创建 kind = 2 的频道并关联 event_id。clan 频道挂在第一阶段已有的 follow + clan 关系表上。
8. 完全自研部分
以下四块没有任何开源参考,是 TraderX 的差异化所在,也是开发精力的重点:
social-signal:喊单提取、信号表现追踪、归因打分与信誉分。- 持仓徽章服务:读取统一账本,生成快照与实时徽章。
- 事件房间编排:订阅 Event 生命周期,自动创建、归档房间并关联市场。
- 卡片解析中的 MarketMapping 查询与跨三条腿的一键下单链路。
9. 开发节奏(4 周)
| 周 | 内容 | 说明 |
|---|---|---|
| 第 1 周 | social-core、social-gateway、social-db、social-pubsub | 从 Buzz 改造,主要是删减与替换 auth,先打通收发 |
| 第 2 周 | social-ratelimit、social-permissions、管线反垃圾 | 上线前提,不可后推 |
| 第 3 周 | social-unfurl 交易卡片、持仓快照、raw_signals 写入、事件房间自动创建 | 差异化功能首发 |
| 第 4 周 | social-push、social-audit | 推送与存证 |
| 上线后 | 中文搜索、AI 成员、信号打分 UI、clan 成交流、仓位门槛群 | 第二阶段 |
中文搜索建议直接采用 Tantivy + jieba 分词(纯 Rust),而非 Postgres 中文扩展。
10. 待决问题
- 持仓徽章默认公开粒度:仅方向,还是方向 + 规模区间?
- 喊单识别规则:显式指令(如
/long)还是 NLP 自动识别,或两者并存? - 存证链作用域:全局单链还是每个 clan 一条链?
- TG Bot 桥接是否纳入首发范围?
- 私信是否需要端到端加密(影响反垃圾与合规审计能力)?