业务数据模块.md 30 KB

业务数据模块

本文引用的文件

  • CaseInfo.java
  • CaseDataSourceRegistry.java
  • CaseRoutingDataSource.java
  • PersonBasicInfoService.java
  • CallRecordService.java
  • TransRecordService.java
  • GovernTreeService.java
  • SearchQueryService.java
  • SearchDocIndexService.java
  • DmService.java
  • TimeSeriesService.java

目录

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 典型跨模块查询场景
  8. 性能优化建议
  9. 常见问题排查
  10. 结论

引言

本文件面向调查分析场景,汇总 person、call、trans、track、govern、search、dm、otg、external 等模块的职责与协作方式。这些模块共同围绕“案件数据源”组织数据:以 CaseInfo 描述案件元信息,通过 CaseDataSourceRegistry 和 CaseRoutingDataSource 把当前用户打开的案件路由到对应 DuckDB 库;同时由 dm 模块负责文件解析、清洗并落库,govern 提供治理树统一入口,search 提供全文检索增强,otg 提供时序和特殊日期能力,external 映射外部表。底层 MyBatis-Plus 访问 PostgreSQL/pgvector(平台、向量索引)以及各案件的 DuckDB 数据。

项目结构

后端代码集中在 ai-server 中,按领域划分为多个 module:

  • 公共层:common 下包含案件上下文、数据源路由、缓存、常量、异常、基础实体等。
  • 业务模块:person、call、trans、track、govern、search、dm、otg、external。
  • 平台服务:plat 提供案件管理、系统配置、用户、健康检查等。
  • AI 和数据清洗相关:aiclean、agent、knowledge 等作为辅助能力。

    graph TB
    subgraph "公共基础设施"
        CaseInfo["案件元信息<br/>CaseInfo"]
        Registry["案件数据源注册表<br/>CaseDataSourceRegistry"]
        Routing["动态数据源路由<br/>CaseRoutingDataSource"]
        Context["案件上下文<br/>CaseContextHolder"]
    end
    
    subgraph "业务模块"
        Person["人员模块<br/>person"]
        Call["通话模块<br/>call"]
        Trans["交易模块<br/>trans"]
        Track["轨迹模块<br/>track"]
        Govern["治理模块<br/>govern"]
        Search["搜索模块<br/>search"]
        Dm["数据管理模块<br/>dm"]
        Otg["时序模块<br/>otg"]
        External["外部表模块<br/>external"]
    end
    
    subgraph "数据存储"
        PG["PostgreSQL / pgvector"]
        DuckDB["DuckDB 案件库"]
        ES["Elasticsearch"]
    end
    
    Person --> Governance["治理树<br/>GovernTreeService"]
    Call --> Governance
    Trans --> Governance
    Track --> Governance
    External --> Governance
    Governance --> DuckDB
    Person --> DuckDB
    Call --> DuckDB
    Trans --> DuckDB
    Track --> DuckDB
    External --> DuckDB
    Dm --> DuckDB
    Dm --> Search
    Otg --> DuckDB
    Search --> ES
    Platform["平台服务<br/>plat"] --> PG
    Platform --> DuckDB
    Registry --> DuckDB
    Routing --> DuckDB
    Context --> Routing
    

图表来源

  • CaseInfo.java:1-101
  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68
  • GovernTreeService.java:1-200
  • DmService.java:1-200
  • SearchQueryService.java:1-108
  • TimeSeriesService.java:1-94

章节来源

  • CaseInfo.java:1-101
  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68

核心组件

  • 案件元信息 CaseInfo:记录案件名称、密码、数据库名、数据库路径、类型、备注、创建时间、初始化状态、归属用户和工作空间路径。它是“案件”的业务标识,也是数据隔离的根。
  • 案件数据源注册表 CaseDataSourceRegistry:管理已打开 DuckDB 数据源的生命周期,按案件物理键 case{caseId} 注册,限制最大打开数,校验数据库文件存在且非空,并在开案时补建 file_ai_profile 表。
  • 动态数据源路由 CaseRoutingDataSource:将注解或直连使用的逻辑 key slave 映射到当前案件的物理 key,未开案或未注册时返回明确错误,防止串库。
  • 人员服务 PersonBasicInfoService:维护人员基本信息、照片、手动编号、自动识别编号,并与 CaseDataCache 联动刷新人员关联。
  • 通话服务 CallRecordService:基于治理配置选择通话表名,支持分页、时间范围、对方号码过滤。
  • 交易服务 TransRecordService:根据治理配置在 trans_record 与 trans_record_all 之间切换,构建金额、卡号、对手、摘要等多条件查询。
  • 治理服务 GovernTreeService:构建治理树、加载治理节点表格数据、按人员筛选交易、处理自定义条件 SQL。
  • 搜索服务 SearchQueryService:使用 Elasticsearch 对当前案件文档进行关键词模糊匹配和高亮,最多返回 500 条。
  • 搜索写入服务 SearchDocIndexService:按案件批量写入、熔断降级、刷新索引、按文件删除、按案件清理。
  • 数据管理服务 DmService:文件预览、上传、预处理、清洗任务调度、AI 识别档案、与治理树和搜索索引集成。
  • 时序服务 TimeSeriesService:查询时间序列、跳转查询、详情展示,结合治理配置表头。

章节来源

  • CaseInfo.java:1-101
  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68
  • PersonBasicInfoService.java:1-194
  • CallRecordService.java:1-39
  • TransRecordService.java:1-155
  • GovernTreeService.java:1-200
  • SearchQueryService.java:1-108
  • SearchDocIndexService.java:1-200
  • DmService.java:1-200
  • TimeSeriesService.java:1-94

架构总览

调查场景中,数据从外部文件进入 dm 模块,经清洗后落到 DuckDB 案件库;人员、通话、交易、轨迹、外部表等业务模块通过 MyBatis-Plus 访问 DuckDB;治理树聚合多表数据并提供统一查询入口;搜索模块用 Elasticsearch 提供全文检索;时序模块提供时间维度分析和跳转;平台服务用 PostgreSQL/pgvector 管理用户、模型、知识库、系统信息等。

sequenceDiagram
    participant User as "用户"
    participant Plat as "平台服务 plat"
    participant Registry as "CaseDataSourceRegistry"
    participant Routing as "CaseRoutingDataSource"
    participant DM as "DmService"
    participant DuckDB as "DuckDB 案件库"
    participant Govern as "GovernTreeService"
    participant Search as "SearchDocIndexService"
    participant ES as "Elasticsearch"

    User->>Plat: "打开案件 caseId"
    Plat->>Registry: "open(caseId, dbPath)"
    Registry->>Routing: "注册物理 key case{caseId}"
    Registry->>DuckDB: "建立连接并补建 file_ai_profile"
    User->>DM: "上传并清洗文件"
    DM->>DuckDB: "写入清洗结果"
    DM->>Search: "批量写入全文索引"
    Search->>ES: "bulk 写入"
    User->>Govern: "查询治理树"
    Govern->>DuckDB: "按治理配置表名查询"
    User->>Search: "关键词搜索"
    Search->>ES: "wildcard + caseId 过滤"

图表来源

  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68
  • DmService.java:1-200
  • GovernTreeService.java:1-200
  • SearchDocIndexService.java:1-200
  • SearchQueryService.java:1-108

详细组件分析

案件数据源与路由

案件是数据隔离的最小单元。CaseInfo 保存案件元信息,包括数据库路径;CaseDataSourceRegistry.open 会校验 DuckDB 文件存在且非空,按 case{caseId} 注册数据源,并限制最大打开数量。CaseRoutingDataSource.getDataSource 将逻辑 key slave 转为物理 key,若未打开则返回“请先打开案件”错误。

flowchart TD
    Start(["打开案件"]) --> CheckContext["获取 caseId"]
    CheckContext --> ValidateDb["校验 DuckDB 文件存在且非空"]
    ValidateDb --> CreateDS["创建 DuckdbUnpooledDataSource"]
    CreateDS --> RegisterKey["注册物理 key case{caseId}"]
    RegisterKey --> EnsureTable["补建 file_ai_profile 表"]
    EnsureTable --> Done(["可被 slave 路由访问"])

图表来源

  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68

章节来源

  • CaseDataSourceRegistry.java:1-200
  • CaseRoutingDataSource.java:1-68

人员模块 person

PersonBasicInfoService 提供人员基本信息的增删改查,并维护人员电话、银行卡等关联编号。其更新和批量排序操作标注 @DS(StrConsts.DS_KEY_SLAVE),确保写入 DuckDB 而非 PostgreSQL。它还依赖 CaseDataCache 合并自动识别出的人员和编号,避免重复。

classDiagram
    class PersonBasicInfoService {
        +create(dto)
        +delete(personName)
        +update(dto)
        +getPersons()
        +getPersonNames()
        +personOrder(list)
        +flushPerson(set)
    }
    class PersonBasicInfoMapper
    class PersonLibNoMapper
    class CaseDataCache

    PersonBasicInfoService --> PersonBasicInfoMapper : "MyBatis-Plus"
    PersonBasicInfoService --> PersonLibNoMapper : "维护编号"
    PersonBasicInfoService --> CaseDataCache : "读取自动编号"

图表来源

  • PersonBasicInfoService.java:1-194

章节来源

  • PersonBasicInfoService.java:1-194

通话模块 call

CallRecordService 通过治理配置决定实际通话表名,支持分页、时间范围、对方号码过滤,并对“未知号码”做特殊处理。它依赖 GovernConfService 获得动态表名,最终交给 Mapper 执行分页查询。

flowchart TD
    Query["接收 CallRecordQuery"] --> ResolveTable["调用 GovernConfService 获取通话表名"]
    ResolveTable --> NormalizeOtherPhones["规范化 otherPhones"]
    NormalizeOtherPhones --> PageQuery["调用 baseMapper.selectCallRecordPage"]
    PageQuery --> Return(["返回分页结果"])

图表来源

  • CallRecordService.java:1-39

章节来源

  • CallRecordService.java:1-39

交易模块 trans

TransRecordService 根据治理配置在 trans_record 与 trans_record_all 之间切换查询目标,支持金额比较符、卡号、对手姓名、摘要、分行、备注、文件编号、工作表编号等条件。当查询来源为现金关键字时,还会先拉取现金关键字列表并拼接 like 条件。

flowchart TD
    Start(["接收 TransRecordQuery"]) --> DecideTable{"治理配置表名"}
    DecideTable -->|trans_record| BuildWrapper1["构建 TransRecord 查询包装器"]
    DecideTable -->|trans_record_all| BuildWrapper2["构建 TransRecordAll 查询包装器"]
    BuildWrapper1 --> ApplyConditions["应用金额、对手、卡号、时间等条件"]
    BuildWrapper2 --> ApplyConditions
    ApplyConditions --> CashKeys{"是否按现金关键字过滤"}
    CashKeys -->|是| LoadKeys["加载 TransCashKey 列表"]
    LoadKeys --> LikeExt1["拼接 ext1 like 条件"]
    CashKeys -->|否| Execute["执行分页查询"]
    LikeExt1 --> Execute
    Execute --> Convert{"是否需要转换"}
    Convert -->|是| MapToEntity["TransRecordAllConvert 转换"]
    Convert -->|否| Return(["返回 Page<TransRecord>"])
    MapToEntity --> Return

图表来源

  • TransRecordService.java:1-155

章节来源

  • TransRecordService.java:1-155

治理模块 govern

GovernTreeService 承担治理树构建、治理节点表格分页、按人员筛选交易等职责。它组合了人员、通话、交易、账户等多个 Mapper,并通过 GovernConfService 获取动态表名。对于治理树节点,还支持注入 AI 模板 ID,以便前端渲染动态表。

classDiagram
    class GovernTreeService {
        +getTree()
        +getTreeTablePage(dto)
        +getTreeTransPersonPage(dto)
    }
    class PersonBasicInfoMapper
    class TransRecordMapper
    class TransRecordAllMapper
    class CallRecordMapper
    class CallRecordAllMapper
    class AcctOpenInfoMapper
    class GovernConfService
    class GovernTreeMapper

    GovernTreeService --> PersonBasicInfoMapper
    GovernTreeService --> TransRecordMapper
    GovernTreeService --> TransRecordAllMapper
    GovernTreeService --> CallRecordMapper
    GovernTreeService --> CallRecordAllMapper
    GovernTreeService --> AcctOpenInfoMapper
    GovernTreeService --> GovernConfService
    GovernTreeService --> GovernTreeMapper

图表来源

  • GovernTreeService.java:1-200

章节来源

  • GovernTreeService.java:1-200

搜索模块 search

搜索模块分为查询侧和写入侧:

  • SearchQueryService:对 Elasticsearch 发起 wildcard 查询,按 content 字段匹配小写关键词,并按 dateTime 降序返回最多 500 条,同时对 content 高亮。
  • SearchDocIndexService:按案件批量攒批写入,失败熔断降级,完成后 flush 并 refresh;支持按文件删除和按案件清理。

    sequenceDiagram
    participant Client as "客户端"
    participant QuerySvc as "SearchQueryService"
    participant ES as "Elasticsearch"
    participant IndexSvc as "SearchDocIndexService"
    
    Client->>QuerySvc: "keyword"
    QuerySvc->>QuerySvc: "读取 CaseContextHolder.caseId"
    QuerySvc->>ES: "wildcard(content) + filter(caseId)"
    ES-->>QuerySvc: "搜索结果"
    QuerySvc-->>Client: "SearchDTO 列表(含高亮)"
    
    Note over IndexSvc: "清洗流程写入"
    IndexSvc->>IndexSvc: "按 caseId 攒批"
    IndexSvc->>ES: "bulk 写入"
    IndexSvc->>ES: "refresh"
    

图表来源

  • SearchQueryService.java:1-108
  • SearchDocIndexService.java:1-200

章节来源

  • SearchQueryService.java:1-108
  • SearchDocIndexService.java:1-200

数据管理模块 dm

DmService 是数据入湖和清洗的核心:

  • 文件预览:并发扫描文件,提取基本信息和结构,使用虚拟线程提升吞吐。
  • 文件上传:仅允许 csv/xls/xlsx 落盘解析,其他后缀登记为不支持解析。
  • 清洗调度:驱动 PreTask、CleanTask、DataLoader/DataCleaner 链,并将结果写入 DuckDB。
  • 搜索索引:清洗完成后调用 SearchDocIndexService.add/flush 同步全文索引。
  • AI 识别:非表格文件抽取样本并提交 FileAiProfileService 识别。

    flowchart TD
    Upload["上传文件"] --> Preview["预解析文件"]
    Preview --> PreTask["PreTask 解析链路"]
    PreTask --> CleanTask["CleanTask 清洗任务"]
    CleanTask --> DuckDB["写入 DuckDB 案件库"]
    CleanTask --> SearchIndex["SearchDocIndexService.add"]
    SearchIndex --> Flush["全量完成 flush+refresh"]
    NonTable["非表格文件"] --> AiProfile["FileAiProfileService 建档识别"]
    

图表来源

  • DmService.java:1-200
  • SearchDocIndexService.java:1-200

章节来源

  • DmService.java:1-200
  • SearchDocIndexService.java:1-200

时序模块 otg

TimeSeriesService 提供时间序列数据的查询、跳转和详情展示。它支持按时间范围和类型过滤,并通过 GovernConfService 获取表头映射。跳转查询走特定 Mapper SQL,适合复杂时序分析。

flowchart TD
    Query["TimeSeriesQuery"] --> Range["计算起止时间"]
    Range --> Types["收集类型集合"]
    Types --> Select["baseMapper.selectTimeSeriesList"]
    Select --> Jump["jumpTimeSeries 跳转查询"]
    Jump --> Detail["detailTimeSeries 详情"]

图表来源

  • TimeSeriesService.java:1-94

章节来源

  • TimeSeriesService.java:1-94

外部表模块 external

external 模块主要暴露外部数据的 Mapper,如户籍、暂住、车辆违章、刑事人员、证件信息等。这些数据通常由 dm 模块的 ExternalCleaner 清洗入库,供治理树和分析工具查询。该模块本身更多是数据访问接口,业务逻辑多在 dm 清洗器和 govern 查询中体现。

[本节为概念性说明,不直接分析具体文件]

依赖关系分析

业务模块之间的耦合主要体现在:

  • governance 聚合 person、call、trans、external、track 的数据,提供统一树形视图。
  • dm 是数据入湖入口,依赖 govern 的配置、search 的索引、person 的人员编号。
  • search 独立于 DuckDB,但依赖案件上下文,确保按案件隔离。
  • otg 依赖 govern 配置获取表头,提供时序维度分析。
  • person、call、trans、track、external 都通过 MyBatis-Plus 访问 DuckDB,受 CaseRoutingDataSource 控制。

    graph LR
    Govern["GovernTreeService"] --> Person["PersonBasicInfoService"]
    Govern --> Call["CallRecordService"]
    Govern --> Trans["TransRecordService"]
    Govern --> External["External Mappers"]
    Govern --> Track["Track Services"]
    Dm["DmService"] --> Govern
    Dm --> Search["SearchDocIndexService"]
    Dm --> Person
    Search --> ES["Elasticsearch"]
    Otg["TimeSeriesService"] --> Govern
    AllModules["person/call/trans/track/external"] --> DuckDB["DuckDB"]
    DuckDB --> Govern
    

图表来源

  • GovernTreeService.java:1-200
  • DmService.java:1-200
  • SearchDocIndexService.java:1-200
  • TimeSeriesService.java:1-94

章节来源

  • GovernTreeService.java:1-200
  • DmService.java:1-200
  • SearchDocIndexService.java:1-200
  • TimeSeriesService.java:1-94

典型跨模块查询场景

根据人员查通话

  1. 通过 PersonBasicInfoService 获取人员信息及自动/手动关联的电话、卡号。
  2. 使用 CallRecordService.getCallRecord 传入对方号码或本人号码过滤,分页返回通话记录。
  3. 如需结合治理树,可通过 GovernTreeService 获取通话节点并应用自定义条件。

    sequenceDiagram
    participant UI as "前端"
    participant Person as "PersonBasicInfoService"
    participant Call as "CallRecordService"
    participant DuckDB as "DuckDB"
    
    UI->>Person: "获取人员及关联号码"
    Person-->>UI: "电话、卡号集合"
    UI->>Call: "按号码过滤通话记录"
    Call->>DuckDB: "selectCallRecordPage"
    DuckDB-->>Call: "通话记录分页"
    Call-->>UI: "通话列表"
    

图表来源

  • PersonBasicInfoService.java:1-194
  • CallRecordService.java:1-39

章节来源

  • PersonBasicInfoService.java:1-194
  • CallRecordService.java:1-39

根据通话查交易

  1. 从通话记录中提取对方号码、卡号或人员姓名。
  2. 使用 TransRecordService.getTransRecord 按卡号、对手姓名、时间范围过滤交易。
  3. 若需要全局视角,可通过 GovernTreeService.getTreeTransPersonPage 按人员筛选交易。

    flowchart TD
    CallResult["通话结果"] --> Extract["提取对方卡号/姓名"]
    Extract --> TransQuery["TransRecordService 查询"]
    TransQuery --> GovernView["GovernTreeService 人员交易视图"]
    GovernView --> DuckDB["DuckDB 交易表"]
    

图表来源

  • CallRecordService.java:1-39
  • TransRecordService.java:1-155
  • GovernTreeService.java:1-200

章节来源

  • CallRecordService.java:1-39
  • TransRecordService.java:1-155
  • GovernTreeService.java:1-200

结合治理树和轨迹进行分析

  1. 通过 GovernTreeService.getTree 获取治理树,定位轨迹相关节点。
  2. 使用 GovernTreeService.getTreeTablePage 加载轨迹表格分页数据,并可传入自定义条件 SQL。
  3. 结合 TimeSeriesService.jumpTimeSeries 进行时间维度跳转分析,定位关键时间点。

    sequenceDiagram
    participant Analyst as "调查员"
    participant Govern as "GovernTreeService"
    participant Track as "Track Services/Mappers"
    participant Time as "TimeSeriesService"
    
    Analyst->>Govern: "获取治理树"
    Govern-->>Analyst: "轨迹节点"
    Analyst->>Govern: "加载轨迹表格"
    Govern->>Track: "按条件查询轨迹数据"
    Analyst->>Time: "时间序列跳转"
    Time-->>Analyst: "关键时间点序列"
    

图表来源

  • GovernTreeService.java:1-200
  • TimeSeriesService.java:1-94

章节来源

  • GovernTreeService.java:1-200
  • TimeSeriesService.java:1-94

性能优化建议

  • 优先使用治理配置表名:通过 GovernConfService 获取动态表名,避免硬编码,便于在不同数据源间切换。
  • 分页查询:所有列表接口尽量使用 MyBatis-Plus 分页插件,避免一次性拉取大量数据。
  • 条件前置:在 Service 层构造 LambdaQueryWrapper 或 QueryWrapper 时尽早过滤,减少无效数据传输。
  • 避免频繁全表扫描:对人员、卡号、手机号等高频查询字段,应确保 DuckDB 中有合适索引;对文本模糊查询优先使用 search 模块。
  • 搜索写入批量化:利用 SearchDocIndexService 的批量缓冲和熔断机制,避免单条写入造成延迟。
  • 控制并发预览:DmService.preProcessFile 已有并发上限和超时保护,注意合理设置预览并发度。
  • 关闭不必要日志:生产环境关闭 debug 级数据源路由日志,减少 I/O 压力。
  • 合理设置 DuckDB 内存:通过 DuckdbBudgetProperties 控制每案件内存和线程数,避免内存溢出。

[本节为通用性能建议,不直接分析具体文件]

常见问题排查

  • “请先打开案件后再操作数据”:检查是否调用了 CaseDataSourceRegistry.open,以及 CaseContextHolder 是否携带了 caseId。
  • “当前案件数据源未打开或已释放”:检查案件是否被主动退出、登出、被顶下线或会话过期;确认 CaseDataSourceRegistry 中的 opened 映射是否存在。
  • 数据串库:确认所有访问 DuckDB 的代码均通过 @DS(StrConsts.DS_KEY_SLAVE) 或显式走 CaseRoutingDataSource,不要绕过路由。
  • 搜索无结果:检查 ES 是否正常、索引是否 refresh、caseId 是否正确;查看 SearchDocIndexService 是否因熔断丢弃文档。
  • 治理树为空:检查治理节点是否设置了 dataCount > 0,以及 AI 模板 ID 是否正确注入。
  • 清洗失败但主链路正常:查看 SearchDocIndexService 的熔断计数和丢弃文档累计,确认 ES 故障未影响清洗主流程。

章节来源

  • CaseRoutingDataSource.java:1-68
  • CaseDataSourceRegistry.java:1-200
  • SearchDocIndexService.java:1-200

结论

业务数据模块以案件为核心,通过 dm 模块完成数据入湖和清洗,person/call/trans/track/external 等模块围绕 DuckDB 案件库提供业务能力,govern 提供统一治理树入口,search 提供全文检索增强,otg 提供时序分析能力。MyBatis-Plus 作为统一数据访问层,配合 CaseRoutingDataSource 实现按案件路由到 DuckDB,平台服务则继续使用 PostgreSQL/pgvector 管理元数据和向量索引。调查分析的关键在于合理使用治理配置、分页查询、搜索索引和时序跳转,并结合人员、通话、交易、轨迹进行交叉验证。

[本节为总结性内容,不直接分析具体文件]