chore: 初始化文档站仓库
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# AI 集成
|
||||
|
||||
系统集成 **DeepSeek** 大模型,用于分类打标、自然语言查询与记账等场景,并完整记录调用用量。
|
||||
|
||||
## 客户端
|
||||
|
||||
`DeepSeekClient` 基于 **OpenAI 官方 Java SDK**(`com.openai:openai-java`)调用,通过自定义 `baseUrl` 指向 DeepSeek 的 OpenAI 兼容接口:
|
||||
|
||||
- 使用 `OpenAIOkHttpClient`,按 `(baseUrl, apiKey)` 缓存客户端(上限 32,超出后清空重建)。
|
||||
- `temperature = 0.2`,`response_format = {"type":"json_object"}`,非流式,`maxRetries(0)`,请求超时 60 秒。
|
||||
- 取 `choices[0].message.content` 与 `usage`(输入/输出/总 token;缓存命中 token 取 `promptTokensDetails.cachedTokens`,DeepSeek 私有字段读不到时为 0)。
|
||||
- 对外保持 `chat(baseUrl, apiKey, model, systemPrompt, userPrompt)` 与 `DeepSeekResponse` 不变,各 `Ai*ServiceImpl` 无需改动。
|
||||
|
||||
配置存储于 `t_ai_config`(每用户一条):`enabled`、`api_key`、`base_url`(默认 `https://api.deepseek.com`)、`model`(默认 `deepseek-chat`)。API Key 对外脱敏,支持连通性测试。
|
||||
|
||||
## 数据外发范围(隐私)
|
||||
|
||||
调用大模型时**仅发送功能所需的最小字段**,禁止外发敏感数据:
|
||||
|
||||
- **禁止**:`rawData`(账单原始 JSON)、金额、交易号、账户/卡号、姓名、地址、电话等。
|
||||
- **允许**:收支类型、交易渠道、备注(必要时截断/脱敏)、分类与标签的名称/ID、用户主动输入的自然语言文本。
|
||||
|
||||
## 调用类型
|
||||
|
||||
`AiCallType` 定义了 9 类调用,全部记录用量:
|
||||
|
||||
| 枚举值 | 含义 |
|
||||
|---|---|
|
||||
| `SUGGEST` | 交易分类/标签建议(批量 + 单条) |
|
||||
| `TEST` | 连接测试 |
|
||||
| `QUERY` | 自然语言查询解析为筛选条件 |
|
||||
| `RECORD` | 自然语言记账解析为交易记录 |
|
||||
| `DECORATE` | 分类 Emoji / 标签颜色建议 |
|
||||
| `CATEGORY` | 自然语言生成分类 |
|
||||
| `TAG` | 自然语言生成标签 |
|
||||
| `RULE` | 由已打标签记录生成分类与标签规则 |
|
||||
| `STATISTICS` | 自然语言统计查询解析 |
|
||||
|
||||
## 分类/标签建议
|
||||
|
||||
`AiSuggestionServiceImpl`:
|
||||
|
||||
- 先按「类型 + 交易渠道 + 备注」聚合去重,仅将代表记录送模型;单批 ≤ 100 条、总量 ≤ 500 条,超出则分批调用。
|
||||
- 支持严格模式(仅从已有分类/标签中选择)与增强模式(可返回新分类/新标签)。
|
||||
- 应用时分类覆盖写入、标签追加;新建分类按「类型 + 名称」去重,新建标签按名称去重并校验颜色格式。
|
||||
|
||||
## 自然语言查询
|
||||
|
||||
`AiQueryServiceImpl` 在提示词中注入当前分类、标签与当前日期,解析出:类型、交易渠道、分类名、标签名、日期范围预设(今天/昨天/本周/上周/本月/上月/近7天/近30天/今年/去年/自定义/无)、金额区间、备注、是否计入统计、是否未分类等,并在服务端把预设换算为绝对时间。
|
||||
|
||||
## 自然语言记账
|
||||
|
||||
`AiRecordServiceImpl` 解析类型、金额、分类、标签、交易渠道、交易时间、备注、计入统计等字段;金额无效直接报错,时间缺失回退当前时间。
|
||||
|
||||
## 规则生成
|
||||
|
||||
`AiRuleServiceImpl` 由用户勾选的**已打标签记录**反推分类/标签规则:
|
||||
|
||||
- 仅加载属于当前用户且被勾选的记录(单次 ≤ 500 条),附带其标签名称与分类名;提示词字段为 `type / remark / channel / category / tags`(不含金额与 `rawData`),并对相同组合去重。
|
||||
- 解析并校验模型输出(分类 ID 归属与类型、标签归属、名称长度、颜色格式等)。
|
||||
- **去重**:按「类型 + 归一化关键词 + 分类 + 排序标签集合」计算签名;与已有规则或本次建议内部重复的条目标记 `duplicate=true`(前端默认不勾选且禁止导入),`apply` 时后端再次校验并跳过。
|
||||
|
||||
## 统计搜索
|
||||
|
||||
`AiStatisticsServiceImpl` 在提示词中注入分类、标签与当前日期,将自然语言解析为统计面板参数:时间范围(预设→绝对时间)、收支类型、分类、标签、交易渠道、金额区间、备注、统计口径(`statsScope`)与趋势粒度(`dimension`)。
|
||||
|
||||
## 用量记录
|
||||
|
||||
- 每次调用写入 `t_ai_usage`:调用类型、模型、分析条数、输入/输出/总/缓存 token、是否成功、错误信息、耗时。
|
||||
- 记录失败仅告警,不影响主流程。
|
||||
- `GET /api/ai/usage` 返回汇总与最近明细,对应「模型使用情况」页面。
|
||||
|
||||
## 说明
|
||||
|
||||
账单类型识别**不使用 AI**,而是由邮箱账单的**发件人规则**确定,结果确定、可预期。详见[定时通知](/dev/notifications)与[邮箱账单](/guide/email-bills)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [AI 能力(使用说明)](/guide/ai)
|
||||
- [整体架构](/dev/architecture)
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# API 文档
|
||||
|
||||
系统使用 **Knife4j(OpenAPI 3)** 生成接口文档,后端启动后可直接访问。
|
||||
|
||||
## 访问入口
|
||||
|
||||
| 入口 | 地址 | 说明 |
|
||||
|---|---|---|
|
||||
| Knife4j UI | `http://<host>:<port>/doc.html` | 增强版接口文档界面 |
|
||||
| OpenAPI JSON | `http://<host>:<port>/v3/api-docs` | 标准 OpenAPI 描述 |
|
||||
| Swagger UI | `http://<host>:<port>/swagger-ui/index.html` | 原生界面 |
|
||||
|
||||
开发模式下默认端口为 8080,例如 http://localhost:8080/doc.html。
|
||||
|
||||
## 认证
|
||||
|
||||
- 接口统一使用 **JWT Bearer** 鉴权,登录接口返回 token 后,在请求头携带:
|
||||
|
||||
```text
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
- Knife4j 配置中已声明全局 `Bearer` 安全方案,可在文档界面直接填入 token 调试。
|
||||
- 无需鉴权的接口:`/api/user/login`、`/api/user/register`、`/api/user/registration-status`、`/api/database/status`。
|
||||
- 日志实时推送等 SSE 接口因 `EventSource` 无法自定义请求头,兼容通过 `?token=` 查询参数鉴权。
|
||||
|
||||
## 接口分组
|
||||
|
||||
| 控制器 | 路径前缀 | 说明 |
|
||||
|---|---|---|
|
||||
| `UserController` | `/api/user` | 登录、注册、用户信息、注册开关 |
|
||||
| `TransactionController` | `/api/transaction` | 收支记录增删改查、批量操作、导出、交易渠道列表 |
|
||||
| `BillImportController` | `/api/transaction/import` | 账单解析与确认导入 |
|
||||
| `CategoryController` | `/api/category` | 分类管理 |
|
||||
| `TagController` | `/api/tag` | 标签管理 |
|
||||
| `RuleController` | `/api/rule` | 自定义规则(列表、分页查询、增删改、启停) |
|
||||
| `StatisticsController` | `/api/statistics` | 统计汇总、分类、趋势、分组 |
|
||||
| `AiController` | `/api/ai` | AI 建议、查询、记账、规则生成、统计搜索、用量 |
|
||||
| `NotificationController` | `/api/notification` | 通知配置、渠道、发送日志 |
|
||||
| `EmailBillController` | `/api/email-bill` | 邮箱配置、发件人规则、待处理账单 |
|
||||
| `LogController` | `/api/log` | 访问日志、登录日志、运行日志与 SSE |
|
||||
| `DatabaseController` | `/api/database` | 数据库状态、测试与配置 |
|
||||
| `AppConfigController` | `/api/app` | 应用配置(通知链接、日志保留) |
|
||||
| `UserSettingController` | `/api/setting` | 用户偏好(账单来源统计默认值、不计入统计展示样式) |
|
||||
|
||||
## 关键接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/api/transaction/channels` | 获取系统支持的交易渠道编码列表 |
|
||||
| GET | `/api/rule/page` | 分页查询规则(`keyword` / `enabled` / `type`) |
|
||||
| POST | `/api/ai/rule/suggest` | 依据所选已打标签记录生成规则建议(含 `duplicate` 标记) |
|
||||
| POST | `/api/ai/rule/apply` | 批量创建规则(自动去重、按需新建分类/标签) |
|
||||
| POST | `/api/ai/statistics` | 自然语言解析为统计面板参数 |
|
||||
| GET / PUT | `/api/app/bill-source-include-in-stats` | 查询 / 更新按账单来源的「计入统计」默认值 |
|
||||
|
||||
## 统一响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
- `code` 非 200 表示业务失败,`message` 为错误信息。
|
||||
- 分页接口的 `data` 为 `PageResult`(`records` / `total` 等)。
|
||||
- 数据库未配置时返回业务码 `5031`,前端据此跳转初始化引导页。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [整体架构](/dev/architecture)
|
||||
- [项目结构](/dev/project-structure)
|
||||
@@ -0,0 +1,105 @@
|
||||
# 整体架构
|
||||
|
||||
本章介绍系统的分层结构与关键技术设计。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 层级 | 技术 |
|
||||
|---|---|
|
||||
| 后端 | Spring Boot 3.3.6 + Java 21 + MyBatis-Plus 3.5.12 |
|
||||
| 数据库 | SQLite(本地单文件)/ MySQL |
|
||||
| 前端 | Vue 3 + TypeScript + Vite + Naive UI + ECharts + Pinia + vue-i18n |
|
||||
| 认证 | JWT(HMAC) |
|
||||
| 对象映射 | MapStruct 1.6.3 |
|
||||
| 账单解析 | EasyExcel(xlsx)/ Apache POI(xls)+ commons-csv + PDFBox + Zip4j |
|
||||
| AI | DeepSeek(OpenAI Java SDK) |
|
||||
| 通知 | Jakarta Mail(SMTP / IMAP)+ Server 酱 |
|
||||
| API 文档 | Knife4j(OpenAPI 3) |
|
||||
|
||||
## 后端分层
|
||||
|
||||
```text
|
||||
controller 接口层,统一返回 Result<T>,经 UserContext 取当前用户
|
||||
│
|
||||
service 业务层(接口 + impl),事务、跨表校验、聚合
|
||||
│
|
||||
mapper MyBatis-Plus BaseMapper,少量自定义 SQL
|
||||
│
|
||||
entity 数据库表映射实体
|
||||
```
|
||||
|
||||
辅助包:
|
||||
|
||||
| 包 | 职责 |
|
||||
|---|---|
|
||||
| `dto` / `vo` | 入参请求对象 / 出参视图对象 |
|
||||
| `converter` | MapStruct 实体↔VO 转换(`VoConverter`) |
|
||||
| `importer` | 账单解析器(`BillParser` 及实现、`BillArchiveExtractor`) |
|
||||
| `exporter` | Excel 导出样式处理 |
|
||||
| `notification` | 通知渠道发送器(工厂 + 各渠道实现) |
|
||||
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
|
||||
| `interceptor` | JWT 鉴权、访问日志 |
|
||||
| `config` | Web、JWT、Knife4j、MyBatis-Plus、动态数据源、SPA 路由回退 |
|
||||
| `client` | DeepSeek 客户端 |
|
||||
| `email` | IMAP 邮箱账单抓取(监听、拉取、解析、存储) |
|
||||
| `common` | `Result`、`PageResult`、业务异常、全局异常处理 |
|
||||
| `util` | `JwtUtil`、`UserContext`、`AppLogUtils` 等 |
|
||||
| `enums` | `BillType`、`AiCallType`、`StatisticsDimension` 等 |
|
||||
|
||||
## 请求处理流程
|
||||
|
||||
```text
|
||||
浏览器
|
||||
│ /api/**
|
||||
▼
|
||||
AccessLogInterceptor 记录访问日志(方法/路径/耗时/状态/IP/用户)
|
||||
│
|
||||
JwtInterceptor 校验 Bearer Token(兼容 ?token= 供 SSE),写入 userId
|
||||
│
|
||||
Controller UserContext.getUserId()
|
||||
│
|
||||
Service → Mapper → 数据库(动态数据源)
|
||||
│
|
||||
GlobalExceptionHandler 统一异常 → Result{code,message,data}
|
||||
```
|
||||
|
||||
- 登录、注册、注册状态、数据库状态等接口无需鉴权。
|
||||
- 数据库未配置时,`UnconfiguredDataSource` 抛出的异常会转为业务码 `5031`,前端据此跳转初始化引导页。
|
||||
|
||||
## 认证与鉴权
|
||||
|
||||
- `JwtUtil` 使用 HMAC 密钥签发 JWT,`subject` 为用户 ID,附带用户名 claim,默认有效期 24 小时。
|
||||
- `JwtInterceptor` 从 `Authorization: Bearer <token>` 解析;由于 `EventSource` 无法自定义请求头,同时兼容 `?token=` 查询参数(用于日志实时推送等 SSE 场景)。
|
||||
- `UserContext` 基于 `RequestContextHolder` 读取拦截器写入的 `userId`,使控制器无需声明 `HttpServletRequest`。
|
||||
|
||||
## 前端结构
|
||||
|
||||
```text
|
||||
src/
|
||||
├── api/ 各模块 REST 接口封装(axios,统一携带 Token、处理 401/业务码)
|
||||
├── views/ 页面组件(Dashboard、TransactionList、BillImport 等)
|
||||
├── components/ 复用组件(账单预览、导入结果、AI 配置、通知配置等)
|
||||
├── router/ 路由与导航守卫(数据库配置校验、登录校验)
|
||||
├── store/ Pinia 状态(user / database / registration)
|
||||
├── locales/ 中/英/日三语文案
|
||||
├── composables/ 组合式函数(关键词历史)
|
||||
└── utils/ 工具(交易渠道标签、SSE 封装等)
|
||||
```
|
||||
|
||||
## 关键设计亮点
|
||||
|
||||
1. **运行时动态数据源**:启动无需数据库,配置后热切换,见[动态数据源](/dev/database)。
|
||||
2. **账单两阶段导入**:解析预览 → 确认导入,按交易号去重,见[账单解析](/dev/bill-parsing)。
|
||||
3. **规则引擎**:分类取最高优先级、标签取并集,见[规则引擎](/dev/rule-engine)。
|
||||
4. **体系化 AI 调用**:9 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
|
||||
5. **SSE 实时能力**:运行日志、邮箱检查进度实时推送。
|
||||
6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [数据模型](/dev/data-model)
|
||||
- [账单解析](/dev/bill-parsing)
|
||||
- [规则引擎](/dev/rule-engine)
|
||||
- [AI 集成](/dev/ai)
|
||||
- [定时通知](/dev/notifications)
|
||||
- [动态数据源](/dev/database)
|
||||
@@ -0,0 +1,89 @@
|
||||
# 账单字段说明
|
||||
|
||||
本章说明支付宝「交易记录明细查询」导出的 CSV 在导入本系统时,各字段的读取与丢弃情况,可作为开发新解析器或调整字段映射的参考。
|
||||
|
||||
## 样例与实现位置
|
||||
|
||||
- 编码:GBK(解析器兼容 UTF-8 / UTF-8 BOM)
|
||||
- 解析器:`src/main/java/cn/rosercode/finance/importer/AlipayBillParser.java`
|
||||
- 落库服务:`src/main/java/cn/rosercode/finance/service/impl/BillImportServiceImpl.java`
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 位置 | 内容 | 是否处理 |
|
||||
|---|---|---|
|
||||
| 第 1 行 | `支付宝交易记录明细查询` | 忽略 |
|
||||
| 第 2 行 | `账号:[...]` | 忽略 |
|
||||
| 第 3 行 | `起始日期:[...] 终止日期:[...]` | 忽略 |
|
||||
| 第 4 行 | `---交易记录明细列表---` | 忽略 |
|
||||
| 第 5 行 | 表头(以「交易号」开头) | 作为列定位依据 |
|
||||
| 数据行 | 每行一笔交易 | 逐行解析 |
|
||||
| 结尾统计 | 待收入/已支出/待支出、导出时间、用户 | 忽略 |
|
||||
|
||||
解析器通过「以 `交易号` 开头」定位表头行;遇到空行、`---` 分隔线或 `共` 开头的统计行即结束。
|
||||
|
||||
## CSV 完整表头(16 列)
|
||||
|
||||
| 序号 | 列名 | 序号 | 列名 |
|
||||
|---|---|---|---|
|
||||
| 1 | 交易号 | 9 | 商品名称 |
|
||||
| 2 | 商家订单号 | 10 | 金额(元) |
|
||||
| 3 | 交易创建时间 | 11 | 收/支 |
|
||||
| 4 | 付款时间 | 12 | 交易状态 |
|
||||
| 5 | 最近修改时间 | 13 | 服务费(元) |
|
||||
| 6 | 交易来源地 | 14 | 成功退款(元) |
|
||||
| 7 | 类型 | 15 | 备注 |
|
||||
| 8 | 交易对方 | 16 | 资金状态 |
|
||||
|
||||
## 完全丢弃的字段(7 个)
|
||||
|
||||
| 列名 | 说明 |
|
||||
|---|---|
|
||||
| 商家订单号 | 未使用 |
|
||||
| 最近修改时间 | 未使用 |
|
||||
| 交易来源地 | 未使用 |
|
||||
| 类型 | 未使用,改用「收/支」推断类型 |
|
||||
| 服务费(元) | 未使用 |
|
||||
| 成功退款(元) | 未使用 |
|
||||
| 备注 | 支付宝自带备注被丢弃;系统备注由「交易对方 · 商品名称」拼接生成 |
|
||||
|
||||
## 读取但未落库的字段(3 个)
|
||||
|
||||
| 列名 | 用途 |
|
||||
|---|---|
|
||||
| 交易状态 | 仅用于类型推断与预览展示 |
|
||||
| 资金状态 | 仅用于「不计收支」行的类型推断 |
|
||||
| 交易创建时间 | 仅当「付款时间」为空时作为交易时间兜底 |
|
||||
|
||||
## 落库映射(写入 `t_transaction`)
|
||||
|
||||
| CSV 列 | 数据库字段 | 说明 |
|
||||
|---|---|---|
|
||||
| 交易号 | `external_no` | 导入去重键 |
|
||||
| 付款时间(缺失时用交易创建时间) | `transaction_time` | 交易发生时间 |
|
||||
| 金额(元) | `amount` | 去除千分位逗号后解析 |
|
||||
| 收/支 | `type` + `include_in_stats` | 「不计收支」默认 `include_in_stats = 0` |
|
||||
| 交易对方 + 商品名称 | `remark` | 拼接为「交易对方 · 商品名称」,截断至 255 字符 |
|
||||
| — | `channel` | 固定为 `ALIPAY` |
|
||||
| — | `category_id` | 取用户选择的默认分类;未选择则为 `NULL` |
|
||||
|
||||
### 类型推断规则
|
||||
|
||||
| 收/支 | 交易状态 / 资金状态 | 推断类型 |
|
||||
|---|---|---|
|
||||
| 支出 | — | `EXPENSE` |
|
||||
| 收入 | — | `INCOME` |
|
||||
| 不计收支 | 交易状态含「退款」 | `INCOME` |
|
||||
| 不计收支 | 资金状态为「已收入」 | `INCOME` |
|
||||
| 不计收支 | 其他(如「交易关闭」) | `EXPENSE` |
|
||||
|
||||
## 可能的后续增强
|
||||
|
||||
1. 将支付宝「备注」并入系统 `remark`(当前完全丢失)。
|
||||
2. 新增字段记录「服务费」/「成功退款」。
|
||||
3. 保留「商家订单号」作为辅助去重或展示。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [账单解析](/dev/bill-parsing)
|
||||
- [支付宝账单(使用文档)](/guide/bills/alipay)
|
||||
@@ -0,0 +1,67 @@
|
||||
# 账单解析
|
||||
|
||||
账单导入采用「**解析预览 → 确认导入**」两阶段流程,解析阶段不落库,用户确认后才写入。
|
||||
|
||||
## 解析器设计
|
||||
|
||||
解析器采用策略模式:`BillParser` 接口声明 `getBillType()` 与 `parse(byte[])`,各实现类对应一种账单类型。服务层构造时用 `EnumMap<BillType, BillParser>` 注册所有实现,按类型分发,便于扩展新来源。
|
||||
|
||||
| 解析器 | 格式 | 表头锚点 | 去重键来源 |
|
||||
|---|---|---|---|
|
||||
| `AlipayBillParser` | CSV(UTF-8/GBK) | 「交易号」 | 交易号 |
|
||||
| `WechatBillParser` | xlsx(POI) | 「交易时间」 | 交易单号 |
|
||||
| `MeituanBillParser` | CSV | 「交易创建时间」 | 交易单号 |
|
||||
| `TaobaoBillParser` | xlsx(POI) | 「订单号」 | 订单号(按订单号聚合多商品行) |
|
||||
| `JdBillParser` | CSV(UTF-8/GBK) | 「交易时间」 | 交易订单号 |
|
||||
| `CmbBillParser` | CSV | 「交易日期」 | 生成 `CMB_日期_类型_金额_余额` |
|
||||
| `CmbPdfParser` | PDF(PDFBox) | 「记账日期」 | 同上(与 CSV 互通) |
|
||||
| `BocomBillParser` | xls(POI) | 「记账日期」 | 生成 `BOCOM_日期_时分秒_类型_金额` |
|
||||
|
||||
各解析器统一输出 `ParsedBillRow`:行号、`externalNo`、交易时间、类型、金额、交易对方、商品、交易状态、原始收/支方向、是否计入统计、是否可导入、是否重复、命中规则与建议分类/标签、`rawData`(保留原始列的 JSON)、错误信息。
|
||||
|
||||
### 解析要点
|
||||
|
||||
- **编码识别**:CSV 自动识别 UTF-8 / UTF-8 BOM / GBK。
|
||||
- **表头定位**:以特定表头文字定位数据区,忽略元信息与统计行。
|
||||
- **银行 PDF**:按字符坐标切列、按纵坐标合并折行还原表格;用带符号金额判断收支;按「联机余额」做连续性校验(误差 0.005),不连续的行标记为不可导入。
|
||||
- **时间兼容**:微信账单兼容 Excel 日期序列号与文本时间;支付宝在付款时间缺失时回退交易创建时间;淘宝订单同样兼容文本时间与 Excel 日期序列号。
|
||||
- **订单聚合**:淘宝订单数据中多商品订单的订单级列(订单号、提交时间、状态、店铺、实付金额、运费)以合并单元格呈现,仅首个商品行有值;解析器按订单号分组聚合,每个订单输出一条记录,金额优先取订单级实付金额,为空时回退「Σ(商品金额×数量)+运费」。
|
||||
- **不计收支处理**:京东流水的「不计收支」行(白条还款、小金库转入、全额退款等)标记为可导入但**不计入统计且默认不勾选**,类型按「交易说明含『退款』→ INCOME,否则 EXPENSE」推断。
|
||||
- **计入统计默认值**:解析完成后按**账单来源**应用「计入统计」默认值(配置于系统设置,见 [动态数据源](/dev/database) 同级的应用配置)。采用「解析器判定 && 来源默认」语义——来源默认不计入时整批置为不计入;来源默认计入时保留解析器对「不计收支/中性」行的排除。确认导入时若请求未显式提供该字段,同样以来源默认兜底。
|
||||
|
||||
## 压缩包解密
|
||||
|
||||
`BillArchiveExtractor` 处理 zip 压缩包:
|
||||
|
||||
- 按魔数 `PK\x03\x04` 判断是否为 zip,并区分「xlsx 本身是 zip」与「账单压缩包」。
|
||||
- 支持 ZipCrypto 与 AES 加密;未提供密码返回错误码 `4001`,密码错误返回 `4002`。
|
||||
- 限制条目数与总大小,防止 zip bomb;解析完成后清理临时文件。
|
||||
|
||||
## 两阶段流程
|
||||
|
||||
### parse(不落库)
|
||||
|
||||
1. 按账单类型分发到对应解析器;若为 zip 则先解密提取。
|
||||
2. 批量查询已存在的 `externalNo`,标记重复行并取消勾选。
|
||||
3. 调用规则引擎批量匹配,生成建议分类与标签(批量查询分类与标签,避免 N+1)。
|
||||
|
||||
### confirm(落库,事务)
|
||||
|
||||
1. 校验默认分类归属与类型,过滤默认标签。
|
||||
2. 再次批量运行规则匹配。
|
||||
3. 逐条校验类型、时间、金额;按 `externalNo` 去重(含本批次内去重)。
|
||||
4. **规则建议分类优先于面板默认分类**;标签取「默认标签 ∪ 规则标签」并去重。
|
||||
5. 批量插入(每 500 条一批),批次失败时回退逐条插入;标签关联在批量插入未回填主键时按 `externalNo` 回查 ID。
|
||||
6. 返回成功 / 跳过 / 失败统计。
|
||||
|
||||
## 去重策略
|
||||
|
||||
- 统一使用「用户 + `externalNo`」唯一约束 `uk_user_external` 去重,`external_no` 为 NULL 的手动记录不受影响。
|
||||
- 有交易号的账单直接使用交易号。
|
||||
- 银行流水无交易号,则用「日期 + 类型 + 金额 + 余额(或时分秒)」生成稳定键,保证同一账单重复导入不会重复;招商银行 CSV 与 PDF 生成规则一致,可混用。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [账单类型与获取](/guide/bills/overview)
|
||||
- [账单导入(使用说明)](/guide/transactions)
|
||||
- [规则引擎](/dev/rule-engine)
|
||||
@@ -0,0 +1,96 @@
|
||||
# 构建与部署
|
||||
|
||||
## 前置条件
|
||||
|
||||
- **JDK 21**(Temurin 21 LTS)
|
||||
- **Node.js**(构建前端时需要)
|
||||
|
||||
## 开发模式
|
||||
|
||||
前后端分离,便于调试:
|
||||
|
||||
```bash
|
||||
# 后端(默认 8080)
|
||||
cd finance-app
|
||||
set JAVA_HOME=D:\venv\jdk-21
|
||||
gradlew.bat bootRun
|
||||
|
||||
# 前端(默认 5173,接口代理到 8080)
|
||||
cd finance-app/frontend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
- 后端地址:http://localhost:8080
|
||||
- 后端 API 文档:http://localhost:8080/doc.html
|
||||
- 前端地址:http://localhost:5173,接口经 Vite 代理到后端 8080
|
||||
|
||||
## 打包可执行 jar(前后端一体化)
|
||||
|
||||
`bootJar` 会先执行前端 `npm run build`,再把 `frontend/dist` 复制到 jar 内的 `BOOT-INF/classes/static/`,单文件同时提供页面与接口。
|
||||
|
||||
```bash
|
||||
gradle bootJar
|
||||
# 或使用宿主机脚本
|
||||
./package.sh
|
||||
```
|
||||
|
||||
输出:`build/libs/finance-app-0.0.1-SNAPSHOT.jar`
|
||||
|
||||
启动(默认 8080,可另指定端口):
|
||||
|
||||
```bash
|
||||
java -jar build/libs/finance-app-0.0.1-SNAPSHOT.jar --server.port=18081
|
||||
```
|
||||
|
||||
访问对应端口即可;刷新 `/dashboard` 等子路由由后端 `SpaErrorController` 回退到 `index.html`。
|
||||
|
||||
::: tip 提示
|
||||
jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。
|
||||
:::
|
||||
|
||||
构建任务链(`build.gradle`):
|
||||
|
||||
- `npmInstall`:安装前端依赖(`node_modules` 存在且 `package.json` 未变化时跳过)
|
||||
- `frontendBuild`:执行 `npm run build`
|
||||
- `bootJar`:依赖 `frontendBuild`,并将前端产物打入静态资源目录
|
||||
|
||||
## Docker 部署
|
||||
|
||||
`run/docker-compose.yml` 定义前后端两个服务,纯复用本地镜像、不构建新镜像:
|
||||
|
||||
| 服务 | 镜像 | 端口 | 说明 |
|
||||
|---|---|---|---|
|
||||
| backend | `eclipse-temurin:21-jre` | 18080→8080 | 挂载 `run/backend/*.jar`,持久化 `config/`、`data/`、`logs/` |
|
||||
| front | `nginx:alpine` | 15180→80 | 挂载 `run/front/dist` 与 Nginx 配置 |
|
||||
|
||||
环境变量 `DB_URL` / `DB_USERNAME` / `DB_PASSWORD` 仅作为首次启动的默认配置来源,也可在界面中完成数据库配置。
|
||||
|
||||
## 桌面 GUI 启动器
|
||||
|
||||
`launcher` 包提供系统托盘 + 控制窗口的桌面启动器(启动/停止服务、打开浏览器、查看日志),通过 `bootGuiJar` 打包:
|
||||
|
||||
```bash
|
||||
gradle bootGuiJar
|
||||
```
|
||||
|
||||
- 入口类:`cn.rosercode.finance.launcher.LauncherMain`
|
||||
- 依赖 JavaFX 与 dorkbox SystemTray,仅加入带 GUI 的 jar(`guiRuntime` 配置),普通 jar 不含这些依赖
|
||||
- 平台默认 Windows(可通过 `-PjavafxPlatform=linux` 本地验证)
|
||||
- JavaFX 与 JNA 原生库需 `requiresUnpack` 从嵌套 jar 解压
|
||||
|
||||
> 注意:该启动器为规划中的能力,具体实现以当前源码为准。
|
||||
|
||||
## 分发形态
|
||||
|
||||
| 形态 | 说明 |
|
||||
|---|---|
|
||||
| jar | `bootJar`,需 JDK 21 运行 |
|
||||
| jar(带 GUI) | `bootGuiJar`,内含桌面启动器 |
|
||||
| 可执行文件(控制台 / GUI) | 规划中,计划采用 `jpackage`(JDK 自带,各平台分别构建) |
|
||||
| Docker 镜像 | 通过 `run/docker-compose.yml` 编排 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [项目结构](/dev/project-structure)
|
||||
- [开发环境与规范](/dev/development)
|
||||
- [快速开始(使用文档)](/guide/getting-started)
|
||||
@@ -0,0 +1,133 @@
|
||||
# 数据模型
|
||||
|
||||
系统共 **18 张表**,覆盖用户、记账、标签、规则、AI、通知、邮箱账单与日志审计。所有表主键为自增 `id`,表间关系均为**逻辑外键**(通过 `user_id`、`category_id` 等字段在应用层维护,未声明物理外键)。
|
||||
|
||||
## 表清单
|
||||
|
||||
| 业务域 | 表名 | 说明 |
|
||||
|---|---|---|
|
||||
| 用户 | `t_user` | 用户表(用户名、BCrypt 密码、昵称) |
|
||||
| 用户 | `t_user_preference` | 每用户多条的用户偏好(键值) |
|
||||
| 记账 | `t_category` | 收支分类(收入/支出,含图标与排序) |
|
||||
| 记账 | `t_transaction` | 收支记录(核心表) |
|
||||
| 标签 | `t_tag` | 标签 |
|
||||
| 标签 | `t_transaction_tag` | 交易-标签关联(多对多) |
|
||||
| 规则 | `t_rule` | 自定义归类规则 |
|
||||
| 规则 | `t_rule_tag` | 规则-标签关联(多对多) |
|
||||
| AI | `t_ai_config` | 每用户一条的 AI 配置 |
|
||||
| AI | `t_ai_usage` | AI 调用用量记录 |
|
||||
| 通知 | `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_rule | 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_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_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`,MySQL 用 `information_schema`、SQLite 用 `PRAGMA table_info` 检测)。
|
||||
- 新用户注册时由 `DefaultDataInitializer` 初始化默认分类、标签与邮箱发件人规则。
|
||||
@@ -0,0 +1,55 @@
|
||||
# 动态数据源
|
||||
|
||||
系统支持在服务启动**之后**再配置数据库,并可在 SQLite 与 MySQL 之间运行时切换,无需启动前准备。
|
||||
|
||||
## 设计目标
|
||||
|
||||
- 未配置数据库时应用仍能正常启动,前端引导进入初始化页面。
|
||||
- 支持 SQLite(本地单文件)与 MySQL 两种数据库。
|
||||
- 保存配置后立即生效并自动建表,无需手动执行 SQL。
|
||||
|
||||
## 核心组件
|
||||
|
||||
| 组件 | 职责 |
|
||||
|---|---|
|
||||
| `DynamicDataSource` | 继承 `AbstractRoutingDataSource`,维护目标数据源映射,切换时重建解析缓存并关闭旧连接池 |
|
||||
| `DataSourceContextHolder` | 保存当前数据源 key 与类型;未配置时使用 `UNCONFIGURED` 占位 |
|
||||
| `UnconfiguredDataSource` | 占位数据源,任何取连接操作抛「数据库未配置」(业务码 5031) |
|
||||
| `DatabaseConfig` / `DatabaseConfigStore` | 配置模型与持久化(`config/database.json`) |
|
||||
| `SchemaInitializer` | 按类型执行 `init.sql`(MySQL)或 `schema-sqlite.sql`(SQLite),幂等建表 |
|
||||
| `SchemaMigrator` | 建表后的幂等结构迁移(如列重命名),启动与切换数据源时执行 |
|
||||
| `DatabaseBootstrap` | 应用启动后加载配置并初始化 |
|
||||
|
||||
`FinanceApplication` 刻意排除了 `DataSourceAutoConfiguration`,以便「零配置也能启动」。
|
||||
|
||||
## 配置来源与顺序
|
||||
|
||||
1. 优先读取本地 `config/database.json`。
|
||||
2. 若无本地配置但存在环境变量 `DB_URL` / `DB_USERNAME` / `DB_PASSWORD`,则作为首次配置并保存。
|
||||
|
||||
保存流程为:**创建并测试新数据源 → 建表 → 结构迁移 → 先落盘再切换**,确保切换失败时可回退。
|
||||
|
||||
## 连接池策略
|
||||
|
||||
- SQLite:连接池固定为 1,避免文件锁冲突。
|
||||
- MySQL:最大 10、最小 2。
|
||||
|
||||
MySQL 的 JDBC URL 会自动附带 UTF-8、关闭 SSL、`Asia/Shanghai` 时区与 `createDatabaseIfNotExist=true`。
|
||||
|
||||
## 跨库方言适配
|
||||
|
||||
统计模块依据 `DataSourceContextHolder.getType()` 选择时间分组函数:
|
||||
|
||||
- MySQL:`DATE_FORMAT` / `QUARTER`
|
||||
- SQLite:`strftime`
|
||||
|
||||
## 切换行为
|
||||
|
||||
登录后可在「系统设置 → 数据库配置」随时切换数据库。切换后原数据库的数据保留,**不做自动迁移**。
|
||||
|
||||
切换成功后会清空进程内的用户偏好缓存(见[整体架构](/dev/architecture)),避免读到旧库的数据。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [快速开始](/guide/getting-started)
|
||||
- [数据模型](/dev/data-model)
|
||||
@@ -0,0 +1,58 @@
|
||||
# 开发环境与规范
|
||||
|
||||
本章记录本项目的开发约定,供开发者与 AI 助手参考。
|
||||
|
||||
## AI 测试环境
|
||||
|
||||
为避免污染真实数据,AI 访问网页、截图、操作界面或验证前端功能时,必须使用**测试环境入口**:
|
||||
|
||||
| 环境 | 前端 | 后端 | 数据库 |
|
||||
|---|---|---|---|
|
||||
| 测试环境 | 5174 | 8081 | 独立 SQLite 空库(`ai-home/data/ai-finance.db`) |
|
||||
| 真实环境 | 5173 | 8080 | MySQL(真实数据) |
|
||||
|
||||
- 两个环境共用同一份源码:前端改动经 Vite HMR 两边同步;后端改动执行 `./reload.sh` 后两边 devtools 同时热重启。
|
||||
- 启动脚本:`run/ai-dev/start-ai-backend.sh`(8081)、`run/ai-dev/start-ai-frontend.sh`(5174)。
|
||||
- `ai-home/`、`run/ai-dev/` 为本地专用,不提交仓库。
|
||||
|
||||
## 前端开发
|
||||
|
||||
- 前端改动**不使用 `npm run build` 验证**,直接依赖 Vite 开发服务器的 HMR。
|
||||
- `npm run build`(`vue-tsc -b && vite build`)的产物仅用于生产部署,与开发模式无关。
|
||||
- 界面文案统一在 `frontend/src/locales/messages.ts` 维护,新增 key 需同时提供中文(zh)、英文(en)、日文(ja)三语。
|
||||
|
||||
## 后端开发
|
||||
|
||||
- 后端以开发模式运行在容器中(Gradle `bootRun`,JDK 21,`--network host`)。
|
||||
- 修改 Java 代码后执行 `./reload.sh`(容器内增量编译),`spring-boot-devtools` 会在 1–3 秒内热重启。
|
||||
- 修改 `src/main/resources` 下的资源文件后,需额外同步资源:
|
||||
|
||||
```bash
|
||||
docker exec finance-backend-dev gradle -I /project/nexus-init.gradle processResources
|
||||
```
|
||||
|
||||
- 仅在新增依赖、修改配置等需要彻底重启时才重启容器。
|
||||
- 数据库支持运行时配置与切换(SQLite / MySQL),切换数据库或新增 JDBC 驱动后需重启容器。
|
||||
|
||||
## 代码规范
|
||||
|
||||
- **Javadoc**:新增 Java 文件的类级 javadoc 末尾必须加一行 `@date`,格式 `yyyy-MM-dd HH:mm:ss`,时间取文件创建时间。
|
||||
- **导入顺序**:由 Spotless 管理,三组、组间空行分隔:`java`/`javax` → 第三方 → `cn.rosercode`。
|
||||
- 格式化:`docker exec finance-backend-dev gradle -I /project/nexus-init.gradle spotlessApply`
|
||||
- `spotlessCheck` 已挂到 `check`,`build` 时会校验。
|
||||
- **日志**:统一使用 Lombok `@Slf4j`,按级别使用(`error` 未预期异常、`warn` 可预期业务失败、`info` 关键写操作与外部调用、`debug` 流程细节)。
|
||||
- **控制器**:方法签名禁止声明 `HttpServletRequest` / `HttpServletResponse`,统一使用 `cn.rosercode.finance.util.UserContext`。
|
||||
- **数据库表结构变更**:需同时维护 `sql/init.sql`(MySQL)与 `sql/schema-sqlite.sql`(SQLite),SQLite 以 `;;` 分隔语句。
|
||||
- **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。
|
||||
- **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。
|
||||
|
||||
## Git 提交规范
|
||||
|
||||
- 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。
|
||||
- commit message 使用 `feat: 中文描述` 风格;纯重构/规范调整用 `refactor:`,构建与依赖配置用 `chore:` 或 `build:`。
|
||||
- 远端为本地 Gitea,主分支 `master`;推送使用一次性内嵌令牌的 URL,避免令牌写入 `origin` 配置。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [项目结构](/dev/project-structure)
|
||||
- [构建与部署](/dev/build-deploy)
|
||||
@@ -0,0 +1,23 @@
|
||||
# 开发文档
|
||||
|
||||
本章面向**开发者**,介绍系统的分层架构、关键设计、工程结构与开发规范。
|
||||
|
||||
## 设计与实现
|
||||
|
||||
- [整体架构](/dev/architecture):技术栈、后端分层、请求流程、认证与前端结构
|
||||
- [数据模型](/dev/data-model):17 张表的字段与关系、双数据库脚本差异
|
||||
- [账单解析](/dev/bill-parsing):解析器策略模式、两阶段导入、去重与 zip 解密
|
||||
- [规则引擎](/dev/rule-engine):规则模型与匹配逻辑
|
||||
- [AI 集成](/dev/ai):OpenAI SDK 客户端、9 类调用类型与用量记录
|
||||
- [定时通知](/dev/notifications):调度、渠道与发送日志
|
||||
- [动态数据源](/dev/database):SQLite / MySQL 运行时配置与切换
|
||||
|
||||
## 工程与规范
|
||||
|
||||
- [项目结构](/dev/project-structure):目录组织与各包职责
|
||||
- [构建与部署](/dev/build-deploy):`bootJar`、Docker 编排、桌面 GUI 启动器与 jpackage
|
||||
- [开发环境与规范](/dev/development):AI 测试环境、前后端开发流程、代码规范、Git 提交规范
|
||||
- [账单字段说明](/dev/bill-fields):支付宝账单导入字段的读取与丢弃情况
|
||||
- [API 文档](/dev/api):Knife4j(OpenAPI 3)接口文档
|
||||
|
||||
> 使用说明请查看[使用文档](/guide/)。
|
||||
@@ -0,0 +1,40 @@
|
||||
# 定时通知
|
||||
|
||||
系统提供账单导入定时提醒,支持多渠道推送,并记录发送日志。
|
||||
|
||||
## 调度
|
||||
|
||||
- `NotificationSchedulingConfig` 开启 `@EnableScheduling`。
|
||||
- `NotificationScheduler` 每分钟检查一次(初始延迟 30 秒):对每个启用的通知配置判断「当前时间是否已到达触发点」(依据周期、提醒时间与星期/日期),到达且存在启用渠道时向所有渠道发送提醒,并更新 `last_sent_at`。
|
||||
- 默认提醒周期为每月 1 日 09:00。
|
||||
- 首次启用时会将 `last_sent_at` 置为当前,避免启用后立即补发历史提醒。
|
||||
|
||||
## 渠道
|
||||
|
||||
`NotificationChannelType` 定义支持的渠道:
|
||||
|
||||
| 渠道 | 说明 |
|
||||
|---|---|
|
||||
| `EMAIL` | 邮箱 SMTP(如 QQ 邮箱),动态构建 `JavaMailSenderImpl`,默认 `smtp.qq.com:465` + SSL |
|
||||
| `SERVERCHAN` | Server 酱 Turbo,推送到个人微信,校验返回 `code == 0` |
|
||||
|
||||
发送器采用工厂模式:`NotificationSender` 接口 + `NotificationSenderFactory` 按类型分发,`AbstractHttpNotificationSender` 封装 JSON/表单 POST。
|
||||
|
||||
## 配置与日志
|
||||
|
||||
- `t_notification_config`:启用开关、周期、每月日期、每周星期、提醒时间、最近发送时间。
|
||||
- `t_notification_channel`:类型、名称、启用、配置 JSON(SMTP 账号或 SendKey)。邮箱密码留空表示不修改,返回时脱敏。
|
||||
- `t_notification_log`:每次发送的结果(成功/失败、渠道类型、说明),面板展示最近 20 条。
|
||||
|
||||
## 接口
|
||||
|
||||
`NotificationController` 提供:配置读写、渠道增删改查与测试、立即发送、发送日志查询。
|
||||
|
||||
## 通知内容
|
||||
|
||||
提醒正文会提示导入新账单,并附带 `AppConfig.notificationUrl` 配置的链接地址(通常指向账单导入页)。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [定时提醒与通知(使用说明)](/guide/notifications)
|
||||
- [邮箱账单](/guide/email-bills)
|
||||
@@ -0,0 +1,93 @@
|
||||
# 项目结构
|
||||
|
||||
## 仓库根目录
|
||||
|
||||
```text
|
||||
finance-app/
|
||||
├── build.gradle # Gradle 构建脚本(依赖、打包、前端集成、Spotless)
|
||||
├── settings.gradle # Gradle 项目设置
|
||||
├── gradlew / gradlew.bat # Gradle Wrapper
|
||||
├── gradle/wrapper/ # Wrapper 配置与 jar
|
||||
├── .gitignore
|
||||
├── README.md # 功能特性与技术栈概览
|
||||
├── src/main/java/ # 后端源码
|
||||
├── src/main/resources/ # 配置、MyBatis XML、建表脚本
|
||||
└── frontend/ # Vue 3 前端
|
||||
```
|
||||
|
||||
> 以上为纳入版本管理的目录与文件;本地开发用的运行编排、脚本与文档(如 `run/`、`reload.sh`、`docs/` 等)不在版本管理中,故不在此列出。
|
||||
|
||||
## 后端包结构
|
||||
|
||||
`src/main/java/cn/rosercode/finance/`
|
||||
|
||||
| 包 | 职责 |
|
||||
|---|---|
|
||||
| `client` | 外部服务客户端(DeepSeek) |
|
||||
| `common` | 通用响应(`Result`、`PageResult`)、业务异常与全局异常处理 |
|
||||
| `config` | 应用配置、动态数据源、Knife4j、MyBatis-Plus、SPA 路由回退 |
|
||||
| `controller` | REST 接口层 |
|
||||
| `converter` | MapStruct 对象转换器(`VoConverter`) |
|
||||
| `dto` | 请求对象 |
|
||||
| `email` | IMAP 邮箱账单抓取(监听、拉取、解析、存储) |
|
||||
| `entity` | 数据库表映射实体 |
|
||||
| `enums` | 枚举(`BillType`、`AiCallType`、`StatisticsDimension` 等) |
|
||||
| `exporter` | Excel 导出处理 |
|
||||
| `importer` | 账单解析器(支付宝/微信/美团/淘宝/京东/银行)与压缩包解密 |
|
||||
| `interceptor` | JWT 鉴权、访问日志 |
|
||||
| `mapper` | 数据访问层(MyBatis-Plus) |
|
||||
| `notification` | 通知渠道发送器 |
|
||||
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
|
||||
| `service` / `service.impl` | 业务层 |
|
||||
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils` 等) |
|
||||
| `vo` | 响应对象 |
|
||||
| `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) |
|
||||
|
||||
## 资源目录
|
||||
|
||||
`src/main/resources/`
|
||||
|
||||
```text
|
||||
application.yml # 应用配置(JWT、MyBatis-Plus、springdoc、端口等)
|
||||
logback-spring.xml # 日志配置(控制台 + 按天滚动文件)
|
||||
mapper/ # MyBatis XML
|
||||
sql/
|
||||
├── init.sql # MySQL 建表脚本
|
||||
├── schema-sqlite.sql # SQLite 建表脚本(以 ;; 分隔语句)
|
||||
├── init_categories.sql # 默认分类种子(MySQL)
|
||||
└── init_tags.sql # 默认标签种子(MySQL)
|
||||
```
|
||||
|
||||
## 前端结构
|
||||
|
||||
`frontend/src/`
|
||||
|
||||
```text
|
||||
App.vue # 应用根组件
|
||||
main.ts # 入口(挂载应用与插件)
|
||||
style.css # 全局样式
|
||||
api/ # 各模块 REST 接口封装(axios)
|
||||
components/ # 复用组件(账单预览、导入结果、AI/通知/数据库配置面板等)
|
||||
locales/ # 中/英/日三语文案(messages.ts)
|
||||
router/ # 路由与导航守卫
|
||||
store/ # Pinia 状态(user / database / registration)
|
||||
utils/ # 工具(交易渠道标签、SSE 封装、渲染辅助)
|
||||
views/ # 页面组件
|
||||
```
|
||||
|
||||
## 运行时目录
|
||||
|
||||
应用运行目录下会自动创建:
|
||||
|
||||
| 目录/文件 | 说明 |
|
||||
|---|---|
|
||||
| `config/database.json` | 数据库配置(SQLite / MySQL) |
|
||||
| `config/app.json` | 应用配置(注册开关、通知链接、日志保留天数、按账单来源的计入统计默认值) |
|
||||
| `data/` | SQLite 数据文件与邮箱账单附件 |
|
||||
| `logs/` | 运行日志文件 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [整体架构](/dev/architecture)
|
||||
- [构建与部署](/dev/build-deploy)
|
||||
- [开发环境与规范](/dev/development)
|
||||
@@ -0,0 +1,53 @@
|
||||
# 规则引擎
|
||||
|
||||
规则引擎用于在账单导入与新建记录时,按条件自动应用分类与标签。
|
||||
|
||||
## 数据模型
|
||||
|
||||
- `t_rule`:`name`、`enabled`、`sort_order`(优先级,越小越优先)、`type`(限定收支类型,可空)、`keyword`(关键词)、`min_amount` / `max_amount`(金额区间)、`category_id`(目标分类)。
|
||||
- `t_rule_tag`:规则与标签的多对多关联。
|
||||
|
||||
## 匹配逻辑
|
||||
|
||||
`RuleServiceImpl.matchBatch` 的核心流程:
|
||||
|
||||
1. 一次性加载当前用户**已启用**的规则,按 `sort_order`、`id` 升序排序;预加载规则标签与引用分类。
|
||||
2. 拼接匹配文本 = `交易对方 + 商品名称 + 备注`,统一转小写。
|
||||
3. 逐条判断是否命中:
|
||||
- **类型**:规则指定了 type 且与记录类型不一致则跳过。
|
||||
- **关键词**:包含匹配(忽略大小写)。
|
||||
- **金额区间**:`min_amount ≤ amount ≤ max_amount`(含边界,任一为空表示不限制)。
|
||||
4. 命中后:
|
||||
- **分类**:取首个命中且与记录类型一致的规则分类。
|
||||
- **标签**:取所有命中规则标签的并集(去重)。
|
||||
5. 返回结果与入参一一对应。
|
||||
|
||||
## 应用位置
|
||||
|
||||
| 场景 | 行为 |
|
||||
|---|---|
|
||||
| 账单解析预览 | 展示命中的规则名与建议分类/标签 |
|
||||
| 账单确认导入 | 规则建议分类优先于面板默认分类;标签与默认标签合并去重 |
|
||||
| 新建/编辑记录 | 不自动应用,但可借助 AI 推荐 |
|
||||
|
||||
## 列表查询与分页
|
||||
|
||||
- `GET /api/rule/list`:返回当前用户全部规则(按优先级排序),供内部复用。
|
||||
- `GET /api/rule/page`:分页查询,支持 `keyword`(匹配规则名称或关键词)、`enabled`、`type` 筛选,返回 `PageResult<RuleVO>`。
|
||||
|
||||
## AI 生成规则
|
||||
|
||||
`AiRuleServiceImpl` 依据用户勾选的**已打标签记录**生成规则建议:
|
||||
|
||||
- 输入字段为记录的类型、备注、交易渠道、已有分类与标签(不含金额与原始账单数据)。
|
||||
- 输出规则经 `RuleService.create` 落库,必要时新建分类/标签,规则默认启用。
|
||||
- **去重**:以「类型 + 归一化关键词 + 分类 + 排序标签集合」为签名;与已有规则或同批已创建规则重复时跳过(`apply` 返回实际创建数),建议阶段则标记 `duplicate` 供前端禁止勾选。
|
||||
|
||||
## 校验
|
||||
|
||||
`RuleServiceImpl.validate` 会校验:`type` 白名单、金额下限不大于上限、目标分类归属当前用户且类型一致。保存规则标签时采用全量替换。
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [规则管理(使用说明)](/guide/rules)
|
||||
- [账单解析](/dev/bill-parsing)
|
||||
Reference in New Issue
Block a user