# ProjectMoneyX — 系统详细设计说明书(DDS) > **版本**:v1.0 | **日期**:2026-03-03 | **状态**:综合定稿 > **技术栈**:后端 Golang + GIN + GORM | 前端 Vue3 + TypeScript + Vuetify | 数据库 SQLite --- ## 1. 设计目标与范围 ### 1.1 设计目标 ProjectMoneyX 定位为 Firefly III 生态的**本地化多源账单数据治理中间件**,核心目标是将分散在支付平台、银行、生活服务及加密货币交易所的零散账单,经过标准化清洗、智能去重与链路合并后,无缝推送至 Firefly III / Data Importer。 本系统需实现以下完整处理链路: 1. 原始文件接入与批次化管理 2. 多平台账单解析与字段标准化(插件化架构) 3. 统一交易模型入库(SQLite 持久化) 4. 严格去重 → 模糊去重 → 链路合并(三层递进) 5. 分类 / 账户 / 标签 / 商户归一化规则映射 6. 导出为 Firefly III / Data Importer 可消费的数据格式(API / CSV) 7. 提供预览、审计、失败重试与全链路可回溯能力 ### 1.2 本期范围(V1.0 / MVP) | 范围项 | 说明 | |--------|------| | 账单来源 | 支付宝、微信支付(优先);建设银行、工商银行(次优先) | | 文件格式 | CSV / Excel / 常见文本格式 | | 持久化 | 本地 SQLite | | 去重 | 基础去重(严格去重)+ 转账闭环链路合并 | | 规则映射 | 基础规则映射(分类 / 账户) | | 导入方式 | API 推送 + 中间文件导出 | | 交互 | 导入预览与确认、失败重试 | ### 1.3 非目标(Out of Scope) - 不直接登录第三方平台自动抓取账单 - 不实现完整 BI 分析平台 - 不替代 Firefly III 本体记账能力 - 不做高频实时同步(本期以"批量导入"为主) --- ## 2. 需求分析与架构判断 ### 2.1 核心业务意图 PRD 的真实意图不是"导入工具",而是一个**本地账单数据治理层**。系统重心在于: - **多源异构字段收敛**:屏蔽支付宝、微信、银行等平台字段差异 - **多因子去重与链路恢复**:消除重复流水,还原真实交易链路 - **可沉淀规则体系**:分类/账户/标签映射可长期积累、可解释、可迁移 ### 2.2 关键设计约束 | # | 约束 | 说明 | |---|------|------| | 1 | 本地优先 | 财务数据敏感,必须本地部署,默认不上传云端 | | 2 | 插件化解析器 | 平台格式变化频繁,适配逻辑必须隔离 | | 3 | 统一交易模型稳定 | 避免下游 Firefly III 或上游平台格式污染核心域模型 | | 4 | 支付宝分类为主标准 | 支付宝拥有最丰富的 22 种交易分类,其他平台应映射到此分类体系 | | 5 | 微信分类需推断 | 微信"交易类型"较粗,需结合"商品"字段推断细分分类 | ### 2.3 技术风险识别 | # | 风险 | 影响 | 应对策略 | |---|------|------|----------| | 1 | 平台格式变更 | 解析器失效 | 插件化 Parser + 版本化适配 | | 2 | 模糊去重误判 | 误合并 / 漏合并 | 分层策略 + 低置信度进人工确认队列 | | 3 | 转账闭环识别不准 | 内部转账漏匹配 | 多因子评分 + 可配置参数 | | 4 | Firefly III 映射不完整 | 导入失败 | 导入前校验 + 失败可重试 | | 5 | 规则增长后性能退化 | 响应变慢 | 规则预筛选 + 索引优化 | --- ## 3. 总体架构设计 ### 3.1 系统分层架构图 系统采用经典的领域驱动设计(DDD)分层思想,严格隔离各平台差异: ```mermaid graph TD subgraph "展现层 (Vue3 + TypeScript + Vuetify)" UI_Import["导入中心
文件上传 / 批次管理"] UI_Preview["数据清洗预览
标准化结果 / 字段对比"] UI_Dedup["去重处理
重复确认 / 链路合并"] UI_Rule["规则管理
分类 / 账户 / 标签配置"] UI_Task["导入任务
执行 / 结果 / 重试"] UI_Audit["数据审计
全链路追溯"] UI_Setting["系统设置
Firefly 连接 / 参数配置"] end subgraph "接入层 (GIN RESTful API)" API_Import["导入 API"] API_Data["数据流转 API"] API_Rule["规则管理 API"] API_Export["导出 API"] API_Config["配置 API"] end subgraph "应用服务层 (Application Service)" SVC_Batch["ImportBatchService
批次编排"] SVC_Pipeline["PipelineService
ETL 流水线"] end subgraph "业务逻辑层 (Core Domain)" subgraph "Adapter 解析层 (插件化)" Parser_Alipay["支付宝解析器"] Parser_WeChat["微信解析器"] Parser_CCB["建行解析器"] Parser_ICBC["工行解析器"] end Norm["Normalize 标准化引擎"] Match["Match 去重引擎"] Link["Link 链路合并引擎"] Rule["Rule 规则映射引擎"] Export["Export 导出推送引擎"] end subgraph "数据持久层 (GORM + SQLite)" DB[(SQLite Database)] end subgraph "外部系统 (External)" FF["Firefly III"] DI["Data Importer"] end UI_Import & UI_Preview & UI_Dedup & UI_Rule & UI_Task & UI_Audit & UI_Setting --> API_Import & API_Data & API_Rule & API_Export & API_Config API_Import & API_Data & API_Rule & API_Export --> SVC_Batch & SVC_Pipeline SVC_Pipeline --> Parser_Alipay & Parser_WeChat & Parser_CCB & Parser_ICBC Parser_Alipay & Parser_WeChat & Parser_CCB & Parser_ICBC --> Norm Norm --> Match --> Link --> Rule --> Export Export --> FF & DI Norm & Match & Link & Rule & Export -.-> DB ``` ### 3.2 分层职责定义 | 层级 | 技术选型 | 核心职责 | |------|----------|----------| | **展现层** | Vue3 + TS + Vuetify | 上传文件、批次管理、预览确认、规则配置、人工确认、导入结果展示 | | **接入层** | GIN Framework | RESTful 接口,统一参数校验、错误处理、响应封装 | | **应用服务层** | Go Service | 编排完整业务流程(ETL Pipeline),不承载具体解析规则 | | **Adapter 层** | Go Plugin Interface | 按平台解析原始文件,输出平台原始记录 DTO | | **Normalize 层** | Go Service | 统一字段、金额、方向、时间、分类原始值 | | **Match 层** | Go Service | 严格去重、模糊去重(多因子评分) | | **Link 层** | Go Service | 转账闭环、订单链路聚合 | | **Rule 层** | Go Service | 分类、账户、对手方、标签映射 | | **Export 层** | Go Service | 适配 Firefly III / Data Importer API 或 CSV/JSON | | **Repository 层** | GORM | 隔离数据库访问,面向领域对象持久化 | | **数据持久层** | SQLite | 本地数据库,存储全量数据与审计链路 | ### 3.3 数据流转拓扑 ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 多源账单导入 │ -> │ 解析器(Parser) │ -> │ 标准化入库 │ -> │ 去重与链路合并 │ -> │ 规则映射 │ -> │ 导出/推送 │ │ (文件上传) │ │ (Adapter层) │ │ (SQLite) │ │ (Dedup+Link) │ │ (Rule Engine) │ │ (API/CSV) │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ ``` --- ## 4. 系统模块划分 ### 4.1 模块清单 | 模块 | 包名 | 职责 | 优先级 | |------|------|------|--------| | 导入中心 | `import-center` | 文件上传、批次管理、来源识别 | P0 | | 解析引擎 | `parser-engine` | 平台解析器注册、装载与执行 | P0 | | 标准化引擎 | `normalize-engine` | 统一模型转换 | P0 | | 去重引擎 | `dedup-engine` | 严格去重与模糊去重 | P0/P1 | | 链路引擎 | `link-engine` | 转账闭环与订单链路合并 | P0 | | 规则引擎 | `rule-engine` | 分类/账户/标签/商户归一化 | P0/P1 | | 导入编排 | `import-orchestrator` | 导入预览、执行、重试 | P0 | | 审计中心 | `audit-center` | 审计日志、处理链追溯 | P0 | | 系统设置 | `settings-center` | Firefly 配置、阈值参数 | P1 | ### 4.2 后端包结构(Go) ```text projectmoneyx-server/ ├── cmd/ │ └── server/ │ └── main.go # 程序入口 ├── internal/ │ ├── config/ # 配置管理 │ │ └── config.go │ ├── handler/ # GIN Handler(接入层) │ │ ├── import_handler.go │ │ ├── transaction_handler.go │ │ ├── dedup_handler.go │ │ ├── rule_handler.go │ │ ├── export_handler.go │ │ └── audit_handler.go │ ├── middleware/ # 中间件 │ │ ├── error_handler.go │ │ └── cors.go │ ├── service/ # 应用服务层 │ │ ├── import_batch_service.go │ │ ├── pipeline_service.go │ │ ├── dedup_service.go │ │ ├── rule_apply_service.go │ │ ├── export_service.go │ │ └── audit_service.go │ ├── domain/ # 领域层 │ │ ├── entity/ # 领域实体 │ │ │ ├── source_file.go │ │ │ ├── raw_record.go │ │ │ ├── transaction.go │ │ │ ├── rule.go │ │ │ ├── import_task.go │ │ │ └── audit_log.go │ │ ├── valueobject/ # 值对象 │ │ │ ├── direction.go │ │ │ ├── platform.go │ │ │ └── batch_status.go │ │ └── repository/ # 仓储接口 │ │ ├── source_file_repo.go │ │ ├── raw_record_repo.go │ │ ├── transaction_repo.go │ │ └── rule_repo.go │ ├── parser/ # Adapter 解析层(插件化) │ │ ├── interface.go # BillParser 接口定义 │ │ ├── registry.go # 解析器注册中心 │ │ ├── alipay/ │ │ │ └── alipay_parser.go │ │ ├── wechat/ │ │ │ └── wechat_parser.go │ │ ├── ccb/ │ │ │ └── ccb_parser.go │ │ └── icbc/ │ │ └── icbc_parser.go │ ├── normalize/ # 标准化引擎 │ │ └── normalizer.go │ ├── matcher/ # 去重引擎 │ │ ├── strict_dedup.go │ │ ├── fuzzy_dedup.go │ │ └── scorer.go │ ├── linker/ # 链路合并引擎 │ │ ├── transfer_linker.go │ │ └── order_linker.go │ ├── rule/ # 规则引擎 │ │ ├── engine.go │ │ ├── category_mapper.go │ │ ├── account_mapper.go │ │ └── merchant_normalizer.go │ ├── exporter/ # 导出引擎 │ │ ├── firefly_exporter.go │ │ └── csv_exporter.go │ ├── dao/ # 数据访问层(GORM 实现) │ │ ├── source_file_dao.go │ │ ├── raw_record_dao.go │ │ ├── transaction_dao.go │ │ └── rule_dao.go │ └── dto/ # 数据传输对象 │ ├── request/ │ └── response/ ├── migrations/ # 数据库迁移 ├── web/ # 前端打包产物 └── go.mod ``` ### 4.3 前端项目结构(Vue3) ```text projectmoneyx-web/ ├── src/ │ ├── api/ # API 调用封装 │ │ ├── import.ts │ │ ├── transaction.ts │ │ ├── dedup.ts │ │ ├── rule.ts │ │ ├── export.ts │ │ └── audit.ts │ ├── views/ # 页面视图 │ │ ├── ImportCenterView.vue # 导入中心 │ │ ├── BatchDetailView.vue # 批次详情 │ │ ├── PreviewView.vue # 清洗结果预览 │ │ ├── DedupReviewView.vue # 去重处理 │ │ ├── RuleConfigView.vue # 规则配置 │ │ ├── ImportTaskView.vue # 导入任务 │ │ ├── AuditTraceView.vue # 审计追溯 │ │ └── SettingsView.vue # 系统设置 │ ├── components/ # 可复用组件 │ │ ├── FileUploader.vue │ │ ├── TransactionTable.vue │ │ ├── RuleEditor.vue │ │ ├── DedupCompare.vue │ │ └── AuditTimeline.vue │ ├── stores/ # Pinia 状态管理 │ │ ├── importStore.ts │ │ ├── transactionStore.ts │ │ └── ruleStore.ts │ ├── types/ # TypeScript 类型定义 │ │ ├── transaction.ts │ │ ├── rule.ts │ │ └── common.ts │ ├── router/ # Vue Router │ │ └── index.ts │ ├── plugins/ # Vuetify 等插件 │ │ └── vuetify.ts │ ├── App.vue │ └── main.ts ├── package.json ├── tsconfig.json └── vite.config.ts ``` --- ## 5. 核心业务流程设计 ### 5.1 主流程时序图 ```mermaid sequenceDiagram participant U as 用户 participant FE as Web UI (Vue3) participant API as GIN API participant SVC as Pipeline Service participant PARSER as Parser Engine participant NORM as Normalize Engine participant MATCH as Match/Link Engine participant RULE as Rule Engine participant EXP as Export Engine participant DB as SQLite (GORM) participant FF as Firefly III / Data Importer U->>FE: 上传账单文件(拖拽/选择) FE->>API: POST /api/v1/import/batches (multipart) API->>SVC: createBatch(files, platform) SVC->>DB: 保存 source_files + 创建 import_batch SVC->>PARSER: 自动识别来源 → 加载对应 Parser PARSER->>PARSER: 逐行解析原始数据 PARSER->>DB: 批量保存 raw_records SVC->>NORM: 标准化转换 NORM->>NORM: 字段映射、金额归一、时区对齐 NORM->>DB: 批量保存 transactions (status=PENDING_CLEAN) SVC->>MATCH: 执行去重流程 MATCH->>MATCH: 1. 严格去重(精确匹配) MATCH->>MATCH: 2. 模糊去重(多因子评分) MATCH->>MATCH: 3. 链路合并(转账闭环) MATCH->>DB: 保存 dedup_relations / link_relations SVC->>RULE: 应用映射规则 RULE->>RULE: 对手方归一 → 分类映射 → 账户映射 → 标签映射 RULE->>DB: 更新 category_mapped / account / tag API-->>FE: 返回预览数据 U->>FE: 查看预览,确认无误 FE->>API: POST /api/v1/import/tasks (确认导入) API->>EXP: 执行导出推送 EXP->>FF: 调用 API 推送 / 生成 CSV 下载 FF-->>EXP: 返回导入结果 EXP->>DB: 保存 import_results, 更新任务状态 API-->>FE: 展示导入结果(成功/失败统计) ``` ### 5.2 批次状态机 ```mermaid stateDiagram-v2 [*] --> CREATED: 创建批次 CREATED --> UPLOADED: 文件上传完成 UPLOADED --> PARSING: 触发解析 PARSING --> PARSED: 解析完成 PARSED --> NORMALIZING: 触发标准化 NORMALIZING --> NORMALIZED: 标准化完成 NORMALIZED --> MATCHING: 触发去重/链路 MATCHING --> MATCHED: 去重/链路完成 MATCHED --> RULE_APPLYING: 触发规则映射 RULE_APPLYING --> PREVIEW_READY: 规则映射完成,可预览 PREVIEW_READY --> IMPORTING: 用户确认导入 IMPORTING --> IMPORT_SUCCESS: 全部导入成功 IMPORTING --> PARTIAL_FAILED: 部分失败 IMPORTING --> IMPORT_FAILED: 全部失败 PARTIAL_FAILED --> RETRYING: 用户重试失败项 IMPORT_FAILED --> RETRYING: 用户重试 RETRYING --> IMPORT_SUCCESS: 重试成功 RETRYING --> PARTIAL_FAILED: 仍有失败 note right of PARSING: 解析失败时\n状态回退至 UPLOADED\n记录错误信息 note right of PREVIEW_READY: 用户可在此阶段\n查看预览结果\n人工确认去重 ``` ### 5.3 ETL Pipeline 编排逻辑 ```go // PipelineService 负责编排整个 ETL 流程 type PipelineService struct { parserRegistry *parser.Registry normalizer *normalize.Normalizer strictDedup *matcher.StrictDedup fuzzyDedup *matcher.FuzzyDedup transferLinker *linker.TransferLinker ruleEngine *rule.Engine batchRepo repository.ImportBatchRepo transactionRepo repository.TransactionRepo } func (s *PipelineService) Process(ctx context.Context, batchID string) error { // 阶段 1: 解析 rawRecords, err := s.parse(ctx, batchID) if err != nil { return err } // 阶段 2: 标准化 transactions, err := s.normalize(ctx, rawRecords) if err != nil { return err } // 阶段 3: 严格去重 transactions, err = s.strictDedup.Execute(ctx, transactions) if err != nil { return err } // 阶段 4: 模糊去重(P1 阶段) transactions, err = s.fuzzyDedup.Execute(ctx, transactions) if err != nil { return err } // 阶段 5: 链路合并 transactions, err = s.transferLinker.Execute(ctx, transactions) if err != nil { return err } // 阶段 6: 规则映射 err = s.ruleEngine.Apply(ctx, transactions) if err != nil { return err } // 更新批次状态为 PREVIEW_READY return s.batchRepo.UpdateStatus(ctx, batchID, "PREVIEW_READY") } ``` --- ## 6. 账单解析与标准化设计 ### 6.1 解析器接口设计 ```go // BillParser 是所有平台解析器必须实现的接口 type BillParser interface { // Platform 返回平台标识符,如 "alipay", "wechat", "ccb" Platform() string // Detect 根据文件元信息和表头判断是否为本平台文件 Detect(fileMeta FileMeta, header []string) bool // Parse 解析指定文件,返回原始记录列表 Parse(ctx context.Context, reader io.Reader) ([]RawBillRecord, error) } // FileMeta 文件元信息 type FileMeta struct { FileName string FileType string // csv, xlsx, txt FileHash string FileSize int64 } // RawBillRecord 原始账单记录(平台相关的中间结构) type RawBillRecord struct { SourcePlatform string SourceRecordID string RawFields map[string]string // 原始 K-V 字段 RowNo int RowFingerprint string } ``` ### 6.2 解析器注册中心 ```go // Registry 解析器注册中心 type Registry struct { parsers []BillParser } func NewRegistry() *Registry { r := &Registry{} // 注册所有解析器 r.Register(&alipay.Parser{}) r.Register(&wechat.Parser{}) r.Register(&ccb.Parser{}) r.Register(&icbc.Parser{}) return r } // Detect 自动检测文件对应的解析器 func (r *Registry) Detect(meta FileMeta, header []string) (BillParser, error) { for _, p := range r.parsers { if p.Detect(meta, header) { return p, nil } } return nil, ErrUnknownPlatform } ``` ### 6.3 支付宝解析规则 支付宝账单数据维度: ``` 交易时间 | 交易分类 | 交易对方 | 对方账号 | 商品说明 | 收/支 | 金额 | 收/付款方式 | 交易状态 | 交易订单号 | 商家订单号 | 备注 ``` #### 支付宝字段映射表 | 原字段 | 目标字段 | 映射说明 | |--------|----------|----------| | 交易时间 | `trade_time` | 解析为 `time.Time`,统一 UTC+8 | | 交易分类 | `category_raw` | 直接作为原始分类(22 种标准分类) | | 交易对方 | `counterparty` | 交易对手名称 | | 对方账号 | `counterparty_account` | 存入扩展字段 | | 商品说明 | `merchant_name` / `note` | 优先作为商户名,辅助作为备注 | | 收/支 | `direction` | "收入" → income, "支出" → expense, "其他" → other | | 金额 | `amount` | 去除 ¥ 符号,解析为 Decimal 正数 | | 收/付款方式 | `payment_method` | 存入扩展字段,可用于账户映射 | | 交易状态 | `trade_status` | "交易成功" / "退款成功" 等 | | 交易订单号 | `source_record_id` / `order_id` | 去除空格,作为唯一标识 | | 商家订单号 | `merchant_order_id` / `parent_order_id` | 可用于链路关联 | | 备注 | `note` | 补充备注 | > **关键设计决策**:支付宝拥有最丰富的 22 种交易分类,系统将其作为**全局统一分类基准字典**。所有其他平台的交易分类最终都应映射到此套分类枚举。 #### 支付宝标准分类枚举(22 类) | 编号 | 分类名称 | 编号 | 分类名称 | |------|----------|------|----------| | 1 | 餐饮美食 | 12 | 退款 | | 2 | 投资理财 | 13 | 教育培训 | | 3 | 日用百货 | 14 | 住房物业 | | 4 | 数码电器 | 15 | 酒店旅游 | | 5 | 交通出行 | 16 | 文化休闲 | | 6 | 充值缴费 | 17 | 运动户外 | | 7 | 信用借还 | 18 | 爱车养车 | | 8 | 转账红包 | 19 | 商业服务 | | 9 | 生活服务 | 20 | 母婴亲子 | | 10 | 家居家装 | 21 | 收入 | | 11 | 医疗健康 | 22 | 其他 | ### 6.4 微信解析规则 微信账单数据维度: ``` 交易时间 | 交易类型 | 交易对方 | 商品 | 收/支 | 金额(元) | 支付方式 | 当前状态 | 交易单号 | 商户单号 | 备注 ``` #### 微信字段映射表 | 原字段 | 目标字段 | 映射说明 | |--------|----------|----------| | 交易时间 | `trade_time` | 解析为 `time.Time`,统一 UTC+8 | | 交易类型 | `category_raw` | 存储原始类型,需通过推断映射到标准分类 | | 交易对方 | `counterparty` | 交易对手名称 | | 商品 | `merchant_name` / `product_desc` | **关键字段**,用于推断实际消费分类 | | 收/支 | `direction` | "收入" → income, "支出" → expense | | 金额(元) | `amount` | 去除 ¥ 符号,解析为 Decimal 正数 | | 支付方式 | `payment_method` | 存入扩展字段 | | 当前状态 | `trade_status` | "支付成功" / "已退款" 等 | | 交易单号 | `source_record_id` / `order_id` | 去除空格 | | 商户单号 | `merchant_order_id` / `parent_order_id` | 链路关联 | | 备注 | `note` | 补充备注 | #### 微信分类推断规则 微信"交易类型"多为支付动作(商户消费、扫二维码付款、转账、红包等),无实际消费语义。需结合"商品"字段进行关键词推断: ```mermaid flowchart TD A[微信原始记录] --> B{交易类型判断} B -->|转账| C[转账红包] B -->|微信红包| C B -->|微信红包-退款| D[退款] B -->|xxx-退款| D B -->|商户消费 / 扫二维码付款| E{商品关键词推断} E -->|美团/外卖/餐厅/咖啡/麦当劳/肯德基| F[餐饮美食] E -->|滴滴/打车/地铁/高铁/火车/机票/加油| G[交通出行] E -->|京东/超市/便利店/百货| H[日用百货] E -->|电费/水费/话费/燃气/宽带| I[充值缴费] E -->|医院/药店/门诊| J[医疗健康] E -->|电影/KTV/游戏/书籍| K[文化休闲] E -->|酒店/景点/旅行| L[酒店旅游] E -->|无法识别| M[其他 → 待人工补充] ``` #### 推断规则配置示例 | 微信交易类型 | 商品关键词 | 推断标准分类 | |-------------|-----------|-------------| | 商户消费 | 美团 / 外卖 / 餐厅 / 咖啡 / 面包 | 餐饮美食 | | 商户消费 | 滴滴 / 打车 / 地铁 / 高铁 / 加油 | 交通出行 | | 商户消费 | 京东 / 超市 / 便利店 / 百货 | 日用百货 | | 商户消费 | 电费 / 水费 / 话费 / 燃气 | 充值缴费 | | 商户消费 | 医院 / 药店 / 体检 | 医疗健康 | | 商户消费 | 电影 / 游戏 / 书籍 | 文化休闲 | | 转账 | — | 转账红包 | | 微信红包 | — | 转账红包 | | xxx-退款 | — | 退款 | > **设计原则**:若商品内容无法识别关键词,先落入"其他"分类,并允许用户通过规则管理补充映射。系统应记录未命中规则的记录,便于后续规则完善。 ### 6.5 统一交易模型 ```mermaid classDiagram class Transaction { +string ID +string TransactionID +string BatchID +string SourcePlatform +string SourceRecordID +time.Time TradeTime +decimal Amount +string Currency +string Direction +string Counterparty +string MerchantName +string CategoryRaw +string CategoryMapped +string OrderID +string ParentOrderID +string PaymentMethod +string Note +string RawPayload +string Status +time.Time CreatedAt +time.Time UpdatedAt } class Direction { <> INCOME EXPENSE TRANSFER REFUND FEE OTHER } class TransactionStatus { <> PENDING_CLEAN CLEANED PENDING_REVIEW READY_TO_IMPORT IMPORTING IMPORTED FAILED DUPLICATE } Transaction --> Direction Transaction --> TransactionStatus ``` #### 标准化规则 | 规则项 | 说明 | |--------|------| | 时间 | 统一存储为 `Asia/Shanghai (UTC+8)` | | 金额 | 统一使用正数(`decimal(18,6)`),方向独立用 `direction` 表达 | | 币种 | 默认 `CNY`,后续可扩展多币种 | | 状态 | 初始为 `PENDING_CLEAN` | | 原始快照 | 完整写入 `raw_payload`(JSON),确保审计可追溯 | | 指纹 | 对关键字段做 SHA256,用于严格去重 | --- ## 7. 去重与链路合并设计 ### 7.1 三层递进处理模型 系统采用"**基础去重 → 模糊去重 → 链路合并**"三层递进去重模型: ```mermaid flowchart TD A[新标准化交易入库] --> B{第一层:严格去重} B -->|命中| X[标记为 DUPLICATE
建立 dedup_relation] B -->|未命中| C{第二层:模糊去重} C -->|高置信度 ≥85| X C -->|中置信度 60-84| D[标记为 PENDING_REVIEW
进入人工确认队列] C -->|低置信度 <60| E{第三层:链路合并} E -->|命中转账闭环| F[合并为 Transfer
建立 link_relation] E -->|命中订单链路| G[聚合为订单链
建立 parent_order] E -->|未命中| H[保留为独立交易
status=CLEANED] style X fill:#ff6b6b,color:#fff style D fill:#ffd93d,color:#333 style F fill:#6bcb77,color:#fff style G fill:#6bcb77,color:#fff style H fill:#4d96ff,color:#fff ``` ### 7.2 严格去重(基础去重 — 精确匹配) #### 唯一判定键 采用三级唯一性判定,优先级从高到低: | 优先级 | 判定键 | 适用场景 | |--------|--------|----------| | 1 | `source_platform` + `source_record_id` | 同一平台重复导入 | | 2 | `source_file_hash` + `row_fingerprint` | 同一文件重复上传 | | 3 | `order_id`(若可信) | 跨批次订单号匹配 | #### 行指纹算法 对以下字段标准化后做 SHA256 哈希,生成 `row_fingerprint`: ```go func GenerateRowFingerprint(t *Transaction) string { raw := fmt.Sprintf("%s|%s|%s|%s|%s|%s", t.TradeTime.Format("2006-01-02 15:04"), // 分钟粒度 t.Amount.String(), t.Direction, normalizeString(t.Counterparty), normalizeString(t.MerchantName), t.OrderID, ) hash := sha256.Sum256([]byte(raw)) return hex.EncodeToString(hash[:]) } ``` #### 严格去重流程 ```go func (s *StrictDedup) Execute(ctx context.Context, txns []*Transaction) ([]*Transaction, error) { var result []*Transaction for _, txn := range txns { // 判定键 1: platform + record_id exists, existingID := s.repo.FindByPlatformAndRecordID( ctx, txn.SourcePlatform, txn.SourceRecordID) if exists { s.createDedupRelation(ctx, txn.ID, existingID, "strict", 100) txn.Status = "DUPLICATE" continue } // 判定键 2: file_hash + fingerprint exists, existingID = s.repo.FindByFingerprint(ctx, txn.RowFingerprint) if exists { s.createDedupRelation(ctx, txn.ID, existingID, "strict", 100) txn.Status = "DUPLICATE" continue } result = append(result, txn) } return result, nil } ``` ### 7.3 模糊去重(多因子评分 — P1 阶段) #### 多因子评分模型 | 因子 | 分值 | 评分说明 | |------|------|----------| | 时间在 ±5 分钟内 | 30 | 时间差越小得分越高,超出窗口直接 0 分 | | 金额精确一致 | 30 | 金额一致得满分,差额在手续费容差内得部分分 | | 交易方向一致 | 10 | 方向相同得满分 | | 订单号相同/相近 | 15 | 完全一致 15 分,包含关系 10 分 | | 对手方相似 | 10 | Levenshtein 相似度 + contains 判定 | | 来源关联规则命中 | 5 | 预配置的平台关联规则 | #### 判定阈值(可配置) | 分值范围 | 判定结果 | 处理方式 | |----------|----------|----------| | ≥ 85 | 自动判定重复 | 自动标记 DUPLICATE | | 60 ~ 84 | 疑似重复 | 标记 PENDING_REVIEW,进入人工确认队列 | | < 60 | 不判定重复 | 保留独立交易 | #### 评分算法伪代码 ```go type FuzzyScorer struct { TimeWindow time.Duration // 默认 5 分钟 AmountEpsilon float64 // 金额容差(手续费) } func (s *FuzzyScorer) Score(a, b *Transaction) int { score := 0 // 时间因子 (30分) timeDiff := math.Abs(a.TradeTime.Sub(b.TradeTime).Minutes()) if timeDiff <= s.TimeWindow.Minutes() { score += int(30 * (1 - timeDiff/s.TimeWindow.Minutes())) } // 金额因子 (30分) if a.Amount.Equal(b.Amount) { score += 30 } else if a.Amount.Sub(b.Amount).Abs().LessThan(decimal.NewFromFloat(s.AmountEpsilon)) { score += 20 } // 方向因子 (10分) if a.Direction == b.Direction { score += 10 } // 订单号因子 (15分) if a.OrderID != "" && a.OrderID == b.OrderID { score += 15 } else if strings.Contains(a.OrderID, b.OrderID) || strings.Contains(b.OrderID, a.OrderID) { score += 10 } // 对手方因子 (10分) score += int(10 * counterpartySimilarity(a.Counterparty, b.Counterparty)) // 来源规则因子 (5分) if s.platformLinked(a.SourcePlatform, b.SourcePlatform) { score += 5 } return score } ``` ### 7.4 链路合并(转账闭环 + 订单链路) #### 7.4.1 转账闭环识别 **典型场景**:银行卡支出 1000 元(流向支付宝),支付宝收入 1000 元 → 合并为一笔内部转账。 **识别规则(5 项全部满足)**: 1. 金额一致 2. 一条为支出(expense),一条为收入(income) 3. 时间在可配置窗口内(默认 ±30 分钟) 4. 来源平台不同但账户映射可闭环 5. 非退款、非手续费类型 ```go func (l *TransferLinker) Detect(a, b *Transaction) *LinkResult { // 条件 1: 金额一致 if !a.Amount.Equal(b.Amount) { return nil } // 条件 2: 方向互补 if !((a.Direction == "expense" && b.Direction == "income") || (a.Direction == "income" && b.Direction == "expense")) { return nil } // 条件 3: 时间窗口 if math.Abs(a.TradeTime.Sub(b.TradeTime).Minutes()) > l.TimeWindow { return nil } // 条件 4: 不同平台 if a.SourcePlatform == b.SourcePlatform { return nil } // 条件 5: 非退款/手续费 if a.Direction == "refund" || b.Direction == "refund" { return nil } if a.Direction == "fee" || b.Direction == "fee" { return nil } return &LinkResult{ ParentTransactionID: selectPrimary(a, b).ID, ChildTransactionID: selectSecondary(a, b).ID, LinkType: "transfer", FromAccount: mapToAccount(getExpenseSide(a, b)), ToAccount: mapToAccount(getIncomeSide(a, b)), } } ``` #### 7.4.2 订单链路合并 **典型场景**:京东订单 + 微信支付,一笔真实消费产生多条流水。 **合并策略**: - 保留更完整的业务记录为主交易(优先保留有商品详情的记录) - 其他记录挂为关联来源 - 形成 `parent_order_id` 聚合链路 ### 7.5 人工确认机制 ```mermaid sequenceDiagram participant U as 用户 participant FE as Web UI participant API as GIN API participant DB as SQLite FE->>API: GET /api/v1/dedup/reviews?batchId=xxx API->>DB: 查询 status=PENDING_REVIEW 的记录 DB-->>API: 返回疑似重复关系列表 API-->>FE: 展示疑似重复对比(评分、命中因子、原始数据) U->>FE: 确认合并 / 拒绝合并 FE->>API: POST /api/v1/dedup/reviews/{id}/confirm 或 /reject API->>DB: 更新 dedup_relation 状态 API->>DB: 更新 transaction 状态 API-->>FE: 返回处理结果 ``` --- ## 8. 规则引擎设计 ### 8.1 规则模型 ```mermaid classDiagram class Rule { +string ID +string RuleType +int Priority +string PlatformScope +JSON Conditions +JSON Actions +bool Enabled +string Description +time.Time CreatedAt +time.Time UpdatedAt } class RuleType { <> CATEGORY_MAPPING ACCOUNT_MAPPING COUNTERPARTY_NORMALIZE TAG_MAPPING MERCHANT_NORMALIZE FIREFLY_FIELD_MAPPING } class RuleHit { +string ID +string TransactionID +string RuleID +string MatchedCondition +string BeforeValue +string AfterValue +time.Time CreatedAt } Rule --> RuleType Rule "1" --> "*" RuleHit : produces ``` ### 8.2 规则匹配条件 规则条件采用 JSON 结构化存储,支持以下匹配维度: | 条件类型 | 字段 | 说明 | 示例 | |----------|------|------|------| | 平台过滤 | `platform` | 指定生效平台 | `"alipay"` | | 原始分类 | `category_raw` | 原始分类匹配 | `"餐饮美食"` | | 关键词 | `keywords` | 商品/商户名关键词 | `["美团", "外卖"]` | | 正则 | `regex` | 正则表达式匹配 | `"^滴滴.*出行$"` | | 金额范围 | `amount_range` | [min, max] | `[0, 50]` | | 方向 | `direction` | 收支方向 | `"expense"` | | 对手方 | `counterparty` | 对手方包含 | `"支付宝"` | #### 规则条件 JSON 示例 ```json { "platform": "wechat", "conditions": { "category_raw": "商户消费", "keywords": ["美团", "外卖", "饿了么"], "direction": "expense" }, "actions": { "category_mapped": "餐饮美食", "merchant_normalized": "外卖平台" } } ``` ### 8.3 规则执行顺序 规则引擎按以下**严格顺序**执行,确保前置归一化提升后续规则命中率: ```mermaid flowchart LR A["1. 对手方归一化
COUNTERPARTY_NORMALIZE"] --> B["2. 商户归一化
MERCHANT_NORMALIZE"] B --> C["3. 分类映射
CATEGORY_MAPPING"] C --> D["4. 账户映射
ACCOUNT_MAPPING"] D --> E["5. 标签映射
TAG_MAPPING"] E --> F["6. Firefly 字段映射
FIREFLY_FIELD_MAPPING"] ``` **执行原则**: - 同一类型内按 `priority` 升序执行(数字越小优先级越高) - 先做归一再做分类,可提升规则命中率与稳定性 - 每条交易记录所有命中的规则 ID 和前后字段对比 ### 8.4 规则引擎核心实现 ```go type Engine struct { ruleRepo repository.RuleRepo hitRepo repository.RuleHitRepo } func (e *Engine) Apply(ctx context.Context, txns []*Transaction) error { // 按类型分组加载启用的规则 ruleGroups := e.loadRulesGroupByType(ctx) executionOrder := []string{ "COUNTERPARTY_NORMALIZE", "MERCHANT_NORMALIZE", "CATEGORY_MAPPING", "ACCOUNT_MAPPING", "TAG_MAPPING", "FIREFLY_FIELD_MAPPING", } for _, txn := range txns { for _, ruleType := range executionOrder { rules := ruleGroups[ruleType] for _, rule := range rules { if !rule.MatchPlatform(txn.SourcePlatform) { continue } if rule.MatchConditions(txn) { before := txn.Snapshot() rule.ApplyActions(txn) after := txn.Snapshot() // 记录命中日志 e.hitRepo.Save(ctx, &RuleHit{ TransactionID: txn.ID, RuleID: rule.ID, MatchedCondition: rule.ConditionsJSON, BeforeValue: before, AfterValue: after, }) break // 同类型首条命中即停止(优先级控制) } } } } return nil } ``` ### 8.5 可解释性设计 每条交易保留完整的规则命中记录,用于前端"为何被分到餐饮/交通"的解释展示: | 信息项 | 说明 | |--------|------| | 命中规则 ID | 关联 rules 表 | | 命中条件摘要 | 匹配的具体关键词/正则 | | 变更前值 | 规则执行前的字段值 | | 变更后值 | 规则执行后的字段值 | | 命中时间 | 规则执行时间戳 | --- ## 9. Firefly III / Data Importer 适配设计 ### 9.1 两段式规则映射策略 ```mermaid flowchart LR subgraph "第一阶段 — ProjectMoneyX 负责" A1["字段级映射
异构字段 → 统一模型"] A2["业务分类映射
对手方/描述 → 分类/标签"] A3["商户名归一化
别名 → 统一名称"] end subgraph "第二阶段 — Firefly III 负责" B1["最后一层字段适配"] B2["临时补充规则"] B3["导入格式兼容"] end A1 --> A2 --> A3 --> B1 --> B2 --> B3 ``` ### 9.2 导出模式 #### 模式 A:API 推送模式(优先) ```mermaid sequenceDiagram participant EXP as Export Engine participant DI as Data Importer participant FF as Firefly III EXP->>EXP: 构建 Firefly 兼容 DTO EXP->>DI: POST /api/v1/import (batch) DI->>FF: 转发至 Firefly III FF-->>DI: 返回导入结果 DI-->>EXP: 返回成功/失败明细 EXP->>EXP: 更新 import_results 表 ``` #### 模式 B:中间文件导出模式 - 生成完全符合 Data Importer 规范的标准 CSV / JSON - 用户手动下载后在 Data Importer 中执行导入 - 适合 API 不可用或权限受限场景 ### 9.3 Firefly 交易类型映射 | 内部 Direction | Firefly Type | 说明 | |----------------|--------------|------| | `expense` | `withdrawal` | 支出 | | `income` | `deposit` | 收入 | | `transfer` | `transfer` | 内部转账 | | `refund` | `deposit` | 退款(作为收入处理) | | `fee` | `withdrawal` | 手续费(作为支出处理) | ### 9.4 导入前校验清单 | # | 校验项 | 说明 | |---|--------|------| | 1 | 必填字段完整性 | amount, trade_time, direction 不可为空 | | 2 | 金额格式合法性 | 必须为正数,精度不超过 6 位小数 | | 3 | 时间格式合法性 | 必须为有效日期时间 | | 4 | 账户映射完整性 | 来源平台必须有对应的 Firefly 账户映射 | | 5 | 重复导入拦截 | 检查 transaction_id 是否已在 Firefly III 中存在 | | 6 | 未确认记录检查 | 是否存在 PENDING_REVIEW 状态的疑似重复记录 | ### 9.5 导入后反馈 ```go type ImportResult struct { TaskID string TransactionID string Status string // success / failed ErrorCode string // 错误码 ErrorMessage string // 错误描述 FireflyTxnID string // Firefly III 返回的交易 ID RetryCount int // 已重试次数 CreatedAt time.Time } ``` - 导入成功/失败数量统计 - 失败原因分类展示(字段缺失、格式错误、账户不存在等) - 失败记录可单独重试(无需整批重做) --- ## 10. 数据库详细设计(SQLite + GORM) ### 10.1 ER 关系图 ```mermaid erDiagram IMPORT_BATCHES ||--o{ SOURCE_FILES : contains SOURCE_FILES ||--o{ RAW_RECORDS : contains IMPORT_BATCHES ||--o{ TRANSACTIONS : generates RAW_RECORDS ||--|| TRANSACTIONS : normalizes_to TRANSACTIONS ||--o{ DEDUP_RELATIONS : source TRANSACTIONS ||--o{ LINK_RELATIONS : linked TRANSACTIONS ||--o{ RULE_HITS : matched RULES ||--o{ RULE_HITS : referenced IMPORT_BATCHES ||--o{ IMPORT_TASKS : owns IMPORT_TASKS ||--o{ IMPORT_RESULTS : produces TRANSACTIONS ||--o{ AUDIT_LOGS : traced IMPORT_BATCHES { string id PK "UUID 主键" string status "批次状态" int total_files "文件总数" int total_records "记录总数" int success_count "成功数" int failed_count "失败数" int duplicate_count "重复数" datetime created_at "创建时间" datetime updated_at "更新时间" } SOURCE_FILES { string id PK "UUID 主键" string batch_id FK "批次 ID" string file_name "原始文件名" string file_hash "文件 SHA256 哈希" string source_platform "来源平台" string file_type "csv/xlsx/txt" int file_size "文件大小(bytes)" datetime uploaded_at "上传时间" } RAW_RECORDS { string id PK "UUID 主键" string source_file_id FK "来源文件 ID" int row_no "行号" string source_platform "平台" string source_record_id "原始流水号" string row_fingerprint "行指纹 SHA256" text raw_payload "原始 JSON 快照" string parse_status "解析状态" text parse_error "错误信息" } TRANSACTIONS { string id PK "UUID 主键" string transaction_id UK "业务唯一 ID" string batch_id FK "导入批次" string raw_record_id FK "原始记录 ID" string source_platform "来源平台" string source_record_id "原始记录号" datetime trade_time "交易时间" decimal amount "金额 decimal(18,6)" string currency "币种" string direction "方向" string counterparty "对手方" string merchant_name "商户名" string category_raw "原始分类" string category_mapped "映射分类" string account_mapped "映射账户" string tags "标签(逗号分隔)" string order_id "订单号" string parent_order_id "父链路号" string payment_method "支付方式" text note "备注" text raw_payload "原始记录 JSON" string status "状态" string firefly_txn_id "Firefly 交易 ID" datetime imported_at "导入时间" datetime created_at "创建时间" datetime updated_at "更新时间" } DEDUP_RELATIONS { string id PK "UUID 主键" string src_transaction_id FK "原交易 ID" string target_transaction_id FK "目标交易 ID" string relation_type "strict/fuzzy" int confidence "置信度 0-100" string status "auto/confirmed/rejected" text reason_json "判定依据 JSON" datetime created_at "创建时间" } LINK_RELATIONS { string id PK "UUID 主键" string parent_transaction_id FK "主交易 ID" string child_transaction_id FK "子交易 ID" string link_type "transfer/order/refund/fee" text reason_json "关联依据 JSON" datetime created_at "创建时间" } RULES { string id PK "UUID 主键" string rule_type "规则类型" int priority "优先级(越小越高)" string platform_scope "平台范围(all/alipay/wechat)" text conditions_json "条件 JSON" text actions_json "动作 JSON" bool enabled "是否启用" string description "规则描述" datetime created_at "创建时间" datetime updated_at "更新时间" } RULE_HITS { string id PK "UUID 主键" string transaction_id FK "交易 ID" string rule_id FK "规则 ID" text matched_condition "命中条件摘要" text before_value "变更前值" text after_value "变更后值" datetime created_at "执行时间" } IMPORT_TASKS { string id PK "UUID 主键" string batch_id FK "批次 ID" string export_mode "api/csv" string status "pending/running/success/partial_failed/failed" int total_count "总记录数" int success_count "成功数" int failed_count "失败数" datetime started_at "开始时间" datetime finished_at "完成时间" } IMPORT_RESULTS { string id PK "UUID 主键" string task_id FK "任务 ID" string transaction_id FK "交易 ID" string status "success/failed" string error_code "错误码" text error_message "错误描述" string firefly_txn_id "Firefly 返回 ID" int retry_count "重试次数" datetime created_at "创建时间" } AUDIT_LOGS { string id PK "UUID 主键" string entity_type "实体类型" string entity_id "实体 ID" string action "操作类型" text before_snapshot "变更前快照" text after_snapshot "变更后快照" string operator "操作者" datetime created_at "操作时间" } ``` ### 10.2 关键索引设计 | 表名 | 索引名 | 索引列 | 用途 | |------|--------|--------|------| | `transactions` | `idx_txn_platform_record` | `source_platform, source_record_id` | 严格去重判定键 1 | | `transactions` | `idx_txn_fingerprint` | `row_fingerprint` | 严格去重判定键 2 | | `transactions` | `idx_txn_batch` | `batch_id` | 批次查询 | | `transactions` | `idx_txn_trade_time` | `trade_time` | 模糊去重时间分桶 | | `transactions` | `idx_txn_order` | `order_id` | 订单号匹配 | | `transactions` | `idx_txn_status` | `status` | 状态过滤 | | `source_files` | `idx_sf_hash` | `file_hash` | 文件重复上传拦截 | | `raw_records` | `idx_rr_fingerprint` | `row_fingerprint` | 行级去重 | | `rules` | `idx_rule_type_priority` | `rule_type, priority` | 规则执行顺序 | | `dedup_relations` | `idx_dedup_src` | `src_transaction_id` | 去重关系查询 | ### 10.3 GORM Model 定义示例 ```go // Transaction GORM 模型 type Transaction struct { ID string `gorm:"primaryKey;type:varchar(36)"` TransactionID string `gorm:"uniqueIndex;type:varchar(64)"` BatchID string `gorm:"index;type:varchar(36)"` RawRecordID string `gorm:"type:varchar(36)"` SourcePlatform string `gorm:"type:varchar(32);index:idx_platform_record"` SourceRecordID string `gorm:"type:varchar(128);index:idx_platform_record"` TradeTime time.Time `gorm:"index;not null"` Amount decimal.Decimal `gorm:"type:decimal(18,6);not null"` Currency string `gorm:"type:varchar(16);default:'CNY'"` Direction string `gorm:"type:varchar(16);not null"` Counterparty string `gorm:"type:varchar(255)"` MerchantName string `gorm:"type:varchar(255)"` CategoryRaw string `gorm:"type:varchar(128)"` CategoryMapped string `gorm:"type:varchar(128)"` AccountMapped string `gorm:"type:varchar(128)"` Tags string `gorm:"type:varchar(512)"` OrderID string `gorm:"index;type:varchar(128)"` ParentOrderID string `gorm:"type:varchar(128)"` PaymentMethod string `gorm:"type:varchar(128)"` Note string `gorm:"type:text"` RawPayload string `gorm:"type:text"` RowFingerprint string `gorm:"index;type:varchar(64)"` Status string `gorm:"index;type:varchar(32);default:'PENDING_CLEAN'"` FireflyTxnID string `gorm:"type:varchar(128)"` ImportedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } func (Transaction) TableName() string { return "transactions" } ``` --- ## 11. API 接口设计(GIN RESTful) ### 11.1 统一响应格式 ```go type Response struct { Code int `json:"code"` // 0=成功, 非0=错误码 Message string `json:"message"` // 成功/错误说明 Data interface{} `json:"data"` // 业务数据 } type PageResponse struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` Total int64 `json:"total"` Page int `json:"page"` Size int `json:"size"` } ``` ### 11.2 导入中心 API | 方法 | 路径 | 说明 | |------|------|------| | `POST` | `/api/v1/import/batches` | 上传账单文件,创建批次 | | `GET` | `/api/v1/import/batches` | 获取批次列表 | | `GET` | `/api/v1/import/batches/:batchId` | 获取批次详情 | | `POST` | `/api/v1/import/batches/:batchId/process` | 触发解析与清洗流水线 | | `GET` | `/api/v1/import/batches/:batchId/preview` | 获取清洗预览结果 | | `DELETE` | `/api/v1/import/batches/:batchId` | 删除批次 | #### 上传账单文件 ``` POST /api/v1/import/batches Content-Type: multipart/form-data 参数: - files[] (必填) 账单文件 - sourcePlatform (可选) 指定来源平台, 如 "alipay", "wechat" - autoDetect (可选) 是否自动识别, 默认 true 响应: { "code": 0, "message": "ok", "data": { "batchId": "550e8400-e29b-41d4-a716-446655440000", "status": "UPLOADED", "filesCount": 2, "detectedPlatforms": ["alipay", "wechat"] } } ``` ### 11.3 交易记录 API | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/v1/transactions` | 分页查询交易记录 | | `GET` | `/api/v1/transactions/:id` | 获取交易详情(含规则命中记录) | | `GET` | `/api/v1/transactions/:id/trace` | 获取交易完整处理链路 | ### 11.4 去重确认 API | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/v1/dedup/reviews` | 获取疑似重复列表 | | `GET` | `/api/v1/dedup/reviews/:reviewId` | 获取重复详情(含评分因子) | | `POST` | `/api/v1/dedup/reviews/:reviewId/confirm` | 确认合并 | | `POST` | `/api/v1/dedup/reviews/:reviewId/reject` | 拒绝合并 | ### 11.5 规则管理 API | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/v1/rules` | 获取规则列表(支持类型/平台过滤) | | `POST` | `/api/v1/rules` | 创建规则 | | `PUT` | `/api/v1/rules/:id` | 更新规则 | | `DELETE` | `/api/v1/rules/:id` | 删除规则 | | `POST` | `/api/v1/rules/evaluate` | 重新评估规则(修改规则后触发) | | `POST` | `/api/v1/rules/:id/test` | 测试规则命中预览 | ### 11.6 导入/导出 API | 方法 | 路径 | 说明 | |------|------|------| | `POST` | `/api/v1/import/tasks` | 创建导入任务(确认导入到 Firefly) | | `GET` | `/api/v1/import/tasks/:taskId` | 获取导入任务详情 | | `POST` | `/api/v1/import/tasks/:taskId/retry` | 重试失败项 | | `GET` | `/api/v1/export/csv/:batchId` | 导出批次为 CSV 文件 | ### 11.7 审计与系统 API | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/api/v1/audit/logs` | 获取操作日志列表 | | `GET` | `/api/v1/settings` | 获取系统配置 | | `PUT` | `/api/v1/settings` | 更新系统配置(Firefly 连接等) | | `POST` | `/api/v1/settings/test-connection` | 测试 Firefly III 连接 | --- ## 12. 前端页面设计(Vue3 + Vuetify) ### 12.1 信息架构与导航 ```mermaid graph LR NAV[侧边导航栏] --> A[导入中心] NAV --> B[数据清洗] NAV --> C[去重处理] NAV --> D[规则管理] NAV --> E[导入任务] NAV --> F[数据审计] NAV --> G[系统设置] A --> A1[文件上传页] A --> A2[批次列表页] A --> A3[批次详情页] B --> B1[清洗结果预览页] C --> C1[重复记录处理页] D --> D1[规则列表页] D --> D2[规则编辑页] E --> E1[导入任务列表页] E --> E2[导入结果详情页] F --> F1[审计追溯页] F --> F2[交易处理链路页] G --> G1[Firefly 连接配置] G --> G2[去重参数配置] ``` ### 12.2 页面职责与交互说明 #### 12.2.1 导入中心 — 文件上传页 | 元素 | 说明 | |------|------| | 拖拽上传区域 | 支持拖拽或点击选择文件,支持批量选择 | | 来源识别结果 | 自动检测文件来源平台,允许手动修改 | | 批量文件列表 | 显示已选文件名、大小、检测到的平台 | | 处理按钮 | "开始处理"触发 ETL Pipeline | | 历史批次入口 | 快速跳转至批次列表 | **核心组件**:`FileUploader.vue` - 使用 Vuetify `v-file-input` + 自定义拖拽区域 - 文件选择后立即调用 `POST /api/v1/import/batches` - 上传进度条实时展示 #### 12.2.2 清洗结果预览页 | 元素 | 说明 | |------|------| | 交易列表表格 | 分页展示标准化后的交易记录 | | 字段对比面板 | 点击任一行展开:原始字段 vs 标准字段对比 | | 规则命中说明 | 显示该条交易命中的具体规则和分类依据 | | 状态标记 | 以颜色标签区分:待清洗/已清洗/重复/待确认 | | 筛选器 | 支持按来源平台、分类、方向、状态过滤 | **核心组件**:`TransactionTable.vue` - 使用 Vuetify `v-data-table` 实现分页排序 - 行展开(`expanded`)显示详情面板 - 支持批量选择操作 #### 12.2.3 去重处理页 | 元素 | 说明 | |------|------| | 疑似重复列表 | 成对展示疑似重复的交易记录 | | 评分展示 | 展示模糊去重的总分和各因子得分 | | 对比视图 | 左右并排对比两条记录的关键字段 | | 操作按钮 | 确认合并 / 拒绝合并 / 暂时跳过 | | 链路视图 | 展示已识别的转账闭环和订单链路 | **核心组件**:`DedupCompare.vue` - 左右分栏对比布局 - 差异字段高亮显示 - 评分因子可展开查看 #### 12.2.4 规则配置页 | 元素 | 说明 | |------|------| | 规则列表 | 按类型分组展示,支持拖拽排序优先级 | | 规则编辑器 | 可视化编辑条件(关键词/正则/金额范围等) | | 动作配置 | 选择映射目标(分类/账户/标签) | | 测试预览 | 输入测试数据,预览规则命中结果 | | 启用/禁用 | 一键切换规则状态 | **核心组件**:`RuleEditor.vue` - 条件构建器:支持多条件 AND/OR 组合 - 使用 Vuetify `v-select`、`v-text-field`、`v-chip` 构建 - 测试按钮触发 `POST /api/v1/rules/:id/test` #### 12.2.5 导入结果页 | 元素 | 说明 | |------|------| | 统计概览 | 成功/失败/总计数量,饼图展示 | | 失败记录列表 | 展示失败原因分类 | | 重试操作 | 支持单条重试和批量重试 | | 导出按钮 | 下载 CSV 中间文件 | #### 12.2.6 审计追溯页 | 元素 | 说明 | |------|------| | 交易搜索 | 按交易 ID / 订单号 / 时间范围搜索 | | 处理链路时间线 | 纵向时间线展示完整处理链路 | | 各阶段快照 | 原始文件 → 原始记录 → 标准化 → 规则命中 → 导入结果 | | 操作日志 | 展示人工干预操作记录 | **核心组件**:`AuditTimeline.vue` - 使用 Vuetify `v-timeline` 组件 - 每个节点可展开查看详细快照数据 ### 12.3 前端路由设计 ```typescript // router/index.ts const routes = [ { path: '/', redirect: '/import' }, { path: '/import', name: 'ImportCenter', component: () => import('@/views/ImportCenterView.vue'), meta: { title: '导入中心', icon: 'mdi-upload' } }, { path: '/import/batch/:batchId', name: 'BatchDetail', component: () => import('@/views/BatchDetailView.vue'), meta: { title: '批次详情' } }, { path: '/preview/:batchId', name: 'Preview', component: () => import('@/views/PreviewView.vue'), meta: { title: '清洗预览' } }, { path: '/dedup', name: 'DedupReview', component: () => import('@/views/DedupReviewView.vue'), meta: { title: '去重处理', icon: 'mdi-content-duplicate' } }, { path: '/rules', name: 'RuleConfig', component: () => import('@/views/RuleConfigView.vue'), meta: { title: '规则管理', icon: 'mdi-cog-outline' } }, { path: '/tasks', name: 'ImportTask', component: () => import('@/views/ImportTaskView.vue'), meta: { title: '导入任务', icon: 'mdi-export' } }, { path: '/audit', name: 'AuditTrace', component: () => import('@/views/AuditTraceView.vue'), meta: { title: '数据审计', icon: 'mdi-history' } }, { path: '/settings', name: 'Settings', component: () => import('@/views/SettingsView.vue'), meta: { title: '系统设置', icon: 'mdi-tune' } } ] ``` --- ## 13. 非功能设计 ### 13.1 性能设计 **目标**:单次导入 1 万条记录在主流程内完成解析与清洗,去重计算应在 30 秒内完成。 | 优化策略 | 说明 | |----------|------| | 批量插入 | `raw_records` 和 `transactions` 使用 GORM `CreateInBatches`,每批 500 条 | | 关键索引 | `source_platform + source_record_id`、`batch_id`、`trade_time`、`order_id` | | 模糊去重分桶 | 按 `trade_time` 时间分桶,避免全表扫描 | | 规则预筛选 | 按平台和启用状态预加载规则,减少无效匹配 | | 异步处理 | ETL Pipeline 使用 goroutine 异步执行,前端轮询状态 | | 连接池 | SQLite 使用 WAL 模式提升并发读写性能 | #### SQLite 性能配置 ```go func initDB(dbPath string) *gorm.DB { db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) sqlDB, _ := db.DB() sqlDB.SetMaxOpenConns(1) // SQLite 单写 sqlDB.SetMaxIdleConns(10) // 启用 WAL 模式 db.Exec("PRAGMA journal_mode=WAL") db.Exec("PRAGMA synchronous=NORMAL") db.Exec("PRAGMA cache_size=-64000") // 64MB cache return db } ``` ### 13.2 安全设计 | 安全措施 | 说明 | |----------|------| | 本地部署 | 默认本地运行,敏感账单数据不上传云端 | | API Token 加密 | Firefly III API Token 使用 AES 加密存储 | | 审计日志脱敏 | 日志中账号、订单号局部遮罩(如 `138****1234`) | | 文件安全 | 上传文件限制类型和大小(默认最大 50MB) | | CORS 配置 | 仅允许本地来源访问 API | ### 13.3 可维护性设计 | 设计原则 | 说明 | |----------|------| | 解析器插件化 | 新增平台只需实现 `BillParser` 接口并注册 | | 规则条件 JSON 化 | 规则存储为 JSON,灵活扩展匹配条件 | | 导入器解耦 | Export 层独立,可替换下游目标(不限于 Firefly III) | | 分层 DTO/VO/Entity | Handler → DTO → Service → Entity → DAO,职责清晰 | | 事务分阶段 | 每个 ETL 阶段独立事务,避免超长事务 | ### 13.4 可追溯性设计 | 追溯能力 | 实现方式 | |----------|----------| | 任一导入结果 → 原始文件 | 通过 `transaction.raw_record_id → raw_record.source_file_id → source_file` | | 任一规则命中 → 解释说明 | 通过 `rule_hits` 表记录命中条件和前后字段值对比 | | 任一合并操作 → 判定依据 | 通过 `dedup_relations.reason_json` 和 `link_relations.reason_json` | | 任一操作 → 操作日志 | 通过 `audit_logs` 表记录实体变更和操作者信息 | ### 13.5 部署架构 ```mermaid graph TD subgraph "Docker 容器 / 本地部署" FE["前端静态资源
(Vue3 Build → /web)"] BE["后端服务
(Go Binary :8080)"] DB["SQLite 数据库
(/data/projectmoneyx.db)"] end subgraph "外部依赖" FF["Firefly III
(可选, API 推送)"] DI["Data Importer
(可选, API 推送)"] end FE -->|"嵌入式静态服务"| BE BE --> DB BE -->|"HTTP API"| FF BE -->|"HTTP API"| DI ``` **部署方式**: 1. **Docker 部署**(推荐):单容器包含前后端 + SQLite 2. **二进制部署**:交叉编译为单体可执行文件,前端资源使用 `embed` 嵌入 ```dockerfile # Dockerfile 示例 FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN go build -o projectmoneyx ./cmd/server FROM alpine:latest WORKDIR /app COPY --from=builder /app/projectmoneyx . COPY --from=builder /app/web ./web VOLUME /data EXPOSE 8080 CMD ["./projectmoneyx", "--db", "/data/projectmoneyx.db"] ``` --- ## 14. 关键实现建议(Go + GIN + GORM) ### 14.1 分层编码规范 | 层级 | 职责 | 规范 | |------|------|------| | `handler` | 仅做参数绑定、校验、返回 | 不包含业务逻辑,调用 service 方法 | | `service` | 编排业务流程 | 不直接操作数据库,调用 repository | | `domain/entity` | 领域实体定义 | 包含业务方法和校验逻辑 | | `domain/repository` | 仓储接口定义 | 仅定义接口,不含实现 | | `dao` | GORM 数据访问实现 | 实现 repository 接口 | | `parser` / `matcher` / `rule` / `exporter` | 独立可测试组件 | 纯函数风格,易于单元测试 | ### 14.2 推荐核心 Service 对象 ```go // 核心 Service 清单 type ImportBatchService struct { ... } // 批次管理 type PipelineService struct { ... } // ETL 流水线编排 type ParserRegistry struct { ... } // 解析器注册中心 type TransactionNormalizeService struct { ... } // 标准化服务 type DedupMatchService struct { ... } // 去重匹配服务 type TransferLinkService struct { ... } // 转账链路合并服务 type RuleApplyService struct { ... } // 规则应用服务 type FireflyExportService struct { ... } // Firefly 导出服务 type AuditTraceService struct { ... } // 审计追溯服务 ``` ### 14.3 事务边界设计 建议以下阶段分别事务化,避免超长事务导致 SQLite 锁定: ```mermaid flowchart LR T1["事务 1
文件入库 + 原始记录入库"] --> T2["事务 2
标准化结果落库"] T2 --> T3["事务 3
去重/链路关系落库"] T3 --> T4["事务 4
规则命中落库"] T4 --> T5["事务 5
导入结果落库"] ``` ```go // 事务示例 func (s *PipelineService) processNormalize(ctx context.Context, batchID string) error { return s.db.Transaction(func(tx *gorm.DB) error { // 在事务内完成标准化 + 批量入库 transactions := s.normalizer.NormalizeAll(ctx, rawRecords) return tx.CreateInBatches(transactions, 500).Error }) } ``` ### 14.4 错误处理策略 ```go // 统一错误码定义 const ( ErrCodeSuccess = 0 ErrCodeBadRequest = 40000 ErrCodeFileParseError = 40001 ErrCodeUnknownPlatform = 40002 ErrCodeDuplicateFile = 40003 ErrCodeRuleInvalid = 40004 ErrCodeExportFailed = 40005 ErrCodeInternal = 50000 ) // 全局错误处理中间件 func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) > 0 { err := c.Errors.Last() // 根据错误类型返回对应错误码和消息 c.JSON(http.StatusOK, Response{ Code: mapErrorCode(err), Message: err.Error(), }) } } } ``` ### 14.5 配置管理 ```go // config/config.go type Config struct { Server ServerConfig `yaml:"server"` Database DatabaseConfig `yaml:"database"` Firefly FireflyConfig `yaml:"firefly"` Dedup DedupConfig `yaml:"dedup"` } type ServerConfig struct { Port int `yaml:"port" default:"8080"` Mode string `yaml:"mode" default:"release"` // debug/release } type DatabaseConfig struct { Path string `yaml:"path" default:"./data/projectmoneyx.db"` } type FireflyConfig struct { BaseURL string `yaml:"base_url"` APIToken string `yaml:"api_token"` // AES 加密存储 ImporterURL string `yaml:"importer_url"` Enabled bool `yaml:"enabled"` } type DedupConfig struct { FuzzyTimeWindow int `yaml:"fuzzy_time_window" default:"5"` // 分钟 FuzzyThresholdHigh int `yaml:"fuzzy_threshold_high" default:"85"` // 自动判定 FuzzyThresholdLow int `yaml:"fuzzy_threshold_low" default:"60"` // 疑似判定 TransferTimeWindow int `yaml:"transfer_time_window" default:"30"` // 分钟 AmountEpsilon float64 `yaml:"amount_epsilon" default:"0.01"` // 金额容差 } ``` --- ## 15. 版本演进规划 ### 15.1 V1.0(MVP — 优先上线) ```mermaid gantt title V1.0 MVP 开发计划 dateFormat YYYY-MM-DD section 基础架构 项目骨架搭建 :a1, 2026-03-10, 3d 数据库表结构建模 :a2, after a1, 2d GIN 路由与中间件 :a3, after a2, 2d section 解析引擎 支付宝 Parser :b1, after a3, 3d 微信 Parser :b2, after b1, 3d 解析器注册中心 :b3, after a3, 1d section 核心流程 标准化引擎 :c1, after b2, 3d 严格去重 :c2, after c1, 2d 转账闭环合并 :c3, after c2, 3d 基础规则映射 :c4, after c3, 3d section 导入导出 CSV 导出 :d1, after c4, 2d Firefly API 推送 :d2, after d1, 3d 导入预览与确认 :d3, after d2, 2d section 前端 导入中心页面 :e1, after a3, 5d 清洗预览页面 :e2, after e1, 4d 规则配置页面 :e3, after e2, 4d 导入结果页面 :e4, after e3, 3d ``` **核心交付物**: - 支付宝 / 微信 Parser - 严格去重 + 转账闭环 - 基础规则映射(分类 / 账户) - 导入预览与确认 - CSV 导出 + Firefly API 推送 - 基础前端(导入 → 预览 → 确认 → 结果) ### 15.2 V1.5(增强版) | 功能 | 说明 | |------|------| | 建行 / 工行 Parser | 扩展银行账单支持 | | 模糊去重评分 | 多因子评分模型上线 | | 人工确认流程 | 疑似重复的人工确认队列 | | 商户别名归一化 | 商户别名库与归一化规则 | | 批次管理增强 | 批次列表、详情、删除 | | 手动合并/拆分 | 支持用户手动操作交易链路 | | 标签映射 | 规则引擎支持标签维度 | ### 15.3 V2.0(进阶版) | 功能 | 说明 | |------|------| | 京东 / 美团 Parser | 电商与生活服务平台支持 | | 订单链路增强 | 京东订单 + 微信支付链路聚合 | | 多币种支持 | 加密货币交易所流水适配 | | 可视化规则调试 | 规则测试与命中预览 | | 简易统计报表 | 导入概览、分类占比、来源分布 | | 对账可视化 | 孤儿记录展示 + 手动干预 | | 多账本支持 | 对接多个 Firefly III 实例 | --- ## 16. 结论 ProjectMoneyX 不是一个"简单导入工具",而是一个面向 Firefly III 的**本地账单数据治理中台**。 ### 核心价值 > 1. **汇聚多源账单** — 一站式接入支付平台、银行、生活服务、交易所 > 2. **统一交易语义** — 屏蔽平台差异,建立标准化交易模型 > 3. **智能去重合并** — 消除重复,还原真实交易链路与转账闭环 > 4. **本地规则沉淀** — 分类/账户/标签映射可长期积累、可解释、可迁移 > 5. **无缝对接导入** — 丝滑推送至 Firefly III / Data Importer ### 核心架构决策 | 决策 | 理由 | |------|------| | **插件化 Adapter 架构** | 平台差异收敛在 Adapter 层,格式变更只需更新对应 Parser | | **支付宝分类字典作为统一基准** | 支付宝 22 种分类最丰富,其他平台映射到此体系 | | **微信"交易类型 + 商品"联合推断** | 微信交易类型粗粒度,需结合商品字段推断实际消费分类 | | **规则映射以本地为主** | 规则可沉淀、可解释、可迁移,不绑定在 Data Importer 实例 | | **三层递进去重策略** | 严格 → 模糊 → 链路合并,兼顾准确性与覆盖率 | | **SQLite 作为核心数据底座** | 不仅是缓存,更是清洗结果与审计链路的持久化存储 | | **ETL 分阶段事务** | 避免超长事务,每个阶段独立可重试 | ### 技术栈总结 ``` ┌─────────────────────────────────────────────────────┐ │ 前端: Vue3 + TypeScript + Vuetify (Material Design) │ ├─────────────────────────────────────────────────────┤ │ 后端: Golang + GIN (RESTful API) + GORM (ORM) │ ├─────────────────────────────────────────────────────┤ │ 数据库: SQLite (WAL 模式, 本地优先) │ ├─────────────────────────────────────────────────────┤ │ 部署: Docker 容器化 / 跨平台二进制单体 │ └─────────────────────────────────────────────────────┘ ```