番茄小说纯 Rust 实现。
当前主线已经统一成单一 Rust 运行链:
- Rust 负责对外 HTTP API、上游请求编排、缓存和内容解密
- Rust 负责
registerkey请求、缓存和解密 key 解析 - Rust 原生
rnidbgsigner 已内嵌进fq-api - signer 资源和运行时已编进
fq-api,启动时自动解包到临时目录 - Java signer、Maven 构建链、
unidbgjar 回退路径已删除
crates/api: Rust API 服务crates/signer-native: Rust 原生 signer 库assets/fq-signer: 构建期嵌入的 signer 资源vendor/rnidbg: 裁剪后的 rnidbg 运行时最小子集vendor/rnidbg/android/sdk23: 项目默认内嵌的运行时目录configs/config.yaml: 默认配置.github/workflows/ci.yml: 编译与测试
当前目录分层约定:
crates/: 所有 Rust 源码assets/: 会被打进二进制的静态资源vendor/: 提交到仓库的第三方源码tools/: 导入和维护脚本
当前 vendor/rnidbg 只保留项目实际会编译和运行到的部分:
android/sdk23emulatorsparse_listunicorn
其中 unicorn 也已经进一步裁成当前项目使用的 AArch64 构建子集,不再保留其它架构目标源码。
GET /searchGET /book/{book_id}GET /toc/{book_id}GET /chapter/{book_id}/{chapter_id}
配置加载顺序:
configs/config.yaml
关键项:
fq.upstream: 番茄上游地址与超时fq.signer.restart_cooldown_ms: 内嵌 signer 重建节流fq.signer.android_sdk_api: 模拟上报给库的 Android SDK levelfq.cache.postgres_url: 可选 PostgreSQL 章节主缓存fq.prefetch: 章节分桶预取fq.auto_heal: 连续错误后的 registerkey 失效、设备轮换、signer 重启自愈fq.device_profile: 当前生效设备信息fq.device_pool: 可选设备池
可用环境变量:
FQRS_DB_URLDB_URLFQRS_SIGNER_ANDROID_SDK_APIFQRS_DEVICE_POOL_STARTUP_NAMEFQRS_UPSTREAM_DROP_HEADERSFQRS_UPSTREAM_DROP_QUERY_PARAMSFQRS_COOKIE_OVERRIDEFQRS_USER_AGENT_OVERRIDEFQRS_DEVICE_JSON_OVERRIDEFQ_SIGNER_RESOURCE_ROOTRNIDBG_BASE_PATH
兼容保留:
- 默认不需要配置任何资源路径
UNIDBG_RESOURCE_ROOT仍可用,但只是FQ_SIGNER_RESOURCE_ROOT的旧名字兼容- 当前二进制默认内嵌的是更接近原版 unidbg 行为的
sdk23运行时 - 当前默认
fq.signer.android_sdk_api: 23 RNIDBG_BASE_PATH只在你明确指定外部运行时目录时才需要
- 修改
configs/config.yaml - 构建:
cargo build --release --workspace如果想一次性补齐依赖、跑测试和构建,也可以直接用脚本:
./start.sh deps
./start.sh test
./start.sh build脚本在 root 环境下默认会把 Rust 安装到系统目录:
CARGO_HOME=/usr/local/cargo
RUSTUP_HOME=/usr/local/rustup并自动写入 /usr/local/bin 链接和 /etc/profile.d/fq-rust-env.sh。
如果你明确不想装到系统目录,可以显式传:
SYSTEM_RUST_INSTALL=false ./start.sh build- 启动:
./target/release/fq-api启动后可直接请求:
curl "http://127.0.0.1:9999/search?key=斗破苍穹&page=1&size=20&tabType=3"
curl "http://127.0.0.1:9999/book/7185502456775208503"
curl "http://127.0.0.1:9999/toc/7185502456775208503"
curl "http://127.0.0.1:9999/chapter/7185502456775208503/7185502456775209001"如果你要做上游请求头 A/B 实验,可以临时裁掉最终发出的部分请求头:
FQRS_UPSTREAM_DROP_HEADERS=x-soter ./target/release/fq-api
FQRS_UPSTREAM_DROP_HEADERS=x-medusa,x-soter ./target/release/fq-api
FQRS_UPSTREAM_DROP_HEADERS=authorization,x-soter ./target/release/fq-api说明:
- 这个变量是逗号分隔、大小写不敏感
- 它作用在最终发出的上游请求头,不改 signer 原始输出格式
- 适合快速排查
HTTP 200但body为空时,究竟是哪组头触发了上游策略
如果你要做上游 query 参数 A/B 实验,也可以临时裁掉最终请求 URL 里的部分参数:
FQRS_UPSTREAM_DROP_QUERY_PARAMS=need_version ./target/release/fq-api
FQRS_UPSTREAM_DROP_QUERY_PARAMS=need_version,book_type,player_so_load ./target/release/fq-api说明:
- 这个变量同样是逗号分隔、大小写不敏感
- 它会在 signer 计算签名前先修改最终 URL,所以签名和实际请求保持一致
如果你要做设备画像 / 区域 A/B 实验,可以直接用环境变量覆盖当前生效设备和设备池里的所有 profile:
FQRS_COOKIE_OVERRIDE='install_id=573270579220059' ./target/release/fq-api
FQRS_USER_AGENT_OVERRIDE='com.dragon.read.oversea.gp/68132 (Linux; U; Android 13; zh_CN; Pixel 7; Build/TQ3A.230805.001;tt-ok/3.12.13.4-tiktok)' ./target/release/fq-api
FQRS_DEVICE_JSON_OVERRIDE='{"device":{"cdid":"override-cdid","device_type":"Pixel 7","device_brand":"Google","rom_version":"TQ3A.230805.001","device_id":"1778337441136410","install_id":"573270579220059"}}' ./target/release/fq-api说明:
FQRS_COOKIE_OVERRIDE直接替换cookieFQRS_USER_AGENT_OVERRIDE直接替换user-agentFQRS_DEVICE_JSON_OVERRIDE用 JSON 做局部覆盖,未提供的字段保持原配置- 这 3 个 override 会同时作用到当前 profile 和设备池里的 profile,避免轮换后实验失效
仓库当前默认内嵌的是 sdk23。下面这套流程主要用于实验性导入外部运行时,例如从 Android 12 / API 31 GSI 提取一套 sdk31 目录;它不会自动替换主线默认底座。
- 从官方 Android 12 / API 31 GSI 解压出
system.img - 把
system.img挂载成只读目录 - 运行脚本生成 rnidbg 目录:
tools/import_rnidbg_sdk.sh /path/to/mounted/system vendor/rnidbg/android/sdk31生成后运行:
cargo build --release --workspace
./target/release/fq-api如果你仍然想强制用某个外部目录覆盖内嵌版本,再显式指定:
RNIDBG_BASE_PATH="$PWD/vendor/rnidbg/android/sdk31" ./target/release/fq-api脚本只会复制当前项目需要的最小文件集:
system/bin/lssystem/bin/shsystem/lib64/libc++.sosystem/lib64/libc.sosystem/lib64/libcrypto.sosystem/lib64/libdl.sosystem/lib64/liblog.sosystem/lib64/libm.sosystem/lib64/libssl.sosystem/lib64/libstdc++.sosystem/lib64/libz.so
常见挂载方式:
simg2img system.img system.raw.img
mkdir -p /tmp/android12-system
sudo mount -o loop,ro system.raw.img /tmp/android12-system
tools/import_rnidbg_sdk.sh /tmp/android12-system vendor/rnidbg/android/sdk31
sudo umount /tmp/android12-system仓库还带了一个手动 workflow:
用法:
- 在 GitHub Actions 页面手动运行
Build rnidbg SDK - 填入
system_image_url - 如果输入是 zip,必要时再填
image_entry - workflow 会:
- 下载 system image
- 自动解 zip
- 自动把 sparse image 转成 raw
- loop 只读挂载
- 调用
tools/import_rnidbg_sdk.sh - 上传
${sdk_name}.tar.gzartifact
这条 workflow 适合生成实验用外部运行时目录;当前主线默认仍然使用 sdk23 底座。
主工作流是 .github/workflows/ci.yml:
cargo test --workspacecargo build --workspace --release- 上传
fq-api
当前按单镜像部署:
- 只构建 Rust
- 运行阶段不再需要 Java
- 运行阶段只包含
fq-api和配置文件 - signer 资源与运行时目录由二进制自解包
- 运行阶段使用
gcr.io/distroless/cc-debian12:nonroot
本地启动:
docker compose up --build相关文件:
工作流在 docker-publish.yml。
会推送多架构镜像:
<DOCKERHUB_USERNAME>/fq-rust