关联交易线索追踪系统

本文分享基于Neo4j图数据库构建的关联交易线索追踪系统实践。系统将分散的账户/交易/关系数据转化为可解释的关系网络,实现了路径查询、环形检测、账户/团伙融合三大核心能力。通过策略分层、规则前置与工程化优化,帮助风控、尽调等场景高效发现可疑资金链路。

关联交易线索追踪系统

本文面向“异常资金链分析”这一类场景,分享如何基于Neo4j 图数据库把分散的账户/交易/关系数据组织成可解释的图,并在此之上落地三项关键能力:路径查询环形检测账户融合/团伙融合的图构建


1. 背景:我们在做什么系统?为什么需要这些能力?

很多业务分析的本质不是“查一张表”,而是在大量实体与关系中找到可解释的链路与模式。线索发现系统面向的就是这类问题:把企业/人物/账户/交易/关联关系等数据组织成图,在图上做查询、归并和分析,输出前端可视化的关系网络与路径结果,帮助业务人员更快定位线索。

1.1 系统的作用

把分散的实体与交易数据变成“可追踪、可解释、可视化”的关系网络,用于关系链路发现、资金流向追踪、异常模式识别等场景。

1.2 典型应用场景

  • 企业关系网络分析:查某公司与上下游、关联公司/人员的关系链,辅助尽调、风控、合规审查。
  • 资金流向追踪:从一个主体出发,追踪资金的去向/来源,识别中转账户、关键节点。
  • 交易路径发现:给定两组主体(或账户),找它们之间的“最短链路”或“多条备选链路”,用于解释“为什么有关联”。
  • 异常模式识别(环形/回流):检测资金是否出现循环回到起点的现象,辅助识别对敲、回流等可疑模式。
  • 账户融合/团伙识别:把“账户层”归并到“户主层”,或把人物归并到“团伙层”,把图简化成更容易阅读的业务视角。

1.3 数据与技术形态

  • 数据形态:节点(人物/企业/银行账户/团伙/行业等)+ 关系(交易、账户归属、社会关系、常规关联等),关系边往往带属性(交易时间、金额、方式、备注等)。
  • 存储形态:图数据在 Neo4j,元数据与配置在 MySQL(例如 schema、显示配置、默认数据库选择、屏蔽节点等)。
  • 服务形态:Django 提供 API,前端拿到 nodes + edges + path_list 后做可视化与交互。

1.4 “一个路径查询”分三套实现

为什么“一个路径查询”要分三套实现?,因为用户在不同交互下的诉求差异很大:

  • 只要一条最短解释链:要快、要稳,最好秒出结果。
  • 要多条备选链路:要可控(方向、过滤、终止、屏蔽),且不能把系统拖慢。
  • 深层路径(跳数大):搜索空间会指数级膨胀,需要更强的算法与工程化保护(例如临时子图、动态 K、资源回收)。

对应到系统里的三套实现,我们大致这样使用(后文第 2 节会展开):

  • 最短一条路径(交互“只要 1 条”):当 limit == 1 时优先走最短路径能力;若存在屏蔽节点等约束,则用可控的路径扩展方式保证“查询阶段就排除不该出现的节点”。作用是极致响应速度 + 结果可解释
  • 浅层多路径(深度较小):当 maxLevel 不大且需要多条备选链路时,使用可带方向/过滤/终止/屏蔽的扩展查询。作用是约束友好 + 性能稳定
  • 深层多路径(深度较大):当 maxLevel 较大时切换到图算法引擎(例如 K 最短路径),配合临时子图与动态参数控制。作用是避免搜索爆炸 + 在可控资源下给到足够候选路径

2. 统一的路径查询:同一个入口,自动选择最合适的引擎

路径查询是图分析系统里最容易“要结果就慢、要速度就不全”的功能。我们在实现上把它收敛为一个统一入口:输入起点/终点集合、方向、深度范围、返回条数、交易类型偏好,以及可选的“屏蔽节点”,然后在内部做策略分流。

2.1 关键输入与业务语义

  • 起点/终点:支持多起点、多终点组合,避免前端多次请求。
  • 方向控制(交易方向):
    • 0:正向(起点 → 终点)
    • 1:反向(起点 ← 终点)
    • 2:双向(无向/混合)
  • 深度范围minLevel/maxLevel 控制路径长度(跳数)。
  • limit:返回多少条路径。这里我们把 limit == 1 当作“最短/最合适的一条”的强场景做特别优化。
  • 交易类型开关:支持“只查大额/只查小额/两者都查”的过滤偏好。
  • 屏蔽节点(blacklist):用于业务上“隐藏/排除”的实体(例如误报节点、敏感节点、噪声节点),要求它们不能出现在路径中

2.2 策略一:limit == 1 的极速最短路径

当用户只要一条路径时,系统优先走最省的路线:

  • 无屏蔽节点:直接走 Neo4j 原生最短路径能力,配合方向模式(正向/反向/双向)去匹配,性能最好。
  • 有屏蔽节点:原生最短路径不方便在“搜索阶段”严格排除节点,因此改用 APOC 做路径扩展,并在扩展参数里通过 blacklistNodes 把屏蔽节点“前置过滤”。同时对结果按跳数排序,取最短一条。

这样做的收益是:常见交互(只看一条最短关系链)能做到尽可能快,而且屏蔽规则在查询阶段就生效,不会出现“查出来再删掉导致还剩空结果”的体验问题。

2.3 策略二:浅层多路径(maxLevel < 6)用 APOC,兼顾性能与可控性

当深度不大但需要多条路径时,我们使用 APOC 的扩展能力:

  • 关系过滤:把“基础关系”(如社会关系、银行账户等)与“交易类关系”(账户交易/大额/小额)拼成 relationshipFilter,并且根据方向参数生成不同的过滤串。
  • 终止节点:用 terminatorNodes 明确终点集合,避免无意义扩散。
  • 屏蔽节点:直接使用 APOC 的 blacklistNodes,在搜索阶段完成排除。
  • 去重:对同一条节点序列的重复路径做合并(避免同形路径大量重复占用返回名额)。

这一路径的特点是:对业务条件(方向、过滤、终止、屏蔽)非常友好,而且深度不大时性能稳定。

2.4 策略三:深层多路径(maxLevel >= 6)用 GDS Yen’s + 临时子图投影

当深度上来以后,APOC 扩展很容易出现“搜索空间爆炸”。这时我们切换到 GDS(Graph Data Science)方案,核心思路是:

  1. 动态创建临时子图投影:只在本次查询生命周期内创建一个投影图,查询完立刻删除,避免污染全局、也避免长期占用内存。
  2. 方向控制更精细:并不是所有关系都要受“交易方向”影响。当前实现里把“交易类关系”(至少包含 账户交易,并可按前端开关把 大额交易/小额交易 纳入)设为可控方向:
    • 正向:NATURAL
    • 反向:REVERSE
    • 双向:UNDIRECTED 其他关系保持 NATURAL,保证“社会关系/账户关系”等非交易边不被错误翻转。
  3. 先估计最短跳数 d,再动态设置 K
    • 先用一次最短路计算得到最短跳数 d
    • 再设定 K = 2^d,并加上下限/上限(例如最少给足备选路径,最多防止爆炸) 这个策略的直觉是:越远的点对,需要更多候选路径才更可能找到“业务可用”的多条链路,但也必须设硬上限保护系统。
  4. Yen’s K 最短路径输出后重建关系信息:GDS 输出的是节点序列,我们会基于相邻节点对去匹配真实关系,从而把边属性(交易时间、金额、方式等)带回给前端展示。
  5. 屏蔽节点处理:由于投影与重建的关系,屏蔽节点在深路径场景下会在结果阶段再做一次过滤(确保最终路径不包含任何屏蔽节点)。
  6. 资源释放:无论查询成功与否,都要保证临时投影图被删除,避免累积资源泄漏。

深路径采用这套方案后,收益主要是:稳定性更强、可控性更强,不会因为一次深查询把系统拖死。


3. 路径查询 API:参数治理与“屏蔽节点”全链路

路径查询的 API 入口做了两类事情:

  • 参数归一化:方向、深度、条数、时间范围、金额范围、交易次数范围等全部在入口处转换为后端统一的类型和默认值。
  • 屏蔽节点注入:从当前默认数据库配置中读取“屏蔽节点列表”,并把它作为 blacklist_nodes 传入路径查询引擎。

这样做的价值是:屏蔽规则不需要散落在各处查询逻辑里,API 入口只负责“把规则补齐”,底层统一执行。

此外,路径查询的结果在返回前会经过图构建(见第 4 节),并把结果写入用户维度的缓存,用于“同一用户连续操作(隐藏/追加/切换融合模式)时避免重复查库”。


4. 环形检测:从“扩展路径”到“可解释的循环链”

环形检测的业务目标是:从某个主体相关账户出发,找到资金在网络中“绕一圈又回到自己”的循环链路,用于识别回流、对敲、洗钱等模式。

4.1 起点集合:从“人/企业”定位到“账户集合”

用户在界面上选的是“某个主体”,但图上真正承载资金流的是银行账户节点。实现上会先根据主体信息(例如账户所有人字段的包含匹配)查询出一组账户节点 ID,作为后续扩展的起点集合。

4.2 用 APOC 做 BFS 扩展,并把“回到起点集合”设为终止条件

核心查询逻辑是 APOC 扩展:

  • relationshipFilter:限制在资金相关与必要的辅助关系上(例如账户交易、存现、提现、账户关系、社会关系等),并可带方向倾向(常见做法是以“向外扩散”为主)。
  • bfs:使用广度优先更利于先发现短环。
  • terminatorNodes:设为起点账户集合,使得路径在“回到任一起点账户”时自然收束,形成环。
  • blacklistNodes:同样支持屏蔽节点在搜索阶段排除。
  • uniqueness:使用全局关系去重策略,减少重复遍历。

4.3 时序校验:把“有环”变成“合理的资金链”

图上存在环并不等于资金链合理。环形检测会对结果做一层时序检查

  • 只对交易类边(例如账户交易)做时间顺序约束
  • 当发现时间逆序过大时剔除该路径

这一步的意义是:把“拓扑上的环”过滤成“更符合真实业务发生顺序的环”,提高结果可解释性与可用性。

最后,环形检测结果会复用同一套图构建与融合逻辑输出给前端(见第 4 节)。


5. 图构建与融合:把“账户层图”变成“主体层关系图”

Neo4j 的原始查询结果通常是 “nodes(path) + relationships(path)” 的集合,但前端想要的是:

  • 节点:有样式、图标、显示名称、聚类信息
  • 边:有方向、类别、展示字段(交易时间/金额)、并能被路径列表高亮

因此我们设计了统一的构图函数,把查询结果转换为适合可视化的图数据结构,并在此阶段完成“账户融合”和“团伙融合”。

5.1 团伙融合:先把“社会关系”提炼成“人物 → 团伙”的映射

系统支持一种图简化:当社会关系边的一端是“团伙”节点时,将相关人物映射到团伙节点上。实现上会先遍历查询结果中的社会关系边,构建 person_id -> groupNode 映射。

这一步放在构图前的原因是:后续无论是处理节点还是处理边,都可以用同一张映射表做快速替换,不需要重复查库。

5.2 账户融合:批量查询户主,避免 N+1

账户融合的目标是把“银行账户节点”替换为其户主(人物/企业)节点,从而把关系呈现从“账户之间”提升到“主体之间”。

这部分你这版做了关键优化:不是对每个账户逐个查询户主,而是:

  • 先从查询结果里收集所有出现过的银行账户 ID(去重)
  • 再用一次批量查询拿到“账户 → 户主”的映射
  • 构造轻量的户主节点对象缓存下来,供后续替换使用

这样可以把户主查询从 N 次变成 1 次批量,在路径结果较多、账户较多时差距非常明显。

5.3 端点替换:不仅替换节点,也要替换边的起止端点

账户融合如果只替换节点而不替换边端点,会导致图结构断裂或出现“边仍指向账户节点”的混乱。构图时会对每条关系同时做:

  • 起点如果是银行账户 → 替换为户主
  • 终点如果是银行账户 → 替换为户主
  • 再叠加团伙融合的替换
  • 若起止最终落在同一节点(自环)则跳过

5.4 路径可视化支持:每条边携带“所属路径集合”

为了支持前端“从路径列表点选 → 图中高亮这条路径”,构图阶段会为每条边附加它属于哪些路径的 ID 列表(例如 id2path_ids),并同时生成面向 UI 的 path_list(路径标题、长度、途经节点摘要、边 ID 列表)。

5.5 去重与图质量:边去重 + 双向边折叠 + 离散点过滤

真实数据里很常见:

  • 同一条关系在多个路径中出现
  • 双向关系导致 “A—B” 与 “B—A” 都出现在图中
  • 构图后出现不连通的离散点(影响可读性)

因此构图后会做几层整理:

  • 按边 ID 去重:同一关系只保留一条。
  • 双向关系折叠:对特定类别(例如被视为“无向关系”的类别)只保留同一无向对的一条边,减少视觉噪声。
  • 离散点过滤:只保留至少出现在边端点集合里的节点;再叠加屏蔽节点过滤,保证前端看到的是“紧凑且可解释的子图”。

最后再进行节点大小计算、边样式调整、图例统计等,用于提升可读性与交互体验。


6. 总结:把“图算法能力”变成“可落地的业务产品”

回头看这套实现,核心不是“用哪个算法更高级”,而是:

  • 入口统一:同一 API/同一核心函数,减少分叉与维护成本
  • 策略分层limit == 1、浅层多路径、深层多路径分别走最合适的引擎
  • 规则前置:屏蔽节点尽量在查询阶段生效,结果阶段再兜底
  • 工程化优化:批量查询替代 N+1、临时子图生命周期管理、去重与折叠提升可视化质量
  • 可解释性:环形检测加时序校验、路径列表可高亮、边上带交易摘要

如果你也在做“图数据库 + 关系分析 + 可视化”的系统,希望这篇思路能对你有所启发。