后端服务架构.md 25 KB

后端服务架构

本文引用的文件

  • ai-server/pom.xml
  • application.yaml
  • application-dev.yaml
  • application-pro.yaml
  • DataSourceConfig.java
  • CaseRoutingDataSource.java
  • CaseDataSourceReaper.java
  • SaTokenWebConfig.java
  • CorsConfig.java
  • AuthController.java
  • SystemController.java
  • SseService.java

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 新增业务模块标准模板
  8. 性能与可观测性建议
  9. 故障排查指南
  10. 结论

简介

本文件聚焦 ai-server 的 Spring Boot 后端服务架构,目标是帮助开发者理解:

  • 模块化原则:plat、agent、aiclean、knowledge、graph、person、call、trans、track、govern、search、dm、otg、external 等业务模块的职责边界。
  • 分层结构:每个模块通常由 Controller / Service / Mapper(MyBatis-Plus)组成,实体放在 com.zsjz.ai.common.model.<module>.entity 下。
  • 多数据源切换:基于 MyBatis-Plus + Dynamic Datasource,主库 PostgreSQL/pgvector,案件级 DuckDB,通过注解和自定义路由动态切换。
  • 横切能力:Sa-Token 认证、跨域、SSE 心跳保活、定时任务、工作空间与案件数据源清理等。
  • 扩展方式:给出新增模块的标准步骤,以及性能与可观测性建议。

项目结构

ai-server 是一个 Spring Boot 应用,采用“公共基础设施 + 业务模块”的组织方式:

  • common:跨模块配置、上下文、缓存、异常、统一响应、数据源路由、安全拦截等横切能力。
  • module:按领域划分的业务模块,每个模块内部再分 controller、service、mapper、entity、vo、dto、config 等包。
  • resources:Spring Boot 配置文件,包含通用配置、开发环境与生产环境 profile。
  • scripts:部署脚本;sql:数据库初始化与增量脚本。

    graph TB
    Client["前端或调用方"] --> Web["Spring MVC<br/>Controller 层"]
    Web --> Common["横切能力<br/>Sa-Token / CORS / 全局异常"]
    Web --> Modules["业务模块<br/>plat / agent / aiclean / knowledge / graph / person / call / trans / track / govern / search / dm / otg / external"]
    Modules --> Services["Service 业务逻辑"]
    Services --> Mappers["Mapper<br/>MyBatis-Plus"]
    Mappers --> DS["数据源路由<br/>CaseRoutingDataSource"]
    DS --> PG["PostgreSQL / pgvector"]
    DS --> DuckDB["DuckDB 案件库"]
    Services --> Redis["Redis"]
    Services --> ES["Elasticsearch"]
    

图示来源

  • application.yaml:1-193
  • application-dev.yaml:1-87
  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68

章节来源

  • application.yaml:1-193
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

核心组件

  • Spring Boot 基础栈:web、validation、redis、aop、configuration-processor、Lombok、Slf4j。
  • 数据访问:MyBatis-Plus 3.5.17、Dynamic Datasource 4.5.0、JSqlParser、PostgreSQL 驱动、pgvector Java SDK、DuckDB JDBC。
  • 认证授权:Sa-Token 1.46.0,仅做登录态校验,不引入完整权限体系。
  • AI 生态:AgentScope 2.0.4、Spring AI BOM、DashScope/Ollama/OpenAI 模型扩展、mem0、RAG simple、Redis 扩展、Tika 文档读取。
  • 推理与向量:DJL HuggingFace tokenizers、ONNX Runtime CPU、本地 bge-m3 嵌入模型(可选)。
  • 外部系统:Neo4j、Elasticsearch、Redis。

这些依赖集中在 ai-server 的 Maven 工程中,并配合 application.yaml 中的环境变量占位符实现可部署化。

章节来源

  • ai-server/pom.xml:1-200
  • application.yaml:1-193

架构总览

ai-server 的请求路径如下:

  1. 浏览器或桌面客户端发起 HTTP 请求。
  2. 先经过 CORS Filter,处理预检请求和跨域头。
  3. 进入 Sa-Token Filter,包装会话上下文。
  4. 经过 LicenseFilter、SaInterceptor 校验登录。
  5. 到达 Controller,再由 Service 执行业务逻辑。
  6. 需要数据库时,通过 MyBatis-Plus Mapper 访问数据源。
  7. 数据源根据注解或上下文,路由到 PostgreSQL、pgvector 或当前案件的 DuckDB。
  8. 长连接场景使用 SSE 推送清洗、治理、研判进度;定时任务负责资源回收与看门狗。

    sequenceDiagram
    participant Browser as "浏览器"
    participant Cors as "CORS Filter"
    participant Sa as "Sa-Token Filter"
    participant Auth as "SaInterceptor"
    participant Ctrl as "Controller"
    participant Svc as "Service"
    participant Map as "Mapper"
    participant DS as "CaseRoutingDataSource"
    participant PG as "PostgreSQL/pgvector"
    participant Duck as "DuckDB"
    
    Browser->>Cors: "HTTP 请求"
    Cors-->>Browser: "跨域响应头"
    Browser->>Sa: "带 x-token 的请求"
    Sa-->>Auth: "携带会话上下文"
    Auth->>Ctrl: "校验通过后转发"
    Ctrl->>Svc: "调用业务方法"
    Svc->>Map: "执行 SQL"
    Map->>DS: "按 @DS 或 slave key 获取数据源"
    alt "主库或普通业务"
        DS->>PG: "PostgreSQL"
    else "案件数据"
        DS->>Duck: "DuckDB 案件库"
    end
    Duck-->>Map: "结果集"
    PG-->>Map: "结果集"
    Map-->>Svc: "实体或 VO"
    Svc-->>Ctrl: "业务结果"
    Ctrl-->>Browser: "Result 包装响应"
    

图示来源

  • CorsConfig.java:1-70
  • SaTokenWebConfig.java:1-65
  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68

详细组件分析

模块化原则与职责边界

业务模块按领域划分,典型结构为:

  • module/<domain>/controller:对外 REST 接口。
  • module/<domain>/service:业务编排与事务边界。
  • module/<domain>/mapper:MyBatis-Plus Mapper 接口。
  • common/model/<domain>/entity:持久化实体。
  • module/<domain>/vo / dto:视图对象与入参对象。

各模块主要职责如下:

  • plat:平台基础能力,包括用户、认证、系统信息、表结构管理、SSE 推送、案例基本信息、塔台监听等。
  • agent:智能体能力,包括模型管理、聊天会话、消息、意图中间件、提示词加载、工作空间脚手架、RAG 索引状态、专家与工具包等。
  • aiclean:AI 清洗看门狗、DuckDB 辅助执行、模板元数据向量索引等。
  • knowledge:知识库相关控制器与服务。
  • graph:图谱查询、历史关系等图数据能力。
  • person:人员画像、亲密度、常住人口、分组等。
  • call:通话记录、夜间通话、连续通话、敏感通话等。
  • trans:交易流水、大额、频率、资金流向、固定存款、金融类、敏感交易等。
  • track:轨迹追踪,包括快递、同乘、同住、手机基站、本地轨迹、会面等。
  • govern:治理任务、治理树、治理配置、结果处理器等。
  • search:搜索相关控制器与服务。
  • dm:数据建模、文件识别、清洗错误日志、边配置、人物关系边等。
  • otg:特殊日期、时间序列、搜索入口等。
  • external:外部数据,如逃犯、车辆、共同居住、刑事信息等。

这种组织方式使每个模块只关注自身领域对象,并通过 Service 暴露业务能力;Controller 保持薄,只做参数校验与 Result 包装。

章节来源

  • AuthController.java:1-82
  • SystemController.java:1-200

多数据源配置与切换机制

ai-server 使用 MyBatis-Plus 与 Dynamic Datasource 组合,实现主库 PostgreSQL/pgvector 与案件级 DuckDB 的动态切换。

关键设计点:

  • spring.datasource.dynamic.primary 默认指向 master,即 PostgreSQL。
  • spring.datasource.dynamic.strict=true 保证写错数据源 key 时直接报错,而不是静默回退到主库。
  • 业务中大量 Mapper 标注 @DS(StrConsts.DS_KEY_SLAVE),表示走“从库分支”。
  • 项目用 CaseRoutingDataSource 替换 Dynamic Datasource 自动装配的 DynamicRoutingDataSource,把逻辑 key slave 映射到物理 key case{caseId}。
  • 物理 key 不能含下划线,否则会被当成数据源组名,导致随机串库。
  • 未打开案件时,路由会返回 400 业务异常,而不是抛 500 服务端异常。
  • DuckDB 作为案件库,由 CaseDataSourceRegistry 管理生命周期,并由 CaseDataSourceReaper 兜底回收。

    flowchart TD
    Start["业务调用 Mapper"] --> CheckDS{"是否标注 slave?"}
    CheckDS -->|否| UseMaster["使用 master:PostgreSQL/pgvector"]
    CheckDS -->|是| GetCase["从 CaseContextHolder 取 caseId"]
    GetCase --> HasCase{"caseId 是否存在?"}
    HasCase -->|否| Error400["返回 400:请先打开案件"]
    HasCase -->|是| PhysicalKey["生成物理 key:case{caseId}"]
    PhysicalKey --> Exists{"DuckDB 数据源已注册?"}
    Exists -->|否| Error400b["返回 400:数据源未打开或已释放"]
    Exists -->|是| ReturnDuck["返回 DuckDB 连接"]
    

图示来源

  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

章节来源

  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68
  • application.yaml:1-193
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

Sa-Token 认证与会话保活

Sa-Token 在项目中承担“登录态校验”职责,不负责复杂角色权限:

  • 所有非白名单接口都会触发 StpUtil.checkLogin()。
  • 白名单包括 /auth/login、/sys/health、/sys/checkAuth、/sys/info、/sys/code、/sys/noNet、/sys/map/**、/error。
  • 登录后请求会记录一次“活跃”,用于判断账号是否正在被其他设备顶下线。
  • 生产默认单端登录,后登录顶掉前登录;联调时可开启多端。
  • Redis 是登录态硬依赖,不可用时直接抛异常。
  • 前端通过 x-token 请求头传递令牌,需与后端 Sa-Token 配置一致。

    sequenceDiagram
    participant Client as "前端"
    participant Sys as "SystemController"
    participant Auth as "AuthController"
    participant Sa as "Sa-Token"
    participant Redis as "Redis"
    
    Client->>Sys: "GET /sys/health"
    Sys-->>Client: "健康检查成功"
    Client->>Auth: "POST /auth/login"
    Auth->>Sa: "创建会话"
    Sa->>Redis: "写入 token 与登录映射"
    Auth-->>Client: "返回 x-token"
    Client->>Client: "后续请求携带 x-token"
    Client->>Sys: "业务请求"
    Sa->>Redis: "校验 token"
    Sys-->>Client: "返回业务数据"
    

图示来源

  • SaTokenWebConfig.java:1-65
  • AuthController.java:1-82
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

章节来源

  • SaTokenWebConfig.java:1-65
  • AuthController.java:1-82
  • application-dev.yaml:1-87
  • application-pro.yaml:1-87

SSE 心跳保活与实时推送

SSE 用于向前端推送清洗、治理、研判等任务的实时进度:

  • 每个登录用户维护一个 SseEmitter,避免多人使用时互相覆盖。
  • 同一用户重复连接会断开旧连接,保留最新连接。
  • 建立连接后立即推送一条初始消息,便于前端快速感知连接成功。
  • 定时任务每 5 秒向所有活跃连接发送心跳,防止代理或浏览器断开空闲连接。
  • 推送失败时关闭对应连接,并记录日志。
  • 推送内容会补齐当前 caseId,让前端能按案件丢弃上一个任务的残留进度。

    sequenceDiagram
    participant Frontend as "前端"
    participant Sse as "SseService"
    participant Scheduler as "调度器"
    
    Frontend->>Sse: "建立 SSE 连接"
    Sse-->>Frontend: "返回 SseEmitter"
    Sse->>Frontend: "推送连接成功消息"
    loop "每 5 秒"
        Scheduler->>Sse: "触发 fixedSee()"
        Sse->>Frontend: "推送心跳消息"
    end
    Frontend->>Sse: "断开连接"
    Sse-->>Frontend: "移除连接引用"
    

图示来源

  • SseService.java:1-172
  • application.yaml:1-193

章节来源

  • SseService.java:1-172
  • application.yaml:1-193

定时任务与工作空间、案件数据源清理

定时任务运行在 Spring 调度线程池中,默认大小为 4,避免单个批量任务拖住 SSE 心跳:

  • SSE 心跳:每 5 秒广播心跳。
  • 数据源回收:周期性扫描已打开的案件 DuckDB 数据源,若归属 token 失效且无在途任务,则关闭数据源并清理缓存。
  • AI 清洗看门狗:标记长时间没有推进的清洗批次为失败,等待用户重新触发。
  • 附件孤儿清理:清理长期未发消息对应的聊天附件。
  • 工作空间脚手架与 Agent 生命周期:由 agent 模块负责工作空间初始化与清理。

CaseDataSourceReaper 的核心策略是“保守回收”:

  • 优先按 owner token 判断会话是否存活。
  • 若该案件仍有业务模块注册的“在途任务探针”,则跳过回收。
  • 查询会话状态失败时,宁可多占内存也不误关正在使用的案件数据源。

    flowchart TD
    Start["定时触发回收"] --> Empty{"是否有已打开案件?"}
    Empty -->|否| End["结束本轮"]
    Empty -->|是| Iterate["遍历每个 caseId"]
    Iterate --> Owner["获取归属 userId 与 token"]
    Owner --> Alive{"归属会话仍有效?"}
    Alive -->|是| Next["下一个案件"]
    Alive -->|否| Busy{"是否有在途任务?"}
    Busy -->|是| Skip["跳过回收"]
    Busy -->|否| Close["关闭 DuckDB 数据源并清理缓存"]
    Close --> Next
    Next --> Iterate
    

图示来源

  • CaseDataSourceReaper.java:1-125
  • application.yaml:1-193

章节来源

  • CaseDataSourceReaper.java:1-125
  • application.yaml:1-193

依赖关系分析

ai-server 的依赖可分为四层:

  • 表现层:Spring MVC Controller,提供 REST API。
  • 业务层:Service 封装领域逻辑,协调多个 Mapper、缓存、外部系统。
  • 数据层:MyBatis-Plus Mapper 与 Dynamic Datasource 路由。
  • 外部依赖:PostgreSQL、pgvector、DuckDB、Redis、Elasticsearch、Neo4j、外部大模型与推理引擎。

    graph LR
    Controller["Controller"] --> Service["Service"]
    Service --> Mapper["Mapper"]
    Mapper --> DataSource["CaseRoutingDataSource"]
    DataSource --> Postgres["PostgreSQL / pgvector"]
    DataSource --> Duck["DuckDB"]
    Service --> Cache["Redis"]
    Service --> Search["Elasticsearch"]
    Service --> Graph["Neo4j"]
    

图示来源

  • application.yaml:1-193
  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68

章节来源

  • application.yaml:1-193
  • DataSourceConfig.java:1-38
  • CaseRoutingDataSource.java:1-68

新增业务模块标准模板

新增业务模块应遵循以下流程:

  1. 新建 module 包

    • 在 com.zsjz.ai.module.<new-domain> 下创建模块根包。
    • 至少包含 controller、service、mapper、vo、dto。
    • 如果模块有实体,将实体放在 com.zsjz.ai.common.model.<new-domain>.entity。
  2. 定义实体与 Mapper

    • 实体使用 Lombok 简化代码。
    • Mapper 继承 MyBatis-Plus 的基础接口,必要时编写 XML。
    • 如果读主库,不加 @DS;如果读案件数据,加 @DS(StrConsts.DS_KEY_SLAVE)。
  3. 实现 Service

    • Service 封装业务规则、事务边界、缓存操作。
    • 如需异步或后台任务,结合 SSE 推送进度。
    • 如需访问 DuckDB,确保当前用户已打开案件。
  4. 注册 Controller 路由

    • 使用 @RestController 与 @RequestMapping。
    • 返回统一 Result 包装。
    • 需要登录的方法自动受 Sa-Token 保护;公开接口加入白名单。
  5. 维护数据库脚本

    • 在 sql 目录下新增或更新建表、索引、初始数据脚本。
    • 明确说明所属环境、版本号、是否幂等。
    • 如果是案件库表,注意 DuckDB 与 PostgreSQL 的差异。
  6. 验证多数据源行为

    • 确认 Mapper 的 @DS 注解正确。
    • 测试未开案时的 400 错误。
    • 测试开案后 DuckDB 数据可读。
    • 测试 token 过期后数据源被回收。

      flowchart TD
      NewModule["新建模块包"] --> Entity["定义 entity"]
      Entity --> MapperDef["定义 mapper"]
      MapperDef --> ServiceImpl["实现 service"]
      ServiceImpl --> ControllerDef["实现 controller"]
      ControllerDef --> Route["注册路由"]
      Route --> Sql["维护数据库脚本"]
      Sql --> Test["验证主库与案件库"]
      Test --> Done["完成"]
      

[此图为概念流程,不对应具体源码文件]

性能与可观测性建议

性能建议

  • 控制并发 DuckDB 实例:通过 zsjz.duckdb.max-open-cases 限制同时打开的案件数,避免内存耗尽。
  • 分摊 DuckDB 预算:按 memory-ratio 与 threads-per-instance 控制每个案件实例的资源占用。
  • 限制文件 AI 识别并发:zsjz.file-ai.max-concurrent 与 max-pending 防止一次性上传打爆队列。
  • 限制向量模型资源:本地 bge-m3 模型首次加载耗时较长,可通过 eager-load=false 懒加载,并结合 intra-op-threads、opt-level 调整。
  • 合理设置 SSE 心跳:5 秒心跳适合大多数代理与浏览器,但高并发时需评估心跳负载。
  • 扩大调度线程池:默认 4 线程已隔离心跳、数据源回收、AI 清洗看门狗、附件清理,避免相互阻塞。
  • 避免跨域滥用:生产环境收敛 allowed-origin-patterns,减少预检请求。

可观测性建议

  • 日志级别:数据源路由、SSE 心跳、数据源回收都应保留 debug 日志,用于排查串库、连接泄漏。
  • 指标采集:建议接入 Micrometer 或 Prometheus,采集:
    • 已打开 DuckDB 数量。
    • SSE 活跃连接数。
    • Redis、PostgreSQL、Elasticsearch 探测耗时。
    • AI 清洗批次成功率、失败率、超时率。
  • 链路追踪:对长耗时接口增加 traceId,便于前端上报问题后定位后端链路。
  • 健康检查增强:除 /sys/health 外,可增加 /sys/detailedHealth,暴露各依赖的健康状态。
  • 告警规则:
    • DuckDB 打开数接近上限。
    • SSE 连接数异常下降。
    • Redis 不可用。
    • AI 清洗批次大量超时。

章节来源

  • application.yaml:1-193
  • SseService.java:1-172
  • CaseDataSourceReaper.java:1-125

故障排查指南

常见问题

  • 前端连不上后端
    • 检查 CORS 是否启用,桌面端 file:// 必须允许跨域。
    • 检查是否因预检请求被 Sa-Token 拦截,CORS Filter 必须排在最前。
  • 登录后仍报 401
    • 检查前端是否正确设置 x-token。
    • 检查 Redis 是否可用,Sa-Token 强依赖 Redis。
  • 查不到案件数据
    • 检查是否已打开案件。
    • 检查 Mapper 是否标注 @DS(slave)。
    • 检查 DuckDB 数据源是否已注册。
  • 数据串库
    • 检查物理 key 是否含下划线。
    • 检查 strict=true 是否生效。
  • SSE 断流
    • 检查代理是否支持 SSE。
    • 检查心跳是否被防火墙或负载均衡器中断。
  • DuckDB 无法新打开案件
    • 检查 max-open-cases 是否已满。
    • 检查 CaseDataSourceReaper 是否正常回收。

排查步骤

  1. 查看 /sys/health 与 /sys/serviceHealth,确认后端与依赖可用性。
  2. 查看 Sa-Token 是否创建 token,Redis 中是否有登录映射。
  3. 查看数据源路由日志,确认 slave 是否路由到 case{caseId}。
  4. 查看 DuckDB 连接数与内存使用情况。
  5. 查看 SSE 连接数与心跳日志。
  6. 查看清洗、治理任务的看门狗日志。

章节来源

  • CorsConfig.java:1-70
  • SaTokenWebConfig.java:1-65
  • CaseRoutingDataSource.java:1-68
  • SseService.java:1-172
  • CaseDataSourceReaper.java:1-125

结论

ai-server 的后端架构以 Spring Boot 为基础,通过模块化拆分业务领域,通过 MyBatis-Plus 与 Dynamic Datasource 实现 PostgreSQL/pgvector 与 DuckDB 的多数据源切换,通过 Sa-Token 实现轻量认证,通过 SSE 与定时任务实现实时推送与资源回收。新增模块时应严格遵循 controller-service-mapper 分层,并在数据库脚本中维护表结构。未来可继续补充指标采集、链路追踪与健康检查增强,以提升系统的可观测性与稳定性。