# AI 数据清洗接口 **本文引用的文件 ** - [AiCleanController.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java) - [AiCleanSynonymController.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java) - [CleanErrorLogController.java](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java) - [AiCleanService.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java) - [AiCleanSynonymService.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java) - [AiCleanTemplateVectorIndex.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java) - [AiCleanStatusEnum.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStatusEnum.java) - [AiCleanExitEnum.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanExitEnum.java) - [AiCleanStageEnum.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStageEnum.java) - [AiRejectReason.java](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiRejectReason.java) - [CleanErrorLog.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/entity/CleanErrorLog.java) - [CleanErrorLogQuery.java](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/query/CleanErrorLogQuery.java) - [ai_clean.sql](file://sql/ai_clean.sql) - [ai_clean_synonym_init.sql](file://sql/ai_clean_synonym_init.sql) ## 目录 1. [简介](#简介) 2. [项目结构与接口范围](#项目结构与接口范围) 3. [核心组件与数据流](#核心组件与数据流) 4. [接口总览](#接口总览) 5. [/aiClean 智能清洗接口](#aiclean-智能清洗接口) 6. [/aiClean/synonym 同义词接口](#aicleansynonym-同义词接口) 7. [/cleanLog CleanErrorLogController 错误日志接口](#cleangelog-cleannerorlogcontroller-错误日志接口) 8. [清洗流程与 pgvector、DuckDB 的关系](#清洗流程与-pgvector-duckdb-的关系) 9. [失败定位与排查指南](#失败定位与排查指南) 10. [性能与限制](#性能与限制) 11. [结论](#结论) ## 简介 本文件说明 AI 数据清洗链路的 REST 接口,覆盖: - `/aiClean`:AI 清洗任务提交、执行、撤销、行拒绝明细、动态表分页、模板向量索引状态与重建。 - `/aiClean/synonym`:表头同义词库的查询、新增、修改、删除、启用/停用。 - `/cleanLog`(由 `CleanErrorLogController` 暴露):清洗错误日志分页查询。 这些接口共同支撑以下能力: - 上传 CSV/Excel 后,对未匹配既有模板的 sheet 进行 AI 判定和清洗。 - 将命中既有模板的数据走原有清洗链路,把没有合适模板的数据写入 AI 动态表。 - 用 pgvector 维护模板语义向量索引,辅助模板候选召回。 - 用 DuckDB 临时数据源完成结构探测、列类型推断、规则试跑与批量清洗。 - 记录每行的清洗拒绝原因,并通过错误日志表辅助定位问题。 ## 项目结构与接口范围 AI 清洗相关接口集中在三个控制器中: - `AiCleanController`:挂在 `/aiClean`,是独立于 `/dm`、`/govern` 的新链路。 - `AiCleanSynonymController`:挂在 `/aiClean/synonym`,管理评分归一用的词对。 - `CleanErrorLogController`:挂在 `/cleanLog`,提供清洗错误日志分页查询。 ```mermaid graph TB Client["前端或外部调用方"] --> AC["AiCleanController
/aiClean/*"] Client --> ASC["AiCleanSynonymController
/aiClean/synonym/*"] Client --> CEL["CleanErrorLogController
/cleanLog/list"] AC --> ACS["AiCleanService"] AC --> TVI["AiCleanTemplateVectorIndex"] AC --> JOB["AiCleanJobMapper
ai_clean_job"] ASC --> ASS["AiCleanSynonymService"] ASS --> DB["PostgreSQL
ai_clean_synonym"] CEL --> ELQ["CleanErrorLogQuery"] ELQ --> ELM["CleanErrorLogMapper
clean_error_log"] subgraph "AI 清洗存储" JOB DB ELM end ``` **图示来源** - [AiCleanController.java:1-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L1-L138) - [AiCleanSynonymController.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L1-L70) - [CleanErrorLogController.java:1-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L1-L30) **章节来源** - [AiCleanController.java:1-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L1-L138) - [AiCleanSynonymController.java:1-70](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L1-L70) - [CleanErrorLogController.java:1-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L1-L30) ## 核心组件与数据流 ### 控制器层 | 组件 | 职责 | |---|---| | `AiCleanController` | 暴露智能清洗任务提交、同步执行、任务列表、撤销、拒绝明细、动态表分页、模板向量索引状态与重建接口。 | | `AiCleanSynonymController` | 暴露同义词词条列表、创建、更新、删除、启用/停用接口。 | | `CleanErrorLogController` | 暴露清洗错误日志分页查询接口。 | ### 服务层 | 组件 | 职责 | |---|---| | `AiCleanService` | 实现 S1→S7 主流程:探测结构、匹配模板、生成规则、校验置信度、走既有链路或写 AI 动态表、记录任务与拒绝明细。 | | `AiCleanSynonymService` | 管理 `ai_clean_synonym` 表,校验“同一归一化词全局唯一”“不做链式传递”,并在事务提交后重建全局缓存。 | | `AiCleanTemplateVectorIndex` | 把既有模板和 AI 模板的结构元数据嵌入为向量,写入 pgvector 表,供模板语义召回与 Agent 选表使用。 | ### 数据层 | 表 | 用途 | |---|---| | `ai_clean_template` | AI 模板定义,含中文名、物理表名、表头指纹、分类、版本、置信度等。 | | `ai_clean_field` | AI 模板字段,决定 DuckDB 动态表 DDL 与前端展示列。 | | `ai_clean_rule` | 清洗函数链快照,以枚举 hint JSON 形式保存。 | | `ai_clean_job` | 单个 sheet/block 的清洗任务轨迹,含状态、出口、token 成本、行统计、失败原因。 | | `ai_clean_batch` | 智能清洗批次状态机:JUDGING → CLEANING_EXISTING → LOADING_AI → DONE/PARTIAL。 | | `ai_clean_synonym` | 表头同义词库,用于评分归一和绑定域映射。 | | `clean_error_log` | 清洗失败的错误日志,按 batchId/fileId/sheetId 等过滤。 | **章节来源** - [AiCleanService.java:1-260](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L1-L260) - [AiCleanSynonymService.java:1-178](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L1-L178) - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) - [ai_clean.sql:1-214](file://sql/ai_clean.sql#L1-L214) - [ai_clean_synonym_init.sql:1-42](file://sql/ai_clean_synonym_init.sql#L1-L42) ## 接口总览 ### /aiClean | HTTP 方法 | URL | 主要参数 | 响应体 | 说明 | |---|---|---|---|---| | POST | `/aiClean/submit` | `fileId` | `Result` | 为未匹配模板的 sheet 建 AI 清洗任务,幂等。 | | POST | `/aiClean/run` | `jobId` | `Result` | 同步执行一个清洗任务。 | | GET | `/aiClean/jobs` | `fileId`(可选) | `Result>` | 当前案件下的任务列表。 | | POST | `/aiClean/revert` | `jobId` | `Result` | 撤销该 job 在 AI 动态表中的数据。 | | GET | `/aiClean/job/rejections` | `jobId`、`reason`(可选)、`page`、`size` | `Result>` | 分页返回行级拒绝明细。 | | GET | `/aiClean/data/page` | `aiTemplateId`、`orderKey`(可选)、`asc`、`page`、`size` | `Result>` | 分页读取 AI 动态表数据。 | | POST | `/aiClean/refreshTree` | 无或任意 body | `Result` | 刷新当前案件的 AI 治理树节点。 | | GET | `/aiClean/templateVector/status` | 无 | `Result` | 查看向量索引是否可用、维度、文档数。 | | POST | `/aiClean/templateVector/rebuild` | 无 | `Result` | 全量重建模板向量索引。 | ### /aiClean/synonym | HTTP 方法 | URL | 请求体 | 响应体 | 说明 | |---|---|---|---|---| | GET | `/aiClean/synonym/list` | 无 | `Result>` | 列出全部词条,含停用。 | | POST | `/aiClean/synonym/create` | `AiCleanSynonym` | `Result` | 新增词对。 | | POST | `/aiClean/synonym/update` | `AiCleanSynonym` | `Result` | 修改词对。 | | POST | `/aiClean/synonym/delete` | `{id}` | `Result` | 删除词对。 | | POST | `/aiClean/synonym/toggle` | `{id, status}` | `Result` | 启用或停用词条。 | ### /cleanLog | HTTP 方法 | URL | 查询参数 | 响应体 | 说明 | |---|---|---|---|---| | GET | `/cleanLog/list` | `id`、`batchId`、`fileId`、`sheetId`、`page`、`limit` | `Result>` | 分页查询清洗错误日志。 | **章节来源** - [AiCleanController.java:39-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L39-L138) - [AiCleanSynonymController.java:26-70](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L26-L70) - [CleanErrorLogController.java:15-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L15-L30) ## /aiClean 智能清洗接口 ### POST /aiClean/submit 为上传文件中尚未匹配到既有模板的 sheet 创建 AI 清洗任务。 - **HTTP 方法**:POST - **URL**:`/aiClean/submit` - **请求参数**: - `fileId`:必填,Long,表示已上传文件的根记录 id。 - **请求方式**:表单参数。 - **响应体**:`Result`,表示新建的任务数量。 - **行为**: - 查找 `pid=fileId` 且未绑定模板的 sheet。 - 若某个 sheet 已有对应任务,则跳过;并发重复提交会因唯一键冲突被忽略。 - 新任务初始状态为 `PENDING`。 ```mermaid sequenceDiagram participant FE as "前端" participant Ctrl as "AiCleanController" participant Svc as "AiCleanService" participant Job as "ai_clean_job" FE->>Ctrl: POST /aiClean/submit?fileId=xxx Ctrl->>Svc: submitUnmatched(caseId, fileId) Svc->>Job: 插入 PENDING 任务 Job-->>Svc: 成功或唯一键冲突 Svc-->>Ctrl: 新建任务数 Ctrl-->>FE: Result ``` **图示来源** - [AiCleanController.java:52-63](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L52-L63) - [AiCleanService.java:118-156](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L118-L156) - [ai_clean.sql:110-148](file://sql/ai_clean.sql#L110-L148) **调用示例** - curl: ``` curl -X POST "http://host/aiClean/submit?fileId=123" ``` - 前端 FormData: ```js const fd = new FormData() fd.append('fileId', '123') fetch('/aiClean/submit', { method: 'POST', body: fd }) ``` **常见错误** - `fileId` 不存在:不会创建任务,返回新建数为 0。 - 并发重复提交:只记录一次任务,不重复计数。 **章节来源** - [AiCleanController.java:52-63](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L52-L63) - [AiCleanService.java:118-156](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L118-L156) - [ai_clean.sql:110-148](file://sql/ai_clean.sql#L110-L148) ### POST /aiClean/run 同步执行一个 AI 清洗任务。模型调用可能耗时数秒,批量场景应轮询任务列表而非逐个等待。 - **HTTP 方法**:POST - **URL**:`/aiClean/run` - **请求参数**: - `jobId`:必填,Long。 - **响应体**:`Result`,包含状态、出口、置信度、token 成本、行统计、失败原因等。 - **权限**:内部通过 `caseAccessGuard` 校验任务归属当前案件。 ```mermaid sequenceDiagram participant FE as "前端" participant Ctrl as "AiCleanController" participant Svc as "AiCleanService" participant Job as "ai_clean_job" FE->>Ctrl: POST /aiClean/run?jobId=xxx Ctrl->>Svc: run(jobId) Svc->>Svc: 探测结构、匹配模板、生成规则、校验 alt 命中既有模板 Svc->>Svc: 走既有 doClean Svc->>Job: 标记 DISPATCHED_A 或 A2/A3 else 无模板 Svc->>Svc: 写 AI 动态表 Svc->>Job: 标记 LOADING/SUCCESS end Svc-->>Ctrl: AiCleanJob Ctrl-->>FE: Result ``` **图示来源** - [AiCleanController.java:65-71](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L65-L71) - [AiCleanService.java:198-230](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L198-L230) - [AiCleanExitEnum.java:1-56](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanExitEnum.java#L1-L56) **调用示例** - curl: ``` curl -X POST "http://host/aiClean/run?jobId=456" ``` **失败处理** - 异常时任务状态设为 `FAIL`,并记录最多 900 字符的失败原因。 **章节来源** - [AiCleanController.java:65-71](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L65-L71) - [AiCleanService.java:198-230](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L198-L230) - [AiCleanStatusEnum.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStatusEnum.java#L1-L76) ### GET /aiClean/jobs 查询当前案件下的 AI 清洗任务列表,可按 `fileId` 过滤。 - **HTTP 方法**:GET - **URL**:`/aiClean/jobs` - **请求参数**: - `fileId`:可选,Long。 - **响应体**:`Result>`。 - **排序**:按创建时间倒序。 - **权限**:跨案件读取会被拒绝。 **章节来源** - [AiCleanController.java:73-82](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L73-L82) - [ai_clean.sql:110-148](file://sql/ai_clean.sql#L110-L148) ### POST /aiClean/revert 撤销某个 job 对应的 AI 动态表数据。 - **HTTP 方法**:POST - **URL**:`/aiClean/revert` - **请求参数**: - `jobId`:必填,Long。 - **响应体**:`Result`。 **章节来源** - [AiCleanController.java:84-87](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L84-L87) ### GET /aiClean/job/rejections 分页获取某个 job 的行级失败明细。 - **HTTP 方法**:GET - **URL**:`/aiClean/job/rejections` - **请求参数**: - `jobId`:必填,Long。 - `reason`:可选,String,按拒绝原因过滤。 - `page`:默认 1。 - `size`:默认 20。 - **响应体**:`Result>`,结构包含分页字段 `records`、`total`、`size`、`current`;每条记录包含行号、阻塞列、原因、原值预览、该行全部列错误信息。 - **权限**:跨案件查询会被拒绝。 **拒绝原因枚举** | 原因 | 含义 | 是否整行拒绝 | |---|---|---| | `REQUIRED_EMPTY` | 必填列为空 | 是 | | `REQUIRED_TYPE_INVALID` | 必填列类型解析失败 | 是 | | `REQUIRED_FUNC_ERROR` | 必填列清洗函数执行异常 | 是 | | `REQUIRED_LENGTH_OVERFLOW` | 必填列超长或精度溢出 | 是 | | `OPTIONAL_TYPE_INVALID` | 非必填列类型不合法,单元格置空 | 否 | | `OPTIONAL_FUNC_ERROR` | 非必填列函数执行失败,单元格置空 | 否 | | `OPTIONAL_LENGTH_OVERFLOW` | 非必填列超长或精度溢出,单元格置空 | 否 | | `WRITER_ERROR` | 写入数据库失败 | 是 | ```mermaid flowchart TD Start["进入 rejections"] --> LoadJob["加载 job"] LoadJob --> CheckCase["校验案件归属"] CheckCase --> BuildQuery["构建分页查询"] BuildQuery --> FilterReason{"是否指定 reason?"} FilterReason -->|是| ApplyReason["按 AiRejectReason 过滤"] FilterReason -->|否| SkipReason["不过滤"] ApplyReason --> Page["分页取数"] SkipReason --> Page Page --> MapRows["映射为 records + total + size + current"] MapRows --> Return["返回 Result>"] ``` **图示来源** - [AiCleanController.java:89-100](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L89-L100) - [AiRejectReason.java:1-49](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiRejectReason.java#L1-L49) **调用示例** - curl: ``` curl "http://host/aiClean/job/rejections?jobId=456&reason=REQUIRED_TYPE_INVALID&page=1&size=20" ``` **章节来源** - [AiCleanController.java:89-100](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L89-L100) - [AiRejectReason.java:1-49](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiRejectReason.java#L1-L49) ### GET /aiClean/data/page 分页读取 AI 动态表数据,治理树上 `id<0` 的节点通常走此接口。 - **HTTP 方法**:GET - **URL**:`/aiClean/data/page` - **请求参数**: - `aiTemplateId`:必填,Long,指向 `ai_clean_template.id`。 - `orderKey`:可选,String,排序列键。 - `asc`:默认 `true`,Boolean,升序或降序。 - `page`:默认 1。 - `size`:默认 20。 - **响应体**:`Result>`,结构类似治理树分页,包含 `records`、`total`、`size`、`current`。 **章节来源** - [AiCleanController.java:102-110](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L102-L110) ### POST /aiClean/refreshTree 手动重建当前案件的 AI 治理节点,幂等。 - **HTTP 方法**:POST - **URL**:`/aiClean/refreshTree` - **请求体**:可选,任意字符串 body。 - **响应体**:`Result`。 **章节来源** - [AiCleanController.java:112-116](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L112-L116) ### GET /aiClean/templateVector/status 查看 AI 模板向量索引状态。 - **HTTP 方法**:GET - **URL**:`/aiClean/templateVector/status` - **响应体**:`Result`,可观测是否可用、使用的嵌入模型、向量维度、当前文档数。 **章节来源** - [AiCleanController.java:118-123](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L118-L123) - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) ### POST /aiClean/templateVector/rebuild 全量重建 AI 模板向量索引。 - **HTTP 方法**:POST - **URL**:`/aiClean/templateVector/rebuild` - **响应体**:`Result`,表示实际写入的文档数。 - **说明**: - 适用于存量模板补建、更换嵌入模型后维度变化导致旧向量表不可用。 - 同步执行;模板量级通常为几百到几千,首次或换模型时才需要。 - 调用方应按接口超时预期处理。 **章节来源** - [AiCleanController.java:125-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L125-L138) - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) ## /aiClean/synonym 同义词接口 ### GET /aiClean/synonym/list 列出全部同义词词条,包括停用的,按标准词排序。 - **HTTP 方法**:GET - **URL**:`/aiClean/synonym/list` - **响应体**:`Result>`。 **章节来源** - [AiCleanSynonymController.java:37-41](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L37-L41) - [AiCleanSynonymService.java:47-63](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L47-L63) ### POST /aiClean/synonym/create 新增词对。 - **HTTP 方法**:POST - **URL**:`/aiClean/synonym/create` - **请求体**:`AiCleanSynonym`,至少包含 `stdWord`、`aliasWord`、`remark`,可选 `status`。 - **响应体**:`Result`。 - **校验规则**: - 标准词与别名不能为空。 - 标准词与别名不能相同。 - 同一归一化词在全表只能出现一次,避免歧义。 - 不做链式传递:别名必须直接指向标准词。 - 若无 `createBy`,自动填充当前用户。 ```mermaid flowchart TD Start["接收 AiCleanSynonym"] --> Trim["去除首尾空白"] Trim --> Validate["校验 stdWord/aliasWord 非空且不等于"] Validate --> Unique{"全局唯一?"} Unique -->|否| Error["抛出 ServerException"] Unique -->|是| Insert["插入 ai_clean_synonym"] Insert --> Refresh["事务提交后重建 GlobalCache"] Refresh --> Return["返回 Result"] ``` **图示来源** - [AiCleanSynonymController.java:43-51](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L43-L51) - [AiCleanSynonymService.java:65-86](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L65-L86) **调用示例** - curl: ``` curl -X POST "http://host/aiClean/synonym/create" \ -H "Content-Type: application/json" \ -d '{"stdWord":"交易金额","aliasWord":"发生额","remark":"自定义映射"}' ``` - 前端 FormData: ```js const fd = new FormData() fd.append('stdWord', '交易金额') fd.append('aliasWord', '发生额') fd.append('remark', '自定义映射') fetch('/aiClean/synonym/create', { method: 'POST', body: fd }) ``` **章节来源** - [AiCleanSynonymController.java:43-51](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L43-L51) - [AiCleanSynonymService.java:65-86](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L65-L86) - [ai_clean_synonym_init.sql:1-42](file://sql/ai_clean_synonym_init.sql#L1-L42) ### POST /aiClean/synonym/update 修改词对。 - **HTTP 方法**:POST - **URL**:`/aiClean/synonym/update` - **请求体**:`AiCleanSynonym`,`id` 必填,其他字段按需更新。 - **响应体**:`Result`。 - **行为**:更新后重建全局缓存,改词即时生效,无需重启。 **章节来源** - [AiCleanSynonymController.java:53-59](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L53-L59) - [AiCleanSynonymService.java:88-114](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L88-L114) ### POST /aiClean/synonym/delete 删除词对。 - **HTTP 方法**:POST - **URL**:`/aiClean/synonym/delete` - **请求体**:`{id}`。 - **响应体**:`Result`。 **章节来源** - [AiCleanSynonymController.java:61-66](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L61-L66) - [AiCleanSynonymService.java:116-123](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L116-L123) ### POST /aiClean/synonym/toggle 启用或停用词条。 - **HTTP 方法**:POST - **URL**:`/aiClean/synonym/toggle` - **请求体**:`{id, status}`。 - **响应体**:`Result`。 - **说明**:停用词条仍保留在表中,但不再参与评分和绑定域。 **章节来源** - [AiCleanSynonymController.java:68-70](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanSynonymController.java#L68-L70) - [AiCleanSynonymService.java:125-142](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanSynonymService.java#L125-L142) ## /cleanLog CleanErrorLogController 错误日志接口 ### GET /cleanLog/list 分页查询清洗错误日志。 - **HTTP 方法**:GET - **URL**:`/cleanLog/list` - **查询参数**: - `id`:可选,Long,按日志主键过滤。 - `batchId`:可选,Long,按智能清洗批次过滤。 - `fileId`:可选,Long,按文件过滤。 - `sheetId`:可选,Long,按 sheet 过滤。 - `page`、`limit`:来自父类 `Query`,MyBatis-Plus 分页参数。 - **响应体**:`Result>`。 - **排序**:按 `createDateTime` 升序。 **实体字段** | 字段 | 类型 | 说明 | |---|---|---| | `id` | Long | 主键。 | | `fileId` | Long | 文件 id。 | | `sheetId` | Long | sheet id。 | | `batchId` | Long | 智能清洗批次 id。 | | `fileName` | String | 文件名。 | | `sheetName` | String | sheet 名称。 | | `content` | String | 错误内容。 | | `createDateTime` | LocalDateTime | 创建时间。 | ```mermaid classDiagram class CleanErrorLogController { +list(query) } class CleanErrorLogQuery { +id +batchId +fileId +sheetId } class CleanErrorLog { +id +fileId +sheetId +batchId +fileName +sheetName +content +createDateTime } CleanErrorLogController --> CleanErrorLogQuery : "接收查询参数" CleanErrorLogController --> CleanErrorLog : "返回分页结果" ``` **图示来源** - [CleanErrorLogController.java:15-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L15-L30) - [CleanErrorLogQuery.java:1-14](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/query/CleanErrorLogQuery.java#L1-L14) - [CleanErrorLog.java:1-41](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/entity/CleanErrorLog.java#L1-L41) **调用示例** - curl: ``` curl "http://host/cleanLog/list?batchId=100&fileId=123&page=1&limit=20" ``` **章节来源** - [CleanErrorLogController.java:15-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L15-L30) - [CleanErrorLogQuery.java:1-14](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/query/CleanErrorLogQuery.java#L1-L14) - [CleanErrorLog.java:1-41](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/entity/CleanErrorLog.java#L1-L41) ## 清洗流程与 pgvector、DuckDB 的关系 ### 整体流程 AI 清洗主流程由 `AiCleanService` 驱动,大致分为: 1. 探测 sheet 结构,提取表头、表块。 2. 使用指纹、打分、同义词和 LLM 匹配既有模板或生成 AI 模板。 3. 根据出口决定数据去向: - 出口 A/A2:走既有 `doClean`,写入业务表。 - 出口 A3/B:新建 AI 模板和 DuckDB 动态表,写入 `ai_clean_{id}`。 4. 记录任务状态、token 成本、行统计、拒绝原因。 5. 通过 `/aiClean/job/rejections` 和 `/cleanLog/list` 暴露失败细节。 ```mermaid flowchart TD Upload["上传 CSV/Excel"] --> Probe["结构探测
SheetProbe"] Probe --> Match["模板匹配
HeaderScorer + HeaderFingerprint + LLM"] Match --> Decision{"命中既有模板?"} Decision -->|是| Existing["出口 A/A2/A3
既有业务表或降级 AI 表"] Decision -->|否| NewTpl["出口 B
新建 AI 模板 + DuckDB 动态表"] Existing --> Stats["记录任务统计"] NewTpl --> Stats Stats --> Rejects["记录行级拒绝"] Rejects --> Log["写入 clean_error_log"] ``` **图示来源** - [AiCleanService.java:1-260](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L1-L260) - [AiCleanExitEnum.java:1-56](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanExitEnum.java#L1-L56) - [ai_clean.sql:1-214](file://sql/ai_clean.sql#L1-L214) ### pgvector 的作用 `AiCleanTemplateVectorIndex` 负责把既有模板和 AI 模板的结构元数据嵌入为向量,写入 pgvector 表。它的作用是: - 为模板语义召回提供候选集合。 - 为 Agent 的 `search_table_schema` 工具提供自然语言选表能力。 - 启动时可自举重建;也可通过 `/aiClean/templateVector/rebuild` 手动触发。 - 向量索引不可用时走 fail-open:不阻断模板指纹、打分、LLM 主链路。 ```mermaid graph TB Tpl["ai_clean_template / table_info"] --> Embed["EmbeddingProvider"] Embed --> PgVec["pgvector 表
rag_tpl_d{dims}"] PgVec --> Recall["模板候选召回"] PgVec --> Agent["Agent 选表"] ``` **图示来源** - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) - [ai_clean.sql:1-60](file://sql/ai_clean.sql#L1-L60) ### DuckDB 的作用 DuckDB 作为本地临时数据源,承担: - 结构探测:读取原始 CSV/Excel,识别表头、合并单元格、多表块。 - 列类型推断:根据采样数据推断字段类型。 - 规则试跑:用清洗函数链验证模板可信度。 - 批量清洗:将清洗后的数据写入 AI 动态表或既有业务表。 ```mermaid graph TB File["CSV/Excel 文件"] --> Loader["AiDuckDb / 数据加载器"] Loader --> Schema["推断 DuckDB 表结构"] Schema --> Verify["规则校验与置信度评估"] Verify --> Writer["写入 AI 动态表或既有表"] ``` **图示来源** - [AiCleanService.java:1-260](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L1-L260) **章节来源** - [AiCleanService.java:1-260](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/service/AiCleanService.java#L1-L260) - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) - [ai_clean.sql:1-214](file://sql/ai_clean.sql#L1-L214) ## 失败定位与排查指南 ### 模板不匹配 **现象** - 任务状态长期停留在 `MATCHING` 或 `NEED_REVIEW`。 - 出口不是 A/A2,而是 B 或待人工确认。 **排查步骤** 1. 调用 `/aiClean/templateVector/status`,确认向量索引是否可用。 2. 若向量索引不可用,调用 `/aiClean/templateVector/rebuild` 重建。 3. 检查同义词配置是否遗漏必要映射。 4. 观察 `detail`、`confidence`、`model_name`、`llm_calls` 字段,判断是否需要人工调整模板。 **章节来源** - [AiCleanController.java:118-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L118-L138) - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) - [AiCleanStatusEnum.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStatusEnum.java#L1-L76) ### 字段映射错误 **现象** - 行级拒绝中出现 `REQUIRED_TYPE_INVALID`、`OPTIONAL_TYPE_INVALID`、`REQUIRED_LENGTH_OVERFLOW` 等。 - 部分列被置空,或整行被拒绝。 **排查步骤** 1. 调用 `/aiClean/job/rejections?jobId=xxx`,查看具体行号和列错误。 2. 根据 `AiRejectReason.label()` 理解中文错误描述。 3. 检查 `ai_clean_field` 中的 `field_type`、`field_len`、`required` 是否与真实数据一致。 4. 如需修正字段映射,重新生成模板或人工确认。 **章节来源** - [AiCleanController.java:89-100](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L89-L100) - [AiRejectReason.java:1-49](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiRejectReason.java#L1-L49) - [ai_clean.sql:60-110](file://sql/ai_clean.sql#L60-L110) ### 任务失败 **现象** - 任务状态为 `FAIL`,`failReason` 有失败原因。 - 智能清洗批次最终为 `PARTIAL` 或 `FAILED`。 **排查步骤** 1. 查看 `/aiClean/jobs` 中该 job 的 `status`、`exit_code`、`fail_reason`。 2. 查看智能清洗批次阶段:JUDGING、CLEANING_EXISTING、LOADING_AI。 3. 若失败发生在既有链路,检查业务表写入逻辑。 4. 若失败发生在 AI 动态表写入,检查 DuckDB 临时数据和 `clean_error_log`。 **章节来源** - [AiCleanStatusEnum.java:1-76](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStatusEnum.java#L1-L76) - [AiCleanStageEnum.java:1-52](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/enums/AiCleanStageEnum.java#L1-L52) - [ai_clean.sql:148-214](file://sql/ai_clean.sql#L148-L214) ### 向量索引异常 **现象** - `/aiClean/templateVector/status` 显示不可用。 - 模板语义召回结果为空。 - 重建接口长时间无响应或报错。 **排查步骤** 1. 确认 pgvector 扩展已安装,向量模型已配置。 2. 调用 `/aiClean/templateVector/status` 查看 unavailable 原因。 3. 调用 `/aiClean/templateVector/rebuild` 全量重建。 4. 观察应用日志,向量索引构建过程是 fail-open,失败不会阻断上传和清洗主流程。 **章节来源** - [AiCleanTemplateVectorIndex.java:1-220](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/index/AiCleanTemplateVectorIndex.java#L1-L220) - [AiCleanController.java:118-138](file://ai-server/src/main/java/com/zsjz/ai/module/aiclean/api/AiCleanController.java#L118-L138) ### 错误日志定位 **建议顺序** 1. 先通过 `/aiClean/job/rejections` 定位具体 job 的具体行。 2. 再通过 `/cleanLog/list` 按 `batchId`、`fileId`、`sheetId` 拉取更宽的错误日志。 3. 结合 `ai_clean_job` 的状态、出口、token 成本和失败原因综合判断。 4. 如果是同义词影响评分,检查 `/aiClean/synonym/list` 是否缺少必要映射。 **章节来源** - [CleanErrorLogController.java:15-30](file://ai-server/src/main/java/com/zsjz/ai/module/dm/controller/CleanErrorLogController.java#L15-L30) - [CleanErrorLogQuery.java:1-14](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/query/CleanErrorLogQuery.java#L1-L14) - [CleanErrorLog.java:1-41](file://ai-server/src/main/java/com/zsjz/ai/common/model/dm/entity/CleanErrorLog.java#L1-L41) ## 性能与限制 - `/aiClean/run` 是同步接口,模型调用可能耗时数秒,批量场景应轮询 `/aiClean/jobs`。 - `/aiClean/templateVector/rebuild` 是全量重建,适合首次上线或换嵌入模型时使用。 - 同义词变更通过事务提交后重建全局缓存,避免重启,但缓存重建失败仅记日志,不影响主流程。 - 向量索引构建失败采用 fail-open,不会阻断上传、模板匹配和数据清洗。 - 行级拒绝默认只打前 N 条应用日志,完整明细在 `ai_clean_reject` 表和 `/aiClean/job/rejections` 中。 [本节为通用性能说明,不直接分析特定代码文件] ## 结论 AI 数据清洗接口通过 `/aiClean`、`/aiClean/synonym` 和 `/cleanLog` 三条路径,分别承担智能清洗任务控制、表头同义词管理和清洗错误日志查询职责。系统以 PostgreSQL 作为持久化主库,以 DuckDB 作为临时计算引擎,以 pgvector 作为模板语义索引后端。对外暴露的分页字段统一遵循 `records`、`total`、`size`、`current` 约定,错误定位则依赖 `AiCleanJob`、`ai_clean_batch`、`ai_clean_reject` 和 `clean_error_log` 四类数据。