Skip to content

Spore 数据库设计参考 ​

本文是 Spore 业务数据库(SQLite)的表结构、字段语义与持久化边界的唯一对外参考。 实现事实来源是 internal/store/migrate.go(建表与迁移)与 internal/store/ 各 DAO 文件;本文描述稳定契约,不复制 SQL。

维护约定:表结构、字段、索引、settings 逻辑键或接口持久化映射发生变化时,必须同步更新本文件,保持文档与实现一致。

目录 ​


1. 定位与运行方式 ​

项值
数据库内嵌 SQLite,驱动 modernc.org/sqlite(纯 Go、无 CGO,保住交叉编译)
文件DATA_DIR/spore.db(运行期伴随 -wal / -shm 侧文件)
访问层internal/store 是唯一入口(store.Open);业务 SQL 不出现在其他包
连接连接池固定 1 连接(SQLite 单写者,从根上消除写锁竞争)
连接级 PRAGMAjournal_mode=WAL、busy_timeout=5000、foreign_keys=ON、synchronous=NORMAL
schema 版本PRAGMA user_version(当前 19);启动时自动迁移,数据库版本高于程序支持时拒绝启动
迁移规则版本化、内嵌、只增不改:每个版本在独立事务内执行 DDL 并同事务写入 user_version;已发布迁移永不修改,新变更一律追加新版本

容量目标:≤100 用户、约 5,000 请求/日(约 180 万行/年),远低于 SQLite 单文件上限,不引入 PostgreSQL/Redis 等外部数据库。

通用字段约定(适用于全部业务表):

  • 时间字段统一 Unix 毫秒时间戳(int64);0/NULL 表示"尚未发生",写入可空时间存 NULL、读取归一为 0。不使用字符串时间(唯一例外:usage_daily.day 是运营时区 YYYY-MM-DD 日键)。
  • 可空文本以空字符串等价 NULL,读写由 internal/store 的 helper 归一,业务层不感知 NULL。
  • JSON 数组/对象统一存 TEXT 列,列名以 _json 后缀标识。

2. Schema 总览 ​

当前版本 v25 包含 18 张业务表、22 个显式索引、4 个数据库外键:

  • 无触发器、无视图、无 CHECK 约束;状态枚举与取值白名单由应用层(DAO)校验,见各表说明。
  • sqlite_sequence 是 SQLite 为 AUTOINCREMENT(cloud_uploads、dump_entries、watch_invite_requests、sent_messages、error_logs)自动维护的内部表,不属于业务 schema。
  • 频道没有独立表:频道维度的一切数据都是 requests 行的聚合(见 第 4 节)。
表用途引入版本
usersTelegram 用户主档:状态、owner、限额、拒绝记录v1
requests一次提取请求的全生命周期,也是频道/业务统计的唯一事实来源v1
usage_daily(用户, 运营日) 的当日用量与重置次数v1
audit_log管理员/系统变更审计v1
events系统异常事件,按 key 去重合并v1
settings运行时键值配置(逻辑键见第 6 节)v1
web_sessions管理端会话(只存 ID 哈希)v1
channel_bindings用户频道绑定(任务成功后复制内容的目标频道)v4
join_requests/join 频道加入申请与审批记录v6
joined_channels读取账号实际加入频道的留痕v6
system_metric_samples进程资源与传输速率低频采样v7
cloud_uploads云盘上传逐文件记录v10
dump_entries缓存频道"干净副本"消息坐标(复用来源)v13
watch_sources监听源(/watch)配置与申请审批(预热缓存频道)v17
watch_events监听转储逐次留痕(哪个 bot 在哪个源转发了哪些消息)v18
watch_invite_requests私有邀请链接监听申请的异步处理状态与安全展示快照v19
sent_messagesbot 发出消息坐标 → 请求的映射(/pin、/cancel 引用回复锚点;含频道副本组首坐标,供事后补置顶)v22
error_logs错误日志中心:请求管线与 Bot 相关环节错误的逐条明细(来源/环节/错误码/根因串/参数快照),管理端筛选查询v25

3. 表数据字典 ​

3.1 users ​

Telegram 用户主档,主键即 Telegram User ID。状态流转:/start 创建 pending → 管理端审批 enabled → disabled / archived(可恢复);owner 不可停用/归档。

字段类型约束说明
idINTEGERPRIMARY KEYTelegram 用户 ID
statusTEXTNOT NULLpending / enabled / disabled / archived(应用层白名单校验)
is_ownerINTEGERNOT NULL DEFAULT 0owner 标记;唯一性由应用层维护(设置 owner 时单语句清零其他用户),无数据库唯一索引
usernameTEXT可空用户名快照(审批/资料刷新时取得)
display_nameTEXT可空显示名快照
noteTEXT可空管理员备注
submit_interval_secINTEGERNOT NULL DEFAULT 10提交间隔(秒)
daily_limitINTEGERNOT NULL DEFAULT 50每日额度
concurrent_limitINTEGERNOT NULL DEFAULT 2未完成任务并发上限
created_atINTEGERNOT NULL创建时间(Unix 毫秒)
first_used_atINTEGER可空首次使用时间
last_used_atINTEGER可空最近使用时间
archived_atINTEGER可空归档时间
last_denied_atINTEGER可空最近拒绝时间
last_denied_reasonTEXT可空最近拒绝的错误码
bind_limitINTEGERNOT NULL DEFAULT 0频道绑定数量上限(v5):0 = 跟随角色默认(普通 1 / owner 3),1–20 为显式值
cloud_downloadINTEGERNOT NULL DEFAULT 0云盘下载权限三态(v11):0 = 跟随角色默认(owner 允许 / 普通拒绝),1 = 显式允许,2 = 显式拒绝;与全局开关是 AND 关系
source_bot_idINTEGERNOT NULL DEFAULT 0来源 bot 数字 ID(v15,首次 /start 的受理 bot);0 = 存量行或 Web 手动添加
source_bot_usernameTEXTNOT NULL DEFAULT ''来源 bot 用户名快照(v15,展示自持,bot 移出池后仍可读)
auto_pinINTEGERNOT NULL DEFAULT 0自动置顶偏好(v20):1 = 该用户的普通任务提交即默认标记置顶(云盘/缓存补写任务不适用)

3.2 requests ​

一次提取请求的全生命周期记录;频道统计、业务统计、DC 分布、Bot 分布全部聚合自本表。状态机:queued → processing → succeeded / failed / cancelled;重试复用同一行,attempt 累计(上限为动态配置 max_request_attempts,默认 3;管理端可重置计数)

字段类型约束说明
idINTEGERPRIMARY KEY请求 ID
user_idINTEGERNOT NULL,FK → users(id)提交用户
source_kindTEXT可空public(用户名链接)/ private(t.me/c/ 内部链接)
channel_keyTEXTNOT NULL频道标识:公开频道为用户名,私有频道为 -100 前缀内部 ID
message_idINTEGERNOT NULL源消息 ID
statusTEXTNOT NULLqueued / processing / succeeded / failed / cancelled
attemptINTEGERNOT NULL DEFAULT 1已尝试次数
error_codeTEXT可空失败原因错误码
error_detailTEXT可空失败根因原始错误串(v23;截断 300 字符 + 省略号,仅管理端展示不发给用户);重试/重置随 error_code 一并清空
media_typeTEXT可空主媒体类型;多成员相册统一 album;空串 = 未记录(失败于消息转换前)
file_sizeINTEGER可空媒体大小(诊断元数据)
file_nameTEXT可空媒体文件名
requested_atINTEGERNOT NULL请求时间
queued_atINTEGER可空入队时间
started_atINTEGER可空开始处理时间
finished_atINTEGER可空终态时间
duration_msINTEGER可空总耗时(毫秒)
delivery_modeTEXTNOT NULL DEFAULT 'upload'投递方式(v2):upload 下载上传 / text 纯文本 / cloud 云盘下载(/download 或补存)/ reuse 缓存频道复用命中 / dump 缓存补写 / split 分卷拆分(超限媒体切段整组投递)/ mixed(历史遗留,仅旧记录)/ reference(历史遗留,机制已移除)
source_media_dc_ids_jsonTEXT可空源媒体所在 Telegram DC ID 去重数组(v8);文本、旧记录为 NULL
media_types_jsonTEXT可空请求实际包含的去重媒体类型数组(v9);相册用它区分纯图片/纯视频/混合
parent_request_idINTEGER可空,无外键补存链路指向的原请求 ID(v10);普通请求为 NULL
cloud_destinationTEXTNOT NULL DEFAULT ''云盘请求的目的地名称(v10);重试/补存重新入队时据此恢复;普通请求空串
sent_chat_idINTEGERNOT NULL DEFAULT 0遗留列(v12):旧"用户聊天坐标复用"的投递目标聊天;现行链路不再写入,仅保留历史行读取兼容
sent_message_ids_jsonTEXTNOT NULL DEFAULT ''遗留列(v12):同上,已发送消息 ID 数组;被 dump_entries 方案取代
bot_idINTEGERNOT NULL DEFAULT 0受理 bot 数字 ID(v15);0 = 存量行或非 Bot 通道创建
bot_usernameTEXTNOT NULL DEFAULT ''受理 bot 用户名快照(v15)
pinINTEGERNOT NULL DEFAULT 0自动置顶标记(v20):1 = 任务成功后需在用户绑定的频道/群组置顶副本组首(/pin <链接> 单次指定或用户 auto_pin 偏好)
pin_okINTEGERNOT NULL DEFAULT 0置顶成功的目标数(v20,worker 收尾回写);重试时清零
pin_totalINTEGERNOT NULL DEFAULT 0参与置顶的目标总数(v20,worker 收尾回写);重试时清零

进程退出中断的 queued/processing 行在下次启动被批量置 failed(INTERRUPTED)。

3.3 usage_daily ​

按 (用户, 运营日) 聚合的当日用量。

字段类型约束说明
user_idINTEGERNOT NULL,复合主键用户 ID
dayTEXTNOT NULL,复合主键运营时区日键 YYYY-MM-DD(全库唯一的字符串"时间"字段)
usedINTEGERNOT NULL DEFAULT 0当日已用次数
reset_countINTEGERNOT NULL DEFAULT 0当日额度重置次数

3.4 audit_log ​

管理员与系统变更的审计流水。典型来源:用户审批/状态/限额、频道绑定与加入审批、请求 retry/cancel/delete/补存、登录登出、密钥重置、设置与通知变更、备份导出导入、事件 resolve/recover。

字段类型约束说明
idINTEGERPRIMARY KEY条目 ID
atINTEGERNOT NULL时间(Unix 毫秒)
actorTEXT可空操作者(admin / system 等)
actionTEXTNOT NULL动作标识(如 auth.login、user.status、request.cloud_archive)
targetTEXT可空操作对象
before_jsonTEXT可空变更前快照(原始 JSON;只放业务字段,不含凭据)
after_jsonTEXT可空变更后快照(同上)

清理语义:批量删除与 audit.delete 记录同事务;全量清除与 audit.clear 同事务,清除后表内仅保留这一条。

3.5 events ​

系统异常事件,按 key 去重合并(UPSERT:count 累加、last_at 更新);已 resolved 的事件再次发生会重新打开。last_notified_at 支撑 30 分钟通知冷却。屏蔽/静音只影响提醒,不删除事件。

字段类型约束说明
idINTEGERPRIMARY KEY事件 ID
keyTEXTNOT NULL,UNIQUE去重键(隐式唯一索引)
severityTEXTNOT NULL级别(info / warn / error)
messageTEXTNOT NULL受控中文描述
countINTEGERNOT NULL DEFAULT 1合并发生次数
first_atINTEGERNOT NULL首次发生时间
last_atINTEGERNOT NULL最近发生时间
last_notified_atINTEGER可空最近通知时间(NULL = 从未通知)
statusTEXTNOT NULL DEFAULT 'open'open / resolved

3.6 settings ​

通用键值表,物理结构只有两列;逻辑键(运行设置、系统设置、通知配置、OAuth、Bot 暂停等)运行期按需写入,新增逻辑键不需要迁移,详见第 6 节。

字段类型约束说明
keyTEXTPRIMARY KEY逻辑键名
value_jsonTEXTNOT NULL值(JSON 文本)

3.7 web_sessions ​

管理端 Web 会话。只存会话 ID 的 SHA-256 哈希,不存原始会话 ID。每次认证请求滑动续期;登录时惰性清理过期行;密钥重置 / OAuth 绑定与解绑 / 数据库导入会清空全部会话。

字段类型约束说明
id_hashTEXTPRIMARY KEY会话 ID 的 SHA-256 哈希
created_atINTEGERNOT NULL创建时间
expires_atINTEGERNOT NULL过期时间(滑动续期)
csrf_tokenTEXTNOT NULL会话级 CSRF token
ipTEXT可空登录 IP
user_agentTEXT可空登录 User-Agent

3.8 channel_bindings ​

用户频道绑定:任务成功后把内容复制到归属用户的绑定频道。channel_id 是 Bot API 的频道数字 ID(-100 前缀)作主键,同一频道只归属一个用户。不存凭据或 access hash,只存展示所需的 username/title。

字段类型约束说明
channel_idINTEGERPRIMARY KEYBot API 频道数字 ID(负数,-100 前缀)
user_idINTEGERNOT NULL,FK → users(id)归属用户
usernameTEXT可空频道公开用户名(私有频道为空)
titleTEXT可空绑定时取得的频道标题
bound_viaTEXTNOT NULL DEFAULT 'bot'绑定来源:bot(用户 /bind)/ web(管理端)
bot_idINTEGERNOT NULL DEFAULT 0路由 bot(v21):绑定经哪台 bot 建立并通过硬校验;仅它受理的任务投递副本/置顶到此;0 = 通配(Web 绑定与历史行),任意受理 bot 均尝试
statusTEXTNOT NULL DEFAULT 'active'软解绑状态机(v24):active 有效 / unbound 已解绑留痕(解绑不删行;重新绑定同频道即复活,unbound 行也允许其他用户接管)
unbind_reasonTEXTNOT NULL DEFAULT ''解绑原因(v24):manual 手动 / channel_gone 副本投递发现频道已不存在自动解绑
unbound_atINTEGERNOT NULL DEFAULT 0解绑时间(Unix 毫秒;0 = 未解绑)
created_atINTEGERNOT NULL绑定时间
updated_atINTEGERNOT NULL最近更新时间(绑定服务刷新标题/用户名时更新;未下发到 API DTO)

3.9 join_requests ​

/join 频道加入申请与审批记录。invite_hash 落库是审批延时执行所必需;展示层一律脱敏(masked_hash),不入日志。状态:pending → approved / rejected(加入失败置 failed)。

字段类型约束说明
idINTEGERPRIMARY KEY申请 ID
user_idINTEGERNOT NULL,FK → users(id)申请人
invite_hashTEXTNOT NULL邀请链接哈希(敏感,只脱敏下发)
channel_titleTEXT可空频道标题
participantsINTEGER可空频道成员数(提交时快照)
statusTEXTNOT NULLpending / approved / rejected / failed(应用层白名单校验)
requested_atINTEGERNOT NULL申请时间
reviewed_atINTEGER可空审批时间(0/NULL = 未审批)
reviewed_byTEXT可空审批人(会话哈希标识)
noteTEXT可空审批备注(如请求制频道等待频道侧批准的说明)

同用户对同一邀请链接的重复 pending 申请靠应用层查重去重,没有数据库唯一索引。

3.10 joined_channels ​

读取账号实际加入频道的留痕表,不是实时频道列表的唯一来源(实时列表 = MTProto 对话遍历 + 本表留痕)。不存 access_hash(数据红线)。

字段类型约束说明
channel_idINTEGERPRIMARY KEY频道数字 ID
titleTEXT可空频道标题
usernameTEXT可空频道公开用户名
kindTEXTNOT NULL DEFAULT 'channel'对象类型(当前恒为 channel)
joined_viaTEXTNOT NULL DEFAULT 'external'留痕来源:join_command(owner /join 即时)/ approved(审批加入)/ external(外部拉入或无留痕)/ watch_source(私有邀请监听源流程加入)/ bind_resolve(绑定邀请解析流程加入)
joined_byINTEGER可空,FK → users(id)触发加入的用户;NULL = 外部加入或未关联用户
joined_atINTEGERNOT NULL加入时间
left_atINTEGER可空退出时间(NULL = 仍在加入中)

3.11 system_metric_samples ​

进程资源与传输速率的低频历史采样(4h/1d 档位的数据源;realtime 档位来自 monitor 内存窗口)。监控服务按固定周期聚合后按主键 upsert,并按保留期清理旧行。NULL = 探针不可用。

字段类型约束说明
sampled_atINTEGERPRIMARY KEY采样时间(Unix 毫秒)
rss_bytesINTEGER可空进程常驻内存
temp_dir_bytesINTEGER可空临时目录占用
download_bytes_per_secondREAL可空全服务下载聚合速率
upload_bytes_per_secondREAL可空全服务上传聚合速率(发送方已消费字节)
cpu_percentREAL可空进程 CPU 占用率(v14,0–100,占全部核心)

3.12 cloud_uploads ​

云盘任务的逐文件上传记录(/download 与管理端补存共用)。注意:request_id 无外键,删除请求行不会级联清理上传明细(历史痕迹有意保留,无独立清理 DAO)。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT记录 ID
request_idINTEGERNOT NULL,无外键所属请求(业务关联)
destinationTEXTNOT NULL目的地名称
remote_pathTEXTNOT NULL远端完整路径(不含目的地前缀)
file_nameTEXTNOT NULL DEFAULT ''文件名(空串 = 未记录)
statusTEXTNOT NULLuploading / succeeded / failed
error_codeTEXTNOT NULL DEFAULT ''失败错误码(成功为空串)
error_detailTEXT可空失败根因原始错误串(v25,截断规则同 requests.error_detail;rclone stderr 等根因不再只进日志)
bytesINTEGERNOT NULL DEFAULT 0已上传字节数
created_atINTEGERNOT NULL开始时间
finished_atINTEGERNOT NULL DEFAULT 0结束时间(0 = 未结束)

3.13 dump_entries ​

任务成功投递后同步写入 bot 自有缓存频道的"干净副本"消息坐标(无脚注 caption,跨用户复用的唯一来源)。只存 bot 自有频道内的消息坐标,不存正文/媒体/凭据。同链接可有多条,"取当前格式最新"生效;副本被删时复用失败自动回落完整链路并重写副本自愈。生命周期独立于 requests(无外键)。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT条目 ID
channel_keyTEXTNOT NULL源链接的频道标识
message_idINTEGERNOT NULL源消息 ID
dump_ids_jsonTEXTNOT NULL缓存频道内的消息 ID 数组(相册保组,按发送顺序)
format_versionINTEGERNOT NULL DEFAULT 0副本布局格式版本(v16):0 = 历史行(相册多 caption 旧形态),1 = "恰好组首一条合并 caption";查询只命中当前版本,历史坐标保留供审计,复用回落完整投递后自愈重写
dump_channel_idINTEGERNOT NULL DEFAULT 0副本所在缓存频道(v24):0 = 升级前存量/未知频道,查询永不命中(不做回填);切换缓存频道后旧频道条目因不匹配自动失效,由复用自愈或管理端迁移工具重建
created_atINTEGERNOT NULL写入时间

3.14 watch_sources ​

监听源(/watch,v17;v18 增补 kind/bot_id/bot_username):配置的源频道/超级群组由 Bot 接收新帖并自动转储缓存频道预热 dump_entries(重复链接直接命中复用)。管理员 Web 添加天然 approved;用户 /watch 申请按配置走审批(pending → approved/rejected)。added_by=0 表示管理员添加(无外键:0 语义不是用户行)。listener 按 status='approved' AND enabled=1 过滤生效源。只存标识与状态,不存消息内容(数据范围红线)。

字段类型约束说明
channel_idINTEGERPRIMARY KEY源频道/超级群组数字 ID(Bot API -100 形态)
kindTEXTNOT NULL DEFAULT ''channel / supergroup(展示用;配置时快照)
usernameTEXTNULL公开源用户名(无 @;私有源为空)。供公开源双键复用(t.me/username 与 t.me/c 两种链接形态都命中条目)
titleTEXTNULL展示标题(配置时快照)
statusTEXTNOT NULLpending / approved / rejected(DAO 白名单校验;Review 仅允许 pending → approved/rejected)
enabledINTEGERNOT NULL DEFAULT 1approved 行的独立暂停开关
added_byINTEGERNOT NULL DEFAULT 00 = 管理员 Web 添加;>0 = 申请人用户 ID
bot_idINTEGERNOT NULL DEFAULT 0用户 /watch 的受理 bot ID(与 requests.bot_id v15 同语义;0 = Web 添加)
bot_usernameTEXTNOT NULL DEFAULT ''受理 bot 用户名快照(展示自持,bot 移出池后历史仍可读)
reviewed_byTEXTNULL审批人(session idHash 或 admin);未审批为空
created_atINTEGERNOT NULL首次写入时间(Upsert 冲突更新不重置)
updated_atINTEGERNOT NULL最近更新时间

3.15 watch_events ​

监听转储逐次留痕(业务统计与「监听记录」页的事实表):哪个 bot、在哪个源、转发了哪些消息、缓存频道落点与路径。每次转储成功(copy)或回退入队(fallback)各落一行;已存在条目的跳过不落。只存 ID 与元数据(数据范围红线);源标题/用户名与 bot 用户名存快照,源或 bot 删除后记录仍可读。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT事件 ID
channel_idINTEGERNOT NULL源频道/群组 ID(-100 形态)
username / titleTEXTNOT NULL DEFAULT ''源快照(NOT NULL 列直接存空串,不经 nullStr)
message_idINTEGERNOT NULL定位消息 ID(相册取首条成员)
member_ids_jsonTEXTNOT NULL转发的源消息 ID 数组(相册为全部成员)
dump_ids_jsonTEXTNOT NULL缓存频道落点消息 ID 数组(fallback 为空数组)
request_idINTEGERNOT NULL DEFAULT 0关联 requests 行(仅 fallback;0 = 无)
bot_idINTEGERNOT NULL DEFAULT 0执行转储的 bot
bot_usernameTEXTNOT NULL DEFAULT ''bot 用户名快照
pathTEXTNOT NULLcopy(服务端复制)| fallback(受保护重传,DAO 白名单校验)
created_atINTEGERNOT NULL事件时间

3.16 watch_invite_requests ​

私有邀请链接监听申请的异步处理记录。活动状态为 pending / waiting_telegram / waiting_bot,终态为 approved / rejected / failed。user_id=0 表示管理员 Web 路径创建(与 watch_sources.added_by 同约定,无数据库外键);>0 为申请人用户 ID。完整 invite_hash 只供加入流程内部使用,Go 模型以 json:"-" 禁止序列化(对外字段为 masked_hash,频道标题与申请时间分别以 channel_title / created_at 下发);保留策略由服务层在状态流转时控制:pending / waiting_telegram 保留完整 hash、failed 保留以便重试,waiting_bot / approved / rejected 清理完整 hash,仅留 masked_hash 供安全展示。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT申请 ID
user_idINTEGERNOT NULL DEFAULT 0申请人;0 = 管理员路径(无外键)
invite_hashTEXT可空完整邀请 hash(敏感;活动阶段内部使用,按保留策略清理)
masked_hashTEXTNOT NULL脱敏展示值,完整 hash 清理后仍保留
statusTEXTNOT NULLpending / waiting_telegram / waiting_bot / approved / rejected / failed
channel_idINTEGERNOT NULL DEFAULT 0邀请解析成功后的频道/群组 ID;0 = 尚未解析
kindTEXTNOT NULL DEFAULT ''channel / supergroup 快照
usernameTEXTNOT NULL DEFAULT ''公开用户名快照;私有源可为空
titleTEXTNOT NULL DEFAULT ''标题快照(模型 JSON 字段 channel_title)
participantsINTEGERNOT NULL DEFAULT 0频道成员数快照;0 = 未解析
enabledINTEGERNOT NULL DEFAULT 1监听开关;由调用方显式传值(用户路径 true,管理员可预录入 false 停用态)
reviewed_byTEXTNOT NULL DEFAULT ''审批人标识(会话哈希或 admin);未审批为空串
noteTEXTNOT NULL DEFAULT ''备注(状态更新时覆盖写入,空串即清除)
bot_idINTEGERNOT NULL DEFAULT 0受理 bot ID
bot_usernameTEXTNOT NULL DEFAULT ''受理 bot 用户名快照
requested_atINTEGERNOT NULL申请时间(Unix 毫秒;模型 JSON 字段 created_at)
updated_atINTEGERNOT NULL最近状态、频道信息、审批人或 hash 清理更新时间

活动列表、全局/按用户计数及相同 hash 查重只统计三个活动状态;终态历史行不阻止同一邀请重新申请。管理列表(全部状态)待审批在前、其余按申请时间倒序;Bot 用户列表按申请时间倒序。

3.17 sent_messages ​

引用回复交互锚点:bot 发出的消息坐标到请求的映射。/pin、/cancel 作为对 bot 消息的回复发送时(Telegram 原生命令用法),按 (bot_id, chat_id, message_id) 三元组反查本表定位请求——坐标按 bot 隔离(用户私聊内消息 ID 按 bot 私有,跨 bot 查不到)。只存运营坐标,不存正文/媒体/凭据(与 dump_entries 同类红线)。写入在各发送点顺手记录返回的 message ID(INSERT OR IGNORE,重复坐标静默去重);不设 GC(行极小,与 requests 同生命周期)。

kind 取值:status(提交/重试补发的进度占位)、media(投递到用户私聊的媒体/文本,相册与分卷逐条)、channel_copy(复制到绑定频道的副本组首,/pin 事后补置顶的定位依据)、failure(任务失败通知)。channel_copy 不作回复入口(bot 收频道消息依赖管理员权限与 privacy 设置)。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT行 ID
request_idINTEGERNOT NULL关联请求(逻辑关联,无外键)
bot_idINTEGERNOT NULL发出消息的受理 bot ID
chat_idINTEGERNOT NULL消息所在聊天(私聊即用户 ID;channel_copy 为频道 ID)
message_idINTEGERNOT NULL消息 ID
kindTEXTNOT NULL消息类别(见上)
created_atINTEGERNOT NULL写入时间(Unix 毫秒)

3.18 error_logs ​

错误日志中心(v25):请求管线与 Bot 相关环节错误的逐条明细,管理端「错误日志」页筛选查询;与 events 互补——事件按 key 合并聚合计数(管要不要通知),本表逐条留痕(管"到底发生了什么")。写入经 internal/errlog 门面(尽力而为:写库失败只记日志;无归属请求的同键错误 60 秒窗口去重防刷库)。数据范围红线:只存错误文本与纯 ID 类参数,不存凭据/消息正文/媒体 URL。

字段类型约束说明
idINTEGERPRIMARY KEY AUTOINCREMENT日志 ID
sourceTEXTNOT NULL错误域:request / botapi / cloud / backup / watch / mtproto(白名单校验)
codeTEXTNOT NULL DEFAULT ''apperr 错误码;空串 = 未分类
stageTEXTNOT NULL DEFAULT ''环节名(fetch / download / split / send / upload / pin / enqueue / test / maintenance 等)
severityTEXTNOT NULL DEFAULT 'error'error(任务失败或进程级异常)/ warn(尽力而为操作失败)
messageTEXTNOT NULL受控中文描述(发生了什么)
detailTEXTNOT NULL DEFAULT ''原始错误串(截断规则同 requests.error_detail)
context_jsonTEXTNOT NULL DEFAULT '{}'参数快照(job_id、bot_id、channel_key、destination 等纯 ID/名称类值)
request_idINTEGERNOT NULL DEFAULT 0关联 requests 行(无外键);0 = 无归属请求;请求详情页据此深链反查
created_atINTEGERNOT NULL写入时间(Unix 毫秒)

保留策略:settings 键 error_log_retention_days(缺省 30 天)周期自动清理(每小时 + 启动即清一次)+ 管理端手动批量/按时间段删除(写审计)。

4. 表关系与约束 ​

4.1 数据库外键(均指向 users(id),均 NO ACTION) ​

表.列引用
requests.user_idusers(id)
channel_bindings.user_idusers(id)
join_requests.user_idusers(id)
joined_channels.joined_byusers(id)

4.2 逻辑关联(有意不设外键) ​

列指向原因
requests.parent_request_idrequests.id补存目标行可能先于该列存在;仅做展示跳转
cloud_uploads.request_idrequests.id上传明细独立保留,不随请求删除级联清理
dump_entries.(channel_key, message_id)同链接 requests 行缓存副本生命周期独立于任何一条请求

4.3 应用层约束(数据库不保证) ​

  • users.status、requests.status、join_requests.status、cloud_uploads.status、events.status 等枚举由 DAO 白名单校验,无 CHECK 约束。
  • users.is_owner 全局唯一性由设置 owner 的业务语句维护。
  • join_requests 无 (user_id, invite_hash, status) 唯一索引,pending 去重靠应用层查重。
  • watch_sources.status 由 DAO 白名单校验;重复审批(非 pending 行)返回 STORE_CONSTRAINT;上限校验(总数/每用户)在 watch 服务应用层完成。
  • watch_events 只增不改;watch_events.request_id 关联行可能不存在(请求可被清理),仅做展示跳转。
  • watch_invite_requests.status 由 DAO 白名单校验;活动状态集合统一用于列表、计数和 hash 查重;完整 hash 清理后只保留 masked_hash,保留策略(failed 便于重试、waiting_telegram 保留,waiting_bot/approved/rejected 清理)由服务层控制;user_id=0 为管理员路径(应用层约定,无数据库外键,enabled 由调用方显式传值)。
  • 频道无独立表:管理端"频道统计/频道详情"是 requests 的纯聚合;删除频道 = 删除该频道的全部 requests 行。

5. 索引清单 ​

索引表列引入服务场景
idx_requests_user_requestedrequests(user_id, requested_at)v1用户维度列表与用量统计
idx_requests_channel_requestedrequests(channel_key, requested_at)v1频道聚合、趋势
idx_requests_statusrequests(status)v1状态筛选、未完成任务计数
idx_requests_user_channel_messagerequests(user_id, channel_key, message_id)v1同用户同链接去重
idx_audit_at_idaudit_log(at, id)v3审计时间范围过滤 + 倒序分页
idx_channel_bindings_userchannel_bindings(user_id)v4按用户查绑定
idx_join_requests_statusjoin_requests(status, requested_at)v6审批列表按状态筛选
idx_join_requests_userjoin_requests(user_id, requested_at)v6按用户查申请
idx_joined_channels_activejoined_channels(left_at, joined_at)v6活跃/已退出统计
idx_cloud_uploads_requestcloud_uploads(request_id)v10请求详情的上传明细
idx_requests_channel_message_statusrequests(channel_key, message_id, status)v12跨用户缓存复用查询(不带 user_id 前缀)
idx_dump_entries_linkdump_entries(channel_key, message_id, id)v13同链接取最新副本
idx_watch_events_channelwatch_events(channel_id, id)v18按监听源倒序查询转储记录
idx_watch_invite_requests_activewatch_invite_requests(status, requested_at, id)v19活动申请恢复列表与全局计数
idx_watch_invite_requests_user_activewatch_invite_requests(user_id, status, requested_at, id)v19按用户统计活动申请
idx_watch_invite_requests_hash_activewatch_invite_requests(invite_hash, status)v19相同完整 hash 的活动申请查重
idx_sent_messages_msgsent_messages(bot_id, chat_id, message_id) UNIQUEv22引用回复按坐标反查请求
idx_sent_messages_requestsent_messages(request_id)v22按请求取频道副本坐标(事后补置顶)

隐式索引:events.key 的 UNIQUE 索引,以及各主键索引(含 usage_daily 复合主键)。

6. settings 逻辑 Schema ​

settings 物理上只有 key / value_json 两列;以下逻辑键运行期按需写入(首次保存时才出现,新增键不需要迁移)。生效方式(即时/重启)以配置参考为唯一权威来源。

域键值形态说明
运营timezone字符串运营时区(IANA 名称)
运营dedup_window_min数值重复链接去重窗口(分钟)
运行设置queue_capacity数值队列容量(重启生效)
运行设置worker_count数值worker 数,1–16(重启生效,覆盖环境变量)
运行设置max_file_size / stream_limit / temp_dir_max_size字节数值媒体参数(重启生效)
运行设置memory_budget字节数值内存管道进程级预算(即时生效)
运行设置max_links_per_message数值单条消息最大有效链接数
运行设置channel_copy_enabled / tg_reuse_enabled布尔频道副本同步 / 缓存频道复用总开关
运行设置dump_channel_id / dump_channel_title数值 / 字符串缓存频道(settings 优先于 DUMP_CHANNEL_ID 环境变量)
运行设置last_backup_at毫秒时间戳最近备份时间
运行设置error_log_retention_days数值错误日志保留天数(1–365,缺省 30;即时生效)
系统system_name字符串系统名称(缺省 Spore)
频道加入join_enabled / join_auto_leave_external / join_require_approval / join_mute_enabled / join_archive_enabled布尔/join 总开关与行为配置
频道加入join_max_channels数值活跃加入数量上限(0 = 不限)
传输download_threads / upload_threads / download_connections / upload_connections数值传输并发的数据库覆盖(覆盖环境默认值)
安全access_key_hash字符串Web 访问密钥的 SHA-256 哈希(只存不可逆哈希)
OAuthgithub_oauth_configJSONGitHub OAuth 配置;Client Secret 为 AES-256-GCM 密文字段(根密钥 WEB_OAUTH_ENCRYPTION_KEY 仅来自环境变量,按用途 HKDF 域分离)
OAuthgithub_bindingJSON管理员 GitHub 账号绑定信息
Bot 池bots_pausedJSON手动暂停的 bot 列表(重启/重连后保持)
通知notification_channelsJSON通知通道配置;Bot Token、Webhook URL 与签名密钥为 AES-256-GCM 密文字段
通知notification_policyJSON通知策略与静音计划(与凭据文档分离,策略写入不触碰凭据)

敏感边界:明文凭据一律不入 settings;上表仅有的两类例外(通知通道凭据、OAuth Client Secret)是可轮换的外部服务凭据,必须以密文保存,API 响应、日志与审计不得出现密文或明文。

7. 数据库外的持久化 ​

以下数据不在 SQLite 中,由各包以文件方式管理;它们不随数据库备份导出,也不要在数据库文档或表设计中虚构对应字段。

文件/目录内容管理方
data/session.json用户号 MTProto 会话(等效账号控制权)internal/mtproto
data/bot-session.json / bot-session-<botID>.jsonBot 身份 MTProto 会话(多机器人池每 bot 一份)internal/mtproto
data/peers.jsonChannelID → AccessHash 缓存internal/mtproto
data/bots.json机器人池 token 列表internal/botlist
data/cloud-drive.json云盘配置与网盘凭据internal/cloudarchive
data/pending-cloud-drive.json云盘配置导入候选标记internal/cloudarchive
data/pending-import.db / pending-import.json数据库备份导入候选与确认标记internal/store
data/spore.db.rollback-*导入前的回滚副本internal/store
data/tmp/大媒体临时文件(任务结束即清理)internal/media
spore.db-wal / spore.db-shmSQLite WAL 侧文件(非独立数据)SQLite

8. API 与持久化映射 ​

本表按管理端 API 功能域回答"数据从哪来、写到哪"。完整端点契约(字段、错误码、分页)见 API 参考;API 变化时请同步更新本表对应行。

API / 功能域读取写入说明
GET /healthz无无纯进程探针
GET /readyzsettings(一次读取验证)无数据库可读写验证
登录 / 登出 / 会话(/api/v1/login*、/api/v1/session*)settings.access_key_hash、settings.github_*、web_sessionsweb_sessions(创建/续期/删除/惰性清理)、audit_log会话只存 ID 哈希
GET /api/v1/overviewusers、requests、join_requests、joined_channels、settings无队列/Bot/MTProto/文件状态来自内存,不落库
GET /api/v1/version/check无无上游 Releases 查询 + 服务端缓存
GET /api/v1/system-metricssystem_metric_samples(4h/1d 档)无realtime 档来自 monitor 内存窗口
GET /api/v1/statsrequests、users无全部指标与分布聚合自 requests
用户管理(/api/v1/users*)users、usage_daily、requests(聚合)users、usage_daily、audit_logeffective_*、remaining_today、total_requests 为派生字段;reset-quota 写 usage_daily.reset_count
申请审批(/api/v1/applications*)users(status=pending)users、audit_logapprove → enabled,reject → disabled
请求记录(/api/v1/requests*)requests、users、cloud_uploads(详情)requests、audit_logretry/cancel/delete 只改 requests 行并写审计
云盘配置(GET/PUT /api/v1/cloud-drive、/test)无无读写 data/cloud-drive.json 文件,不进数据库
云盘补存(/api/v1/requests/*/cloud-archive*)requests、cloud_uploadsrequests(新建 delivery_mode=cloud 行)、audit_logworker 执行期写 cloud_uploads;同链接成功记录用于远端复用判定
缓存补写(/api/v1/requests/*/dump-backfill*)requests、dump_entries、settings(缓存频道)requests(新建 delivery_mode=dump 行)、audit_logworker 执行期写 dump_entries;全程不向原用户发消息
云盘配置备份(/api/v1/cloud-drive/backup*)无无独立加密 ZIP 流程,只操作 data/ 文件
频道统计(/api/v1/channels*)requests(聚合)requests(删除时)频道删除 = 删除该频道全部请求行
频道绑定(/api/v1/channel-bindings*)channel_bindings、userschannel_bindings、audit_log绑定校验经 Bot API getChat/getChatMember,结果落库
频道加入(/api/v1/channel-join/*)join_requests、joined_channels、usersjoin_requests、joined_channels、audit_log已加入频道列表 = MTProto 实时遍历 + 本表留痕;invite_hash 只下发脱敏值
事件中心(/api/v1/events*)eventsevents、audit_log手动 resolve 与系统自动恢复共用语义
审计日志(/api/v1/audit*)audit_logaudit_log删除与清理审计同事务;clear 后仅保留一条 audit.clear
运行设置 / 系统设置(/api/v1/settings、/api/v1/system/config)settingssettings、audit_log逻辑键见第 6 节;值未变化时不写审计
通知设置(/api/v1/notification/*)settings.notification_*settings、audit_log凭据密文保存;策略与静音计划在 notification_policy
OAuth(/api/v1/oauth/*、/auth/github*)settings.github_*settings、web_sessions(绑定/解绑清空全部会话)、audit_logSecret 密文;响应与审计不回显
数据备份(/api/v1/backup*)全库快照(VACUUM INTO)settings.last_backup_at、audit_log导入候选在启动期应用,语义见第 9 节
受控重启(/api/v1/restart)无audit_logSIGTERM 优雅退出,不写业务表
MTProto 状态 / 重连(/api/v1/mtproto/*)无无会话状态机在内存
机器人池(/api/v1/bots*)data/bots.json、settings.bots_paused、内存运行态data/bots.json、settings.bots_paused、audit_logtoken 只进不出(不回显)
CSV 导出(/users/export.csv 等)users、usage_daily、requests无与对应列表接口同源;导出动作写审计

Bot 与 worker 侧的关键写入(无 HTTP 端点,补全全景):

  • /start:创建或刷新 users 的 pending 行;
  • 链接提交(准入链):单事务内检查限额并写 requests(queued 行)、累加 usage_daily、更新 users 的使用/拒绝时间;
  • worker:按阶段更新 requests 状态与终态;云盘任务写 cloud_uploads;成功投递写 dump_entries 缓存副本坐标;
  • 绑定、加入、审批等管理动作与系统事件:写 audit_log / events。

9. 备份、导入与版本兼容 ​

导出:VACUUM INTO 生成在线一致快照,只含业务数据库;不含 Session、peers、云盘配置、临时媒体(见第 7 节)。

导入校验(ValidateBackup):普通文件、SQLite 可打开、integrity_check=ok、user_version ≤ 当前迁移版本(高版本拒绝),且必要表存在、必要列存在:

  • 基础集:users(id,status,username,display_name)、requests(id,user_id,source_kind,channel_key,message_id,status)、usage_daily(user_id,day,used)、audit_log(id,at,action)、events(id,key,severity,message,status)、settings(key,value_json)、web_sessions(id_hash,expires_at,csrf_token);
  • 按备份版本校验的结构:v8 requests.source_media_dc_ids_json、v9 requests.media_types_json、v11 users.cloud_download、v19 watch_invite_requests.id(用于确认 v19 表存在)、v22 sent_messages.request_id(用于确认 v22 表存在)。

低版本备份导入后由当前 Store 补齐迁移;新增迁移时必须评估:新表/新列是否属于"低版本备份导入后必须存在"的兼容面,若是则扩展上述按版本校验并同步更新本节与对应测试。

应用:导入需显式确认并写入 marker,下次启动原子替换;替换时保留当前库的 settings(访问密钥、OAuth、运行配置)、清空 web_sessions、写 backup.import.applied 审计;旧库保留带时间戳的 rollback 副本。

10. 迁移历史 ​

规则:已发布迁移永不修改;新变更一律追加新版本,并同步更新本文第 2–5 节与本表。

版本变更
v1初始基线:users、requests、usage_daily、audit_log、events、settings、web_sessions 与 4 个 requests 索引
v2requests.delivery_mode(默认 upload,旧行自动归属历史语义)
v3audit_log 时间分页索引 idx_audit_at_id
v4channel_bindings 表与用户索引
v5users.bind_limit
v6join_requests、joined_channels 表与索引
v7system_metric_samples 表
v8requests.source_media_dc_ids_json
v9requests.media_types_json
v10cloud_uploads 表与索引;requests.parent_request_id、requests.cloud_destination
v11users.cloud_download
v12requests.sent_chat_id、requests.sent_message_ids_json(现为遗留列)与 idx_requests_channel_message_status
v13dump_entries 表与索引
v14system_metric_samples.cpu_percent
v15requests.bot_id、requests.bot_username;users.source_bot_id、users.source_bot_username
v16dump_entries.format_version(缓存副本布局格式版本;历史行不再命中复用)
v17watch_sources 监听源配置与申请审批表
v18重建 watch_sources 补齐类型/受理 bot 字段;新增 watch_events 与频道索引
v19watch_invite_requests 私有邀请链接监听申请表(管理员路径 user_id=0,无外键)及活动状态、用户与 hash 索引
v20requests.pin、requests.pin_ok、requests.pin_total;users.auto_pin(自动置顶标记、结果回写与用户级偏好)
v21channel_bindings.bot_id(绑定路由到 bot:仅该 bot 受理的任务投递;0 = 通配)
v22sent_messages 表与坐标/请求两索引(引用回复锚点:/pin、/cancel 回复 bot 消息反查请求;含频道副本组首坐标供事后补置顶)
v23requests.error_detail(失败根因原始错误串落库,管理端详情页展示;重试/重置随 error_code 清空)
v24dump_entries.dump_channel_id(副本所在缓存频道,0 = 存量永不命中);channel_bindings 软解绑状态机(status/unbind_reason/unbound_at,解绑不删行)
v25error_logs 错误日志中心表与四索引(来源/错误码/请求/时间);cloud_uploads.error_detail(单文件上传失败根因,对称 requests v23)