架构总览

容器视图(系统分层) #

%%{init: {"flowchart": {"nodeSpacing": 28, "rankSpacing": 60, "curve": "basis"}} }%%
flowchart TD
    subgraph CLIENT["1. 客户端层"]
        direction TB
        FE_WEB["Web 前端 单一应用<br/>含取证 + 数据分析两模块"]
        FE_API["第三方 API Client<br/>实验室对接"]
    end

    subgraph EDGE["2. 接入层"]
        direction TB
        GATEWAY["gateway-bff<br/>Traefik + WebSocket Hub + 聚合查询<br/>TLS / JWT / 限流 / 路由<br/>内置许可校验(进程内读 dongle)"]
    end

    subgraph PLATFORM["3. 平台基础服务层(user-profile / watchdog + Consul 第三方)"]
        direction TB
        CONSUL["Consul 第三方<br/>服务发现 + KV 配置 + 健康检查"]
        USER_PROFILE["user-profile-service<br/>JWT 签发 + JWKS + 用户资料 + 案件 ACL"]
        WATCHDOG["watchdog-service<br/>核心服务在线巡查<br/>健康检查 + 异常告警"]
    end

    subgraph CORE["4. 核心域 3 个"]
        direction TB
        CASE["case-service<br/>案件 + 检材 + 数据源元数据"]
        TASK["task-service<br/>任务调度 + 插件注册 + 报表生成调度"]
        EXT["evidence-extract-service<br/>取证原始数据 SoR<br/>静态 type ~50 + 动态 type 注册"]
    end

    subgraph BIZ["5. 业务派生域 5 个"]
        direction TB
        REC["records-service<br/>资金 + 话单 OLAP 派生"]
        GR["graph-service<br/>实体图谱 派生"]
        TRJ["trajectory-service<br/>轨迹 + 行程 派生"]
        CONT["content-service<br/>全文检索 派生"]
        REPORT["report-service<br/>报告导出 跨业务聚合 PDF/Excel/BCP"]
    end

    subgraph COMPUTE["6. AI 与计算层 4 个"]
        direction TB
        AI["ai-service<br/>LLM / 嵌入 / RAG / OCR"]
        CHAT["chat-service<br/>多轮对话 + Assistants API<br/>持久化会话"]
        MCP["mcp-service<br/>MCP 协议代理<br/>暴露平台工具给 LLM 客户端"]
        CE["compute-engine<br/>Python Pandas case-scoped"]
    end

    subgraph BUS["7. 消息总线"]
        direction TB
        NATSJS["NATS JetStream<br/>领域事件 + 进度推送"]
    end

    subgraph STORE["8. 共享有状态服务"]
        direction TB
        PG[("PostgreSQL<br/>OLTP")]
        ES[("ElasticSearch<br/>全文检索")]
        SR[("StarRocks<br/>OLAP")]
        NEO[("Neo4j<br/>图")]
        QD[("Qdrant<br/>向量")]
        MINIO[("MinIO<br/>对象存储 含 report-output")]
    end

    FE_WEB --> GATEWAY
    FE_API --> GATEWAY

    GATEWAY --> USER_PROFILE
    GATEWAY --> CASE
    GATEWAY --> TASK
    GATEWAY --> EXT
    GATEWAY --> REC
    GATEWAY --> GR
    GATEWAY --> TRJ
    GATEWAY --> CONT
    GATEWAY --> REPORT
    GATEWAY --> AI
    GATEWAY --> CHAT

    TASK -->|ExecuteReportTask 报表类任务委派| REPORT
    REPORT -->|gRPC 拉数据| REC
    REPORT -->|gRPC 拉数据| GR
    REPORT -->|gRPC 拉数据| TRJ
    REPORT -->|gRPC 拉数据| CONT
    REPORT -.UpdateTaskProgress 回调.-> TASK

    CHAT -->|调 LLM| AI
    CHAT -->|工具调用| MCP
    MCP -->|gRPC 调业务工具| BIZ
    MCP -->|gRPC 调业务工具| EXT

    TASK -->|插件结果 SoR 写入| EXT
    EXT -->|extract.TYPE.created 事件| NATSJS
    NATSJS -->|订阅派生加工| BIZ
    BIZ -->|gRPC| COMPUTE
    TASK --> CE

    CORE <-->|事件| NATSJS
    BIZ <-->|事件| NATSJS
    COMPUTE -->|进度| NATSJS
    NATSJS --> GATEWAY

    USER_PROFILE --> PG
    WATCHDOG -.心跳检查.-> CORE
    WATCHDOG -.告警事件.-> NATSJS
    CONSUL -.KV 存储 内置 raft.- CONSUL
    CASE --> PG
    TASK --> PG
    EXT -->|case 分库 SoR| PG
    BIZ --> PG
    GR -->|持有 schema| NEO
    AI -->|持有 collection| QD
    CONT -->|派生索引| ES
    REC -->|派生 OLAP| SR
    BIZ --> MINIO
    CASE --> MINIO
    TASK --> MINIO

    classDef edge fill:#ffd9b3,stroke:#cc6600;
    classDef infra fill:#e1d5e7,stroke:#9673a6;
    classDef core fill:#cce5ff,stroke:#0066cc;
    classDef biz fill:#d5e8d4,stroke:#82b366;
    classDef compute fill:#fff2cc,stroke:#d6b656;
    classDef bus fill:#f8cecc,stroke:#b85450;
    classDef store fill:#f0f0f0,stroke:#666;
    class GATEWAY edge;
    class CONSUL,USER_PROFILE,WATCHDOG infra;
    class CASE,TASK,EXT core;
    class REC,GR,TRJ,CONT,REPORT biz;
    class AI,CHAT,MCP,CE compute;
    class NATSJS bus;
    class PG,ES,SR,NEO,QD,MINIO store;

关键时序图 #

%%{init: {'theme':'base','themeVariables':{'fontFamily':'Segoe UI, Microsoft YaHei, sans-serif','fontSize':'13px'}}}%%
sequenceDiagram
  autonumber
  actor U as 用户·浏览器
  participant GW as 网关 Gateway
  participant US as user-server

  U->>GW: 登录(账号 / 密码)
  GW->>US: 转发校验凭据
  US-->>GW: JWT(签名)+ ① 系统级权限 
  GW-->>U: 返回 JWT 与 ①②
  note over U: ① 系统级权限 → 渲染可见页面 / 可用功能
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Segoe UI, Microsoft YaHei, sans-serif','fontSize':'13px'}}}%%
sequenceDiagram
  autonumber
  actor U as 用户·浏览器
  participant GW as 网关 Gateway
  participant CS as case-server
  participant DB as PostgreSQL

  U->>GW: GET /cases?cursor=..&size=20(带 JWT)
  GW->>GW: 验签 JWT,取出 ① 系统级权限 + ② dataScope + home_org_code(信令牌)
  GW->>CS: 转发(注入 X-User-Id + dataScope + home_org_code)
  note over CS: 按机构编码规则(省2市2县2类别2顺序4)解析 home_org_code<br/>取末非零段 → 得本单位前缀(无需回 user-server、无需另存 level)<br/>基线 = 我创建/负责 ∪ case_grant(user_id=我);按 dataScope 追加机构分支
  alt dataScope = 本人
    CS->>DB: 基线 + keyset
  else dataScope = 本单位
    CS->>DB: 基线 ∪ org_code = :my_code + keyset
  else dataScope = 本单位及下级
    CS->>DB: 基线 ∪ org_code LIKE :prefix||'%'(左锚定 → btree 区间扫描)+ keyset
  end
  DB-->>CS: 一页案件(按时间倒序)
  CS-->>U: 列表 + 下一页游标
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Segoe UI, Microsoft YaHei, sans-serif','fontSize':'13px'}}}%%
sequenceDiagram
  autonumber
  actor U as 用户·浏览器
  participant GW as 网关 Gateway
  participant CS as case-server
  participant DB as PostgreSQL

  U->>GW: 打开案件 case_id(带 JWT)
  GW->>CS: 取案件(注入 X-User-Id )
  note over CS: 用令牌 ②前缀(信前端) + 本地 创建者/负责人 + case_grant(我,本案) 判可见<br/>都不命中 → 403(直接拼 case_id 也越不过)
  CS->>DB: 取 case 行 + case_grant(我,本案)
  DB-->>CS: case + 我对本案的 level
  CS-->>U: 案件详情 + creator_id + 我对本案的 level(read/write)
  note over U: 仅 UI:高亮 / 置灰写按钮(真正写在阶段四再判)
%%{init: {'theme':'base','themeVariables':{'fontFamily':'Segoe UI, Microsoft YaHei, sans-serif','fontSize':'13px'}}}%%
sequenceDiagram
  autonumber
  actor OP as 操作者·浏览器
  participant GW as 网关 Gateway
  participant IS as identity-service<br/>(User / Scope 模块)
  participant DB as PostgreSQL

  note over OP,DB: ===== 第一部分 · 按警号查目标用户 + 创建分享授权(ShareGrant)=====
  note over OP: 已进入某案件,case_id 已知
  OP->>GW: 输入目标「警号」查询(GET /users?keyword=警号)
  GW->>IS: ListUsers(keyword)(注入操作者 X-User-Id)
  note over IS: keyword 模糊匹配 username / real_name / police_no<br/>(无"精确警号"专用接口)
  IS->>DB: 按 keyword 模糊查 user
  DB-->>IS: 命中用户
  IS-->>OP: 用户列表(user_id / 姓名 / 警号 / 任职单位)

  OP->>GW: 对本案创建分享授权(POST /share-grants)<br/>{grantee_user_id, resource_type:"case", resource_id:case_id, access_level:"write", reason, expires_at}
  GW->>IS: CreateShareGrant(注入 X-User-Id → grantor)
  note over IS: grantor = ctx X-User-Id;biz 仅校验 access_level∈{read,write}<br/>无"操作者 admin / 子树"校验(鉴权止于网关 BearerAuth)
  IS->>DB: Upsert share_grant(事务 + 行锁 FOR UPDATE)
  note over DB: 幂等键 = (grantee_user_id, resource_type, resource_id) 的未过期记录<br/>命中→更新(access_level 取 write 优先,回写新 grantor / reason / expires_at);否则插入
  DB-->>IS: grant_id
  IS-->>OP: 分享授权成功(grant_id)

  note over OP,DB: ===== 第二部分 · 调整目标用户授权(identity-service 实际写操作,二选一)=====
  note over OP,IS: 代码无 home_org_code / 无"改组织机构代码"提权;以下为实际可用的授权调整写操作
  alt 提升数据边界 · SetDataScope
    OP->>GW: PUT /users/{user_id}/data-scope { scope_type:"dept_and_sub" }
    GW->>IS: SetDataScope(注入 X-User-Id)
    note over IS: biz 仅校验 scope_type∈{self,dept,dept_and_sub}<br/>无操作者角色 / 子树校验
    IS->>DB: 覆盖式更新该用户数据边界
    DB-->>IS: ok
    IS-->>OP: 数据边界已更新(→ dept_and_sub)
  else 加入单位(任职) · AddUserDept
    OP->>GW: POST /users/{user_id}/depts { dept_code:目标单位 }
    GW->>IS: AddUserDept(注入 X-User-Id)
    note over IS: 解析 dept_code → dept_id;biz 不校验操作者权限
    IS->>DB: 新增 user↔dept 任职一行(多对多)
    DB-->>IS: dept_id
    IS-->>OP: 已加入单位(dept_id)
  end

ER 图(完整字段版) #

erDiagram
    user {
        int64 id PK
        varchar username "未删范围内唯一"
        varchar police_no "警号·未删范围内唯一·必填"
        varchar password_hash
        varchar real_name
        varchar phone
        varchar email
        varchar avatar_url
        varchar status "enabled/disabled"
        bool force_change_password
        timestamp last_login_at
        timestamp created_at
        timestamp updated_at
        timestamp deleted_at "软删·NULL=未删"
    }

    dept {
        int64 id PK
        int64 parent_id "父单位·顶级=0"
        varchar name
        varchar code "全库唯一"
        int sort_order
        varchar path "物化路径·如/1/5/12/"
        varchar status "enabled/disabled"
        timestamp created_at
        timestamp updated_at
    }

    role {
        int64 id PK
        varchar name
        varchar code "全库唯一"
        smallint type "1=系统/2=业务"
        varchar description
        varchar status "enabled/disabled"
        varchar inherited_scope_type "高权限角色可=all"
    }

    permission {
        int64 id PK
        varchar service_id "注册服务标识"
        varchar parent_code "父权限编码·树形"
        varchar name
        varchar code "全库唯一·跨服务不重复"
        int sort_order
        varchar status "enabled/disabled"
        bool deprecated
        varchar version
        timestamp registered_at
    }

    user_dept {
        int64 id PK
        int64 user_id FK "唯一(user_id,dept_id)"
        int64 dept_id FK "唯一(user_id,dept_id)"
        timestamp created_at
    }

    user_role {
        int64 id PK
        int64 user_id FK
        int64 role_id FK
    }

    role_permission {
        int64 id PK
        int64 role_id FK
        int64 permission_id FK
    }

    user_data_scope {
        int64 id PK
        int64 user_id FK "唯一·一人一档"
        varchar scope_type "self/dept/dept_and_sub"
    }

    share_grant {
        int64 id PK
        int64 grantor_user_id FK "授权方"
        int64 grantee_user_id FK "被授权方"
        varchar resource_type "业务侧不透明"
        int64 resource_id "业务侧不透明"
        varchar access_level "read/write"
        varchar reason "授权原因枚举"
        timestamp expires_at "NULL=永久"
        timestamp created_at
    }

    user_last_seen {
        int64 user_id PK "=user.id"
        timestamp last_seen_at
        int64 current_dept_id "当前单位·无则0"
        varchar client_ip
    }

    user ||--o{ user_dept : "1:N 任职"
    dept ||--o{ user_dept : "1:N 容纳"
    user ||--o{ user_role : "1:N 拥有"
    role ||--o{ user_role : "1:N 被分配"
    role ||--o{ role_permission : "1:N 授权"
    permission ||--o{ role_permission : "1:N 被授权"
    user ||--o| user_data_scope : "1:1 数据边界"
    user ||--o| user_last_seen : "1:1 在线统计"
    user ||--o{ share_grant : "1:N 授出(grantor)"
    user ||--o{ share_grant : "1:N 收到(grantee)"
    dept ||--o{ dept : "自引用 parent_id+path"
    permission ||--o{ permission : "自引用 parent_code"
    dept |o..o{ user_last_seen : "current_dept_id 弱关联(可为0)"