本文引用的文件
外部表模块 external 用于管理来自第三方或外部来源的数据,例如出入境记录、协居人员、同车违章、违法犯罪人员、在逃人员等。这些外部数据并不直接写入内部业务主表(如 person、call、trans、track),而是通过独立的 DuckDB 外部表存储,并通过字段映射、清洗加载流程、搜索索引和查询服务,最终与内部实体打通。
该模块的核心职责包括:
当前仓库中已实现的外部数据来源至少包括:
| 外部数据来源 | 对应外部表 | 说明 |
|---|---|---|
| 出入境证件信息 | cert_info |
人员出入境证件基础信息 |
| 出入境记录信息 | entry_rec |
人员出入口岸记录 |
| 机动车信息 | vehicle_info |
车辆登记信息 |
| 驾驶证信息 | lic_info |
驾驶人档案信息 |
| 机动车违章信息 | veh_violation |
车辆违法记录 |
| 同户人 | household |
同一户籍人员 |
| 同住址 | co_resident |
协居人员 |
| 同车违章 | co_veh_viol |
同车违法关联人员 |
| 违法犯罪人员信息 | criminal |
违法犯罪人员基础信息 |
| 在逃人员登记信息 | fugitive |
在逃人员登记 |
| 在逃人员撤销信息 | fugi_revoke |
在逃人员撤销记录 |
| 在逃同案人员 | fugi_co |
在逃同案人员 |
| 在逃同案撤销人员 | fugi_co_revoke |
在逃同案撤销人员 |
外部表模块涉及 SQL 建表脚本、Java 实体、MyBatis Mapper、数据清洗器、数据加载器、工厂注册以及搜索索引模型。其组织方式不是单一包内集中,而是按职责拆分:
graph TB
SQL["SQL 建表脚本<br/>external_tables.sql"] --> FieldMap["字段映射配置<br/>external_table_field.sql"]
FieldMap --> Cleaner["数据清洗器<br/>ExternalEntryRecCleaner 等"]
Cleaner --> Loader["数据加载器<br/>ExternalEntryRecLoader 等"]
Loader --> DuckDB["DuckDB 外部表<br/>entry_rec 等"]
Loader --> SearchDoc["搜索文档模型<br/>SearchDoc"]
SearchDoc --> Index["Elasticsearch 索引<br/>zsjz_search"]
Mapper["MyBatis Mapper<br/>EntryRecMapper"] --> DuckDB
图表来源
章节来源
外部表使用 DuckDB 表结构定义,字段以字符串为主,便于从 Excel 或 CSV 导入后直接落库。目前已定义的表包括出入境证件、出入境记录、机动车、驾驶证、车辆违法、同户人、协居、同车违章、违法犯罪人员、在逃人员及其撤销和同案相关表。
以出入境记录表为例,关键字段包括:
| 字段名 | 含义 | 用途 |
|---|---|---|
id |
主键 | 行唯一标识 |
fileId |
文件标识 | 关联外部文件来源 |
sheetId |
工作表标识 | 区分不同工作表 |
cid |
身份证号 | 与内部人员匹配的关键维度 |
certNo |
证件号码 | 辅助身份匹配 |
ioDate |
出入日期 | 时间维度查询 |
ioTime |
出入时间 | 精确时间查询 |
port |
出入口岸 | 地点维度查询 |
vehicle |
交通工具 | 与交通轨迹关联 |
photo |
照片 | 附件或展示用途 |
章节来源
字段映射由 table_info 和 table_field 两类元数据组成。虽然仓库未给出这两张表的完整 DDL,但从 external_table_field.sql 可见其作用:
table_info 表示一张外部表模板,包含中文名、英文名、分类、是否内部表、是否有物理表等;table_field 表示该模板下的字段,包含中文名称、英文字段名、字段类型、是否必填、排序等。示例中所有外部数据源都被归类为“公安数据库”,且标记为有内部表、有物理表。这意味着这些外部数据不仅会被清洗入库,还会进入系统的标准化处理链路。
章节来源
EntryRec 是出入境记录的外部实体模型,继承自 Cleaned,并使用 MyBatis Plus 的注解映射到 entry_rec 表。它把外部列名映射为 Java 字段,并提供一个 str() 方法,将多个字段拼接成搜索文本。
| Java 字段 | 外部表字段 | 说明 |
|---|---|---|
personName |
personName |
姓名 |
certType |
certType |
证件类别 |
certNo |
certNo |
证件号码 |
gender |
gender |
性别 |
nation |
nation |
国籍 |
visaType |
visaType |
签证种类 |
cid |
cid |
身份证号 |
birth |
birth |
出生日期 |
ioDate |
ioDate |
出入日期 |
ioTime |
ioTime |
出入时间 |
port |
port |
出入口岸 |
transport |
transport |
交通方式 |
vehicle |
vehicle |
交通工具 |
visaNo |
visaNo |
签证号码 |
country |
country |
前往或归来国家 |
photo |
photo |
照片 |
str() 方法把所有可搜索字段用特殊分隔符连接,供搜索索引使用。
章节来源
EntryRecMapper 是一个简单的 MyBatis Plus Mapper,指向外部数据源。它使用了动态数据源注解,目标数据源键为从常量类中读取的从库键。XML 映射文件为空,表示使用默认 CRUD。
这个设计表明:外部表通常不需要复杂业务 SQL,主要走标准增删改查;复杂查询可能由上层服务组合或通过搜索索引完成。
章节来源
外部表模块的整体流程如下:
sequenceDiagram
participant User as "用户或任务"
participant ETL as "流式 ETL 监听器"
participant Factory as "清洗工厂"
participant Cleaner as "外部数据清洗器"
participant Loader as "外部数据加载器"
participant DuckDB as "DuckDB 外部表"
participant Search as "搜索索引服务"
User->>ETL: "上传外部表格"
ETL->>Factory: "根据主模板 ID 创建清洗器和加载器"
Factory-->>ETL: "返回 ExternalEntryRecCleaner / ExternalEntryRecLoader"
ETL->>Cleaner: "解析并清洗一行外部数据"
Cleaner-->>Loader: "输出 EntryRec 实体"
Loader->>DuckDB: "写入 entry_rec 外部表"
Loader->>Search: "写入搜索文档"
Search-->>User: "可被后续查询检索"
图表来源
外部表并不是靠 Java 注解自动发现,而是通过枚举值、SQL 元数据和工厂静态注册共同完成。
StrategyType 中为每个外部数据类型分配了数字编码,并与 SQL 中的 main_id 对应。例如:
| 枚举 | 编码 | 中文名称 |
|---|---|---|
CERT_INFO |
51 | 出入境证件信息 |
ENTRY_REC |
52 | 出入境记录信息 |
VEHICLE_INFO |
53 | 机动车信息 |
LIC_INFO |
54 | 驾驶证信息 |
VEH_VIOLATION |
55 | 机动车违章信息 |
HOUSEHOLD |
56 | 同户人 |
CO_RESIDENT |
57 | 同住址 |
CO_VEH_VIOL |
58 | 同车违章 |
CRIMINAL |
59 | 违法犯罪人员信息 |
FUGITIVE |
60 | 在逃人员登记信息 |
FUGI_REVOKE |
61 | 在逃人员撤销信息 |
FUGI_CO |
62 | 在逃同案人员 |
FUGI_CO_REVOKE |
63 | 在逃同案撤销人员 |
external_table_field.sql 中通过插入 table_info 和 table_field 完成外部表模板注册。每条外部表模板都包含:
id:模板主键;main_id:与枚举编码一致;table_name_cn:中文表名;table_name_en:英文表名;classify:数据来源分类;has_inner:是否内部表;has_table:是否有物理表。CleanFactory 在静态块中把所有外部数据类型绑定到具体的清洗器和加载器。新增外部数据源时,需要同时注册:
StrategyType 枚举;章节来源
外部数据清洗分为两个阶段:
以出入境记录为例:
flowchart TD
Start["开始处理外部行"] --> CreateEntity["创建 EntryRec 实体"]
CreateEntity --> MapFields["映射外部字段到实体属性"]
MapFields --> Validate{"字段是否有效"}
Validate -->|否| Skip["跳过或记录脏数据"]
Validate -->|是| WriteDuckDB["写入 DuckDB 外部表"]
WriteDuckDB --> BuildDoc["构建搜索文档"]
BuildDoc --> AddIndex["批量写入 Elasticsearch"]
AddIndex --> End["结束"]
Skip --> End
图表来源
ExternalEntryRecCleaner 继承自外部数据清洗抽象类,只负责创建 EntryRec 实体。具体字段解析逻辑由父类或上游解析器完成。这种设计让每种外部表只需要一个轻量级清洗器。
ExternalEntryRecLoader 继承自通用数据加载器,负责:
entry_rec 表;加载器中显式写入的列包括主键、文件标识、工作表标识、姓名、证件信息、身份信息、时间、口岸、交通工具和国家等。
章节来源
外部表本身不直接替代 person、call、trans、track,而是作为补充视图和关联维度。打通方式包括:
cid、姓名、证件号与内部人员匹配;当前代码中,EntryRec 没有直接写 person 表,而是写入 entry_rec 外部表,并生成搜索文档。因此,“打通”更多体现在查询和分析层,而不是强制覆盖内部主数据。
章节来源
classDiagram
class StrategyType {
+CERT_INFO
+ENTRY_REC
+VEHICLE_INFO
+LIC_INFO
+VEH_VIOLATION
+HOUSEHOLD
+CO_RESIDENT
+CO_VEH_VIOL
+CRIMINAL
+FUGITIVE
+FUGI_REVOKE
+FUGI_CO
+FUGI_CO_REVOKE
+fromCode(code)
}
class CleanFactory {
-DATA_CLEANER_MAP
-DATA_LOADER_MAP
+createDataCleaner(strategyType, funcRegx, currentSheet)
+createDataLoader(strategyType)
+registerDataCleaner(strategyType, supplier)
+registerDataLoader(strategyType, supplier)
}
class ExternalEntryRecCleaner {
+createEntity()
}
class ExternalEntryRecLoader {
+doWrite(entity)
}
class EntryRec {
+str()
}
class EntryRecMapper
class SearchDoc
CleanFactory --> StrategyType : "按枚举选择策略"
CleanFactory --> ExternalEntryRecCleaner : "注册清洗器"
CleanFactory --> ExternalEntryRecLoader : "注册加载器"
ExternalEntryRecCleaner --> EntryRec : "创建实体"
ExternalEntryRecLoader --> EntryRec : "消费实体"
EntryRecMapper --> EntryRec : "映射外部表"
ExternalEntryRecLoader --> SearchDoc : "构建搜索文档"
图表来源
章节来源
ExternalEntryRecLoader 使用 DuckDB 追加器逐列写入,并在每次写入后构建搜索文档。搜索文档写入由 SearchDocIndexService 做批量缓冲,避免频繁远程索引更新。
建议:
外部数据同步应遵循以下原则:
fileId、sheetId、id 的记录应可重复导入而不产生重复数据;外部数据常见脏数据包括:
建议清洗策略:
字段对齐应分三层:
table_info 明确数据来源、分类、是否内部表;table_field 明确中英文字段名、类型、排序;cid 映射为内部人员身份证号。章节来源
搜索文档模型 SearchDoc 明确强调:所有案件共用单一索引,但通过 caseId 强制注入;查询和删除时必须携带 caseId 过滤。文档 _id 由服务端组装,通常为 caseId|tableName|id。
这意味着外部表数据虽然是共享外部数据,但在系统查询侧应按案件隔离,防止跨案件泄露。
推荐查询模式:
SearchDoc;caseId;caseId 过滤;当前仓库证据显示搜索索引和服务层强调 caseId 隔离,但没有看到完整的角色权限控制实现。建议补充:
章节来源
如果报错提示未找到某个策略类型,通常原因包括:
StrategyType 中缺少对应枚举;CleanFactory 中没有注册对应的清洗器或加载器;main_id 与枚举编码不一致;table_info 和 table_field。章节来源
可能原因:
caseId;EntryRec.str() 生成的字段。章节来源
可能原因:
table_field 中字段顺序与实际 Excel 列不一致;章节来源
外部表模块 external 是一个围绕外部来源数据构建的数据接入与分析支撑层。它通过 SQL 表结构定义、字段映射、清洗加载器、DuckDB 外部表和 Elasticsearch 搜索索引,把协居、车辆违法、违法犯罪、在逃人员等外部数据纳入系统可用范围。
该模块的设计优点在于:
caseId 实现案件级查询隔离。未来改进重点应放在:
person、call、trans、track 的关联分析和可视化。