容器视图(系统分层)
#
%%{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)"