Files
ledgerly-docs/dev/data-model.md
T
rosercode 41d1a4d5a9 docs: 补充管理页批量操作说明并同步规则/AI 规则文档
为规则、分类、标签管理页新增的「批量操作模式」与多选删除补充使用与开发文档,同时补上此前已完成但未同步的 AI 规则建议可编辑、编辑后实时重算去重等文档,并登记批量模式截图脚本。

变更内容:
- guide/categories-tags.md:补充分类与标签的批量删除说明,并回填 tags-batch.png、categories-batch.png 配图。
- guide/rules.md:批量删除与应用匹配账单改为需先进入「批量操作」模式(默认不显示勾选框)。
- dev/api.md:新增 DELETE /api/tag/batch、/api/category/batch,并补齐规则命中账单预览/一键应用、AI 规则去重校验等接口。
- dev/data-model.md:说明标签删除清理交易标签与规则标签关联、分类删除保留逻辑引用(账单显示未分类)。
- dev/rule-engine.md、dev/ai.md、dev/preferences.md、guide/ai.md、guide/transactions.md:同步 AI 规则建议可编辑、编辑后实时重算去重、应用开关默认开启并持久化等说明。
- scripts/ts/capture-elements.ts:新增 captureBatchModes()(标签/分类批量模式截图)并纳入 main()。

说明:
- 截图经测试环境(前端 5174 / 后端 8081,虚构数据)生成,已上传 OSS,正文以 OSS 长期地址引用;public/screenshots 目录不入库。
2026-10-14 01:07:46 +08:00

11 KiB
Raw Blame History

数据模型

系统共 24 张表,覆盖用户、记账、账单关系、渠道、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 id,表间关系均为逻辑外键(通过 user_id、category_id 等字段在应用层维护,未声明物理外键)。

表清单

业务域 表名 说明
用户 t_user 用户表(用户名、BCrypt 密码、昵称)
用户 t_user_preference 每用户多条的用户偏好(键值)
记账 t_category 收支分类(收入/支出,含图标与排序)
记账 t_transaction 收支记录(核心表)
记账 t_channel 用户自定义交易渠道(内置渠道由枚举维护)
标签 t_tag 标签
标签 t_transaction_tag 交易-标签关联(多对多)
账单关系 t_transaction_group 账单关系组(父子/同级)
账单关系 t_transaction_group_item 关系组成员(角色与顺序)
规则 t_rule 自定义归类规则
规则 t_rule_tag 规则-标签关联(多对多)
AI t_ai_config 每用户一条的 AI 配置(含视觉模型)
AI t_ai_usage AI 调用用量记录
AI t_ai_image 图片记账上传的截图及其解析摘要
AI t_ai_image_transaction 图片与解析生成交易的关联
AI t_ai_sql_history 智能查数提问历史(每用户多条)
通知 t_notification_config 每用户一条的提醒配置
通知 t_notification_channel 通知渠道
通知 t_notification_log 通知发送日志
邮箱账单 t_email_bill_config IMAP 邮箱配置
邮箱账单 t_email_bill_sender_rule 发件人规则
邮箱账单 t_email_bill_message 拉取到的账单消息
日志 t_http_log 接口访问日志
日志 t_login_log 登录日志

关系概览

以用户为中心(一对多)

主体 关系 对象 关联字段
t_user 1 : N t_category user_id
t_user 1 : N t_transaction user_id
t_user 1 : N t_tag user_id
t_user 1 : N t_channel user_id
t_user 1 : N t_transaction_group user_id
t_user 1 : N t_rule user_id
t_user 1 : N t_ai_image user_id
t_user 1 : N t_notification_channel user_id
t_user 1 : N t_email_bill_config user_id
t_user 1 : N t_email_bill_sender_rule user_id
t_user 1 : N t_email_bill_message user_id
t_user 1 : N t_user_preference user_id
t_user 1 : N t_ai_usage / t_http_log / t_login_log / t_notification_log user_id

一对一(每用户一条)

主体 关系 对象 关联字段
t_user 1 : 1 t_ai_config user_id(唯一)
t_user 1 : 1 t_notification_config user_id(唯一)

记账与标签

主体 关系 对象 关联字段 / 中间表
t_category 1 : N t_transaction transaction.category_id(可空=未分类)
t_transaction N : M t_tag 中间表 t_transaction_tag(联合唯一)
t_category 1 : N t_rule rule.category_id(命中目标分类)
t_rule N : M t_tag 中间表 t_rule_tag(联合唯一)
t_ai_image 1 : N t_ai_image_transaction image_id
t_transaction 1 : N t_ai_image_transaction transaction_id
t_transaction_group 1 : N t_transaction_group_item group_id(联合唯一 group_id + transaction_id)
t_transaction 1 : N t_transaction_group_item transaction_id

交易记录通过 channel 编码关联渠道:内置渠道编码取自 ChannelType 枚举,自定义渠道编码取自 t_channel.code(用户内唯一),因此渠道改名不影响历史记录。

通知与邮箱账单

主体 关系 对象 关联字段
t_notification_channel 1 : N t_notification_log channel_id(可空)
t_email_bill_config 1 : N t_email_bill_message config_id
t_email_bill_sender_rule 1 : N t_email_bill_message sender_rule_id(可空)

邮箱配置与发件人规则的默认分类通过 expense_category_id / income_category_id 逻辑引用 t_category;默认标签以 JSON 数组字符串保存(未用关联表)。

核心表字段

t_transaction(收支记录)

字段 说明
user_id 所属用户
category_id 分类(可空,未分类)
type INCOME / EXPENSE
amount 金额(DECIMAL(12,2))
channel 交易渠道(如 ALIPAY / WECHAT / CASH / BANK)
remark 备注
remark_hidden 备注是否隐藏
external_no 外部账单交易号,导入去重
transaction_time 交易发生时间
include_in_stats 是否计入统计(默认 1)
raw_data 原始字段 JSON(账单导入的原始账单行,或图片记账由模型提取的原始字段;手动记录为空)

关键索引:唯一索引 uk_user_external (user_id, external_no) 实现导入去重;idx_user_time (user_id, transaction_time) 支撑按时间查询与统计。

t_category / t_tag

  • 分类:name、type、icon(Emoji)、sort_order。
  • 标签:name、color、sort_order,唯一索引 uk_user_name (user_id, name) 保证用户内标签名唯一。
  • 删除标签时会一并清理 t_transaction_tag 与 t_rule_tag 中的关联记录(单条与批量删除行为一致);删除分类仅移除分类本身,账单与规则上的 category_id 作为逻辑外键保留(账单会显示为「未分类」)。

t_rule

name、enabled、sort_order(优先级)、type、keyword、min_amount、max_amount、category_id。

t_transaction_group / t_transaction_group_item(账单关系)

  • t_transaction_group:user_id、type(PARENT_CHILD 父子 / SIBLING 同级)、name、remark;索引 user_id。
  • t_transaction_group_item:group_id、transaction_id、role(PARENT / CHILD / MEMBER)、sort_order(同级链路的顺序);唯一索引 (group_id, transaction_id) 防止同一账单在同一组内重复,另有 transaction_id 索引。
  • 约束:一个关系组至少两条成员、上限 50 条;父子关系必须且只能有一条 PARENT;一笔账单不能同时加入两个同类型的关系组。删除账单时会自动清理其成员记录,成员不足两条或父子组缺父时整组删除(仅解除关联,不删除账单)。详见账单关系(使用说明)。

t_channel(自定义交易渠道)

字段 说明
user_id 所属用户
code 渠道编码,后端生成(CUSTOM_ + 随机串),稳定不变,交易记录按此关联
name 渠道名称,用户可修改
sort_order 排序序号,数值越小越靠前

唯一索引 uk_channel_user_code (user_id, code)、uk_channel_user_name (user_id, name) 分别保证用户内编码与名称唯一。仅存放用户自建渠道,内置渠道由 ChannelType 枚举维护。

t_ai_config / t_ai_image

  • t_ai_config:每用户一条,含 enabled、api_key、base_url、model 与 vision_model(图片记账使用的视觉模型,默认 deepseek-flash)。
  • t_ai_image:图片记账上传的截图,一次上传可产生多条(最多 5 张),含 user_id、file_path(相对应用运行目录,落盘于 data/ai-images/)、mime_type、file_size、width、height、model(解析所用视觉模型)、summary(识别摘要)、record_count(该图识别出的记录数)。
  • t_ai_image_transaction:图片与交易记录的关联,含 image_id、transaction_id,唯一索引 uk_ai_image_transaction (image_id, transaction_id);每笔记录按模型标注的来源图分别建立关联。
  • t_ai_sql_history:智能查数(自然语言转 SQL)的提问历史,含 user_id、query_text(截断 500 字符)、created_at;索引 (user_id, created_at)。

去重键:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(TransactionDedupUtils),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。

t_user_preference

按用户存储的键值偏好:pref_key 为偏好键,pref_value 为标量字符串或 JSON。已使用的偏好键包括:billSourceIncludeInStats(各来源计入统计默认值)、excludedDisplayStyle(不计入记录展示样式)、showHiddenData(是否显示已隐藏备注的记录)、chartClickNavigation(统计面板图表点击是否跳转明细)、paginationPrefs(表格分页候选与默认值)、navMenu(左侧导航顺序与显隐)。唯一索引 uk_user_pref (user_id, pref_key) 保证每个用户每个偏好仅一条。未配置时由应用回退系统默认值。

t_email_bill_config

IMAP 连接信息(host/port/use_ssl/username/password/folder)、状态(status/last_error/last_connected_at/last_uid)、默认归类(expense_category_id/income_category_id/tag_ids)与拉取范围(fetch_scope/fetch_limit)。

t_email_bill_message

config_id、uid、message_id、subject、from_address、received_at、附件信息、命中的 sender_rule_id 与 bill_type、状态(PULLED/CONFIRMED/IGNORED)、error。唯一索引 uk_email_bill_message (config_id, uid, attachment_index) 防止重复拉取。

MySQL 与 SQLite 双脚本

系统同时维护 sql/init.sql(MySQL)与 sql/schema-sqlite.sql(SQLite),表结构与语义保持一致,主要差异:

维度 MySQL SQLite
主键自增 BIGINT AUTO_INCREMENT INTEGER PRIMARY KEY AUTOINCREMENT
布尔 TINYINT(1) INTEGER
金额 DECIMAL(12,2) NUMERIC(12,2)
JSON JSON TEXT
时间默认值 DEFAULT CURRENT_TIMESTAMP strftime(...,'localtime')
updated_at 自动更新 ON UPDATE CURRENT_TIMESTAMP AFTER UPDATE 触发器
语句分隔符 ; ;;(兼容触发器内部的分号)
列注释 支持 不支持

统计模块会依据当前数据源类型,切换 MySQL 与 SQLite 的时间分组函数(DATE_FORMAT/QUARTER 与 strftime)。

初始化与迁移

  • 应用启动时由 SchemaInitializer 执行对应建表脚本(幂等)。
  • 建表脚本只做 CREATE TABLE IF NOT EXISTS,无法修改已有库的列;需要就地变更列名或新增列时由 SchemaMigrator 在启动/切换数据源时幂等迁移(如 t_transaction.payment_method → channel 改名、t_ai_config.vision_model 加列;MySQL 用 information_schema、SQLite 用 PRAGMA table_info 检测列是否存在)。
  • 新用户注册时由 DefaultDataInitializer 初始化默认分类、标签与邮箱发件人规则。