# JumpServer & EasyNode 深度架构分析 — Nexus 技术参考报告

> 分析日期：2026-05-21 | 分析师：软件架构师 | 状态：ADRs 提议中

---

## 1. 执行摘要

**分析范围**：jumpserver/jumpserver、jumpserver/koko、jumpserver/luna、jumpserver/lion、chaos-zhu/easynode

对 Nexus 的核心参考价值排序：

| 排名 | 项目 | 技术栈 | Stars | 最大参考价值 | 直接可复用度 |
|------|------|--------|-------|-------------|-------------|
| 1 | **JumpServer Core** | Python + Django | 30.5k | 资产 Platform 模型、RBAC 授权链路、会话审计存储方案 | 设计思路完全可复用，代码需 Django→FastAPI 迁移 |
| 2 | **Koko** | Go + Vue | 531 | SSH 连接池、WebSocket-TTY 代理、asciicast 录像、Redis 房间模式 | Go→Python 重写，架构模式可直接借鉴 |
| 3 | **EasyNode** | Vue + Node.js | 2k | Web SSH 数据流、批量指令并行队列、凭据加密存储、脚本库设计 | 架构简洁适合小规模参考，Node.js→Python 重写 |
| 4 | **Luna** | Angular + TypeScript | 303 | xterm.js 集成方式、多标签终端管理、文件管理器 UI | 直接复用前端方案 |
| 5 | **Lion** | Go + Vue | 22 | RDP/VNC Guacamole 连接器 | 仅当 Nexus 需要图形协议时参考 |

---

## 2. 资产模型对比分析

### 2.1 JumpServer 资产模型架构

```
                         ┌──────────────────┐
                         │    Platform      │  (模板层)
                         │  - name (唯一)    │
                         │  - category      │  host/device/database/cloud/web/gpt/custom
                         │  - type          │  linux/windows/mysql/k8s...
                         │  - charset       │
                         │  - protocols ────┼──▶ PlatformProtocol (1:N)
                         │  - automation ───┼──▶ PlatformAutomation (1:1)
                         └────────┬─────────┘
                                  │ FK (PROTECT)
                                  ▼
              ┌──────────────────────────────────────┐
              │              Asset                   │  (实例层 - 多表继承)
              │  - name, address                     │
              │  - platform (FK)                     │
              │  - nodes (M2M → Node)                │
              │  - zone (FK → Zone, 可选)            │
              │  - connectivity                      │
              │  - gathered_info (JSON)              │
              │  ┌──────┬──────┬──────┬──────┐      │
              │  │ Host │Device│Database│Cloud│ ...  │  (子类特化)
              │  └──────┴──────┴──────┴──────┘      │
              └──────────────────────────────────────┘
                                  │ M2M
                                  ▼
              ┌──────────────────────────────────────┐
              │              Node                    │  (组织层 - 键位树)
              │  - key: "1:2:3"  (冒号分隔层级)      │
              │  - value: "WebServers"               │
              │  - full_value: "/Default/Prod/Web"   │
              │  - child_mark (子节点计数器)          │
              │  - assets_amount (缓存)               │
              └──────────────────────────────────────┘
```

### 2.2 核心设计决策解读

#### Platform = 资产模板/类型定义

- 不在 Asset 表直接存储 `category` 和 `type`，而是通过 `platform FK` 派生
- 查询时用 `F()` 表达式优化：`queryset.annotate(category=F("platform__category"))`
- **优点**：修改资产分类只需换平台引用，无需改 Asset 记录

#### Node = 键位树（Key-Based Tree）

- 不存 `parent_id` 自引用，而是用 `key` 字符串编码层级：`"1:2:3"`
- 查询所有后代：`Q(key__istartswith=f'{self.key}:')` —— 一次查询解决
- 查询祖先：`key.split(':')` 算出来，`key__in=[...]` 一次查询
- **优点**：避免了递归 CTE 和嵌套集（nested set）的复杂度

#### 多表继承

- `Asset` 是基表，`Host`, `Device`, `Database`, `Cloud` 等是子表
- 每个子类存储类别独有的字段（Host 有 os/arch，Database 有 db_name/db_type）

### 2.3 Nexus 适配建议

| JumpServer 设计 | Nexus 适配 | 改造点 |
|----------------|-----------|--------|
| Platform 模板 | **直接采纳** — 创建 `platforms` 表作为资产类型模板 | Django FK → SQLAlchemy `relationship` + `ForeignKey` |
| Node 键位树 | **采纳但简化** — 2000+ 服务器规模用 `parent_id` 自引用更直观，配合 PostgreSQL `ltree` 扩展 | 键位树在 SQLAlchemy 中无原生优势 |
| Asset 多表继承 | **改为单表+JSON** — 用 `extra_attrs JSONB` 替代多表继承 | Django 多表继承 → SQLAlchemy 单表 + JSONB |
| Protocol 实例级覆盖 | **采纳** — 允许每个 Asset 覆盖默认端口 | 直接复用设计 |
| Node 缓存映射 | **采纳但用 Redis** — `{node_id: [asset_ids]}` 的 Redis Hash 缓存 | Django 三级缓存 → 直接用 Redis |

### 2.4 推荐的 Nexus Asset 模型

```python
# SQLAlchemy Async 模型
class Platform(Base):
    __tablename__ = "platforms"
    id: int (PK)
    name: str (unique)           # "Linux", "Windows", "MySQL"
    category: str                # "host", "database", "device"
    type: str                    # "linux", "mysql", "switch"
    default_protocols: JSONB     # [{"name":"ssh","port":22,"primary":true}]

class Asset(Base):
    __tablename__ = "assets"
    id: UUID (PK)
    name: str
    address: str                 # IP 或域名
    platform_id: FK → Platform
    node_id: FK → Node (可选)
    tags: M2M → Tag
    protocols: JSONB            # 实例级覆盖: [{"name":"ssh","port":2222}]
    extra_attrs: JSONB          # 替代多表继承: {"os":"ubuntu","arch":"x86_64",...}
    connectivity: str           # "ok","err","unknown"
    last_checked_at: datetime

class Node(Base):
    __tablename__ = "nodes"
    id: UUID (PK)
    name: str
    parent_id: FK → Node (nullable)  # 自引用树
    # 或直接用 PostgreSQL ltree:
    # path: ltree                  # "root.production.web"
```

---

## 3. 权限体系对比分析

### 3.1 JumpServer 四层权限模型

```
Layer 1: 认证 (Authentication)
  └── 密码/MFA/LDAP/CAS/OIDC/SAML2/OAuth2/RADIUS

Layer 2: RBAC (角色权限 — 控制管理界面操作)
  └── Role → RoleBinding → User
      ├── SystemAdmin / SystemAuditor / SystemUser (全局)
      └── OrgAdmin / OrgAuditor / OrgUser            (组织级)

Layer 3: Asset Permission (资产授权 — 谁能访问什么)
  └── AssetPermission {
        users: M2M[User]         ← 授权给谁
        user_groups: M2M[Group]
        assets: M2M[Asset]       ← 授权哪些资产
        nodes: M2M[Node]         ← 授权哪些节点(子节点自动继承)
        accounts: JSON[account_ids / "@ALL" / "@USER" / "@INPUT"]
        protocols: JSON          ← 允许的协议
        actions: int             ← connect/upload/download/clipboard
        date_start / date_expired ← 时间窗口
        is_active: bool          ← 启用/停用
      }

Layer 4: ACL (访问控制列表 — 精细化控制)
  ├── LoginAssetACL       → accept/reject/review/notice/face_verify
  ├── CommandFilterACL    → allow/deny/review (正则匹配危险命令)
  ├── ConnectMethodACL    → 控制 WebCLI/SSH/RDP 等连接方式
  └── DataMaskingRule     → 数据库查询脱敏
```

### 3.2 资产授权链核心查询（JumpServer 实现）

```python
# AssetPermission.get_all_assets() — 计算用户可访问的完整资产集
def get_all_assets(self):
    # Step 1: 直接授权的资产
    asset_ids = set(self.assets.all().values_list('id', flat=True))
    # Step 2: 通过节点授权的资产（含后代节点）
    nodes_keys = self.nodes.all().values_list('key', flat=True)
    nodes_asset_ids = Node.get_nodes_all_asset_ids_by_keys(nodes_keys)
    asset_ids.update(nodes_asset_ids)
    return Asset.objects.filter(id__in=asset_ids)
```

### 3.3 Nexus 适配建议

| JumpServer 设计 | Nexus 适配 | 说明 |
|----------------|-----------|------|
| 四层权限模型 | **简化为三层**：认证 → RBAC → 资产授权 | Nexus 初期不需要 ACL 层的全部复杂度 |
| AssetPermission 模型 | **直接采纳核心字段** | users/groups/assets/nodes/accounts/date_range/is_active |
| 命令过滤 | **后期实现**，初期提供命令日志审计 | 使用正则匹配，会话中实时拦截 |
| 账号别名(@ALL/@USER/@INPUT) | **采纳** | JumpServer 的精妙设计 |
| 时间窗口授权 | **采纳** | date_start/date_expired，简单有效 |

### 3.4 推荐的 Nexus 权限模型

```python
class AssetPermission(Base):
    __tablename__ = "asset_permissions"
    id: UUID (PK)
    name: str
    users: M2M[User]
    user_groups: M2M[UserGroup]
    assets: M2M[Asset]
    nodes: M2M[Node]
    accounts: JSONB            # ["root", "@ALL", "@USER"]
    actions: JSONB             # ["connect", "upload", "download"]
    date_start: datetime
    date_expired: datetime
    is_active: bool
```

---

## 4. 组件架构对比分析 (Core + Connector 模式)

### 4.1 JumpServer 组件通信架构

```
                          ┌──────────────────────┐
                          │    Nginx / LB         │
                          └───────┬──────────────┘
                                  │
          ┌───────────────────────┼───────────────────────┐
          │                       │                       │
          ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Core (Django) │    │  Koko (Go)      │    │  Lion (Go)      │
│   :8080 (REST)  │    │  :2222 (SSH)    │    │  :3389 (RDP)    │
│   :8070 (WS)    │    │  :5000 (WS)     │    │  :8081 (WS)     │
└────────┬────────┘    └────────┬────────┘    └────────┬────────┘
         │                      │                      │
         │     Redis Pub/Sub    │                      │
         ├──────────────────────┼──────────────────────┤
         │  DB 0: Session Rooms │                      │
         │  DB 4: Asset Cache   │                      │
         │  DB 6: WS Messages   │                      │
         │                      │                      │
         ▼                      ▼                      ▼
┌─────────────────┐    ┌──────────────────────┐
│  MySQL/Postgres │    │  Target Assets       │
└─────────────────┘    └──────────────────────┘
```

### 4.2 Bootstrap Token 注册流程

```
Koko 启动
  │
  ├─ 1. POST /api/v1/terminal/terminal-registrations/
  │      Header: Authorization: BootstrapToken <TOKEN>
  │      Body: {"name": "Koko-01", "type": "koko", ...}
  │
  ▼
Core 验证 Bootstrap Token (与 config.txt 比对)
  │
  ├─ 2. 生成 Access Key (ID + Secret)
  │      保存到 Core DB (terminal_terminalregistration 表)
  │      返回 {id, key, secret}
  │
  ▼
Koko 保存 Access Key 到本地文件 (data/keys/.access_key)
  │
  ├─ 3. 后续所有 API 调用使用 Access Key
  │      Header: Authorization: Key <ACCESS_KEY_ID>:<SIGNATURE>
  │
  ├─ 4. 心跳保活 (每 30s)
  │      POST /api/v1/terminal/terminal-registrations/<id>/heartbeat/
  │
  ▼
稳定运行
```

### 4.3 用户连接完整流程

```
用户(Browser) --WS--> Luna --WS--> Koko(:5000) --REST--> Core(:8080) --Redis/DB
                       │                                    │
                       │  1. WebSocket Connect               │
                       │  2. TerminalInit(token)             │
                       │                                     │
                       │  3. Koko 验证 ConnectToken ─────────┤
                       │  4. Core 返回(asset, account, cred) │
                       │                                     │
                       │  5. Koko --SSH--> Target Asset      │
                       │  6. I/O 双向流                       │
                       │  7. asciicast 实时录像              │
                       │                                     │
                       │  8. 会话结束，上传录像 ──────────────┤
```

### 4.4 Nexus 适配建议

| JumpServer 设计 | Nexus 适配 |
|----------------|-----------|
| **Connector 分离** | **采纳但简化** — Nexus 可将 SSH 代理内嵌为 FastAPI 子进程或 Docker sidecar，不需要独立的 Go 二进制 |
| **Bootstrap Token + Access Key** | **采纳** — 内部服务间认证的最佳实践 |
| **Redis Pub/Sub 房间模式** | **采纳** — Koko 的多实例会话共享通过 Redis Room，Nexus 多 worker 同理 |
| **Connect Token** | **采纳** — 短生命周期的一次性连接令牌，JumpServer 最精华的安全设计 |

### 4.5 Nexus 推荐架构

```
Nexus API Server (FastAPI)
  ├── /api/v1/*             REST API
  ├── /ws/terminal/{token}  WebSocket 终端端点
  └── SSH Proxy Service     内置 SSH 代理 (asyncssh)
        │
        ├── 连接池管理
        ├── asciicast 录像
        └── 命令过滤拦截
```

---

## 5. Web SSH 方案对比

### 5.1 三项目数据流对比

#### Koko (Go) — 最成熟但最重

```
浏览器(Luna, xterm.js)
  │ WebSocket (JSON: {type:"TERMINAL_DATA", data:"ls\n"})
  ▼
Koko HTTPD Server (:5000)
  ├── UserWebsocket  → 解析消息类型
  ├── tty.go         → TerminalInit(connectToken) 验证
  ├── srvconn/ssh.go → 建立 SSH 连接到目标
  └── 双向 I/O 代理
       ├── 浏览器 → Koko → SSH (stdin)
       ├── SSH → Koko → 浏览器 (stdout)
       ├── asciicast 录像 (asciinema/asciinema.go)
       └── 窗口 resize: TerminalResize → ssh.Window{Width,Height}
```

#### EasyNode (Node.js) — 最简洁

```
浏览器(Vue, xterm.js)
  │ WebSocket (ws://host:8082/terminal?hostId=xxx)
  ▼
Koa Server
  ├── controller/terminal.js → 处理 terminal WS
  ├── controller/ssh.js      → 凭证管理 (CRUD for NeDB)
  └── ssh2 npm 库            → SSH 连接到目标
       └── SSH Stream ↔ WebSocket 双向转发
```

#### Luna (Angular) — Web Terminal UI 参考

```
Luna 核心组件:
  ├── pages-terminal     → 终端页面容器 (多标签)
  ├── elements-terminal  → xterm.js 包装组件
  ├── elements-replay-asciicast  → 录像回放
  ├── elements-sftp      → SFTP 文件管理 UI
  └── 通信层
       ├── WebSocket → /ws/terminal/?token=xxx
       └── HTTP REST → Core API (/api/v1/...)
```

### 5.2 终端数据流关键细节

#### WebSocket 消息协议 (Koko)

```json
// 浏览器 → Koko
{"id":"uuid","type":"TERMINAL_INIT","data":"{\"cols\":80,\"rows\":24,\"code\":\"...\"}"}
{"id":"uuid","type":"TERMINAL_DATA","data":"ls -la\n"}
{"id":"uuid","type":"TERMINAL_RESIZE","data":"{\"cols\":120,\"rows\":40}"}
{"id":"uuid","type":"TERMINAL_BINARY","raw":"<base64>"}

// Koko → 浏览器
{"id":"uuid","type":"TERMINAL_SESSION","data":"{\"session\":{...}}"}
{"id":"uuid","type":"TERMINAL_DATA","data":"[01;34mfile[0m\n"}
{"id":"uuid","type":"CLOSE"}
```

#### SSH 连接复用 (Koko)

```go
// koko/pkg/srvconn/ssh.go
type SSHClient struct {
    *gossh.Client
    refCount int32              // 引用计数
    traceSessionMap map[*gossh.Session]time.Time // 活跃 session 追踪
    ProxyClient *SSHClient      // 支持跳板机代理链
}

func (s *SSHClient) AcquireSession() // refCount++ 复用连接
func (s *SSHClient) ReleaseSession() // refCount-- 还回连接池
```

### 5.3 Nexus 推荐方案

**综合最优方案：Koko 协议 + EasyNode 简洁架构 + Python 生态实现**

| 维度 | 建议 | 技术选型 |
|------|------|---------|
| WebSocket 协议 | **采用 Koko 的消息格式** (TERMINAL_INIT/DATA/RESIZE/CLOSE) | 已验证的协议设计 |
| SSH 后端 | **Python asyncssh** (纯 Python, async/await 原生支持) | 比 paramiko 更适合 FastAPI 异步 |
| 连接池 | **采用 Koko 的引用计数模式** | 复用 SSH 连接，减少握手开销 |
| SFTP | **asyncssh 内置 SFTP 支持** | 无需额外库 |
| 窗口 resize | **WebSocket SIGWINCH → ssh.resize_pty()** | 标准实现 |
| 前端 | **xterm.js + xterm-addon-fit/weblinks/webgl** | 社区最成熟的 Web Terminal 方案 |
| 录像格式 | **asciicast v2 (.cast.gz)** | 与 asciinema 生态兼容 |

---

## 6. 会话审计方案

### 6.1 JumpServer 审计架构

```
会话生命周期:
  Session.created → Session.connected → Session.disconnected → Session.ended

录像生成:
  KoKo: SSH I/O → asciinema/asciinema.go → .cast.gz (单文件)
    或: → part.1.cast + part.2.cast + ... + replay.json (流式分片)

存储路径:
  MEDIA_ROOT/replay/{YYYY-MM-DD}/{session_id}/
    ├── {session_id}.cast.gz          # asciicast SSH 录像
    ├── {session_id}.replay.json      # 元数据清单 (v3+)
    ├── {session_id}.part.1.cast      # 流式分片
    └── {session_id}.part.N.cast

存储后端:
  Local (default_storage) → Celery 异步上传 → S3/OSS/MinIO/Ceph (SERVER_REPLAY_STORAGE)

命令日志:
  session.cmd_filter_rules_acls → CommandFilterACL 实时匹配
  session.command_amount         → 统计计数
```

### 6.2 审计相关模型

```python
# JumpServer terminal/models/session.py (核心字段推断)
class Session:
    id: UUID
    user: FK[User]
    asset: FK[Asset]
    account: FK[Account]
    protocol: str               # "ssh", "rdp", "mysql"
    remote_addr: str            # 客户端 IP
    is_finished: bool
    date_start: datetime
    date_end: datetime
    has_replay: bool
    has_command: bool
    command_amount: int
    terminal: FK[TerminalRegistration]  # 哪个 Connector 处理的
```

### 6.3 Nexus 审计方案推荐

| 功能 | 方案 |
|------|------|
| 录像格式 | **asciicast v2** (.cast.gz) — 纯文本+gzip，存储成本低，可流式播放 |
| 录像生成 | Python `asciinema` 兼容的输出格式，在 SSH 代理层直接生成 |
| 录制模式 | **流式分片** (part-based) — 每 5 分钟或 1MB 切一片，避免单文件过大 |
| 存储 | 本地磁盘 → 定时任务上传 S3/MinIO (异步) |
| 回放 | 前端 xterm.js + asciinema-player 组件 |
| 命令日志 | 在 SSH stdin 流中按 `\n` 分割提取，存入 `command_logs` 表 |
| 实时监控 | WebSocket 推送在线会话列表，支持管理员实时旁观 (Room 模式) |
| 保留策略 | 按天数自动过期删除 (配置化) |

---

## 7. 架构决策建议 (ADR 格式)

### ADR-001: 资产模型采用 Platform 模板 + 单表 Asset + JSONB 扩展属性

**状态：** 提议

**上下文：** Nexus 管理 2000+ 服务器，覆盖 Linux/Windows/网络设备/数据库。JumpServer 使用 Django 多表继承实现分类，但这对 SQLAlchemy + FastAPI 过于复杂。

**决策：** 采用 JumpServer 的 Platform 概念（资产类型模板），但用单表 `Asset` + `extra_attrs JSONB` 替代多表继承。

**后果：**
- 优点：查询简单，JSONB 索引支持高效过滤，新增资产类型无需改表
- 缺点：无法在数据库层面约束特定类型的必填字段
- 缓解：Pydantic 验证层做类型特定的字段校验

---

### ADR-002: Web SSH 采用 BFF (Backend For Frontend) 模式，内嵌 SSH 代理

**状态：** 提议

**上下文：** JumpServer 将 Core API 和 Koko 分离为不同进程（Django + Go）。这种分离增加了部署复杂度。Nexus 初期不需要支持多协议(RDP/VNC)。

**决策：** Nexus 将 SSH 代理功能内嵌在 FastAPI 进程中：
- WebSocket 端点 `/ws/terminal/{token}` 直接在 FastAPI 中处理
- 使用 `asyncssh` 库建立 SSH 连接
- 连接池管理通过 Redis 共享给所有 worker（支持多进程部署）
- Connect Token 机制照搬 JumpServer 的设计

**后果：**
- 优点：部署简化（一个服务），开发效率高（全 Python）
- 缺点：SSH 代理负载与 API 负载共用资源，大规模场景需要独立部署
- 缓解：架构预留分离接口，未来可拆分为独立 SSH Proxy 服务

---

### ADR-003: 会话录像采用 asciicast v2 格式 + 流式分片上传

**状态：** 提议

**上下文：** JumpServer 的 asciicast 格式生态成熟。EasyNode 没有录像功能。

**决策：**
- 终端 I/O 按 asciicast v2 格式记录（每行 JSON: `[time, type, data]`）
- 每 5 分钟或 1MB 生成一个 part 文件
- 会话结束后合并为 .cast.gz
- 异步上传到 MinIO/S3

**后果：**
- 优点：与 asciinema 生态兼容，纯文本可搜索，gzip 压缩率高
- 缺点：不支持 RDP/VNC 图形协议录像（Nexus 不涉及）

---

### ADR-004: 权限体系采用 RBAC + AssetPermission 两层

**状态：** 提议

**上下文：** JumpServer 四层（认证+RBAC+AssetPermission+ACL）对 Nexus 初期过度设计。

**决策：**
- Layer 1: JWT 认证 + MFA (TOTP)
- Layer 2: RBAC 角色 (admin/operator/auditor/user)
- Layer 3: AssetPermission (用户/用户组 → 资产/节点 + 账号 + 时间窗口 + 动作)

ACL 层（命令过滤、连接方式控制）推迟到 v2。

**后果：**
- 优点：实现简单，2000+ 服务器规模够用
- 缺点：缺少命令级精细化控制
- 缓解：v1 提供命令日志审计（事后追溯），v2 加上实时命令过滤

---

### ADR-005: 前端终端方案采用 xterm.js + WebSocket 自定义协议

**状态：** 提议

**上下文：** Luna 使用 Angular + xterm.js，EasyNode 使用 Vue + xterm.js。两个项目的 xterm.js 集成模式类似。

**决策：**
- 前端：纯 HTML/Tailwind CSS v4 + 原生 JS 的 xterm.js（不引入 Vue/Angular 框架，与 Nexus 现有技术栈一致）
- WebSocket 消息格式：采用 Koko 的消息类型定义（TERMINAL_INIT/DATA/RESIZE/CLOSE/SFTP_*）
- 多标签页：轻量级标签管理器

**后果：**
- 优点：零额外框架依赖，体积小，性能好
- 缺点：需要自行实现终端标签管理、会话恢复等 UI 逻辑

---

## 8. 技术迁移评估：Django → FastAPI

### 8.1 关键差异对照表

| 维度 | Django / DRF | FastAPI | 迁移难度 |
|------|-------------|---------|---------|
| **ORM** | Django ORM (同步, lazy queryset) | SQLAlchemy 2.0 Async (显式 async, eager loading) | **高** |
| **序列化器** | DRF Serializer (声明式) | Pydantic v2 (类型注解) | **中** |
| **路由** | Django URL conf + ViewSet | FastAPI APIRouter + path decorators | **中** |
| **中间件** | Django Middleware | FastAPI Middleware / Depends | **低** |
| **权限** | Django Permission + DRF Permission | FastAPI Depends + 自定义权限函数 | **中** |
| **分页** | DRF PageNumberPagination | fastapi-pagination 或自行实现 | **低** |
| **过滤** | django-filter + FilterSet | SQLAlchemy 查询参数 + fastapi-filter | **中** |
| **信号** | Django Signals | SQLAlchemy Events | **低** |
| **Celery** | Django-Celery | Celery (独立) | **低** |
| **WebSocket** | Django Channels | FastAPI 原生 WebSocket | **低** |
| **组织隔离** | `current_org` 线程局部变量 | ContextVar 或中间件注入 | **中** |

### 8.2 关键迁移模式

#### Django ORM → SQLAlchemy Async 查询等价写法

```python
# Django: 分页 + 过滤 + 预加载
assets = Asset.objects.filter(
    platform__name="Linux", is_active=True
).select_related("platform").prefetch_related("nodes").order_by("name")
paginator = PageNumberPagination()
page = paginator.paginate_queryset(assets, request)

# SQLAlchemy Async: 等价写法
from sqlalchemy import select
from sqlalchemy.orm import selectinload

stmt = (
    select(Asset)
    .options(
        selectinload(Asset.platform),
        selectinload(Asset.nodes),
    )
    .where(Asset.platform.has(name="Linux"), Asset.is_active == True)
    .order_by(Asset.name)
)
page = await paginate(session, stmt, offset=offset, limit=limit)
```

#### DRF ViewSet → FastAPI Router

```python
# Django DRF
class AssetViewSet(OrgBulkModelViewSet):
    model = Asset
    filterset_class = AssetFilterSet
    search_fields = ("name", "address", "comment")

    @action(methods=["GET"], detail=True, url_path="platform")
    def platform(self, request, pk=None): ...

# FastAPI
router = APIRouter(prefix="/api/v1/assets/assets", tags=["assets"])

@router.get("/")
async def list_assets(
    search: str = Query(None),
    platform: str = Query(None),
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user),
): ...

@router.get("/{asset_id}/platform")
async def get_asset_platform(
    asset_id: UUID,
    db: AsyncSession = Depends(get_db),
): ...
```

#### Django Signals → SQLAlchemy Events

```python
# Django
@receiver(post_save, sender=Session)
def on_session_end(sender, instance, **kwargs):
    if instance.is_finished:
        upload_replay_to_s3.delay(instance.id)

# SQLAlchemy Async
@event.listens_for(Session, "after_update")
def on_session_update(mapper, connection, target):
    if target.is_finished:
        # 注意: async event 需要特殊处理
        asyncio.ensure_future(upload_replay(target.id))
```

### 8.3 Django ORM ↔ SQLAlchemy 2.0 常用查询对照

| Django ORM | SQLAlchemy 2.0 Async |
|------------|---------------------|
| `qs.filter(Q(a=1) \| Q(b=2))` | `where(or_(Model.a==1, Model.b==2))` |
| `qs.annotate(cnt=Count('assets'))` | `select(Model).options()` + 子查询或 `column_property` |
| `qs.values_list('id', flat=True)` | `select(Model.id)` + `scalars().all()` |
| `qs.select_related('platform')` | `.options(selectinload(Model.platform))` |
| `qs.prefetch_related('nodes')` | `.options(selectinload(Model.nodes))` |
| `qs.bulk_create(objs)` | `session.add_all(objs)` 配合 server-side cursor |
| `qs.bulk_update(objs, ['name'])` | `session.execute(update(Model), [...])` |
| `qs.get_or_create()` | 手动 select → insert/return |
| `Atomic(transaction)` | `async with session.begin():` |
| `Node.get_all_children()` (key istartswith) | `where(Node.path.like(f'{parent_path}.%'))` |

### 8.4 迁移风险与注意事项

1. **查询性能**：Django ORM 的 lazy loading 在 SQLAlchemy Async 中必须显式声明为 `selectinload` 或 `joinedload`，否则 N+1 问题立现
2. **事务管理**：Django 的 `ATOMIC_REQUESTS` 自动包裹请求，FastAPI 需要显式管理 `async with session.begin()`
3. **多租户过滤**：JumpServer 的 `current_org` 线程局部变量在 async 环境中不安全，必须用 `ContextVar` 或依赖注入
4. **WebSocket 中访问数据库**：Django Channels 支持同步 ORM，FastAPI WebSocket 中需用 `AsyncSession`
5. **Pydantic 验证层**：CRUD 场景下，需要在 Pydantic schema 和 SQLAlchemy model 间建立清晰的转换层（create_dto / update_dto / response_dto）

---

## 9. 建议的 Nexus 实现优先级

| 优先级 | 模块 | 参考项目 | 工作量估计 |
|--------|------|---------|-----------|
| **P0** | 资产 CRUD + Platform 模板 + Node 树 | JumpServer | 2-3 周 |
| **P0** | JWT 认证 + MFA | JumpServer | 1 周 |
| **P0** | Web SSH 终端 (WebSocket + asyncssh + xterm.js) | Koko + EasyNode + Luna | 3-4 周 |
| **P1** | RBAC + AssetPermission 授权 | JumpServer | 2 周 |
| **P1** | SFTP 文件管理 (Web 界面) | Luna + EasyNode | 1-2 周 |
| **P1** | 会话录像 (asciicast) + 存储 | JumpServer | 2 周 |
| **P2** | 批量指令下发 | EasyNode | 1-2 周 |
| **P2** | 命令日志 + 审计 | JumpServer | 1 周 |
| **P2** | 凭据托管 (加密存储) | EasyNode | 1 周 |
| **P3** | 脚本库 | EasyNode | 1 周 |
| **P3** | 命令过滤 ACL | JumpServer | 2 周 |

---

## 参考来源

- [JumpServer Architecture Docs](https://docs.jumpserver.org/zh/latest/architecture/)
- [JumpServer DeepWiki - Asset Models](https://deepwiki.com/jumpserver/jumpserver/5.2-asset-platform-and-protocol-models)
- [JumpServer DeepWiki - Permission System](https://deepwiki.com/jumpserver/jumpserver/6.2-asset-permission-system)
- [JumpServer DeepWiki - Terminal Components](https://deepwiki.com/jumpserver/jumpserver/7.1-terminal-components-and-protocols)
- [JumpServer DeepWiki - Session Recording](https://deepwiki.com/jumpserver/jumpserver/7.3-session-recording-and-storage)
- [JumpServer DeepWiki - ACLs](https://deepwiki.com/jumpserver/jumpserver/6.3-access-control-lists-(acls))
- [Koko GitHub](https://github.com/jumpserver/koko)
- [Luna GitHub](https://github.com/jumpserver/luna)
- [EasyNode GitHub](https://github.com/chaos-zhu/easynode)
