极空间NAS部署道理鱼全栈媒体管理指南

1. 为什么“道理鱼”在极空间NAS上值得专门折腾一次?

“道理鱼”这个名字乍一听像某款冷门国产App,但实际它是个在小众音乐爱好者圈子里悄悄发酵了两年的全栈媒体管理工具——不是播放器,不是下载器,而是把本地音乐库、MV视频、有声书资源、元数据清洗、封面自动补全、跨设备同步、甚至带AI辅助标签生成能力全部打包进一个Docker容器里的“媒体中枢”。它不依赖云服务,不上传你的曲库,所有逻辑跑在你自己的硬件上。而极空间NAS,恰恰是当前消费级NAS里对Docker支持最“省心”的那一类:Web界面直装、权限隔离清晰、存储路径结构稳定、系统更新不轻易破坏容器环境。这两者组合,不是简单“能跑”,而是形成了一个低维护成本、高自主权、真正属于你个人的媒体操作系统

我最早是在2023年冬天试过群晖DS220+,当时为了跑道理鱼,得手动SSH进系统,改Docker daemon.json加--insecure-registry,再配PostgreSQL外部挂载卷权限,最后发现极空间Z4的Web界面里点三下就能完成的事,在群晖上花了整整一个周末还卡在Redis连接超时。这不是工具好坏的问题,而是极空间把“让普通用户安全地用好Docker”这件事,真的当核心功能来打磨了。它默认开启的Docker守护进程、预置的cgroup v2支持、以及对/var/lib/docker目录的独立分区管理,让道理鱼这种需要PostgreSQL+Redis+Web服务三进程协同的全栈应用,第一次在我手里实现了“部署即可用”。

更关键的是场景适配性。道理鱼的核心价值不在“播放”,而在“理清”。比如你硬盘里存着500GB的无损音乐,但文件名是“001.flac”“track_2.wav”,专辑封面缺失,艺术家字段全是“Unknown”;又或者你收藏了200部经典MV,但分散在不同年份的文件夹里,没有统一归档;再比如孩子听的《凯叔西游记》有声书,每集标题是“第1集_20220315.mp3”,根本没法按故事线排序。道理鱼干的就是这事:它用本地运行的MusicBrainz API客户端批量抓取元数据,用FFmpeg自动提取视频缩略图并生成WebP封面,用自研的文本向量模型(非联网调用)对有声书章节标题做语义聚类,最终在Web界面上给你呈现一个带时间轴、可打星标、能按情绪/场景/语速筛选的立体媒体库。这整套流程,必须全程离线、低延迟、高IO吞吐——而极空间Z4 Pro的双2.5G网口+NVMe缓存加速,正好卡在这个性能甜蜜点上。

所以这不是又一个“NAS装Docker玩玩”的教程,而是一次针对真实家庭媒体管理痛点的系统性落地。它解决的不是“能不能播”,而是“怎么让几千个音频视频文件,真正变成你能记住、能调用、能传承的数字资产”。接下来我会从零开始,把每一个操作背后的“为什么”拆开讲透,包括那些官方文档绝不会写的坑:比如为什么storage目录必须用ext4而非Btrfs、为什么postgres子目录要提前chown到999:999、以及道理鱼Web界面首次加载时那个卡住37秒的“元数据初始化”到底在后台干了什么。

2. 极空间NAS上的Docker环境本质:别被Web界面骗了

很多人以为极空间的Docker就是“点点鼠标装个镜像”,其实它的底层架构比表面看到的复杂得多。理解这个,是避免后续所有诡异问题的前提。

极空间OS(基于Debian 11定制)的Docker引擎并非直接使用上游Docker CE,而是经过深度裁剪的版本。它禁用了Docker BuildKit(所以你不能在极空间上直接docker build),移除了docker-compose CLI(必须用Web界面或API调用),但保留了完整的containerd运行时和runc沙箱。最关键的是,它的Docker Root Dir被硬编码为/var/lib/docker,且该路径强制绑定到系统盘而非数据盘。这意味着:你所有镜像层、容器层、卷数据,默认都存在系统盘上——而极空间Z2/Z4的系统盘只有32GB eMMC,装几个大镜像就可能触发磁盘告警。

解决方案不是去改daemon.json(极空间会定期重置),而是利用它的Volume Mount机制。当你在Web界面创建容器时,它实际执行的是类似这样的命令:

docker run -d \ --name daoliyu-web \ --restart=unless-stopped \ -v /mnt/data/Docker/daoliyu/storage:/app/storage \ -v /mnt/data/Docker/daoliyu/plugins:/app/plugins \ -v /mnt/data/Docker/daoliyu/postgres:/var/lib/postgresql/data \ -v /mnt/data/Docker/daoliyu/redis:/data \ -p 8080:8080 \ --network=bridge \ daoliyu/web:latest

注意看这几个-v参数:/mnt/data/开头的路径,才是你真正应该操作的“数据落盘区”。极空间把/mnt/data/映射为用户可见的数据盘根目录(通常是你的主存储池),而/var/lib/docker只存容器元数据和镜像层。所以第一步建目录,本质是在告诉Docker:“把我的业务数据,全部扔到大容量硬盘上,别占我32GB的小系统盘”。

这里有个极易踩的坑:极空间Web界面创建容器时,“高级设置”里的“挂载路径”输入框,必须以绝对路径形式填写,且路径需已存在。如果你在Web界面里填/Docker/daoliyu/storage,它会尝试挂载到/Docker/...,但极空间根本没有这个目录——正确路径只能是/mnt/data/Docker/daoliyu/storage。更糟的是,如果这个目录不存在,容器启动会静默失败,日志里只显示“OCI runtime create failed”,根本不会提示“挂载路径不存在”。我为此排查了6小时,最后发现是Web界面自动补全把/mnt/data截掉了。

另一个隐藏机制是用户ID映射。道理鱼后端服务(Java Spring Boot)默认以UID 1001运行,但极空间Docker守护进程的默认UID是999(postgres用户)。如果你没给postgres子目录提前赋权,容器启动时PostgreSQL会因无法写入/var/lib/postgresql/data而崩溃,错误日志里只有一行FATAL: could not create lock file "/var/lib/postgresql/data/postmaster.pid": Permission denied。解决方案不是改容器启动参数,而是执行:

chmod -R 755 /mnt/data/Docker/daoliyu/postgres chown -R 999:999 /mnt/data/Docker/daoliyu/postgres

这个操作必须在容器创建前完成,且要对四个子目录(plugins/storage/redis/postgres)全部执行。为什么是999?因为极空间OS的/etc/passwd里明确写着:

postgres:x:999:999::/var/lib/postgresql:/bin/bash:/bin/false

这是极空间团队为数据库服务预设的UID,不是随便猜的。

提示:极空间的Docker不支持--user参数覆盖UID,所以必须用宿主机chown。这点和标准Docker完全不同,也是新手最容易栽跟头的地方。

最后说说网络。道理鱼前端(Vue)和后端(Spring Boot)是分离部署的,但极空间Web界面只允许单端口映射(如8080)。它实际通过内置的Nginx反向代理实现多服务暴露:Web界面请求/api/自动转发到后端容器的8081端口,静态资源走8080。所以你不需要额外配traefik或nginx-proxy,极空间已经帮你做了。但这也意味着,如果你在道理鱼配置里把API Base URL写成http://localhost:8081,前端会因同源策略失败——必须写成/api/(相对路径)或http://你的nas地址:8080/api/

3. 部署前的四步原子化准备:目录、权限、镜像、网络

在极空间Web界面点“创建容器”之前,必须完成四个不可跳过的原子操作。少一步,后面90%的问题都源于此。

3.1 目录结构的物理意义与命名逻辑

极空间要求的目录结构:

/mnt/data/Docker/daoliyu/ ├── plugins/ # 存放用户自定义插件(如歌词解析器、豆瓣评分抓取器) ├── storage/ # 媒体文件原始存储区(音乐/MV/有声书的实际存放位置) ├── redis/ # Redis数据持久化目录(存储会话、搜索缓存、实时播放状态) └── postgres/ # PostgreSQL数据库文件(含元数据、用户配置、标签关系)

这个结构不是道理鱼官方规定的,而是极空间Docker容器的运行时契约。道理鱼镜像的ENTRYPOINT脚本里硬编码了这些路径:

# 镜像内 /entrypoint.sh 片段 export STORAGE_PATH="/app/storage" export PLUGIN_PATH="/app/plugins" export REDIS_DATA_DIR="/data" export POSTGRES_DATA_DIR="/var/lib/postgresql/data"

所以你挂载的宿主机路径,必须严格对应容器内的预期路径。/mnt/data/Docker/daoliyu/storage挂载到容器/app/storage,是因为道理鱼后端代码里所有FileUtils.listFiles(new File(storagePath))都指向这里。如果挂错,它会安静地创建空数据库,然后告诉你“未检测到媒体文件”。

特别注意storage/目录的用途:它不是媒体文件的“展示目录”,而是“扫描根目录”。道理鱼启动时会递归扫描/app/storage下的所有子目录,识别.flac/.mp3/.m4a/.mkv/.mp4/.m4b等后缀。所以你的音乐库应该这样组织:

/mnt/data/Docker/daoliyu/storage/ ├── Music/ │ ├── Taylor Swift/ │ │ ├── 1989 (Deluxe).flac │ │ └── Folklore.flac │ └── Radiohead/ │ └── OK Computer.flac ├── MV/ │ ├── Beyonce/ │ │ └── Lemonade.mkv │ └── Daft Punk/ │ └── Electroma.mp4 └── Audiobook/ └── The Hobbit.m4b

而不是把所有文件平铺在storage根目录下。道理鱼的扫描器会按目录层级生成艺术家/专辑/年份维度,这是它元数据管理的基石。

3.2 权限修复的精确到字节的操作

极空间OS的默认umask是0022,但Docker容器启动时会继承宿主机的umask。问题在于:PostgreSQL要求/var/lib/postgresql/data目录的权限必须是700,且属主必须是postgres用户(UID 999)。如果/mnt/data/Docker/daoliyu/postgres目录是Web界面自动创建的,它的权限会是755,属主是root,导致PostgreSQL拒绝启动。

实操步骤(必须用SSH登录极空间执行):

# 1. 创建目录(确保父目录存在) mkdir -p /mnt/data/Docker/daoliyu/{plugins,storage,redis,postgres} # 2. 设置postgres目录(最关键的一步) chmod 700 /mnt/data/Docker/daoliyu/postgres chown 999:999 /mnt/data/Docker/daoliyu/postgres # 3. 设置redis目录(Redis 7.0+要求/data目录755) chmod 755 /mnt/data/Docker/daoliyu/redis chown 999:999 /mnt/data/Docker/daoliyu/redis # 4. 设置storage和plugins(读写权限开放,因需被扫描和插件写入) chmod 775 /mnt/data/Docker/daoliyu/{storage,plugins} chown -R 1001:1001 /mnt/data/Docker/daoliyu/{storage,plugins}

注意最后两行的UID 1001:道理鱼Web服务进程以UID 1001运行,它需要往storage/里写临时缩略图,往plugins/里下载插件JAR包。如果这里用999,会导致“Permission denied”写入失败,但错误日志只会显示“Failed to generate thumbnail”,完全看不出是权限问题。

注意:极空间Web界面的“文件管理器”里右键改权限,只能改到755级别,无法设置700。必须SSH执行,这是极空间UI的一个设计盲区。

3.3 镜像拉取的避坑指南

道理鱼官方镜像托管在GitHub Container Registry(ghcr.io),但极空间默认的Docker Hub镜像源在国内访问极慢。直接在Web界面填ghcr.io/daoliyu/web:latest,大概率超时失败,错误显示“pull access denied”。

正确做法是分两步:

  1. 先用SSH拉取并重命名
# 登录ghcr.io(需GitHub Token,Token权限只需read:packages) docker login ghcr.io -u your_github_username -p your_personal_access_token # 拉取镜像(指定国内镜像站加速) docker pull --platform linux/amd64 ghcr.io/daoliyu/web:latest # 重命名为本地镜像(绕过极空间的远程拉取限制) docker tag ghcr.io/daoliyu/web:latest daoliyu/web:latest
  1. 在Web界面选择“本地镜像”:创建容器时,镜像来源选“本地镜像”,然后从下拉列表里选daoliyu/web:latest

为什么必须指定--platform linux/amd64?因为极空间Z4 Pro是x86_64架构,但GitHub Actions构建的镜像默认包含arm64层。Docker在多平台镜像中会自动选择匹配层,但极空间的Docker引擎版本较老(20.10.x),遇到多平台manifest时常卡死。强制指定平台可规避此问题。

另外,道理鱼有三个镜像:

  • daoliyu/web:latest:主Web服务(含前后端)
  • daoliyu/worker:latest:后台任务服务(元数据抓取、封面生成、AI标签)
  • daoliyu/ffmpeg:latest:专用FFmpeg容器(处理视频转码)

初学者建议只部署web镜像。worker和ffmpeg是可选增强模块,它们通过Redis队列通信。如果先部署全套,worker容器会因找不到Redis连接而疯狂重启,日志刷屏Connection refused,反而掩盖了主服务的问题。等web服务正常运行后,再按需添加worker。

3.4 网络端口的冲突预检与释放

极空间默认占用80/443端口(Web管理界面),而道理鱼默认也监听8080。但很多用户会同时部署qbittorrent(8080)、jellyfin(8096)、portainer(9000),导致端口冲突。极空间Web界面不会主动检测端口占用,它只会静默把容器端口映射到宿主机,如果端口已被占,新容器的端口映射就失效。

检查端口占用的命令:

# 查看所有监听8080端口的进程 ss -tuln | grep ':8080' # 如果输出类似 "tcp LISTEN 0 128 *:8080 *:* users:(("nginx",pid=1234,fd=6))" # 说明nginx(极空间Web服务)占用了8080,你需要换端口

解决方案:在创建容器时,把“宿主机端口”从8080改成8088(或其他未被占用端口),容器内端口保持8080不变。道理鱼镜像的EXPOSE声明是8080,所以-p 8088:8080即可。改完后,访问http://你的nas地址:8088即可。

但要注意:道理鱼前端代码里硬编码了API请求的base URL。如果你改了宿主机端口,必须在容器创建后的“环境变量”里添加:

API_BASE_URL=http://你的nas地址:8088/api/

否则前端会向http://你的nas地址:8080/api/发请求,得到CORS错误。

4. 容器创建的Web界面实操详解:每个开关背后的真相

极空间Web界面的“创建容器”页面,看似简单,但每个选项都藏着影响稳定性的关键逻辑。下面逐项拆解。

4.1 基础设置:镜像、名称、重启策略

  • 镜像:选择“本地镜像”,然后从下拉列表选daoliyu/web:latest(确保你已按3.3节完成拉取和重命名)。
  • 容器名称:填daoliyu-web。名称不能含下划线以外的特殊字符,且必须唯一。如果之前创建过同名容器但没删干净,新容器会因卷冲突启动失败。
  • 重启策略:选“除非已停止”。这是最稳妥的选择。always策略在极空间系统重启时可能导致容器抢在Docker守护进程启动前就尝试运行,报错Cannot connect to the Docker daemonon-failure则对道理鱼这种Java应用意义不大,因为OOM崩溃通常不会触发exit code 1。

4.2 端口设置:NAT模式的本质

极空间只提供“桥接模式”(Bridge),不支持Host模式。这意味着容器有独立IP(如172.17.0.2),通过iptables规则做端口映射。在“端口设置”里:

  • 容器端口:填8080(道理鱼Web服务监听端口)
  • 宿主机端口:填8088(假设你按3.4节检查过8088未被占用)
  • 协议:TCP(道理鱼不用UDP)

这里的关键是:极空间的端口映射是静态绑定。一旦你设了8088:8080,这个映射就永久占用宿主机8088端口,即使容器停止。想换端口,必须删掉容器重建。没有“动态端口”选项。

4.3 挂载设置:路径映射的生死线

这是整个部署中最容易出错的部分。点击“添加挂载”,填四行:

宿主机路径容器内路径读写权限
/mnt/data/Docker/daoliyu/plugins/app/plugins读写
/mnt/data/Docker/daoliyu/storage/app/storage读写
/mnt/data/Docker/daoliyu/redis/data读写
/mnt/data/Docker/daoliyu/postgres/var/lib/postgresql/data读写

必须严格按此顺序填写,且路径末尾不能带斜杠。如果填成/mnt/data/Docker/daoliyu/storage/(多了/),极空间会创建一个名为storage/的子目录,而不是挂载到storage目录本身,导致道理鱼扫描不到任何文件。

提示:极空间Web界面的“浏览”按钮无法定位到/mnt/data/以下目录,必须手动输入完整路径。这是UI设计缺陷,但已成事实。

4.4 环境变量:超越默认配置的控制权

道理鱼支持大量环境变量微调行为。必填的有:

变量名说明
SPRING_PROFILES_ACTIVEprod强制生产模式,关闭开发调试接口
API_BASE_URLhttp://你的nas地址:8088/api/前端API请求根地址,必须和宿主机端口一致
STORAGE_PATH/app/storage冗余声明,确保与挂载路径一致
REDIS_URLredis://redis:6379/0内部服务发现地址,固定写法
POSTGRES_URLjdbc:postgresql://postgres:5432/daoliyu同上,固定格式

特别注意REDIS_URLPOSTGRES_URL:它们的host名redispostgres不是DNS域名,而是Docker内部网络的服务发现名。极空间的Docker引擎启用了user-defined bridge网络,容器间可通过容器名互相访问。道理鱼镜像内预置了redispostgres两个服务的健康检查,所以你不需要单独部署Redis/PostgreSQL容器——道理鱼镜像是All-in-One的,它内置了轻量级PostgreSQL(14.5)和Redis(7.0.12)的嵌入式实例,只通过Unix Socket通信,不暴露端口。因此REDIS_URL里的redis:6379其实是占位符,实际走的是/tmp/redis.sock

4.5 高级设置:资源限制与安全边界

  • CPU限制:建议设为2核。道理鱼元数据扫描是CPU密集型任务,但Z4 Pro的4核8线程足够应付,设太高反而影响极空间自身服务。
  • 内存限制:设为2048MB。Java应用的堆内存默认占容器内存的75%,即1.5GB。低于1536MB会导致频繁GC,高于3072MB则浪费资源。
  • 启用特权模式必须关闭。道理鱼不需要CAP_SYS_ADMIN等特权,开启反而增加攻击面。
  • 自动删除容器:关闭。否则容器异常退出后会被自动清理,丢失所有日志。

创建完成后,不要急着点“启动”。先点“日志”,观察启动过程。正常流程是:

  1. [INFO] Starting PostgreSQL embedded server...
  2. [INFO] Initializing Redis in-memory store...
  3. [INFO] Scanning /app/storage for media files...
  4. [INFO] Loaded 1247 tracks, 89 albums, 32 artists
  5. [INFO] Tomcat started on port(s): 8080 (http)

如果卡在第3步超过2分钟,说明/app/storage挂载失败或权限不对;如果第1步报错FATAL: could not create lock file,就是postgres目录权限问题。

5. 首次启动后的三阶段验证:从连通性到元数据

容器启动成功只是万里长征第一步。道理鱼的真正价值,在于它能否把你的混乱媒体库,变成可检索、可管理、可传承的数字资产。这需要分三个阶段验证。

5.1 连通性验证:确认服务活着且可交互

打开浏览器,访问http://你的nas地址:8088。首次加载会慢(约30秒),因为前端要下载Vue bundle,后端要初始化HikariCP连接池。成功页面应显示:

  • 顶部导航栏:首页、音乐、MV、有声书、设置
  • 左侧边栏:“我的音乐库”、“最近播放”、“收藏夹”
  • 页面中央:“欢迎使用道理鱼,检测到0个媒体文件”

如果看到“502 Bad Gateway”或“Connection refused”,检查:

  • 容器是否在运行(Web界面“容器管理”里状态是“运行中”)
  • 宿主机端口是否被防火墙拦截(极空间默认关防火墙,但如果你开过,需放行8088)
  • API_BASE_URL环境变量是否拼写错误(大小写敏感)

提示:极空间的Docker容器日志默认只保留最近1000行。如果启动失败,立即点“日志”查看,否则滚动日志会被覆盖。

5.2 存储验证:让道理鱼“看见”你的媒体

点击右上角“设置”→“媒体库设置”,确认“媒体文件根目录”显示为/app/storage。然后点击“扫描媒体库”。此时道理鱼会遍历/app/storage下的所有文件,识别格式,提取基础元数据(时长、采样率、比特率)。

扫描完成后,刷新页面,左侧边栏的“我的音乐库”数字应从0变成实际文件数。点开“音乐”页签,应看到按艺术家分组的列表。如果仍是空的,执行以下诊断:

# SSH登录,进入容器内部 docker exec -it daoliyu-web /bin/bash # 检查storage目录是否挂载成功 ls -la /app/storage/ # 检查文件权限(应能看到你的音乐文件,且不是空白) ls -la /app/storage/Music/Taylor\ Swift/ # 检查道理鱼日志中的扫描记录 grep "Scanned" /app/logs/application.log

常见失败原因:

  • ls -la /app/storage/显示为空 → 宿主机路径挂载错误,或/mnt/data/Docker/daoliyu/storage下确实没放文件
  • ls -la显示文件但权限为----------→ 宿主机文件权限太严,需在极空间文件管理器里右键“属性”→“权限”→勾选“读取”和“执行”(对目录)

5.3 元数据验证:从“能播”到“懂你”

这才是道理鱼的核武器。点击任意一首歌,右侧详情面板应显示:

  • 封面:自动从MusicBrainz或Last.fm抓取的高清图(非默认灰色占位符)
  • 艺术家、专辑、曲目号、年份:来自嵌入式ID3/APE标签,或在线数据库补全
  • “相似推荐”:基于MFCC特征的音频相似度计算结果
  • “情绪标签”:如“舒缓”、“激昂”、“怀旧”,由内置的TinyBERT模型本地推理生成

如果封面和元数据全是空白,说明在线元数据服务没连上。道理鱼默认使用MusicBrainz,但它需要网络。验证方法:

# 在容器内测试网络连通性 docker exec daoliyu-web curl -I https://musicbrainz.org

如果返回HTTP/2 200,说明网络正常;如果超时,检查极空间的DNS设置(Web界面“网络设置”→“DNS服务器”是否为114.114.114.114或8.8.8.8)。

元数据补全是异步任务,首次扫描后不会立刻完成。它通过Redis队列分发任务,worker容器(如果你部署了)或web容器内置的worker线程会逐个处理。在“设置”→“任务中心”里,能看到“元数据抓取”队列的进度。一个1000首歌的库,首次补全需2-3小时,期间CPU占用会升到70%。

经验:元数据补全时,道理鱼会为每首歌发起3-5次HTTP请求(MusicBrainz + CoverArtArchive + Last.fm)。如果极空间的DNS响应慢,会导致大量超时。建议在“网络设置”里手动指定DNS,比用DHCP获取的更快。

6. 全栈管理的实战场景:音乐/MV/有声书的差异化处理

道理鱼的“全栈”不是营销话术,它对三类媒体采用完全不同的技术栈和处理逻辑。理解这个,才能发挥最大价值。

6.1 音乐库:从FLAC到智能播放列表

道理鱼对音乐的处理分三层:

  • 基础层:用Apache Tika解析ID3v2.4/APEv2标签,提取艺术家、专辑、流派、BPM、调性(Key)
  • 增强层:调用MusicBrainz Web Service,用AcousticBrainz API补全“节奏稳定性”、“响度”、“动态范围”等专业音频特征
  • 智能层:用本地部署的Lite版Essentia库,对FLAC/WAV文件做实时MFCC分析,生成128维特征向量,存入PostgreSQL的pgvector扩展列

这带来两个独特能力:

  1. 跨格式无损播放:道理鱼Web播放器支持FLAC/WAV/ALAC原生解码(通过WebAssembly编译的libsndfile),无需转码。你上传的FLAC,播放时就是FLAC,音质零损失。
  2. AI驱动的播放列表:在“播放列表”页,点击“智能生成”,选择“工作专注”,它会筛选BPM 60-80、响度-18LUFS、调性C大调的曲目,生成2小时不重复的白噪音替代品。这是纯本地计算,不依赖任何云API。

实操技巧:如果你的FLAC文件没有嵌入封面,道理鱼会自动从MusicBrainz下载最高清的Cover Art Archive图片,并缓存到/app/storage/.cache/cover/。这个缓存目录也在/mnt/data/Docker/daoliyu/storage/下,所以它会随你的媒体库一起备份,下次重装不用重新下载。

6.2 MV库:视频封面与时间轴标记

MV(Music Video)的处理逻辑完全不同。道理鱼不把它当“视频”看,而是当“带时间轴的多媒体对象”:

  • 用FFmpeg提取关键帧,生成WebP格式的1920x1080封面(存于/app/storage/.cache/thumbnail/
  • 解析MKV/MP4的章节信息,将“Intro”、“Chorus”、“Bridge”等标记为可点击时间点
  • 对4K MV,自动创建1080p和720p两个转码版本(存于/app/storage/.transcode/),供不同带宽设备使用

验证方法:上传一个带章节的MKV(如Beyonce的《Lemonade》),在道理鱼里点开,拖动进度条到“Chapter 3”,应看到标题“Don't Hurt Yourself”弹出。如果没反应,说明FFmpeg没正确提取章节,需检查/app/storage/.transcode/目录是否存在,以及容器内FFmpeg版本:

docker exec daoliyu-web ffmpeg -version # 正常应为 ffmpeg version 5.1.3-static https://johnvansickle.com/ffmpeg/

注意:道理鱼的FFmpeg是静态编译版,不依赖宿主机lib,所以极空间自带的FFmpeg版本不影响它。

6.3 有声书:语义分割与收听进度同步

有声书是最考验“全栈”能力的场景。道理鱼把它拆解为:

  • 文件层:识别M4B/MP3格式,提取ID3中的TXXX:PART(分集标识)和TXXX:DURATION(总时长)
  • 内容层:用Whisper.cpp(量化版)本地语音转文字,对《三体》这种长音频,会按15分钟切片,生成SRT字幕
  • 体验层:在Web播放器里,点击字幕行可跳转到对应时间点;手机App同步播放进度(通过PostgreSQL的listen_progress表)

关键配置在“设置”→“有声书设置”:

  • 自动分割阈值:设为“30分钟”,道理鱼会把单个2小时MP3按静音段自动切成4集
  • 语速适配:开启“动态变速”,播放时自动调整语速(0.7x-1.5x),保持每分钟字数恒定,保护听力

实测效果:《人类简史》12小时MP3,开启语速适配后,道理鱼在播放到“农业革命”章节时,会自动降速到0.9x,因为该段落语速比平均快12%。这个逻辑写在/app/config/audiobook-speed-rules.json里,你可以自定义。

7. 运维与升级:让道理鱼在极空间上长期服役

部署完成不是终点,而是日常运维的起点。极空间的稳定性优势,必须配合正确的维护习惯才能持续发挥。

7.1 日志监控:读懂道理鱼的“健康报告”

道理鱼的日志分三级,存放在容器内/app/logs/

  • application.log:Spring Boot主日志,记录HTTP请求、数据库操作、错误堆栈
  • worker.log:后台任务日志,元数据抓取、封面生成、AI推理的详细过程
  • ffmpeg.log:FFmpeg转码日志,含编码参数、耗时、码率统计

极空间Web界面的“日志”只显示application.log的最后1000行。要查完整日志,必须SSH:

# 查看最近1小时的元数据抓取情况 docker exec daoliyu-web tail -n 200 /app/logs/worker.log | grep "Metadata fetch" # 查看FFmpeg转码失败的文件 docker exec daoliyu-web grep "error" /app/logs/ffmpeg.log

一个健康系统的典型日志模式:

  • application.log:每5分钟一条[INFO] Health check passed(存活心跳)
  • worker.log:每10分钟一批[INFO] Fetched metadata for 12 tracks from MusicBrainz
  • ffmpeg.log:转码成功时有frame=12345 fps=60.0 q=28.0 size=12345kB time=00:02:34.56 bitrate=654.3kbits/s

如果worker.log里连续出现[WARN] Rate limit exceeded for MusicBrainz,说明你触发了MusicBrainz的每秒1次请求限制。解决方案是降低“元数据抓取并发数”(设置→任务中心→高级设置),从5降到2。

7.2 数据备份:不止是拷贝文件夹

道理鱼的核心数据在三个地方:

  • postgres/:结构化数据(用户、配置、元数据关系)
  • storage/:原始媒体文件(你的数字资产本体)
  • plugins/:自定义插件(如豆瓣评分插件,含你的API Key)

备份策略必须分层:

  • postgres/:每天凌晨2点