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.