Files
ledgerly-docs/dev/data-model.md
T
rosercode d2e4a51f2b feat: 补充图片记账、交易渠道管理与前端偏好文档
- 新增「交易渠道管理」指南与「前端用户偏好」开发文档,并加入侧边栏导航
- 图片记账:补充多图上传、实时识别进度、结果勾选导入与「来源图片」回看,AI 配置新增视觉模型说明
- 数据模型补充 t_channel、t_ai_config/t_ai_image 等表与「类型+金额+时间」去重键说明,表数更新为 21 张
- API 文档补充 /api/channel、/api/transaction/batch、/api/ai/record/image(/stream)、/api/ai/image、/api/ai/channel 等接口
- 截图脚本新增渠道页、图片记账、来源图片与渠道 AI 辅助截图,并放宽导航超时
2026-09-21 22:19:55 +08:00

162 lines
8.7 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.
# 数据模型
系统共 **21 张表**,覆盖用户、记账、渠道、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。
## 表清单
| 业务域 | 表名 | 说明 |
|---|---|---|
| 用户 | `t_user` | 用户表(用户名、BCrypt 密码、昵称) |
| 用户 | `t_user_preference` | 每用户多条的用户偏好(键值) |
| 记账 | `t_category` | 收支分类(收入/支出,含图标与排序) |
| 记账 | `t_transaction` | 收支记录(核心表) |
| 记账 | `t_channel` | 用户自定义交易渠道(内置渠道由枚举维护) |
| 标签 | `t_tag` | 标签 |
| 标签 | `t_transaction_tag` | 交易-标签关联(多对多) |
| 规则 | `t_rule` | 自定义归类规则 |
| 规则 | `t_rule_tag` | 规则-标签关联(多对多) |
| AI | `t_ai_config` | 每用户一条的 AI 配置(含视觉模型) |
| AI | `t_ai_usage` | AI 调用用量记录 |
| AI | `t_ai_image` | 图片记账上传的截图及其解析摘要 |
| AI | `t_ai_image_transaction` | 图片与解析生成交易的关联 |
| 通知 | `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_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 |
> 交易记录通过 `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_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)`;每笔记录按模型标注的来源图分别建立关联。
> **去重键**:图片记账与批量导入使用「收支类型 + 金额 + 交易时间(到秒)」作为业务去重键(`TransactionDedupUtils`),用于标记疑似重复并在导入时跳过,弥补这类记录没有外部交易号的不足。
### t_user_preference
按用户存储的键值偏好:`pref_key` 为偏好键(如 `billSourceIncludeInStats`、`excludedDisplayStyle`),`pref_value` 为标量字符串或 JSON。唯一索引 `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` 初始化默认分类、标签与邮箱发件人规则。