Files
ledgerly-docs/dev/data-model.md
T
rosercode ca1b8c05d5 docs: 补充统计面板图表点击跳转开关说明
统计面板图表的点击下钻交互新增了可关闭开关(默认开启),可在统计面板筛选区或设置页「外观设置」关闭;关闭后点击趋势柱/饼图不再跳转,并隐藏点击提示与饼图手型光标。本次同步更新使用与开发文档,并重截外观设置面板配图。

变更内容:
- `guide/dashboard.md`:筛选条件小节与「点击图表查看明细」小节补充开关说明(两处入口、关闭效果与实时同步)。
- `guide/settings.md`:外观设置由三组改为四组,新增「图表点击查看明细」并对其余分组重新编号;配图 `elements/panel-display.png` 重截并上传 OSS。
- `dev/preferences.md`:后端按用户同步偏好总览表加行,并新增「图表点击跳转明细」小节(接口、默认值、存储键、共享 composable 与消费入口)。
- `dev/api.md`:新增 `GET/PUT /api/setting/chart-click-navigation` 接口。
- `dev/data-model.md`:`t_user_preference` 已用偏好键补充 `chartClickNavigation`。
2026-10-13 16:02:49 +08:00

175 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据模型
系统共 **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_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`;一笔账单不能同时加入两个**同类型**的关系组。删除账单时会自动清理其成员记录,成员不足两条或父子组缺父时整组删除(仅解除关联,不删除账单)。详见[账单关系(使用说明)](/guide/relations)。
### 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` 初始化默认分类、标签与邮箱发件人规则。