logo
Public
0
0
WeChat Login
新建文件 README.md

FlyNarwhal 架构分析文档

概述

FlyNarwhal 是一个基于 Compose Multiplatform 的飞牛影视跨平台客户端。本文档详细分析了其核心架构,特别是 fntv-proxy 组件的作用以及登录后的功能关联关系。


核心组件架构

1. fntv-proxy - 前置代理服务

1.1 基本属性

属性
类型本地前置代理 (Local Forward Proxy)
默认端口1999
监听地址http://127.0.0.1:1999
实现语言Go (编译后的二进制文件)
启动方式应用启动时自动启动,关闭时自动停止

1.2 文件位置查找顺序

fntv-proxy 可执行文件按以下优先级查找:

  1. 当前工作目录:<user.dir>/fntv-proxy
  2. 父目录:<user.dir>/../fntv-proxy
  3. 资源目录:<compose.application.resources.dir>/fntv-proxy
  4. 可执行文件所在目录:
    • <exeDir>/app/resources/fntv-proxy
    • <exeDir>/fntv-proxy
  5. 类路径提取(首次运行时):
    • Windows: <exeDir>/app/resources/fntv-proxy
    • macOS: ~/Library/Application Support/fly-narwhal/proxy
    • Linux: ~/.local/share/fly-narwhal/proxy

1.3 平台支持

操作系统架构可执行文件名
WindowsAMD64/ARM64/386fntv-proxy.exe
macOSAMD64/ARM64fntv-proxy
LinuxAMD64/ARM64fntv-proxy

1.4 核心功能

fntv-proxy 的主要职责是为飞牛影视的 API 请求添加鉴权相关的请求头,具体包括:

  • 请求鉴权处理:为视频和字幕请求添加必要的认证信息
  • 请求签名:可能包括时间戳、nonce、签名等
  • 设备标识:添加设备 ID、客户端版本等信息
  • 安全隔离:将鉴权算法封装在独立进程中,防止逆向工程

1.5 工作流程

┌─────────────────────────────────────────────────────────────┐ │ FlyNarwhal 应用 │ │ (Kotlin + Compose) │ └────────────────────┬────────────────────────────────────────┘ │ │ HTTP 请求 (携带基础头) │ Authorization, Cookie, User-Agent ↓ ┌─────────────────────────────────────────────────────────────┐ │ fntv-proxy (端口 1999) │ │ (Go) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1. 接收应用请求 │ │ │ │ 2. 添加鉴权请求头(签名、设备标识等) │ │ │ │ 3. 转发到飞牛影视官方 API │ │ │ │ 4. 返回响应给应用 │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ HTTPS 请求 (完整鉴权) ↓ ┌─────────────────────────────────────────────────────────────┐ │ 飞牛影视官方 API 服务器 │ │ (feiniu.com / fnos.net) │ └─────────────────────────────────────────────────────────────┘

2. 登录系统

2.1 登录方式

FlyNarwhal 支持两种登录方式:

方式 A: 5666 端口登录(本地 FNOS 设备)

  • 默认端口: 5666
  • 访问地址: http://[IP地址]:5666https://[域名]:5666
  • 使用场景: 连接本地局域网内的 FNOS 设备
  • 示例:
    • http://192.168.1.100:5666
    • https://my-fnos.local:5666

方式 B: FN ID/域名登录(中继模式)

  • 访问地址: https://5ddd.com/[FN_ID]https://[域名].fnos.net
  • 端口: 无(使用 HTTPS 443)
  • Cookie: 添加 mode=relay 表示使用中继模式
  • 使用场景: 远程访问 FNOS 设备

2.2 登录流程详解

┌─────────────────────────────────────────────────────────────┐ │ 登录界面 (LoginScreen) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 输入字段: │ │ │ │ - 主机地址 (host) │ │ │ │ - 端口 (port, 默认 5666) │ │ │ │ - 用户名/邮箱 (username) │ │ │ │ - 密码 (password) │ │ │ │ - HTTPS 开关 (isHttps) │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ performLogin() ↓ ┌─────────────────────────────────────────────────────────────┐ │ LoginStateManager.handleLogin() │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1. 验证输入完整性 │ │ │ │ 2. 保存显示地址 (displayHost, displayPort) │ │ │ │ 3. 处理主机地址: │ │ │ │ - 如果是 FN ID: 转换为 "5ddd.com/[FN_ID]" │ │ │ │ - 如果是域名: 保持原样 │ │ │ │ - 如果是 IP: 保持原样 │ │ │ │ 4. 中继模式判断: │ │ │ │ - 如果包含 5ddd.com 或 fnos.net │ │ │ │ → isHttps = true │ │ │ │ → 添加 cookie "mode=relay" │ │ │ │ → port = 0 (不使用端口) │ │ │ │ 5. 保存登录信息到 PreferencesManager │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ loginViewModel.login() ↓ ┌─────────────────────────────────────────────────────────────┐ │ FnOfficialApiImpl.login() │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 构建登录请求: │ │ │ │ POST {baseUrl}/api/login │ │ │ │ Headers: │ │ │ │ - Authorization: [token] │ │ │ │ - Cookie: [Trim-MC-token] │ │ │ │ - Accept: application/json │ │ │ │ - User-Agent: Mozilla/5.0... │ │ │ │ Body: {username, password} │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ 登录成功 ↓ ┌─────────────────────────────────────────────────────────────┐ │ 处理登录响应 │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1. AccountDataCache.authorization = token │ │ │ │ 2. AccountDataCache.insertCookie("Trim-MC-token", token)│ │ │ 3. LoginStateManager.updateLoginStatus(true) │ │ │ │ 4. 保存 token 到持久化存储 │ │ │ │ 5. 跳转到首页 │ │ │ └────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

2.3 关键数据结构

AccountDataCache - 登录信息缓存

object AccountDataCache { var authorization: String // 登录 token var cookieMap: MutableMap<String, String> // Cookie var userName: String // 用户名 var password: String // 密码 var isHttps: Boolean // 是否使用 HTTPS var host: String // 实际使用的主机地址 var port: Int // 实际使用的端口 var displayHost: String // 显示的主机地址 var displayPort: Int // 显示的端口 var isLoggedIn: Boolean // 登录状态 var isNasLogin: Boolean // 是否 NAS 登录 var fnId: String // FN ID fun getFnOfficialBaseUrl(): String { // 构建飞牛影视 API 基础 URL // http://[host]:[port] 或 https://[host]:[port] } fun getProxyBaseUrl(): String { // fntv-proxy 地址 return "http://127.0.0.1:1999" } }

3. 登录后功能与 fntv-proxy 的关联

3.1 功能映射关系

功能模块是否使用 fntv-proxy说明
视频播放✅ 是fntv-proxy 为视频请求添加鉴权头
字幕下载✅ 是字幕请求需要鉴权
媒体库列表✅ 是获取媒体库需要鉴权
用户信息✅ 是查询用户信息需要鉴权
收藏/标记✅ 是操作需要鉴权
观看记录✅ 是记录观看状态需要鉴权

3.2 API 请求流程

┌─────────────────────────────────────────────────────────────┐ │ 业务功能模块 │ │ (MediaListViewModel, UserInfoViewModel, etc.) │ └────────────────────┬────────────────────────────────────────┘ │ │ 调用 API ↓ ┌─────────────────────────────────────────────────────────────┐ │ FnOfficialApiImpl │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ val response = fnOfficialClient.post( │ │ │ │ "${AccountDataCache.getFnOfficialBaseUrl()}/api/xxx",│ │ │ │ body │ │ │ │ ) │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ fnOfficialClient 发送请求 ↓ ┌─────────────────────────────────────────────────────────────┐ │ BaseHttpClient (Ktor) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ defaultRequest { │ │ │ │ header(HttpHeaders.Authorization, authorization) │ │ │ │ header(HttpHeaders.Cookie, cookieState) │ │ │ │ header(HttpHeaders.UserAgent, "Mozilla/5.0...") │ │ │ │ header(HttpHeaders.Accept, "application/json") │ │ │ │ } │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ HTTP 请求 (带基础鉴权头) ↓ ┌─────────────────────────────────────────────────────────────┐ │ fntv-proxy (127.0.0.1:1999) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 1. 接收请求 │ │ │ │ 2. 添加额外鉴权头: │ │ │ │ - X-Signature (请求签名) │ │ │ │ - X-Timestamp (时间戳) │ │ │ │ - X-Device-Id (设备 ID) │ │ │ │ - X-Client-Version (客户端版本) │ │ │ │ - 其他加密/鉴权相关头 │ │ │ │ 3. 转发到飞牛影视官方 API │ │ │ └────────────────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────────────┘ │ │ HTTPS 请求 (完整鉴权) ↓ ┌─────────────────────────────────────────────────────────────┐ │ 飞牛影视官方 API (鉴权验证) │ └────────────────────┬────────────────────────────────────────┘ │ │ 响应数据 ↓ ┌─────────────────────────────────────────────────────────────┐ │ fntv-proxy (返回响应) │ └────────────────────┬────────────────────────────────────────┘ │ │ 响应数据 ↓ ┌─────────────────────────────────────────────────────────────┐ │ 业务模块 (处理响应数据) │ └─────────────────────────────────────────────────────────────┘

3.3 关键 API 调用示例

获取媒体库列表

// MediaDbListViewModel val result = fnOfficialApi.getMediaDbList() // 内部调用 GET http://[host]:[port]/api/media/db/list Headers: - Authorization: [token] - Cookie: Trim-MC-token=[token] - User-Agent: Mozilla/5.0... // 经过 fntv-proxy 后添加 GET https://api.feiniu.com/api/media/db/list Headers: - Authorization: [token] - Cookie: Trim-MC-token=[token] - User-Agent: Mozilla/5.0... - X-Signature: [签名] - X-Timestamp: [时间戳] - X-Device-Id: [设备ID]

视频播放请求

// 视频播放 URL 生成 val playUrl = fnOfficialApi.getPlayUrl(mediaId) // 字幕下载 HlsSubtitleUtil(fnOfficialClient, playUrl, subtitle) // 请求经过 fntv-proxy,添加视频流鉴权头 Headers: - Range: bytes=0-... - 视频流鉴权相关头

4. 进程生命周期管理

4.1 fntv-proxy 启动流程

应用启动 (main.kt) ↓ LaunchedEffect(Unit) ↓ ProxyManager.start() ↓ 1. 查找 fntv-proxy 可执行文件(按优先级顺序) 2. 检查进程是否已存在,存在则清理 3. 使用 ProcessBuilder 启动代理进程 4. 创建守护线程处理 stdout/stderr 5. 注册 JVM shutdown hook 确保进程关闭 ↓ fntv-proxy 运行在 1999 端口

4.2 fntv-proxy 停止流程

应用关闭或 onDispose 触发 ↓ ProxyManager.stop() ↓ 1. 尝试优雅关闭(发送停止信号) 2. 等待最多 3 秒 3. 如果未关闭,强制终止进程 4. 清理守护线程 ↓ fntv-proxy 进程结束

5. 为什么需要 fntv-proxy?

5.1 设计原因

原因说明
安全隔离鉴权算法封装在独立进程中,避免在 Kotlin 代码中暴露
防逆向Go 编译的二进制文件比字节码更难逆向分析
算法更新鉴权算法更新时只需替换 fntv-proxy,无需重新发布应用
跨平台统一不同平台的鉴权逻辑在 Go 代码中统一实现
性能优化Go 处理网络请求更高效

5.2 类似架构参考

  • Clash/SSR: 本地代理流量
  • 企业 API 网关: 统一鉴权
  • 浏览器扩展: 请求拦截和修改

6. 安全软件误报说明

6.1 误报原因

fntv-proxy 是一个本地代理程序,会:

  • 从应用内解压到临时目录
  • 监听本地端口(1999)
  • 拦截和转发网络请求

这些行为与恶意软件的特征相似,因此会被安全软件(如 360 杀毒)误报。

6.2 解决方案

  • 添加到安全软件的白名单
  • 忽略风险提示
  • 如果不放心,可使用飞牛官方客户端

7. 配置说明

7.1 应用配置

// fntv-proxy 地址(硬编码) Proxy Base URL: http://127.0.0.1:1999 // FNOS 默认端口 Default Port: 5666

7.2 持久化存储

登录信息存储在本地配置中(使用 Settings API):

说明
username用户名
password密码(如果选择记住)
token登录 token
isHttps是否使用 HTTPS
host主机地址
port端口号
cookieCookie 状态
isLoggedIn登录状态
loginHistory登录历史记录

8. 架构总结

┌─────────────────────────────────────────────────────────────┐ │ FlyNarwhal 应用 │ │ (Compose Multiplatform) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ UI 层 │ │ ViewModel │ │ Manager │ │ │ │ - LoginScreen│ │ - LoginVM │ │ - LoginState│ │ │ │ - HomePage │ │ - MediaList │ │ - Proxy │ │ │ │ - Settings │ │ - UserInfo │ │ Manager │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └─────────────────┴──────────────────┘ │ │ │ │ │ ┌───────▼────────┐ │ │ │ Data Layer │ │ │ │ - API Impl │ │ │ │ - HTTP Client│ │ │ │ - Cache │ │ │ └───────┬────────┘ │ └───────────────────────────┼──────────────────────────────────┘ │ HTTP (基础头) ┌───────────────────────────▼──────────────────────────────────┐ │ fntv-proxy (1999) │ │ 添加完整鉴权请求头 │ └───────────────────────────┬──────────────────────────────────┘ │ HTTPS (完整鉴权) ┌───────────────────────────▼──────────────────────────────────┐ │ 飞牛影视官方 API 服务器 │ │ (feiniu.com / fnos.net / 5ddd.com) │ └─────────────────────────────────────────────────────────────┘

9. 关键技术栈

组件技术
UI 框架Compose Multiplatform
网络库Ktor Client + OkHttp
依赖注入Koin
持久化Settings (Multiplatform Settings)
日志Kermit
fntv-proxyGo

10. 参考资料


文档版本: 1.0 生成日期: 2025

About

No description, topics, or website provided.