springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice

一、 背景

因为项目中需要使用word转pdf功能,因为转换速度原因,最后选用了libreoffice,原因及部署请参考
linux ubuntu环境安装libreoffice,word转pdf

远程调用的话可选docker部署,请看2.3.1

二、springboot整合libreoffice

其实springboot整合libreoffice有两种方式 ,一种是使用本地的libreoffice,一种是使用远程服务的libreoffice(这个好多文章中没有提到,也是自己踩的坑算是)

2.1、整合本地服务

引入pom

复制代码
    <dependency>
                <groupId>org.jodconverter</groupId>
                <artifactId>jodconverter-spring-boot-starter</artifactId>
                <version>4.4.2</version>
            </dependency>
            <dependency>
                <groupId>org.jodconverter</groupId>
                <artifactId>jodconverter-local</artifactId>
                <version>4.4.2</version>
            </dependency>

yml配置

复制代码
jodconverter:
  local:
    enabled: true
    #window地址:  D:\workplaces\jcxx\libreoffice 请自行补全
    #linux地址:   /opt/libreoffice24.2
    office-home: /opt/libreoffice24.2
    # 端口(线程)
    portNumbers: [8101,8102,8103]
    maxTasksPerProcess: 100
    # 任务执行的超时时间
    task-execution-timeout: 360000
    # 任务队列的超时时间
    task-queue-timeout: 360000
    # 一个进程的超时时间
    process-timeout: 360000

使用也很简单

复制代码
    @Resource
    private DocumentConverter documentConverter;

   public void test() {
   
 //流转换
   documentConverter.convert(inputStream).as(DefaultDocumentFormatRegistry.DOCX).to(outStream).as(DefaultDocumentFormatRegistry.PDF).execute();
   //文件转换,sourceFile和targetFile都是File类实例
    documentConverter.convert(sourceFile).to(targetFile).as(DefaultDocumentFormatRegistry.PDF).execute();
   }

2.2、整合远程服务

说一下怎么发现的,学过springboot的应该都知道,整合其他服务时候应该都有个配置类xxxAutoConfiguration

于是发现了除了一个local外,还有个remote,才发现可以直接调用远程服务,发现了那就可以整合使用,如下

pom引入

复制代码
  <dependency>
                <groupId>org.jodconverter</groupId>
                <artifactId>jodconverter-spring-boot-starter</artifactId>
                <version>4.4.2</version>
            </dependency>
           <dependency>
                <groupId>org.jodconverter</groupId>
                <artifactId>jodconverter-remote</artifactId>
                <version>4.4.2</version>
            </dependency>

yml配置

复制代码
jodconverter:
  remote:
    enabled: true
    url: http://192.168.1.16:8100
    ssl:
      enabled: false

注意,一定要加http,我就不小心忽略了这个东西,只写了个ip+端口,结果导致一致报错

复制代码
java.net.MalformedURLException: no protocol 

以下可不看,直接看踩坑

使用方式和local的一样,参考上面

不过比local方式多了一步,要手动启动远程的libreoffice服务

启动命令,附上对应命令的含义
https://help.libreoffice.org/latest/zh-CN/text/shared/guide/start_parameters.html

复制代码
soffice --headless --nologo --nofirststartwizard --norestore --accept="socket,host=0.0.0.0,port=8100;urp;" &

以为到此就结束了吗?不不不,是我想的太简单了

对了,上面的命令还踩了一些坑,百度的时候都是127.0.0.1,想telnet通的的话需要使用0.0.0.0,不过,纠结这个似乎没啥意义,原因似乎不在这里(当时还花了好久排查)

不用0.0.0.0的话,直接报错连不上对应ip端口,改了之后报下面的错

2.3、整合远程服务踩坑

当我在服务器上运行该命令后,满心欢喜的等着转换完成时,突然转换就卡住了,随后报错

复制代码
org.jodconverter.core.office.OfficeException: Remote conversion failed
	at org.jodconverter.remote.task.RemoteConversionTask.execute(RemoteConversionTask.java:162)
	at org.jodconverter.remote.office.RemoteOfficeManagerPoolEntry.doExecute(RemoteOfficeManagerPoolEntry.java:301)
	at org.jodconverter.core.office.AbstractOfficeManagerPoolEntry.lambda$execute$0(AbstractOfficeManagerPoolEntry.java:80)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(SocketInputStream.java)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
	at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
	at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:138)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:56)
	at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
	at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
	at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:157)
	at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
	at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:272)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
	at org.apache.http.client.fluent.Request.internalExecute(Request.java:173)
	at org.apache.http.client.fluent.Executor.execute(Executor.java:262)
	at org.jodconverter.remote.task.RemoteConversionTask.execute(RemoteConversionTask.java:147)
	... 6 more

连接超时?我telnet一下,端口通啊!最最让我恶心的是,我上周快下班时候使用这种方式成功转换了一次,这就给我一种错觉,可能是我启动命令的问题,于是就疯狂尝试修改启动命令,找对应的参数。结果都没卵用。。。。。。于是想上周是不是搞错了,让我误以为这样可行?

结果还真是,命令行启动压根就不能使用remote。不过没找到上周为啥成功的原因?也忘了上周咋成功的了。。。
https://github.com/jodconverter/jodconverter/wiki/LibreOffice-Remote

看到这我就懵了,我还得去安装个Collabora Online 或者LibreOffice Online?(当前,这也是可行的) 。我看了下这两东西基本就是属于web端的在线编辑word了。。。

于是又开始找,不过这回学聪明了点,只在github中的issues中找 ,搜索关键词remote

还真让我找出来两种办法:但是我只成功了一种

对了,扔几个issues链接,有兴趣的可以看下

复制代码
https://github.com/jodconverter/jodconverter/wiki/Migration-Guide-4.4.5
https://github.com/jodconverter/jodconverter/issues/40
https://github.com/jodconverter/jodconverter/issues/350
https://github.com/jodconverter/jodconverter/issues/397
2.3.1、方法一(成功)

也就是https://github.com/jodconverter/jodconverter/issues/397这个里面提到的方案。代码很简单,都可以看下,简单来说就是本地启动一个libreoffice服务,并对外提供接口调用(就一个controller)

docker镜像拉不下来参考这个,我是看第一点成功的https://blog.csdn.net/weixin_50160384/article/details/139861337

jodconverter提供了一个远程服务的接口,我们可以直接docker运行

复制代码
docker run -d -p 8100:8100 --privileged=true -v /usr/share/fonts:/usr/share/fonts -v /opt/application.properties:/etc/app/application.properties ghcr.io/jodconverter/jodconverter-examples:rest 

挂载对应字体,否则中文不显示

-v /usr/share/fonts:/usr/share/fonts

挂载配置文件,用于修改端口等

-v /opt/application.properties:/etc/app/application.properties

配置文件在这看https://github.com/jodconverter/docker-image-jodconverter-examples

对应文件我也贴下

复制代码
# amount of libreOffice instances to start - one for each given port. So this means 2
jodconverter.local.port-numbers: 2002, 2003
# change the tmp folder
jodconverter.local.working-dir: /tmp
# change upload sizes
spring.servlet.multipart.max-file-size: 5MB
spring.servlet.multipart.max-request-size: 5MB
# change the server port (where the REST app is listenting
server.port=8100

然后这个的ip+端口号使用jodconverter.remote方式就成功了

也许有同学已经安装好了libreoffice,想着我这libreoffice不白装了,最后用docker。。。

其实也不然,可以自己将项目打个包放到服务器去运行,不过这个需要自己研究下了
https://github.com/jodconverter/jodconverter-samples

不过我看了下dockerfile文件,可能是这个命令(没用过gradlew )

2.3.2、使用local参数,但是使用远程服务器(失败)

根据https://github.com/jodconverter/jodconverter/wiki/Migration-Guide-4.4.5描述及https://github.com/jodconverter/jodconverter/issues/40,似乎可以使用local来进行访问

于是,有了如下配置

复制代码
jodconverter:
  local:
    enabled: true
  	office-home: D:\workplaces\libreoffice
   	port-numbers: 9999
   	load-document-mode: remote
   	start-fail-fast: true
  	host-name: 120.46.141.243

有一点很让人费解,使用了remote模式,还必须配置office-home。。。我使用远程服务上的地址还不行。。。还有一点,必须要提前启动位于服务器上的9999端口服务,不提前启动程序启动不起来。

然后我尝试了下,虽然也能转换成功,但是用了40多秒。
不清楚是什么原因,解决的小伙伴可以在评论区讨论下

复制代码
soffice --headless --nologo --nofirststartwizard --norestore --accept="socket,host=0.0.0.0,port=9999;urp;" &

三、docker中同时部署应用和libreoffice(不推荐)

单独部署libreoffice的可以自己再查询下,这里就不介绍了

因为一开始我只发现了local 这种方式,所以就在window本地和linux服务器上都部署了下,后来突然想到,丫的服务是用的docker进行部署的。docker里面又没有libreoffice,还访问个屁呀,我linux部署上没啥用啊!

意识到这点后,首先尝试着把Linux中的libreoffice挂载到docker容器中,但以交互模式进去后
soffice 还是libreoffice24.2都执行不了。。。。

那想着只能将libreoffice也弄到容器中去了。。。

于是有了下面的dockerfile文件

复制代码
# 使用基于 Alpine 的 OpenJDK 镜像
FROM registry.cn-beijing.aliyuncs.com/hub-mirrors/openjdk:8-jdk-alpine

# 更新包列表并安装必要的软件
RUN apk add --no-cache bash libreoffice


# 复制 jar 文件到容器
COPY xxx.jar app.jar
COPY fonts/zhFonts /usr/share/fonts

# 设置环境变量
ENV JAVA_HOME=/usr/lib/jvm/default-jvm
ENV LIBREOFFICE_HOME=/usr/lib/libreoffice
ENV PATH=$JAVA_HOME/bin:$LIBREOFFICE_HOME/program:$PATH

# 设置 ENTRYPOINT 以允许使用 exec
ENTRYPOINT ["/bin/bash", "-c"]

# 设置 CMD 以启动 Java 应用
CMD ["java -Djava.security.egd=file:/dev/./urandom -jar app.jar"]

#LibreOffice 6.1.4.2 版本

也许对docker还是不太熟悉,在copy命令的时候源文件似乎不能使用绝对路径,这个让我有点奇怪,

于是把东西全都挪到了/opt目录下

然后执行命令,注意最后有个.

复制代码
docker build -t新镜像名字:TAG.

不推荐的原因就是在于此,一个镜像高达1g。。。

实测后libreoffice和服务都是正常的,可以接受镜像大和构建时间长些的也可以使用这种方式

发现这点后,我感觉这也太low了,毕竟之前jar包也就将近200m,而且使用的是阿里的云效流水线构建工具,这样构建一次得花多久?(没有去公司服务器尝试,自己用云服务器尝试,第一次构建dockerfile拉取libreoffice花了得10多分钟,第二次用dockerfile构建就是秒拉取了,可能也只是第一次慢?不知道用云效如何?有兴趣的可以尝试下)。

尝试着找别的方法解决,于是无意中发现了JodConverterRemoteAutoConfiguration,对应配置类
JodConverterRemoteProperties

四、其他问题

4.1、使用远程的libreoffice时候用excel转pdf的时候样式错乱

本来是这样的格式

变成了这样

反正发现这个问题我是很懵圈的。但没办法,排查呗。

尝试下本地的libreoffice另存为是正常的。

尝试下代码本地的libreoffice也是正常的。

尝试下inux的本地的libreoffice也是正常的。

然后我就开始怀疑是不是remote源码层面的问题,于是去把remote的代码弄到本地试了下,也是正常的。

然后我就想着用自己买的服务器试试,是不是application配置文件的问题,但是,我发现,我自己服务器的远程服务是正常的

那我就意识到了,都是docker容器,不一样的只能是挂载文件,那问题就只能出在字体上了,因为用服务器试过word转pdf没有中文乱码问题(之前有人上传了一些字体),所以服务器上我就没有上传字体,于是就出现了该问题,字体还是要上传全的。
问题就出在字体上,上传上去字体就好了

相关推荐
码事漫谈3 分钟前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈3 分钟前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy30 分钟前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8291 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大61 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
洛神么么哒1 小时前
freeswitch-初级-01-日志分割
后端
蝎子莱莱爱打怪1 小时前
我的2025年年终总结
java·后端·面试
奋进的芋圆1 小时前
TokenRetryHelper 详解与 Spring Boot 迁移方案
java·spring boot·后端
云上小朱2 小时前
软件部署-在k8s部署Hadoop集群
后端
镜花水月linyi2 小时前
Cookie、Session、JWT 的区别?
后端·面试