docs: 补充接口加解密、代码检查与安全配置等开发文档
同步 ledgerly 近期开发改动(接口请求/响应体加解密、代码检查工具、安全与稳定性修复、AI 提示词模板抽取),补齐对应开发文档并修正过时表述,同时在快速开始补充小屏适配说明。 变更内容: - dev/api.md:新增「接口数据加密」小节,说明 RSA + AES-GCM 握手流程、信封格式、会话有效期与失效码、豁免范围及编译期开关。 - dev/architecture.md:请求处理流程补入加密会话拦截器与请求/响应体 Advice,关键设计亮点新增接口数据加密一条。 - dev/development.md:新增「代码检查」小节,登记 Spotless / Checkstyle / SpotBugs 与前端 ESLint / Prettier 配置及运行命令。 - dev/build-deploy.md:新增「安全配置」(JWT_SECRET 覆盖、CORS 放行来源),构建任务链补入前端 lint 校验说明。 - dev/ai.md:修正已重命名的提示词模板名,补充「提示词模板」片段机制与图片记账 SSE 超时、心跳说明。 - dev/project-structure.md:补充 config.crypto / util.crypto 包、prompts 资源目录、crypto-keys.json 运行时文件与交易服务拆分说明。 - guide/getting-started.md:新增「小屏与移动端适配」说明。 - guide/transactions.md:导出说明补充单次 10000 条上限。
This commit is contained in:
@@ -24,6 +24,13 @@
|
|||||||
- **智能查数**:仅发送用户提问与可查询的**表结构说明**,不发送任何账单数据行;数据查询与隔离在本地由安全网关完成。
|
- **智能查数**:仅发送用户提问与可查询的**表结构说明**,不发送任何账单数据行;数据查询与隔离在本地由安全网关完成。
|
||||||
- **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。
|
- **图片记账例外**:用户主动上传的账单/支付截图会**整张**发送给视觉模型(可能含金额、交易对方、订单号等),视为用户主动提供的输入;图片同时落盘于 `data/ai-images/`,仅本人可经 `GET /api/ai/image/{id}` 访问,禁止用于其他外发场景。
|
||||||
|
|
||||||
|
## 提示词模板
|
||||||
|
|
||||||
|
各功能的提示词统一抽取为 **Thymeleaf 文本模板**,存放于 `src/main/resources/prompts/`(`spring.thymeleaf` 配置 `prefix=classpath:/prompts/`、`suffix=.tpl`、`mode=TEXT`),由 `PromptTemplateService` 加载渲染。
|
||||||
|
|
||||||
|
- 每个功能一个模板文件,如 `ai-record.tpl`、`ai-channel.tpl`、`ai-relation.tpl`、`ai-sql.tpl` 等,共 14 个。
|
||||||
|
- **系统提示词与用户提示词合并为同一文件的不同片段**,由注入的 `__fragment` 变量经 `th:if` 选择:一般功能为 `system` / `user` 两段,建议功能(`ai-suggestion.tpl`)为 `strictSystem` / `newSystem` / `user` 三段;服务端调用 `render(template, fragment, vars)` 时传入片段名与变量。
|
||||||
|
|
||||||
## 调用类型
|
## 调用类型
|
||||||
|
|
||||||
`AiCallType` 定义了 13 类调用,全部记录用量:
|
`AiCallType` 定义了 13 类调用,全部记录用量:
|
||||||
@@ -63,7 +70,7 @@
|
|||||||
|
|
||||||
## 生成交易渠道
|
## 生成交易渠道
|
||||||
|
|
||||||
`AiChannelServiceImpl` 根据用户对渠道用途的自然语言描述,使用 `ai-channel-system` 模板生成 2–6 字的渠道名称(返回 `name` 与 `reason`,名称长度上限 20),对应 `POST /api/ai/channel`。前端在新增/编辑记录的「交易渠道 · AI 新建」中使用,确认后经 `POST /api/channel` 创建并选中。
|
`AiChannelServiceImpl` 根据用户对渠道用途的自然语言描述,使用 `ai-channel` 模板(`system` 片段)生成 2–6 字的渠道名称(返回 `name` 与 `reason`,名称长度上限 20),对应 `POST /api/ai/channel`。前端在新增/编辑记录的「交易渠道 · AI 新建」中使用,确认后经 `POST /api/channel` 创建并选中。
|
||||||
|
|
||||||
## 图片记账
|
## 图片记账
|
||||||
|
|
||||||
@@ -71,12 +78,12 @@
|
|||||||
|
|
||||||
1. 校验图片数量、单张与总大小、格式(JPEG/PNG/GIF/WebP,按魔数识别),并确认 AI 已启用且配置了 API Key。
|
1. 校验图片数量、单张与总大小、格式(JPEG/PNG/GIF/WebP,按魔数识别),并确认 AI 已启用且配置了 API Key。
|
||||||
2. 逐张经 `AiImageStorage` 落盘至 `data/ai-images/{userId}/{yyyyMM}/`(经 `AppHome` 解析运行目录),每张写入一条 `t_ai_image`(记录 MIME、尺寸、所用视觉模型等)。
|
2. 逐张经 `AiImageStorage` 落盘至 `data/ai-images/{userId}/{yyyyMM}/`(经 `AppHome` 解析运行目录),每张写入一条 `t_ai_image`(记录 MIME、尺寸、所用视觉模型等)。
|
||||||
3. 以 `ai-record-image-system` 模板构造提示词(注入可用分类、标签、渠道与当前时间,要求模型对每笔输出 `imageIndex` 与 `rawData`),调用 `chatWithImages` 使用**视觉模型**一次性识别。
|
3. 以 `ai-record-image` 模板构造提示词(`system` 片段)(注入可用分类、标签、渠道与当前时间,要求模型对每笔输出 `imageIndex` 与 `rawData`),调用 `chatWithImages` 使用**视觉模型**一次性识别。
|
||||||
4. 复用 `AiRecordJsonParser` 解析 `records` 数组(与文字记账共用:去除代码围栏、匹配分类/标签、校验渠道编码、回退时间),并回填摘要与各图记录数。
|
4. 复用 `AiRecordJsonParser` 解析 `records` 数组(与文字记账共用:去除代码围栏、匹配分类/标签、校验渠道编码、回退时间),并回填摘要与各图记录数。
|
||||||
5. **重复检测**:`TransactionDedupUtils` 以「类型 + 金额 + 交易时间」为去重键,与已有记录及本批其它记录比对,命中则标记 `duplicate=true`(前端默认不勾选)。
|
5. **重复检测**:`TransactionDedupUtils` 以「类型 + 金额 + 交易时间」为去重键,与已有记录及本批其它记录比对,命中则标记 `duplicate=true`(前端默认不勾选)。
|
||||||
6. 按模型标注的 `imageIndex` 将每笔记录关联到来源图片,前端确认后经 `POST /api/transaction/batch` 批量创建(同时写入 `rawData`、建立 `t_ai_image_transaction` 关联,并在导入阶段按同一去重键兜底跳过重复)。
|
6. 按模型标注的 `imageIndex` 将每笔记录关联到来源图片,前端确认后经 `POST /api/transaction/batch` 批量创建(同时写入 `rawData`、建立 `t_ai_image_transaction` 关联,并在导入阶段按同一去重键兜底跳过重复)。
|
||||||
|
|
||||||
**实时进度**:`AiImageRecordStreamService` 基于 SSE 将上述步骤的进度文本推送前端——`POST /api/ai/record/image/stream` 返回 `SseEmitter`,识别在后台线程执行,过程中逐条推送 `progress` 事件,结束推送 `done`(完整结果 JSON)或 `failed`(失败原因)。
|
**实时进度**:`AiImageRecordStreamService` 基于 SSE 将上述步骤的进度文本推送前端——`POST /api/ai/record/image/stream` 返回 `SseEmitter`,识别在固定线程池(8 线程)中执行,过程中逐条推送 `progress` 事件,结束推送 `done`(完整结果 JSON)或 `failed`(失败原因)。连接超时 **5 分钟**,并每 **15 秒**发送一次 SSE 注释行作为心跳保活(前端 SSE 客户端自动跳过注释行)。
|
||||||
|
|
||||||
**图片访问**:`GET /api/ai/image/{id}` 返回原图,`?thumbnail=true` 返回最长边 1280 的 JPEG 缩略图;仅图片所属用户可访问。收支记录「账单详情」弹窗中的来源图片默认加载缩略图,用户可按需切换原图。
|
**图片访问**:`GET /api/ai/image/{id}` 返回原图,`?thumbnail=true` 返回最长边 1280 的 JPEG 缩略图;仅图片所属用户可访问。收支记录「账单详情」弹窗中的来源图片默认加载缩略图,用户可按需切换原图。
|
||||||
|
|
||||||
@@ -97,14 +104,14 @@
|
|||||||
`AiRelationServiceImpl` 依据用户新建关系组时勾选的**关联账单**生成名称与备注,对应 `POST /api/ai/relation`。
|
`AiRelationServiceImpl` 依据用户新建关系组时勾选的**关联账单**生成名称与备注,对应 `POST /api/ai/relation`。
|
||||||
|
|
||||||
- 请求体为 `AiRelationRequest`(`type` + `members`);每个成员仅携带白名单字段——**收支类型、交易渠道、分类名称与备注**(`AiRelationMemberRequest` 明确不含金额、交易号与 `rawData`)。
|
- 请求体为 `AiRelationRequest`(`type` + `members`);每个成员仅携带白名单字段——**收支类型、交易渠道、分类名称与备注**(`AiRelationMemberRequest` 明确不含金额、交易号与 `rawData`)。
|
||||||
- 用户提示词由服务端集中拼接(关系类型中文名 + 成员字段,备注截断 30 字,最多 20 条);系统提示词模板 `ai-relation-system` 要求输出 4–12 字名称、≤40 字备注,并说明借贷场景(借出/归还、分次归还)的判定,只输出 JSON。
|
- 用户提示词由服务端集中拼接(关系类型中文名 + 成员字段,备注截断 30 字,最多 20 条);系统提示词模板 `ai-relation`(`system` 片段)要求输出 4–12 字名称、≤40 字备注,并说明借贷场景(借出/归还、分次归还)的判定,只输出 JSON。
|
||||||
- 解析后名称截断 ≤50 且必须非空、备注 ≤200;经 `AiUsageService` 记录 `AiCallType.RELATION` 用量。前端在[账单关系](/guide/relations)编辑弹窗中用它覆盖名称(备注非空时覆盖备注)。
|
- 解析后名称截断 ≤50 且必须非空、备注 ≤200;经 `AiUsageService` 记录 `AiCallType.RELATION` 用量。前端在[账单关系](/guide/relations)编辑弹窗中用它覆盖名称(备注非空时覆盖备注)。
|
||||||
|
|
||||||
## 智能查数(Text-to-SQL)
|
## 智能查数(Text-to-SQL)
|
||||||
|
|
||||||
`AiSqlQueryService` 将自然语言交给大模型生成**只读 SQL** 并执行,配套独立的提问历史,对应 `POST /api/ai/sql`。
|
`AiSqlQueryService` 将自然语言交给大模型生成**只读 SQL** 并执行,配套独立的提问历史,对应 `POST /api/ai/sql`。
|
||||||
|
|
||||||
- 系统提示词模板 `ai-sql-system`:按当前数据源方言(SQLite / MySQL)注入日期函数说明与**动态表结构**——`TextToSqlSchemaProvider` 从数据库元数据读取列(字段增删改自动同步),并剔除 `raw_data`、`external_no` 等敏感列;要求模型只输出 `{sql, explanation, chartType}`,且**不要自带 `user_id`**(由网关统一注入)。
|
- 系统提示词模板 `ai-sql`(`system` 片段):按当前数据源方言(SQLite / MySQL)注入日期函数说明与**动态表结构**——`TextToSqlSchemaProvider` 从数据库元数据读取列(字段增删改自动同步),并剔除 `raw_data`、`external_no` 等敏感列;要求模型只输出 `{sql, explanation, chartType}`,且**不要自带 `user_id`**(由网关统一注入)。
|
||||||
- **安全网关** `cn.rosercode.ledgerly.util.sql.TextToSqlGuard` 是唯一执行通道:仅允许单条 `SELECT`(拒绝写操作/DDL/多语句/`SELECT INTO`/`FOR UPDATE`/危险函数);表白名单由 `TextToSqlTables` 静态登记(`t_transaction`、`t_category`、`t_tag`、`t_channel`、`t_transaction_group`、`t_rule` 及无 `user_id` 的关联表,新增可查表必须显式加入);对每张表强制注入 `user_id` 隔离条件(主表加 `WHERE`、JOIN 表加 `ON`、关联表用父表归属子查询);强制行数上限 `MAX_ROWS = 1000`。
|
- **安全网关** `cn.rosercode.ledgerly.util.sql.TextToSqlGuard` 是唯一执行通道:仅允许单条 `SELECT`(拒绝写操作/DDL/多语句/`SELECT INTO`/`FOR UPDATE`/危险函数);表白名单由 `TextToSqlTables` 静态登记(`t_transaction`、`t_category`、`t_tag`、`t_channel`、`t_transaction_group`、`t_rule` 及无 `user_id` 的关联表,新增可查表必须显式加入);对每张表强制注入 `user_id` 隔离条件(主表加 `WHERE`、JOIN 表加 `ON`、关联表用父表归属子查询);强制行数上限 `MAX_ROWS = 1000`。
|
||||||
- 结果 VO `AiSqlQueryResultVO` 含 `sql`、`explanation`、`chartType`(`SCALAR`/`TABLE`/`BAR`/`PIE`)、`columns`、`rows`、`rowCount`、`truncated`;列类型映射为 `NUMBER`/`STRING`/`DATE`/`DATETIME`/`BOOLEAN`。
|
- 结果 VO `AiSqlQueryResultVO` 含 `sql`、`explanation`、`chartType`(`SCALAR`/`TABLE`/`BAR`/`PIE`)、`columns`、`rows`、`rowCount`、`truncated`;列类型映射为 `NUMBER`/`STRING`/`DATE`/`DATETIME`/`BOOLEAN`。
|
||||||
- **提问历史**:`AiSqlHistoryService` 在每次成功后写入 `t_ai_sql_history`(按用户),提供 `GET /api/ai/sql/history`(limit 上限 200)与 `DELETE /api/ai/sql/history`。
|
- **提问历史**:`AiSqlHistoryService` 在每次成功后写入 `t_ai_sql_history`(按用户),提供 `GET /api/ai/sql/history`(limit 上限 200)与 `DELETE /api/ai/sql/history`。
|
||||||
|
|||||||
+11
@@ -24,6 +24,17 @@
|
|||||||
- 无需鉴权的接口:`/api/user/login`、`/api/user/register`、`/api/user/registration-status`、`/api/database/status`。
|
- 无需鉴权的接口:`/api/user/login`、`/api/user/register`、`/api/user/registration-status`、`/api/database/status`。
|
||||||
- 日志实时推送等 SSE 接口因 `EventSource` 无法自定义请求头,兼容通过 `?token=` 查询参数鉴权。
|
- 日志实时推送等 SSE 接口因 `EventSource` 无法自定义请求头,兼容通过 `?token=` 查询参数鉴权。
|
||||||
|
|
||||||
|
## 接口数据加密
|
||||||
|
|
||||||
|
后端支持对 `/api/**` 的**请求体与响应体**做端到端加密(RSA + AES-GCM),浏览器登录后自动完成握手,对使用者透明。该能力为**机会式**:未携带加密会话头的请求按明文处理,因此 Knife4j 等工具仍可直接明文调试接口。
|
||||||
|
|
||||||
|
- **握手**:前端先 `GET /api/crypto/public-key` 获取 RSA 公钥(返回 `{enabled, keyId, algorithm, publicKey}`),本地生成 32 字节 AES 密钥并用 **RSA-OAEP(SHA-256)** 加密,再 `POST /api/crypto/handshake`(body `{keyId, key}`)换取 `sessionId`;此后请求统一携带 `X-Crypto-Session: <sessionId>` 头。
|
||||||
|
- **算法**:**AES-256-GCM**(IV 12 字节、认证标签 128 位),GCM 的 AAD 绑定 `sessionId` 以防跨会话重放;AES 会话密钥仅存后端内存,RSA 密钥对在首次启动时自动生成并持久化到运行目录 `config/crypto-keys.json`。
|
||||||
|
- **信封格式**:加解密对象为 JSON 信封 `{"enc":"A256GCM","iv":"<Base64>","data":"<Base64 密文+标签>"}`。
|
||||||
|
- **会话**:有效期 2 小时、上限 2000 个;失效返回业务码 `4201`,前端自动重新握手并重试一次。
|
||||||
|
- **豁免范围**:SSE 流式接口、multipart 文件上传、二进制下载/图片(导出、`/api/ai/image/**`)与握手接口本身不做加解密;`/api/crypto/**` 无需鉴权。
|
||||||
|
- **开关**:由编译期常量 `CryptoConstants.API_ENCRYPTION_ENABLED` 控制;关闭后 `public-key` 返回 `enabled:false`,前端自动回退明文。
|
||||||
|
|
||||||
## 接口分组
|
## 接口分组
|
||||||
|
|
||||||
| 控制器 | 路径前缀 | 说明 |
|
| 控制器 | 路径前缀 | 说明 |
|
||||||
|
|||||||
+6
-1
@@ -50,10 +50,12 @@ entity 数据库表映射实体
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
浏览器
|
浏览器
|
||||||
│ /api/**
|
│ /api/**(可携带加密会话头 X-Crypto-Session)
|
||||||
▼
|
▼
|
||||||
AccessLogInterceptor 记录访问日志(方法/路径/耗时/状态/IP/用户)
|
AccessLogInterceptor 记录访问日志(方法/路径/耗时/状态/IP/用户)
|
||||||
│
|
│
|
||||||
|
CryptoSessionInterceptor 解析加密会话(无会话头则按明文处理)
|
||||||
|
│
|
||||||
JwtInterceptor 校验 Bearer Token(兼容 ?token= 供 SSE),写入 userId
|
JwtInterceptor 校验 Bearer Token(兼容 ?token= 供 SSE),写入 userId
|
||||||
│
|
│
|
||||||
Controller UserContext.getUserId()
|
Controller UserContext.getUserId()
|
||||||
@@ -63,6 +65,8 @@ Service → Mapper → 数据库(动态数据源)
|
|||||||
GlobalExceptionHandler 统一异常 → Result{code,message,data}
|
GlobalExceptionHandler 统一异常 → Result{code,message,data}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **请求/响应体加解密**:`ApiCryptoRequestBodyAdvice` 在参数绑定前解密请求体,`ApiCryptoResponseBodyAdvice` 在写出前加密 `Result` 响应体,二者均依赖 `CryptoSessionInterceptor` 写入的会话。详见 [API 文档 · 接口数据加密](/dev/api#接口数据加密)。
|
||||||
|
|
||||||
- 登录、注册、注册状态、数据库状态等接口无需鉴权。
|
- 登录、注册、注册状态、数据库状态等接口无需鉴权。
|
||||||
- 数据库未配置时,`UnconfiguredDataSource` 抛出的异常会转为业务码 `5031`,前端据此跳转初始化引导页。
|
- 数据库未配置时,`UnconfiguredDataSource` 抛出的异常会转为业务码 `5031`,前端据此跳转初始化引导页。
|
||||||
|
|
||||||
@@ -94,6 +98,7 @@ src/
|
|||||||
4. **体系化 AI 调用**:13 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
|
4. **体系化 AI 调用**:13 类调用类型并记录 token 用量,见 [AI 集成](/dev/ai)。
|
||||||
5. **SSE 实时能力**:运行日志、邮箱检查进度、图片记账识别进度与数据库配置执行进度实时推送。
|
5. **SSE 实时能力**:运行日志、邮箱检查进度、图片记账识别进度与数据库配置执行进度实时推送。
|
||||||
6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。
|
6. **用户偏好缓存**:`t_user_preference` 的读取走 Spring Cache 进程内缓存(`ConcurrentMapCacheManager`),写入时按用户失效,切换数据源时整体清空。
|
||||||
|
7. **接口数据加密**:`/api/**` 请求/响应体支持 RSA + AES-GCM 端到端加密,握手自动完成、对未加密客户端透明,见 [API 文档](/dev/api#接口数据加密)。
|
||||||
|
|
||||||
## 相关文档
|
## 相关文档
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -53,7 +53,7 @@ npm run dev
|
|||||||
java -jar build/libs/ledgerly-0.1-jdk21.jar --server.port=18081
|
java -jar build/libs/ledgerly-0.1-jdk21.jar --server.port=18081
|
||||||
```
|
```
|
||||||
|
|
||||||
构建任务链(`build.gradle`):`npmInstall` → `frontendBuild` → `bootJar`。
|
构建任务链(`build.gradle`):`npmInstall` → `frontendLint` → `frontendBuild` → `bootJar`;`check` 任务另挂 Spotless、Checkstyle、SpotBugs。代码检查不通过会中断构建与打包,详见[开发环境与规范](/dev/development)。
|
||||||
|
|
||||||
::: tip 提示
|
::: tip 提示
|
||||||
jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。
|
jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。
|
||||||
@@ -119,6 +119,11 @@ jar 内置的前端为生产构建,访问时与后端同源,无需额外的
|
|||||||
- 服务:[https://finance.rosercode.cn](https://finance.rosercode.cn)
|
- 服务:[https://finance.rosercode.cn](https://finance.rosercode.cn)
|
||||||
- 文档:[https://docs.finance.rosercode.cn](https://docs.finance.rosercode.cn)
|
- 文档:[https://docs.finance.rosercode.cn](https://docs.finance.rosercode.cn)
|
||||||
|
|
||||||
|
### 安全配置
|
||||||
|
|
||||||
|
- **JWT 签名密钥**:默认使用内置密钥签发 Token(有效期 24 小时);生产环境可通过环境变量 `JWT_SECRET` 覆盖,未配置时回退内置默认值,避免个人自托管场景重启后登录态失效。
|
||||||
|
- **CORS 放行来源**:仅放行本机与局域网来源——`localhost`、`127.0.0.1`、`[::1]`、`192.168.*`、`10.*`、`172.*`(任意端口),并允许携带凭证;生产环境前后端同源,无需额外的跨域配置。
|
||||||
|
|
||||||
### 发布流程
|
### 发布流程
|
||||||
|
|
||||||
服务(构建 → 复制 → 重启):
|
服务(构建 → 复制 → 重启):
|
||||||
|
|||||||
@@ -48,6 +48,39 @@
|
|||||||
- **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。
|
- **对象转换**:实体与 VO 之间统一使用 MapStruct(`converter.VoConverter`),不在 Service 中手写 setter。
|
||||||
- **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。
|
- **定时任务**:基于 Spring `@Scheduled`,新增调度器直接标注即可。
|
||||||
|
|
||||||
|
## 代码检查
|
||||||
|
|
||||||
|
项目统一引入代码检查工具并挂到构建/打包流程,检查不通过即阻断,详见[构建与部署](/dev/build-deploy)。
|
||||||
|
|
||||||
|
**后端(Gradle)**:
|
||||||
|
|
||||||
|
- **Spotless**:格式化与导入顺序(见「代码规范」),`spotlessCheck` 已挂到 `check` 且为 `compileJava` 前置。
|
||||||
|
- **Checkstyle**:配置 `gradle/checkstyle/checkstyle.xml`、抑制清单 `gradle/checkstyle/suppressions.xml`,`maxWarnings = 0`。
|
||||||
|
- **SpotBugs**:配置 `gradle/spotbugs/exclude.xml`,`effort = MAX`、`reportLevel = MEDIUM`;工具版本固定 `4.8.6`(规避其 BCEL 依赖与 Spring Boot BOM 选定 `commons-lang3` 版本的冲突)。
|
||||||
|
- 仅检查主源码(`checkstyleTest` / `spotbugsTest` 已禁用)。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 宿主机
|
||||||
|
./gradlew -I nexus-init.gradle lint # Spotless + Checkstyle + SpotBugs + 前端 ESLint
|
||||||
|
./gradlew -I nexus-init.gradle checkstyleMain spotbugsMain
|
||||||
|
# 容器内(开发模式)
|
||||||
|
docker exec ledgerly-backend-dev gradle -I /project/nexus-init.gradle checkstyleMain spotbugsMain
|
||||||
|
```
|
||||||
|
|
||||||
|
**前端(ESLint + Prettier)**:
|
||||||
|
|
||||||
|
- 配置:`frontend/eslint.config.js`(flat config)、`frontend/.prettierrc.json`、`frontend/.prettierignore`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm run lint # eslint .
|
||||||
|
npm run lint:fix # eslint . --fix
|
||||||
|
npm run format # prettier --write .
|
||||||
|
npm run format:check # prettier --check .
|
||||||
|
```
|
||||||
|
|
||||||
|
- Gradle 的 `frontendLint` 在 `frontendBuild` 前执行;`package.sh` 打包前也会执行 `spotlessCheck checkstyleMain spotbugsMain`,任一检查失败都会中断构建与打包。
|
||||||
|
|
||||||
## Git 提交规范
|
## Git 提交规范
|
||||||
|
|
||||||
- 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。
|
- 提交范围仅包含源码与配置;个人账单原始数据、`docs/`、`reload.sh`、`.gitignore`、`nexus-init.gradle` 及 AI 测试环境基础设施不纳入提交。
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ledgerly/
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `client` | 外部服务客户端(DeepSeek) |
|
| `client` | 外部服务客户端(DeepSeek) |
|
||||||
| `common` | 通用响应(`Result`、`PageResult`)、业务异常与全局异常处理 |
|
| `common` | 通用响应(`Result`、`PageResult`)、业务异常与全局异常处理 |
|
||||||
| `config` | 应用配置、动态数据源、Knife4j、MyBatis-Plus、SPA 路由回退 |
|
| `config` | 应用配置、动态数据源、Knife4j、MyBatis-Plus、SPA 路由回退;`config.crypto` 为接口加解密拦截器与请求/响应体 Advice |
|
||||||
| `controller` | REST 接口层 |
|
| `controller` | REST 接口层 |
|
||||||
| `converter` | MapStruct 对象转换器(`VoConverter`) |
|
| `converter` | MapStruct 对象转换器(`VoConverter`) |
|
||||||
| `dto` | 请求对象 |
|
| `dto` | 请求对象 |
|
||||||
@@ -38,8 +38,8 @@ ledgerly/
|
|||||||
| `mapper` | 数据访问层(MyBatis-Plus) |
|
| `mapper` | 数据访问层(MyBatis-Plus) |
|
||||||
| `notification` | 通知渠道发送器 |
|
| `notification` | 通知渠道发送器 |
|
||||||
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
|
| `scheduler` | 定时任务(通知、日志清理、邮箱账单) |
|
||||||
| `service` / `service.impl` | 业务层 |
|
| `service` / `service.impl` | 业务层(交易记录服务已按职责拆分:查询条件构建、VO 富化、标签写入、批量操作、导出组装) |
|
||||||
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils`、`ImageUtils`、`AppHome`、`TransactionDedupUtils` 等);`util.sql` 为智能查数的安全网关(`TextToSqlGuard` / `TextToSqlTables` / `TextToSqlSchemaProvider`) |
|
| `util` | 工具类(`JwtUtil`、`UserContext`、`AppLogUtils`、`ImageUtils`、`AppHome`、`TransactionDedupUtils` 等);`util.sql` 为智能查数的安全网关(`TextToSqlGuard` / `TextToSqlTables` / `TextToSqlSchemaProvider`);`util.crypto` 为接口加解密服务(`ApiCryptoService` / `CryptoSessionStore` / `RsaKeyManager`) |
|
||||||
| `prompt` | AI 提示词模板加载(`PromptTemplateService`,模板位于 `resources/prompts/`) |
|
| `prompt` | AI 提示词模板加载(`PromptTemplateService`,模板位于 `resources/prompts/`) |
|
||||||
| `vo` | 响应对象 |
|
| `vo` | 响应对象 |
|
||||||
| `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) |
|
| `launcher` | 桌面 GUI 启动器(仅打入带 GUI 的 jar) |
|
||||||
@@ -52,6 +52,7 @@ ledgerly/
|
|||||||
application.yml # 应用配置(JWT、MyBatis-Plus、springdoc、端口等)
|
application.yml # 应用配置(JWT、MyBatis-Plus、springdoc、端口等)
|
||||||
logback-spring.xml # 日志配置(控制台 + 按天滚动文件)
|
logback-spring.xml # 日志配置(控制台 + 按天滚动文件)
|
||||||
mapper/ # MyBatis XML
|
mapper/ # MyBatis XML
|
||||||
|
prompts/ # AI 提示词模板(Thymeleaf TEXT 模式,*.tpl;系统/用户提示词为同文件不同片段)
|
||||||
sql/
|
sql/
|
||||||
├── init.sql # MySQL 建表脚本
|
├── init.sql # MySQL 建表脚本
|
||||||
├── schema-sqlite.sql # SQLite 建表脚本(以 ;; 分隔语句)
|
├── schema-sqlite.sql # SQLite 建表脚本(以 ;; 分隔语句)
|
||||||
@@ -85,6 +86,7 @@ views/ # 页面组件
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `config/database.json` | 数据库配置(SQLite / MySQL) |
|
| `config/database.json` | 数据库配置(SQLite / MySQL) |
|
||||||
| `config/app.json` | 应用配置(注册开关、通知链接、日志保留天数、按账单来源的计入统计默认值) |
|
| `config/app.json` | 应用配置(注册开关、通知链接、日志保留天数、按账单来源的计入统计默认值) |
|
||||||
|
| `config/crypto-keys.json` | 接口加解密用的 RSA 密钥对(首次启动自动生成) |
|
||||||
| `data/` | SQLite 数据文件、邮箱账单附件与图片记账截图(`data/ai-images/`) |
|
| `data/` | SQLite 数据文件、邮箱账单附件与图片记账截图(`data/ai-images/`) |
|
||||||
| `logs/` | 运行日志文件 |
|
| `logs/` | 运行日志文件 |
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,15 @@
|
|||||||
|
|
||||||
系统支持**简体中文、English、日本語**三种语言。登录页、注册页右上角以及主界面顶部栏均可切换语言。
|
系统支持**简体中文、English、日本語**三种语言。登录页、注册页右上角以及主界面顶部栏均可切换语言。
|
||||||
|
|
||||||
|
## 小屏与移动端适配
|
||||||
|
|
||||||
|
系统已适配手机、平板等小屏设备,直接用浏览器访问部署地址即可:
|
||||||
|
|
||||||
|
- **布局**:小屏下左侧导航收起为抽屉,点击顶部菜单按钮展开,切回桌面宽度自动收起。
|
||||||
|
- **表格**:收支记录等表格在窄屏支持**横向滚动**,不会挤压列内容。
|
||||||
|
- **弹窗与浮窗**:宽度自动适配视口,AI 搜索等浮窗可拖拽缩放。
|
||||||
|
- **表单与图表**:表单标签在小屏切换为顶部显示,图表高度随屏宽下调。
|
||||||
|
|
||||||
## 首次使用建议
|
## 首次使用建议
|
||||||
|
|
||||||
1. 完成数据库配置并注册登录。登录后默认进入[日历](/guide/calendar),可先熟悉按天查看收支的用法。
|
1. 完成数据库配置并注册登录。登录后默认进入[日历](/guide/calendar),可先熟悉按天查看收支的用法。
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
| 查看统计 | 携带当前筛选条件跳转到[统计面板](/guide/dashboard) |
|
| 查看统计 | 携带当前筛选条件跳转到[统计面板](/guide/dashboard) |
|
||||||
| 导入账单 | 打开账单导入弹窗 |
|
| 导入账单 | 打开账单导入弹窗 |
|
||||||
| 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 |
|
| 渠道管理 | 进入[交易渠道管理](/guide/channels)页,维护自定义渠道 |
|
||||||
| 导出 Excel | 按当前筛选条件导出 |
|
| 导出 Excel | 按当前筛选条件导出;单次上限 **10000 条**,超出会提示缩小筛选范围后重试 |
|
||||||
| 刷新 | 按当前筛选条件与页码重新加载列表 |
|
| 刷新 | 按当前筛选条件与页码重新加载列表 |
|
||||||
| 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) |
|
| 智能记账 | 用一句话描述收支,AI 解析后预填表单(需启用 AI) |
|
||||||
| 新增记录 | 手动新增一笔记录 |
|
| 新增记录 | 手动新增一笔记录 |
|
||||||
|
|||||||
Reference in New Issue
Block a user