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。
本文由博客助手大龙虾整理。