Netcatty Android 架构设计

Netcatty Android 架构设计文档:分层架构、核心模块详细设计、云同步系统(与桌面端完全兼容)、安全架构、进程服务架构、导航页面架构、与桌面端互操作方案。

一、架构总览

设计原则

原则 说明
Clean Architecture 分层隔离:UI → Domain → Data,依赖方向单向向内
零知识云同步兼容 加密格式与桌面端完全一致,确保双向同步
安全优先 敏感字段 AES-GCM 加密、Android Keystore、生物识别
离线优先 所有数据本地持久化,云同步为增量操作
Compose-first 全 Compose UI,不使用 View 系统

技术栈确认

类别 技术
语言 Kotlin 2.0.21
UI Jetpack Compose + Material 3
DI Hilt
异步 Kotlin Coroutines + Flow
网络 OkHttp 4.12 + Retrofit 2.11
SSH JSch 0.2.16
终端 Termux terminal-emulator
数据库 Room 2.6 + SQLite
加密 Android Keystore + JCA (AES-GCM/PBKDF2)
Min SDK 26 (Android 8.0)

二、分层架构

┌───────────────────────────────────────────────────┐
│              Presentation Layer                     │
│  Screens → Compose Components → ViewModels          │
│  (StateFlow<UiState> + UseCase 调用)                │
├───────────────────────────────────────────────────┤
│              Domain Layer (纯 Kotlin)               │
│  Models + UseCases + Repository Interfaces          │
├───────────────────────────────────────────────────┤
│              Data Layer                              │
│  Repository Impls → Data Sources → Remote Sources   │
│  (Room / EncryptedPrefs / JSch / OkHttp)           │
├───────────────────────────────────────────────────┤
│              Platform Layer                         │
│  Android Keystore / Biometric / Foreground Service  │
└───────────────────────────────────────────────────┘

依赖规则: Presentation → Domain ← Data,Domain 层无 Android SDK 依赖。

三、核心模块详细设计

3.1 SSH/SFTP 引擎 (core-ssh)

SshSessionManager — 单例,管理所有活跃 SSH Session:

@Singleton
class SshSessionManager @Inject constructor(
    private val cryptoManager: FieldCryptoManager
) {
    private val connections = ConcurrentHashMap<String, SshConnection>()
    private val jsch = JSch()

    suspend fun connect(host: Host): Result<TerminalSession>
    fun write(sessionId: String, data: String)
    fun resize(sessionId: String, cols: Int, rows: Int)
    fun disconnect(sessionId: String)
    fun disconnectAll()
}

SshConnection — 单个 SSH 连接的完整状态:

data class SshConnection(
    val id: String,
    val hostId: String,
    val session: Session,         // JSch Session
    val channel: ChannelShell,     // JSch ChannelShell
    val inputStream: InputStream,
    val outputStream: OutputStream,
    var status: ConnectionStatus
)

认证策略:密码 → 密钥 → 键盘交互式(2FA/MFA),对应桌面端 sshAuthHelper.cjs

SftpClient — 复用 SSH Session 的 SFTP Channel:

class SftpClient(private val session: Session) {
    fun connect(): ChannelSftp
    fun listDirectory(path: String): List<SftpFileEntry>
    fun download(remotePath: String, localPath: String, monitor: SftpProgressMonitor?)
    fun upload(localPath: String, remotePath: String, monitor: SftpProgressMonitor?)
}

PortForwardingManager — 支持 Local (-L)、Remote (-R)、Dynamic (-D) 三种类型。

3.2 云同步引擎 (core-sync) ⚠️ 与桌面端完全兼容

这是最关键的模块,确保 Android 和桌面端可双向同步。

架构层次

SyncManager (总协调器)
  ├── SecurityStateMachine (NO_KEY → LOCKED → UNLOCKED)
  ├── EncryptionService (PBKDF2 + AES-256-GCM)
  ├── MergeEngine (三路合并)
  └── CloudAdapter 接口
       ├── GitHubGistAdapter (Device Flow)
       ├── GoogleDriveAdapter (PKCE OAuth)
       ├── OneDriveAdapter (PKCE OAuth)
       ├── WebDavAdapter (Basic Auth)
       └── S3Adapter (Access Key)

EncryptionService — 兼容性关键

object EncryptionService {
    private const val PBKDF2_ITERATIONS = 600_000  // 与桌面端一致!
    private const val SALT_LENGTH = 32
    private const val GCM_IV_LENGTH = 12

    fun deriveKey(password: String, salt: ByteArray): SecretKey {
        val spec = PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, 256)
        val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
        return SecretKeySpec(factory.generateSecret(spec).encoded, "AES")
    }

    fun encryptPayload(payload: SyncPayload, password: String, ...): SyncedFile {
        // 输出格式与桌面端完全一致
        // meta: { version, updatedAt, deviceId, iv, salt, algorithm, kdf }
        // payload: Base64(AES-256-GCM(JSON))
    }

    fun decryptPayload(syncedFile: SyncedFile, password: String): SyncedFile {
        // 可解密桌面端加密的文件(密码相同时)
    }
}

MergeEngine — 三路合并

object MergeEngine {
    fun merge(base: SyncPayload?, local: SyncPayload, remote: SyncPayload): MergeResult {
        // 对每个实体(按 id 标识):
        //   仅 local 有  → 保留 (local addition)
        //   仅 remote 有 → 保留 (remote addition)
        //   base 有、local 删了 → 删除(除非 remote 改了)
        //   两边都改了 → 保留 local(记录冲突)
    }
}

同步流程

用户点击"同步"
  → SecurityState == UNLOCKED?
  → 构建 SyncPayload
  → 对每个 Provider 并行:
     → 下载远端 SyncedFile
     → 远端版本更新?→ 三路合并 → 加密 → 上传
     → 否则直接加密上传

3.3 安全架构

分层加密策略

Layer 1: 应用锁 — BiometricPrompt + EncryptedSharedPreferences
Layer 2: 字段加密 — Host.password, SSHKey.privateKey 用 AES-GCM 加密
Layer 3: Android Keystore — 存储 PBKDF2 salt + verificationHash
Layer 4: 云同步加密 — AES-256-GCM 端到端加密,云端仅存储密文

FieldCryptoManager — 字段级 AES-GCM 加密:

@Singleton
class FieldCryptoManager @Inject constructor(
    private val sessionKeyHolder: SessionKeyHolder
) {
    fun encrypt(plaintext: String): String    // → Base64(iv + ciphertext)
    fun decrypt(encrypted: String): String    // ← 解密
}

SessionKeyHolder — 内存中持有 PBKDF2 派生密钥,应用退出后清除。

BiometricAuthHelper — 生物识别验证后从 EncryptedSharedPreferences 读取密码,派生 SessionKey。

3.4 终端渲染架构

Compose TerminalScreen
  ├── AndroidView { TerminalView (Termux) }
  │    ↕ TerminalSession
  │    ↕ TerminalOutput (buffer + scrollback)
  └── BottomBar (输入栏 + 特殊键)
       [ESC][Tab][Ctrl][↑][↓]  输入框...  [↵]

NetcattyTerminalSession — 桥接 JSch SSH Channel 和 Termux TerminalSession: - JSch InputStream → TerminalSession.write() → TerminalView 渲染 - TerminalView 输入 → SshConnection.write() → JSch OutputStream

四、云同步与桌面端兼容性

兼容性检查清单

检查项 要求
PBKDF2 参数 SHA-256, 600K 迭代, 256-bit key
AES-GCM 参数 12-byte IV, 128-bit tag
SyncedFile JSON 格式 { meta: {...}, payload: "base64..." }
SyncFileMeta 字段 version/updatedAt/deviceId/iv/salt/…
SyncPayload 字段 hosts/keys/snippets/groups/…
三路合并逻辑 与 syncMerge.ts 一致
GitHub Gist 文件名 netcatty-vault.json

不兼容项处理

桌面端功能 Android 处理
本地终端 不支持
Mosh 协议 Phase 6 考虑
串口连接 Phase 6
Monaco 编辑器 轻量编辑器替代
分屏 仅横屏支持
多窗口 不支持

五、进程与服务架构

  • SshConnectionService — 前台服务保活 SSH 连接,通知栏显示活跃连接数
  • SftpTransferService — 文件传输前台服务,支持后台传输、进度通知、取消操作

六、导航与页面架构

MainActivity (单 Activity)
├── BottomBar: Vault | Terminal | SFTP | Settings
├── VaultScreen (主机管理 + 搜索 + 分组)
├── TerminalScreen (终端 + Tab + 输入栏 + AI 侧边栏)
├── SftpScreen (双面板 + 传输队列)
└── SettingsScreen (应用锁/主题/云同步/AI/关于)

七、与桌面端的互操作

数据格式兼容性

数据 兼容方式
Host SyncPayload JSON 字段名 + 结构完全对齐
SSH Key JSch 原生支持 OpenSSH/PuTTY PPK/PKCS8
终端主题 JSON 配色数组直接移植
云同步密文 加密参数完全一致
i18n 从 en.ts / zh-CN.ts 移植

同步冲突处理

当 Android 端和桌面端同时修改了同一主机时: - 三路合并自动解决大部分情况 - 仅当两边都修改了同一个字段时,弹出冲突 UI(USE_LOCAL / USE_REMOTE)

八、包结构

com.netcatty.mobile/
├── core/ssh/          # SSH 引擎
├── core/terminal/     # 终端渲染
├── core/sync/         # 云同步引擎
│   └── adapters/      # GitHub/Google/OneDrive/WebDAV/S3
├── core/crypto/       # 加密 + 生物识别
├── core/ai/           # AI Chat
├── domain/model/      # 数据模型
├── domain/repository/ # 仓库接口
├── domain/usecase/    # 业务逻辑
├── data/local/        # Room DAO + Entity
├── data/remote/       # 远程数据源
├── data/repository/  # 仓库实现
├── ui/screens/       # 页面
├── ui/components/    # 共享组件
└── service/           # 前台服务

九、错误处理

统一错误类型 NetcattyError: - SshConnectionError / SshAuthError / SshDisconnectedError - SftpError / TransferError - SyncError / SyncConflictError / SyncDecryptionError - CryptoError / NetworkError

ViewModel 层统一处理,弹出对话框或 Snackbar。


本文由博客助手大龙虾整理。