前阵子内部系统需要一个在线文档预览能力,业务方手里的合同、方案、PDF格式很杂,希望在浏览器里点开就能直接看,而不是下载到本地再装Office。当时对比了一圈方案,最后定下来用KKFileView,配合docker-compose做容器化部署,一条命令把整个预览服务拉起来。这篇文章就把选型、部署、接入、排查的完整过程整理出来,给同样需要在线文件预览方案的朋友做个参考。
这不是一篇简单的命令行复述,里面会包含我实际踩过的坑、配置调整的思路,以及业务系统对接时必须想清楚的几个细节。文章目标读者是负责内部系统集成或运维的开发者,即使之前完全没接触过这个项目,按着步骤走也能搭出一套能用的预览服务。
1. 为什么绕了一圈最后选了它:选型过程中的真实权衡
1.1 能覆盖哪些预览场景,以及和商业方案的差别
当时摆在桌面上的方案大致有这几类:微软的Office Online Server,需要在Windows服务器上搭场外协作环境,部署重、授权模糊,中小团队基本不会碰;ONLYOFFICE功能确实强,但它更偏向多人协作文档编辑,单为"预览"这个需求去上它,属于用牛刀杀鸡;商业云预览服务大多按量计费,敏感文件还要经过第三方。KKFileView走的是另一条路:一个开源项目,Spring Boot编写,内置了转换与渲染逻辑,通过LibreOffice把Office文件转成PDF或图片再在浏览器中展示,同时对PDF、图片、纯文本、音视频也提供了一套统一的预览页面。
这套方案非常适合"系统里已经有文件,只想给用户塞一个只读预览"的场景。不需要账号体系,不需要在线编辑,只要给一个文件地址就能出预览页。项目本身又提供了Docker镜像,把LibreOffice、JDK、字体这些重依赖全部打包进去了,理论上拉起来就能用。这也是我一开始就倾向容器化的直接原因。
格式覆盖上,日常接触最多的doc、docx、xls、xlsx、ppt、pptx、pdf、jpg、png、txt,它都支持,部分CAD格式和音视频也能处理。具体的支持清单每个版本略有差异,建议以官方格式列表为准,但基本不存在"普通办公文档看不了"的情况。
如果你的真实需求是文档协同编辑、多人同时改一个Excel表格,那KKFileView并不合适,它定位就是只读预览。但反过来讲,也正因为定位单纯,集成成本才低,一般开发半天就能把预览链接拼出来。
1.2 传统部署和容器化部署之间的关键差距
官方传统部署方式是先在服务器上装OpenOffice或LibreOffice,再装JDK,再跑项目jar包,中间还涉及环境变量、字体、so文件依赖,每一步都可能出问题。容器化之后,LibreOffice、JDK、基础字体全部打进镜像,宿主机只需要有Docker环境。这个差异在实际运维中非常大,至少省掉一半的排障时间。我见过好几个因为传统方式部署失败而弃用的人,换成docker-compose之后基本都能半小时内跑起来。
不过容器化也有需要注意的地方:镜像体积不小,拉取时要有耐心;宿主机内核和容器内字体库的兼容性偶尔会闹脾气;数据目录、缓存目录如果不挂出来,容器一删数据全没。所以compose文件里至少要把文件目录、缓存目录、日志目录都映射到宿主机,这是长稳运行的基础。
容器内虽然已经处理过一轮中文字体,但并不意味着所有字体都能正确渲染。特殊字体或者艺术字,转换后可能会有位置偏移,这个和源文件复杂程度有关,属于正常限制,不要指望它能和本地Office打开的效果完全一致。
2. 一份能直接落地的compose文件:从镜像选择到目录规划
2.1 准备工作:先把默认配置从容器里掏出来
第一次部署最忌讳的就是凭感觉写配置。我先把容器用最简单的方式跑起来,然后把容器内的默认配置复制到宿主机,之后再决定改什么。
docker pull kkfileview/kkfileview:latest docker run -d --name kkfileview-tmp -p 8012:8012 kkfileview/kkfileview:latest docker cp kkfileview-tmp:/opt/kkfileview/config ./config docker cp kkfileview-tmp:/opt/kkfileview/log ./log docker cp kkfileview-tmp:/opt/kkfileview/bin/file ./file docker cp kkfileview-tmp:/opt/kkfileview/cache ./cache docker rm -f kkfileview-tmp这样做的目的是把容器内真实的文件结构和配置文件原样拿到宿主机。之后改配置、挂载目录,都会基于这份真实内容,避免因为路径写错导致启动即失败。第一次跑容器时系统会初始化LibreOffice环境,可能需要等一两分钟,这是正常的,不要以为卡住了就强行重启。
2.2 compose文件逐行解析
目录准备完后,我的docker-compose.yml长成这样:
version: "3.8" services: kkfileview: image: kkfileview/kkfileview:latest container_name: kkfileview restart: unless-stopped ports: - "8012:8012" environment: - TZ=Asia/Shanghai volumes: - ./config:/opt/kkfileview/config - ./file:/opt/kkfileview/bin/file - ./cache:/opt/kkfileview/cache - ./log:/opt/kkfileview/log - /usr/share/fonts/custom:/usr/share/fonts/custom:ro mem_limit: 2g ulimits: nofile: soft: 65536 hard: 65536逐项说下我的考虑。端口映射部分,8012是容器内部默认端口,也是官方约定俗成的对外端口,映射到宿主机还是8012,方便记忆。如果宿主机端口被占,改成别的也可以,但业务系统拼接预览地址时要同步修改。
environment里的TZ=Asia/Shanghai一定要加。容器默认时区如果不是东八区,日志时间对不上,排查问题时会平白多一层困惑。
挂载目录是整个compose的核心。config目录挂载宿主机之前,必须确保里面已经有从容器里复制出来的application.properties;file目录是文件落地目录,预览接口下载的临时文件都在这一层;cache目录是文件转换缓存;log目录不用多说,排障全靠它。字体目录是我额外加的,为的是解决某些Office文档在容器内转PDF时中文乱码、字体缺失的问题。把公司统一字体放入宿主机的/usr/share/fonts/custom,以只读方式挂进去,转换效果会稳很多。
mem_limit给到2g,主要考虑的是LibreOffice在转换Excel或超大PPT时的内存峰值。不限制的话,容器可能把整台机器吃满;限制了反而能让OOM发生在容器层,不影响宿主机其他服务。
2.3 启动顺序与第一次自检
配置准备好后启动就机械了:
docker compose up -d docker logs -f kkfileview启动成功的标志是日志里出现类似"端口8012启动成功"或Spring Boot的Started Application消息。然后做三层自检:第一层,浏览器访问http://服务器IP:8012,能看到一个文件上传的Demo页面;第二层,随便传一个PDF上去,能正常展示;第三层,传一个Word或Excel,能完成转换并展示。这三步都过了,部署本身才算真正没问题。
提示:如果Demo页面能打开但Office转换卡住,先别急着怀疑业务系统,大概率是容器首次转换时LibreOffice进程启动比较慢,多等一会,或者看容器日志里有没有soffice相关的报错。
3. 必须动过的几个配置项:藏在镜像里的调优空间
3.1 默认配置结构简述
从容器里复制出来的application.properties,已经把官方所有默认参数都列出来了。我建议不要大改,只需要关注一小部分:端口、文件目录、缓存模式、是否开启远程下载。我实际动过的配置项集中在这几块:
server.port=8012 # 文件落地与缓存 file.upload.dir=/opt/kkfileview/bin/file file.cache.root=/opt/kkfileview/cache # 缓存类型:local/redis/memcached cache.type=local # 是否开启远程url下载,默认开启 file.download.enable=true这里要特别说file.upload.dir和file.cache.root。它们必须和compose里挂载的路径保持一致,否则就会出现"文件在容器里,但目录没挂出来"或者"缓存写不进宿主机"的割裂情况。很多人改了compose但忘了改配置文件里的内部路径,结果数据还是落在容器层,容器一删全没。
3.2 缓存模式怎么选:local与分布式缓存的边界
默认cache.type=local,所有转换结果缓存在本地磁盘。对于单机部署、预览量不是特别大的场景,local已经够用,磁盘扩容也简单。但如果后面要做成多实例负载均衡,local缓存会带来一个问题:同一份文件在A实例转换后,B实例没有缓存,又得重新转一次。所以多实例部署时通常要改成redis或memcached。
我暂时没有上多实例,local缓存配合挂载出来的cache目录,已经能满足日常预览量。要注意的是,如果cache目录被外部定时清理,第一次预览会变慢,但这种慢属于正常的转换耗时,不算故障。清理频率不要设太短,我一般保持7天以上。
3.3 默认配置先别大改:一次精简配置引发的教训
网上有不少"精简版application.properties",我一开始也图省事,把配置改得就剩几行,结果预览Office时找不到文件存储根目录,Spring Boot直接启动失败。后来老老实实从容器里把默认配置copy出来,基于它改端口和目录。教训是:这类组件的配置项往往是联动关系,盲目删减会让系统在运行时找不到依赖路径。保持默认项完整,只改必须改的,最稳妥。
配置里还有一部分是转换模块的开关,比如某些不常用的格式转换模块。如果业务里几乎不会出现那些格式,可以按官方注释把开关关闭,减少日志噪音和资源占用。但改之前一定要弄清楚这个开关对应的依赖是什么,不要为了"精简"而误关核心转换能力。
注意:修改application.properties后,需要重启容器才生效,执行docker compose restart kkfileview。
4. 业务系统接入:一条URL带活整个预览流程
4.1 预览接口的实际调用格式
部署完成只是第一步,真正花时间的是业务系统怎么对接。KKFileView最常用的预览入口是/onlinePreview接口,调用方式是拼接URL:
http://预览服务IP:8012/onlinePreview?url=<文件地址>&name=<文件名>其中url参数是文件真实下载地址,必须经过URL编码;name参数是展示用的文件名,也要一起编码,中文文件名尤其不能漏。业务系统在前端拼好这个地址后,通过iframe或新窗口加载,预览页会自动完成"下载原文件→转换→渲染"的流程。
举个例子,一个业务文件下载地址是:
http://file-server.example.local/attachments/2024/05/16/report.pdf拼接前先编码:
url=http%3A%2F%2Ffile-server.example.local%2Fattachments%2F2024%2F05%2F16%2Freport.pdf最终完整地址就是:
http://kkfileview:8012/onlinePreview?url=http%3A%2F%2Ffile-server.example.local%2Fattachments%2F2024%2F05%2F16%2Freport.pdf&name=%E6%8A%A5%E5%91%8A.pdf这里的%E6%8A%A5%E5%91%8A是"报告"两个字的UTF-8编码。业务系统后端拼地址时别偷懒,直接用现成的URL编码工具类处理就好。
4.2 文件下载地址的可见性与鉴权
这里有个容易忽略的设计问题:传给KKFileView的url是服务器端去请求的,所以这个url对应的接口必须允许预览服务器访问。我在内部系统里统一走了一个附件下载接口,但该接口默认需要登录态。直接拿它拼预览地址,KKFileView去下载时会拿到401。解决方案通常有三种。
一是给下载接口增加白名单IP校验,预览服务器IP在名单内就直接放行;二是为预览场景生成临时签名URL,带过期时间,KKFileView在有效期内可以下载;三是业务系统先把文件推到共享存储,预览服务直接读共享目录,不走HTTP下载。第一种最简单,内网环境我推荐这么做;第二种更安全,适合文件要暴露到外网的场景。
还有一个细节:不要直接把带token的真实下载地址传出去。token一旦出现在预览URL里,等于把这个文件的临时凭证扩散出去了,存在泄露风险。最好由后端做一层二次转发,把token剥掉,只给KKFileView一个临时有效的链接。
4.3 跨域与Cookie问题怎么处理
在前端甚至后端对接过程中,最容易困惑的是"跨域"。其实KKFileView预览页面本身是独立域名加载的,业务系统和它之间的接口调用发生在服务端,不涉及浏览器跨域。真正的跨域点在于预览服务器去请求原文件url时,原文件服务是否允许非浏览器的HTTP请求,也就是服务端到服务端的网络白名单,而不是CORS配置。这一点我最初也理解反了,后来抓包看请求才发现,请求根本不是从浏览器发出的,而是从KKFileView容器发出的。
如果原文件服务在CDN或对象存储上,还要注意防盗链配置,别把预览服务器的请求也拦掉了。我处理过的一个案例就是对象存储的防盗链只允许业务系统域名,忽略了预览服务器,导致预览一直报"文件无法下载"。
5. 部署后必踩的几个坑:完整排查链路与解决办法
5.1 Office预览一直转圈,日志卡在soffice
真实经历:有一次Word文档预览在浏览器里永远转圈,看容器日志没有报错,只是反复看到soffice相关进程。后来发现不是工具坏了,是LibreOffice首次拉起比较慢,加上容器CPU限制太低,转换进程一直被调度优先级压制。我把compose里加上CPU限制并放开来,重启容器后问题消失。所以遇到"转圈"先确认两件事:容器是否给了足够的CPU和内存;日志里soffice相关的输出是在正常跑还是卡死。
如果日志里直接报soffice连接失败,多半是LibreOffice进程崩了。处理方式是重启容器,并检查宿主机可用内存。内存不足时,内核会OOM杀掉转换进程,这时候docker logs通常会看到Out of memory相关记录。
5.2 中文文件名乱码或提示找不到文件
这个坑特别常见。业务系统拼URL时必须对url和name分别编码,不能只对文件名编码。如果整个URL在传递过程中被业务框架二次decode,名称又会变回去,进而引发"目标文件不存在"或转换后乱码。我在后端Java里统一用URLEncoder.encode处理,前端拼好的地址再整体用iframe src加载,乱码问题就消失了。
还要检查原文件url是否已经正确编码。很多文件系统返回的url里含中文或空格,不编码的话KKFileView从参数解析到的地址是残缺的,自然下载失败。编码这件事尽量在后端做,前端JS处理容易因为编码字符集不一致出问题。
5.3 容器重启后预览旧文件报错,文件明明还在
容器重启或升级后,file目录里的文件还在,但预览旧文件却报错,多半是缓存元数据和实际文件对不上。这种情况把cache目录清掉,让它重新转换一次就好。我的处理措施是写了一个定时任务,凌晨低峰期自动清理超过7天的缓存文件,既不占用太多磁盘,又避免元数据过期问题。
提示:清理缓存前确认没有正在进行的预览任务,否则会出现一瞬间的转换中断。
5.4 Demo页面能打开,业务系统却访问不了
如果容器正常运行、预览端口也通了,但业务系统所在网段访问不了,优先检查两台机器间的安全组或防火墙策略。还有一类原因是KKFileView部署服务器上做了nginx反向代理,但代理没配长连接超时,预览大文件时连接被中途掐断。我当时在nginx里调整了proxy_read_timeout到300秒,问题才解决。参考配置片段:
location /onlinePreview { proxy_pass http://127.0.0.1:8012; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 300s; proxy_send_timeout 300s; client_max_body_size 50m; }这里client_max_body_size也要注意,如果业务系统直接走nginx上传文件再预览,默认1m的上传限制几乎必踩。不用nginx转发上传的话,这段可以忽略。
6. 再进一步:安全与规模化之前值得做的准备
6.1 在线预览服务不要裸奔公网
KKFileView本身是一个功能组件,不是完整的权限系统。把它直接暴露到公网,等于把文件预览能力开放给了所有人,配合url参数甚至可以尝试遍历文件。稳妥做法是用nginx做反向代理,在代理层加一层访问控制,比如Basic Auth、来源IP白名单,或对接已有的统一认证。内网环境也建议至少设置防火墙规则,只允许业务系统网段访问8012端口。
6.2 磁盘与容量规划
file目录和cache目录是磁盘消耗大户。大量文件预览会让缓存持续膨胀,尤其Office文件转换后的PDF和图片占空间较大。我的实践是给这两个目录单独挂一块数据盘,并配置磁盘水位告警。如果接入的是对象存储或NFS,可以研究一下把file目录指到共享存储的方案,但这需要修改配置与网络权限,建议预发布环境验证后再上生产。
6.3 升级前把三个目录备好,回滚才不慌
KKFileView迭代不算慢,升级镜像版本之前,我会先备份config、file、cache三个目录,然后在另一台服务器用相同的compose文件先跑一遍预发布验证,确认转换正常后再切换流量。生产环境执行docker compose pull && docker compose up -d后,如果发现问题,把compose里镜像标签换回去重新up,挂载目录不变,数据都还在。这件事值得写成脚本,因为预览服务往往是业务系统接入后才上线的,升级失败的影响面比想象中大。
篇幅所限,安全与规模化这块先写到这里。我实际部署后的最大感受是:KKFileView把复杂的Office转换塞进容器后,日常运维比想象中简单,真正的挑战其实在"业务系统如何把文件安全地交给它"。谁能在接入层把鉴权、编码、网络白名单想清楚,这套预览方案就能长期稳定跑下去。最后再分享一个小技巧:升级镜像前,先备份config、file、cache三个目录,再拉新镜像启动,回滚时会非常从容。