百度网盘直链解析助手的 Flutter 客户端(Android / Windows / iOS)。
App 不硬编码任何上游接口域名,启动时从远端配置接口拉取真实 API 地址,把用户粘贴的网盘分享链接解析为可直接下载的直链,再交给内置的 Gopeed 下载引擎进行多线程下载。
- Android:Gopeed 以
libgopeed.aar形式内嵌,通过 MethodChannel 与 Dart 通信 - Windows:以子进程
gopeed-rest.exe运行,通过本地 REST API 通信
反馈与交流:使用中遇到问题、有功能建议,或想第一时间获取新版本,欢迎加入 QQ 群 1125479685。
下载安装:Android 用户可直接从 Releases 下载已打包好的 APK(arm64-v8a)安装使用,无需自行编译。自行构建方法见「使用前端」,后端部署见「使用后端」。
| 模块 | 说明 |
|---|---|
| 远端配置 | 启动时从 bootstrap 地址拉取 JSON 配置(API 地址、备用地址、Origin/Referer、默认模式、公告等),本地缓存以支持离线启动与多地址回退 |
| 直链解析 | 输入网盘分享链接(支持整段分享文本),调用远端接口以 SSE 流式返回解析进度与结果 |
| 多线程下载 | 内置 Gopeed 引擎,支持暂停 / 继续 / 删除 / 强制删除,单任务进度实时刷新 |
| 下载目录 | 默认 /storage/emulated/0/Download/,支持用户自定义目录并持久化,适配 Android 7–16 的存储权限差异 |
| 云端公告 | 首页展示后端下发的公告内容;内容为空时整块隐藏 |
| 主题 | Material 3,跟随系统亮色 / 暗色,并可手动切换 |
baidujiexi_app/
├── lib/
│ ├── main.dart 应用入口与根 Widget(启动引导、主题切换)
│ ├── core/
│ │ ├── remote_config.dart 远端配置拉取 / 解析 / 缓存(AppConfig)
│ │ ├── api_client.dart 转换接口客户端(session / attest / captcha + SSE 流式解析)
│ │ ├── captcha_solver.dart 曲线滑块验证码自动求解(拟合轨迹,无图像识别)
│ │ ├── crypto.dart 会话签名(clientSign)等加密封装
│ │ ├── models.dart 数据模型(ApiSession / ConvertEvent / ConvertResult…)
│ │ ├── gopeed_engine.dart Gopeed 引擎桥接(MethodChannel / 子进程 + REST)
│ │ ├── download_manager.dart 下载任务管理层(ChangeNotifier 单例,轮询同步进度)
│ │ ├── storage_service.dart 下载目录解析、持久化与存储权限编排
│ │ └── startup_hooks.dart 启动钩子扩展点(StartupHooks)
│ ├── theme/app_theme.dart 调色板与亮 / 暗主题
│ └── ui/
│ ├── home_page.dart 首页(粘贴链接、解析进度、结果卡片、公告)
│ └── downloads_page.dart 下载任务列表
├── android/ Android 工程
│ └── app/src/main/kotlin/com/liuguang/bdjx/
│ ├── MainActivity.kt 入口 Activity(转发权限回调)
│ ├── GopeedBridge.kt Gopeed 引擎 MethodChannel 桥
│ └── StorageBridge.kt 存储目录 / 权限 MethodChannel 桥
├── windows/ Windows 工程(内嵌 gopeed-rest.exe)
├── ios/ iOS 工程骨架
├── api/app/ 后端配置接口(config.php + config.json)
└── test/core_test.dart 核心逻辑单元测试
环境要求:Flutter 3.x(Dart SDK ^3.12.2)。
flutter pub get
# 运行(连接设备 / 桌面)
flutter run
# 校验
flutter analyze # 需 0 issue
flutter test # 单元测试全绿App 启动时若拿不到后端配置,会退回本地缓存;首次启动需能访问 bootstrap 地址,否则停留在加载页。
# Android
flutter build apk --release --target-platform android-arm64
# Windows
flutter build windows --release产物位置:
- Android:
build/app/outputs/flutter-apk/app-release.apk - Windows:
build/windows/x64/runner/Release/
默认 bootstrap 地址定义在 remote_config.dart 的 kDefaultBootstrapUrls。可在构建时覆盖:
flutter build apk --release \
--dart-define=BOOTSTRAP_URL=https://your.domain/api/app/config.php完整字段与后端部署方式见下文「使用后端」。
App 启动时按顺序请求 bootstrap 地址,直到拿到一份合法配置,并写入 shared_preferences 缓存;后续启动优先使用缓存,网络不可达时仍可工作。
AppConfig 字段:
| 字段 | 含义 |
|---|---|
apiBase |
转换接口基础地址(无尾斜杠) |
siteOrigin |
注入请求头 Origin,上游据此校验会话归属 |
referer |
注入请求头 Referer |
fallbackBases |
主地址失败时依次尝试的备用地址 |
defaultMode |
默认转换模式:web(直链)/ exe(PC 下载器) |
configVersion |
后端下发的版本号,用于识别缓存是否过期 |
announcement |
首页公告文案,空字符串表示隐藏 |
extras |
未被显式建模的其余键,原样保留,可用 extra<T>(key) 读取 |
上游
apiBase等网络配置由后端下发,更换上游接口地址无需重新发版;bootstrap 自身地址改变时,需要更新默认地址或构建参数并重新构建客户端。
上游已升级为「会话 → 行为认证 → 滑块验证码 → 转换」四步协议,客户端按同一顺序取齐凭据后再发起转换:
GET /api/session→{ token, ttl, attest }- 必须用 GET;
- 新版响应不再直接下发
secret,而是带attest: 1,需要再做第 2 步换取; - 会话按
ttl缓存并预留 1 分钟安全边界。
POST /api/session/attest→{ secret, ttl }- 请求头带
x-session-token,body 为{ "fp": <行为指纹> }; - 与第 1 步的间隔需 ≥ 1600ms,过快会被判
too-fast,客户端退避后重试; secret仅用于本地签名,不上行。
- 请求头带
GET /api/captcha→ 曲线跟随滑块挑战- 挑战单次有效,每次转换都重新获取;答案恒为把滑块拖到最右端(
captchaAnswer = 283), 轨迹按引导曲线拟合,无需图像识别(见lib/core/captcha_solver.dart)。
- 挑战单次有效,每次转换都重新获取;答案恒为把滑块拖到最右端(
POST /api/downloadConvertUrl(SSE,Accept: text/event-stream)- 请求头:
Origin/Referer/User-Agent,以及x-session-token、x-client-sign; - body:
{ "link": <规范化链接>, "mode": "web|exe", "captchaId", "captchaAnswer", "captchaTrail", "ref": <referer> }; x-client-sign = clientSign(secret, link),只对规范化后的链接字符串加签(不是整个 JSON body)。
- 请求头:
验证码被拒或会话 / 签名失效时,convert() 会刷新对应凭据并自动重试(最多 3 次)。
分享链接会被 normalizeShareLink() 规范化为 https://pan.baidu.com/s/1<surl>?pwd=<提取码>。提取码可来自链接中的 ?pwd=,也可来自粘贴文本里的 提取码:xxxx;签名必须覆盖这个规范化后的完整字符串。
SSE 事件逐步返回(step 字段):queue / ready / link_check / verify_pwd / file_list / import / wait_import / batch_download / batch_progress / done;错误帧 error != 0。
convertToResult() 会把整段事件流聚合成一个 ConvertResult(links / exeUrls / tokens),并通过 onEvent 回调把每一帧透出给 UI 展示进度。
单例桥接层,启动幂等并带存活自愈:已启动时先探测引擎是否仍可达,不可达则复位后完整重启,避免请求命中死端口。
- Android / iOS:
MethodChannel('com.liuguang.bdjx/gopeed'),原生实现见 GopeedBridge.kt,入口类com.gopeed.mobile.Mobile(依赖libgopeed.aar)。 - Windows:以子进程方式拉起
gopeed-rest.exe,从 stdout 解析GOPEED_PORT=<port>(超时 20s),错误行以GOPEED_ERROR=前缀输出。
启动后统一通过 http://127.0.0.1:<port>/api/v1 访问 Gopeed REST API:
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/tasks |
创建下载任务 |
GET |
/tasks |
任务列表 |
PUT |
/tasks/{id}/pause | /continue |
暂停 / 继续 |
DELETE |
/tasks/{id}?force=true |
删除(force 连文件一起删) |
响应统一为 { code, msg, data },code == 0 表示成功;任务状态机为 ready / running / wait / pause / error / done。
DownloadManager 是 ChangeNotifier 单例,以 800ms 间隔轮询引擎任务列表并同步进度到 UI,把引擎状态映射为 DownloadStatus(ready / running / waiting / paused / error / done)。
目录解析优先级:
- 用户自定义目录(
shared_preferences键download_dir_custom,校验可写后生效) - 系统公共下载目录
/storage/emulated/0/Download/ - 应用专属外部目录兜底(无需权限,恒可写)
原生桥 MethodChannel('com.liuguang.bdjx/storage') 提供 getDefaultDir / getFallbackDir / hasPermission / requestPermission / ensureDir(写探针校验可写性),实现见 StorageBridge.kt;运行时权限回调由 MainActivity.onRequestPermissionsResult 转发。
Android 版本与权限矩阵:
| Android 版本 | API | 写公共目录所需 |
|---|---|---|
| 7–9 | 24–28 | 运行时授予 WRITE_EXTERNAL_STORAGE |
| 10 | 29 | requestLegacyExternalStorage="true" + 上述权限 |
| 11–16 | 30–36 | MANAGE_EXTERNAL_STORAGE(「所有文件访问」,需跳系统设置页手动开启) |
Manifest 中相应权限均带 maxSdkVersion 声明(见 AndroidManifest.xml)。
切换目录无需重启引擎:Gopeed 的
createTask(opts.path)支持每任务指定路径,改目录时失效DownloadManager的目录缓存即可,后续新任务自动使用新目录。
StartupHooks.register(...) 提供启动后执行一次的回调扩展点,可在 runApp 之前注册。钩子在远端配置解析完成、首页首帧绘制之后触发;单个钩子抛错会被隔离记录,不影响 App 启动。
后端是一个零依赖的 PHP 接口,位于仓库 api/app/ 目录:
| 文件 | 作用 |
|---|---|
api/app/config.php |
配置接口,处理 GET / POST |
api/app/config.json |
可变配置文件 |
把 api/app/ 整个目录上传到你的站点,保证最终可访问地址形如:
https://<你的域名>/api/app/config.php
- 需要 PHP 7.4+(用到
declare(strict_types=1)、hash_equals、FILTER_VALIDATE_BOOLEAN),无需任何扩展或数据库。 api/app/目录需对 PHP 进程 可写,否则POST保存配置会返回「配置写入失败,请检查目录权限」。- 若站点有 Nginx/Apache 重写规则,务必放行
.php与.json直连。
打开 config.php 修改共享密钥:
const ADMIN_TOKEN = 'CHANGE_ME_admin_token'; // 改成一串足够随机的私密字符串该 token 只用于校验写接口,不要下发到客户端。
curl https://<你的域名>/api/app/config.php返回:
{
"code": 0,
"data": {
"apiBase": "https://file.mdx.wang",
"siteOrigin": "https://file.mdx.wang",
"referer": "https://file.mdx.wang/",
"fallbackBases": [],
"defaultMode": "web",
"configVersion": 1,
"announcement": "欢迎使用百度解析助手!如遇解析失败,请稍后重试或切换网络环境。"
}
}接口带 Access-Control-Allow-Origin: * 与 Cache-Control: no-store,可直接用浏览器打开调试。
请求体为完整配置 JSON,请求头带 X-Admin-Token:
curl -X POST https://<你的域名>/api/app/config.php \
-H "Content-Type: application/json" \
-H "X-Admin-Token: <你的 ADMIN_TOKEN>" \
-d '{
"apiBase": "https://file.mdx.wang",
"fallbackBases": ["https://backup.example.com"],
"defaultMode": "web",
"announcement": "欢迎使用百度解析助手!"
}'服务端会做校验与归一化后写入 config.json,并返回 {"code":0,"msg":"配置已更新","data":{...}}。token 不匹配返回 403。也可以直接在服务器上编辑 config.json(改动即时生效,无需重启)。
| 字段 | 类型 | 说明 |
|---|---|---|
apiBase |
string | 必填,转换接口基础地址,须为 http(s)://,尾部斜杠会被去掉 |
siteOrigin |
string | 注入请求头 Origin;留空则由 apiBase 派生 |
referer |
string | 注入请求头 Referer;留空则为 apiBase + '/' |
fallbackBases |
string[] | 备用地址,自动去重并剔除与 apiBase 相同的项 |
defaultMode |
string | 默认转换模式,仅 web(直链)/ exe(PC 下载器) |
configVersion |
int | 版本号,留空则写入当前时间戳 |
announcement |
string | 首页公告文案,空字符串时整块隐藏 |
后端地址即客户端启动时的 bootstrap 地址。两种方式:
- 改默认地址:修改 remote_config.dart 的
kDefaultBootstrapUrls; - 构建时覆盖(推荐):
flutter build apk --release --target-platform android-arm64 \
--dart-define=BOOTSTRAP_URL=https://<你的域名>/api/app/config.phpApp 启动后会按顺序请求该地址,成功后缓存到本地,之后离线启动也能用旧配置。更换后端下发的上游 apiBase 或修改首页 announcement 公告无需重新发版;bootstrap 自身地址改变时,必须更新默认地址或构建参数并重新构建客户端。
Gopeed 引擎的 libgopeed.aar 只包含 arm64-v8a 预编译产物,因此统一只打包 arm64:
flutter build apk --release --target-platform android-arm64关键配置(android/app/build.gradle.kts):
ndk { abiFilters.clear(); abiFilters.add("arm64-v8a") }必须 先clear()再add(),否则 AAR 内其他 ABI 的libgojni.so会被一并打进 APK(体积从约 26 MB 膨胀到 55 MB+)。- release 构建需同时
isMinifyEnabled = false与isShrinkResources = false只关混淆不关资源压缩会报Removing unused resources requires unused code shrinking to be turned on。 packaging { jniLibs { useLegacyPackaging = true } }引擎.so需解压到磁盘后再加载。
以下文件随仓库提供,以便直接构建运行:
| 文件 | 用途 |
|---|---|
android/app/libs/libgopeed.aar |
Android Gopeed 引擎(含 libgojni.so) |
android/app/src/main/jniLibs/arm64-v8a/libc++_shared.so |
Go 引擎所需 C++ 运行库 |
windows/runner/gopeed-rest.exe |
Windows Gopeed 引擎子进程 |
Windows 端由 windows/runner/CMakeLists.txt 的 POST_BUILD 规则自动把 gopeed-rest.exe 拷贝到可执行文件同级目录。
见 pubspec.yaml:
http—— 会话请求与 SSE 流式转换shared_preferences—— 远端配置与下载目录的本地缓存url_launcher—— 用系统浏览器 / 下载器打开直链path/path_provider—— 路径处理与平台目录获取cupertino_icons—— iOS 风格图标
flutter testtest/core_test.dart 覆盖核心逻辑:分享链接规范化、SSE 帧解析、配置解析与 extras 透传、下载目录解析与持久化、签名计算等。
本项目基于 GNU General Public License v3.0(GPL-3.0)开源。
你可以自由使用、修改和分发本项目,但衍生作品必须以相同协议(GPL-3.0)开源,并保留原始版权声明。完整条款见 LICENSE。