+ 建群契约(待用例)
- 同 batch 插 groups 与 owner 的 group_members。
- 同 batch 插默认 #general 到 channels,再补 owner 的 channel_members。
社交图、群组、频道目录、活动通知、举报与治理审计。
用户身份与资料归 USER_DB;消息正文归每频道 SocialChannel DO;金融状态归 HL / Polymarket。
通用语言(术语定义)以 contexts/social/CONTEXT.md 为准,这里不重复。
user_follows + user_blocks + user_social_counters社交类型与常量 contexts/social/src/domain/social.types.ts| 不能自关注或自屏蔽 | 数据库 | user_follows / user_blocks CHECK |
| 任一方向屏蔽禁止新增关注 | 数据库 | user_follows_not_blocked |
| 计数非负 | 数据库 | user_social_counters CHECK |
groups + group_members + group_bans + group_invites + group_join_requests + group_audit_events社交类型与常量 contexts/social/src/domain/social.types.ts| 至多一个 owner | 数据库 | group_members_one_owner |
| 建群与转让结束后恰好一个 owner 且匹配 owner_id | SQL | ADR-0235:后续建群 / 转让 batch 契约 |
| 邀请码长度与字母表有效、使用不超限 | 数据库 | group_invites CHECK |
channels + channel_members社交类型与常量 contexts/social/src/domain/social.types.ts| 同用户对至多一个私信 | 数据库 | channels_one_dm_per_pair |
| 频道 kind 与 group_id / dm_key 一致 | 数据库 | channels CHECK |
| 每群至多一个默认频道 | 数据库 | channels_one_default_per_group |
social_notifications + social_notification_kinds社交类型与常量 contexts/social/src/domain/social.types.ts| 通知种类必须登记 | 数据库 | social_notifications.kind FK |
content_reports社交类型与常量 contexts/social/src/domain/social.types.ts| target 列与 kind 一致 | 数据库 | content_reports CHECK |
保证方:数据库 约束 / 索引 / 触发器 SQL 仓储里的条件写 / batch 领域代码 domain 层规则 外部 链上等外部权威
| 操作 | 原子性 | channel_members | channels | content_reports | group_audit_events | group_bans | group_invites | group_join_requests | group_members | groups | social_notification_kinds | social_notifications | user_blocks | user_follows | user_social_counters |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0001 schema 与种类种子 apps/server/migrations/social/0001_social_foundation.sql 本票无运行时仓储或业务写接口,其他表由后续用例实现。 | batch | ● |
| 本库字段 | 对应 | 说明 |
|---|---|---|
channel_members.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
channels.created_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
channels.last_sender_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
content_reports.reporter_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
content_reports.target_user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
content_reports.resolved_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_audit_events.actor_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_audit_events.target_user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_bans.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_bans.banned_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_invites.created_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_join_requests.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_join_requests.decided_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_members.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
group_members.invited_by | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
groups.owner_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
social_notifications.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
social_notifications.actor_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
user_blocks.blocker_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
user_blocks.blocked_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
user_follows.follower_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
user_follows.followee_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
user_social_counters.user_id | USER_DB users.user_id;经 users /server 门批量读 | 跨库无外键,不在 social 写用户 SQL。 |
channels.channel_id | SocialChannel DO 实例名 | 消息 seq / version 属 DO;D1 仅目录与摘要,写回实现后续交付。 |
| 表 | 规则 |
|---|---|
channel_members | 解散群的成员行 30 天后清理;私信 hidden_at 只隐藏不删。 清理 cron / 注销编排归后续票。 |
channels | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
content_reports | 已处理后 1 年清理,open 保留。 清理 cron / 注销编排归后续票。 |
group_audit_events | 动作发生后 1 年清理。 清理 cron / 注销编排归后续票。 |
group_bans | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
group_invites | 过期或撤销后 7 天清理。 清理 cron / 注销编排归后续票。 |
group_join_requests | 已决 30 天后清理,pending 保留。 清理 cron / 注销编排归后续票。 |
group_members | 群解散 30 天后清理成员行。 清理 cron / 注销编排归后续票。 |
groups | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
social_notification_kinds | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
social_notifications | 创建后 90 天清理。 清理 cron / 注销编排归后续票。 |
user_blocks | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
user_follows | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
user_social_counters | 保持至显式业务移除;本票不自动清理。 清理 cron / 注销编排归后续票。 |
| 字段 | 级别 | 说明 |
|---|---|---|
group_invites.invite_code | 机密 | 持码可申请或加入群,不写普通日志。 |
content_reports.detail | 个人数据 | 只向授权治理人员开放。 |
channels.last_message_preview | 个人数据 | 按频道访问权限读取。 |
0003 绿色迁移号表示该字段由后续迁移 ALTER TABLE 加入。点表名展开。
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKFKchannel_id | TEXT | PK · FK→channels.channel_id | 频道 ID,同时是 SocialChannel DO 的确定性名字。 | 0001 |
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| joined_at | TEXT | NOT NULL | 加入群或频道的时间,ISO TEXT。 | 0001 |
| last_read_seq | INTEGER | NOT NULL · DEFAULT 0 · CHECK(last_read_seq >= 0) | 已读分界,初始 0;未读 = channels.last_message_seq 减该值,由用例防止超前。 | 0001 |
| last_read_at | TEXT | 最近确认已读时间;从未确认时 NULL。 | 0001 | |
| notify | TEXT | NOT NULL · DEFAULT 'inherit' · CHECK(notify IN ('inherit','all','mentions','none')) | inherit 跟随群,或 all / mentions / none;默认 inherit。 | 0001 |
| pinned_at | TEXT | 本人置顶会话的时间;NULL 表示未置顶。 | 0001 | |
| hidden_at | TEXT | 隐藏私信的时刻;NULL 表示未隐藏,不删除会话。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKchannel_id | TEXT | PK | 频道 ID,同时是 SocialChannel DO 的确定性名字。 | 0001 |
| kind | TEXT | NOT NULL · CHECK(kind IN ('dm','group')) | dm 私信 / group 群频道,决定 group_id 和 dm_key 是否为空。 | 0001 |
| FKgroup_id | TEXT | FK→groups.group_id | 群频道所属群;dm 必须为 NULL。 | 0001 |
| dm_key | TEXT | CHECK(dm_key IS NULL OR ( instr(dm_key, ':') > 1 AND substr(dm_key, 1, instr(dm_key, ':') - 1) < substr(dm_key, instr(dm_key, ':') + 1) AND instr(substr(dm_key, instr(dm_key, ':') + 1), ':') = 0)) | dmKeyOf(a,b) 的规范键;群频道必须为 NULL。 | 0001 |
| name | TEXT | CHECK(name IS NULL OR length(name) BETWEEN 1 AND 40) | 群频道名;dm 为 NULL。 | 0001 |
| topic | TEXT | CHECK(topic IS NULL OR length(topic) BETWEEN 1 AND 200) | 可选频道说明;NULL 表示没有说明。 | 0001 |
| position | INTEGER | NOT NULL · DEFAULT 0 | 群内频道显示顺序,默认 0。 | 0001 |
| access | TEXT | NOT NULL · DEFAULT 'public' · CHECK(access IN ('public','private')) | 群内 public / private;私信必须 private。 | 0001 |
| is_default | INTEGER | NOT NULL · DEFAULT 0 · CHECK(is_default IN (0,1)) | 群的默认频道标记;同群至多一个,dm 必须为 0。 | 0001 |
| created_by | TEXT | NOT NULL | 创建者 user_id。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| archived_at | TEXT | 归档 / 解散时间;NULL 表示仍活动,不立即删除历史。 | 0001 | |
| last_message_seq | INTEGER | NOT NULL · DEFAULT 0 · CHECK(last_message_seq >= 0) | 最后插入消息序号,初始 0;DO 写回,编辑不增加它。 | 0001 |
| last_message_at | TEXT | 最后消息时间;未有消息时 NULL。 | 0001 | |
| last_sender_id | TEXT | 最后消息发送者真人 ID;未有消息时 NULL。 | 0001 | |
| last_message_preview | TEXT | CHECK(last_message_preview IS NULL OR length(last_message_preview) <= 120) | 最多 120 字符的摘要;无消息时 NULL。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKreport_id | TEXT | PK | 举报 ULID。 | 0001 |
| reporter_id | TEXT | NOT NULL | 举报发起用户 ID。 | 0001 |
| target_kind | TEXT | NOT NULL · CHECK(target_kind IN ('user','group','message')) | user / group / message,决定互斥的 target 列。 | 0001 |
| target_user_id | TEXT | user 举报目标;其他类型必须 NULL。 | 0001 | |
| target_group_id | TEXT | group 举报目标;其他类型必须 NULL。 | 0001 | |
| target_channel_id | TEXT | message 举报所属频道;其他类型必须 NULL。 | 0001 | |
| target_message_seq | INTEGER | CHECK(target_message_seq IS NULL OR target_message_seq >= 1) | message 举报的消息插入序;其他类型必须 NULL。 | 0001 |
| reason | TEXT | NOT NULL · CHECK(reason IN ('spam','harassment','scam','illegal','other')) | spam / harassment / scam / illegal / other,封闭集合。 | 0001 |
| detail | TEXT | CHECK(detail IS NULL OR length(detail) BETWEEN 1 AND 500) | 补充说明;NULL 表示未填写。 | 0001 |
| status | TEXT | NOT NULL · DEFAULT 'open' · CHECK(status IN ('open','resolved','dismissed')) | open 等待处置,resolved 已处理,dismissed 已驳回。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| resolved_at | TEXT | 处置时刻;open 必须 NULL,已处置必须有值。 | 0001 | |
| resolved_by | TEXT | 处置人 user_id,与 resolved_at 同时为空或有值。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKFKgroup_id | TEXT | PK · FK→groups.group_id | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| PKseq | INTEGER | PK · CHECK(seq >= 1) | 群内单调序号;同一 D1 写语句选 MAX(seq)+1,复合主键兜底。 | 0001 |
| actor_id | TEXT | NOT NULL | 执行治理动作的真人用户 ID。 | 0001 |
| action | TEXT | NOT NULL · CHECK(action IN ('kick','ban','unban','role_change','delete_message','pin','unpin','archive_channel','archive_group','transfer_owner')) | 封闭的治理动作种类,集合见 social domain。 | 0001 |
| target_user_id | TEXT | 动作涉及的用户;无则 NULL。 | 0001 | |
| channel_id | TEXT | 频道 ID,同时是 SocialChannel DO 的确定性名字。 | 0001 | |
| message_seq | INTEGER | CHECK(message_seq IS NULL OR message_seq >= 1) | 动作涉及消息的插入序;无则 NULL。 | 0001 |
| detail | TEXT | CHECK(detail IS NULL OR length(detail) BETWEEN 1 AND 500) | 可选审计说明,最多 500 字符,不查询 JSON。 | 0001 |
| at | TEXT | NOT NULL | 治理动作发生时刻,保留期限据此计算。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKFKgroup_id | TEXT | PK · FK→groups.group_id | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| banned_by | TEXT | NOT NULL | 执行封禁的管理员 user_id。 | 0001 |
| reason | TEXT | CHECK(reason IS NULL OR length(reason) BETWEEN 1 AND 200) | 可选封禁理由,NULL 表示未填写。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKinvite_code | TEXT | PK · CHECK(length(invite_code) = 10 AND invite_code NOT GLOB '*[^0-9A-HJKMNP-TV-Z]*') | 10 位外部共享代码,不是业务对象 ULID。 | 0001 |
| FKgroup_id | TEXT | FK→groups.group_id · NOT NULL | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| created_by | TEXT | NOT NULL | 创建邀请的成员 user_id。 | 0001 |
| expires_at | TEXT | 过期时刻;NULL 表示无时间限制。 | 0001 | |
| max_uses | INTEGER | CHECK(max_uses IS NULL OR max_uses > 0) | 最多使用次数;NULL 表示不限次数。 | 0001 |
| use_count | INTEGER | NOT NULL · DEFAULT 0 · CHECK(use_count >= 0 AND (max_uses IS NULL OR use_count <= max_uses)) | 已消费次数,默认 0,不能超过 max_uses。 | 0001 |
| revoked_at | TEXT | 撤销时刻;NULL 表示未撤销。 | 0001 | |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKFKgroup_id | TEXT | PK · FK→groups.group_id | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| message | TEXT | CHECK(message IS NULL OR length(message) BETWEEN 1 AND 200) | 申请者附言,可空。 | 0001 |
| status | TEXT | NOT NULL · CHECK(status IN ('pending','approved','rejected')) | pending / approved / rejected,由审批用例条件更新。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| decided_at | TEXT | 审批时刻;pending 必须为 NULL,已决必须有值。 | 0001 | |
| decided_by | TEXT | 审批管理员 ID;与 decided_at 同时为空或有值。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKFKgroup_id | TEXT | PK · FK→groups.group_id | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| role | TEXT | NOT NULL · CHECK(role IN ('owner','admin','member')) | owner / admin / member;转让时同 batch 先降旧主再升新主。 | 0001 |
| joined_at | TEXT | NOT NULL | 加入群或频道的时间,ISO TEXT。 | 0001 |
| invited_by | TEXT | 邀请者 user_id;自助加入为 NULL。 | 0001 | |
| notify | TEXT | NOT NULL · DEFAULT 'all' · CHECK(notify IN ('all','mentions','none')) | 群级通知 all / mentions / none,默认 all。 | 0001 |
| pinned_at | TEXT | 本人置顶会话的时间;NULL 表示未置顶。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKgroup_id | TEXT | PK | 所属群 ID;组内实体挂 groups 外键。 | 0001 |
| name | TEXT | NOT NULL · CHECK(length(name) BETWEEN 1 AND 40) | 对外群名,由管理员修改。 | 0001 |
| description | TEXT | CHECK(description IS NULL OR length(description) BETWEEN 1 AND 300) | 可选群说明,NULL 表示未填写。 | 0001 |
| icon_kind | TEXT | NOT NULL · DEFAULT 'preset' · CHECK(icon_kind IN ('preset','upload')) | preset 预设 / upload R2 上传图标。 | 0001 |
| icon_key | TEXT | NOT NULL · DEFAULT 'default' · CHECK(length(icon_key) BETWEEN 1 AND 256) | 预设 id 或 R2 object key,默认 default。 | 0001 |
| badge | TEXT | NOT NULL · DEFAULT 'none' · CHECK(badge IN ('none','verified','official')) | 认证徽章,首版仅运维直写。 | 0001 |
| visibility | TEXT | NOT NULL · CHECK(visibility IN ('public','private')) | public 可发现 / private 私密。 | 0001 |
| join_policy | TEXT | NOT NULL · CHECK(join_policy IN ('open','approval','invite')) | open 直接加入 / approval 审批 / invite 邀请。 | 0001 |
| channels_enabled | INTEGER | NOT NULL · DEFAULT 0 · CHECK(channels_enabled IN (0,1)) | 是否向 UI 展示频道列表;0 仍有默认频道。 | 0001 |
| owner_id | TEXT | NOT NULL | 唯一群主 user_id,与 owner 成员行一致由用例保证。 | 0001 |
| member_count | INTEGER | NOT NULL · DEFAULT 0 · CHECK(member_count >= 0) | 群成员数,加入 / 离开同 batch 更新,默认 0。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| updated_at | TEXT | NOT NULL | 服务端最近修改时间,ISO TEXT。 | 0001 |
| archived_at | TEXT | 归档 / 解散时间;NULL 表示仍活动,不立即删除历史。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKkind | TEXT | PK | 活动种类的稳定名称。 | 0001 |
| description | TEXT | NOT NULL | 供开发与运维读的种类说明。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| PKnotification_id | TEXT | PK | 通知 ULID,与 user_id 共同作为主键。 | 0001 |
| FKkind | TEXT | FK→social_notification_kinds.kind · NOT NULL | 参照 social_notification_kinds 的活动种类。 | 0001 |
| actor_id | TEXT | 触发活动的用户 ID;无真人触发者时 NULL。 | 0001 | |
| group_id | TEXT | 活动关联群;无则 NULL,不设生命周期 FK。 | 0001 | |
| channel_id | TEXT | 活动关联频道;无则 NULL,不设生命周期 FK。 | 0001 | |
| message_seq | INTEGER | CHECK(message_seq IS NULL OR message_seq >= 1) | 活动关联消息序号;无则 NULL。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| read_at | TEXT | 用户已读时刻;NULL 表示未读。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKblocker_id | TEXT | PK | 屏蔽发起者;私信只对该用户写 hidden_at。 | 0001 |
| PKblocked_id | TEXT | PK | 被屏蔽用户。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKfollower_id | TEXT | PK | 发起关注的用户 ID。 | 0001 |
| PKfollowee_id | TEXT | PK | 被关注的用户 ID。 | 0001 |
| created_at | TEXT | NOT NULL | 由服务端写入的创建时间,ISO TEXT。 | 0001 |
CREATE TRIGGER user_follows_not_blocked BEFORE INSERT ON user_follows
WHEN EXISTS (
SELECT 1 FROM user_blocks
WHERE (blocker_id = NEW.follower_id AND blocked_id = NEW.followee_id)
OR (blocker_id = NEW.followee_id AND blocked_id = NEW.follower_id)
)
BEGIN
SELECT RAISE(ABORT, 'user_follows: blocked in either direction');
END
| 字段 | 类型 | 约束 | 含义 | 迁移 |
|---|---|---|---|---|
| PKuser_id | TEXT | PK | 所属真人用户的稳定 ID;跨库引用 USER_DB,无外键。 | 0001 |
| followers_count | INTEGER | NOT NULL · DEFAULT 0 · CHECK(followers_count >= 0) | 当前粉丝数,默认 0;不允许负值。 | 0001 |
| following_count | INTEGER | NOT NULL · DEFAULT 0 · CHECK(following_count >= 0) | 当前关注数,默认 0;不允许负值。 | 0001 |
| groups_owned_count | INTEGER | NOT NULL · DEFAULT 0 · CHECK(groups_owned_count >= 0) | 当前拥有的群数,默认 0;不允许负值。 | 0001 |