文档在线预览解决方案——openoffice转换

2023-10-26

文档在线预览是一个复杂功能,文档格式的繁复更加增加了难度,虽然office给出了在线预览功能(https://products.office.com/en-us/office-online/view-office-documents-online)但是仍感觉限制多多。

笔者目前研究的方案是借助openoffice转换为pdf在线预览,目前大多浏览器均支持pdf在线预览,目前手机端浏览器还无法支持,有两种方案:1、pdf.js 2、将pdf转为图片预览。

首先,接口定义

public interface ResourceConverter {

    /**
     * 临时文件存储目录
     */
    String CONF_KEY_TEMP_DIR = "temp.dir";
    String CONF_KEY_MAX_PAGE_SIZE = "maxPageSize";

    /**
     * 是否支持转化指定资源
     *
     * @param resourceUri
     * @return
     */
    boolean support(String resourceUri);

    /**
     * @param resourceUri
     * @param config
     * @return
     */
    State trans(String resourceUri, Map<String, Object> config);

    /**
     * 将中间生成的临时文件存储到正式文件仓库,不论成功与否完成后均删除临时文件
     *
     * @param tempImg
     * @param index
     * @return 存储的正式地址URI,若保存失败则返回 null
     */
    String save(File tempImg, int index);

}

因为目的明确,接口定义也就很简单,核心方法就是trans,将给定资源转换并给出结果,针对个性化设置(例如:临时文件存储路径,清晰度等)通过config传递。support方法判断接口实现是否支持给定资源转换,定义此方法为了方便上下文切换算法,借鉴策略模式。为了统一输出结果,笔者定义了State。

public class State {
    private boolean state = false;
    private String info = null;
    private Map<String, String> infoMap = new HashMap();

    State(boolean state, String info) {
        this.state = state;
        this.info = info;
    }

    public static State errorResource(String info) {
        return new State(false, StringUtils.defaultString(info, "原始资源错误"));
    }

    public static State pageSizeLimit() {
        return new State(false, "资源页数超出,上限200页");
    }

   // 省略getter,setter
}

State定义也很简单,bool值说明转换状态成功或失败,info提供简略信息,infoMap存储最终转换后有效信息。同时还提供了两个常用的语义化抽象工厂方法,便于返回状态。

按照惯例,接口实现给出骨架实现(好处:1、确定接口实现的通用功能,方便个性化实现调用,2、易于扩展,一旦接口新增方法,骨架实现可以给出默认实现,以前定义的实现类就不需要更新)

public abstract class AbstractResourcesConverter implements ResourceConverter {

    private static QiniuUtil qiniuUtil = QiniuUtil.getInstance(QiniuUtil.Namespace.ADMIN);

    public abstract State doTransHttpResource(InputStream inputStream, URL url, Map<String, Object> config);

    public abstract State doTransFileResource(File file, String filePath, Map<String, Object> config);

    public abstract String getTempDir(Map<String, Object> config);


    @Override
    public State trans(String resourceUri, Map<String, Object> config) {
        if (StringUtils.isBlank(resourceUri)) {
            return State.errorResource(null);
        }
        if (resourceUri.startsWith("http")) {
            try {
                URL url = new URL(resourceUri);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                //设置超时10S
                conn.setConnectTimeout(10000);
                return doTransHttpResource(conn.getInputStream(), url, config);
            } catch (Exception e) {
                e.printStackTrace();
                return State.errorResource(e.getMessage());
            }
        } else {
            File file = new File(resourceUri);
            if (!file.exists()) {
                return State.errorResource(null);
            }
            return doTransFileResource(file, resourceUri, config);
        }
    }

    protected int getMaxPageSize(Map<String, Object> config) {
        Object dpi = config.get(CONF_KEY_MAX_PAGE_SIZE);
        if (dpi == null) {
            return 200;
        }
        return (int) dpi;
    }

    protected void makeTempDir(Map<String, Object> config) {
        String imgFilePathPrefix = getTempDir(config);
        File dir = new File(imgFilePathPrefix);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    @Override
    public String save(File tempImg, int index) {
        String key = qiniuUtil.upload(tempImg);
        tempImg.delete();
        return key == null ? key : key;
    }
}

笔者给出的骨架实现中,有三点需要说明:
1. 分化了http资源和本地文件资源,并提前处理了错误资源的反馈。
2. 将创建临时文件目录代码做了通用处理
3. 为临时资源的存储也给了默认实现,在正式环境,一般会将资源存储到服务器上,笔者这里将转化资源存储到七牛云上

各种资源的转换实现

使用openoffice将文档类型转为pdf

public class Doc2PdfConverter extends AbstractResourcesConverter {
    private static final Logger logger = LoggerFactory.getLogger(Doc2PdfConvert.class);
    public static final String CONF_KEY_OPEN_OFFICE_HOST = "openoffice.host";
    public static final String CONF_KEY_OPEN_OFFICE_PORT = "openoffice.port";
    public static final String SUPPORT_TYPE_PATTERN = ".*(.doc|.docx|.ppt|.pptx|.xls|.xlsx)$";
    public static final String RESULT_INFO_KEY_PDF = "pdf";

    @Override
    public boolean support(String resourceUri) {
        return Pattern.matches(SUPPORT_TYPE_PATTERN, resourceUri);
    }

    @Override
    public State doTransHttpResource(InputStream inputStream, URL url, Map<String, Object> config) {
        try {
            return doTrans(inputStream, buildFormat(url.toString()), config);
        } catch (Exception e) {
            return State.errorResource(null);
        }
    }

    @Override
    public State doTransFileResource(File file, String filePath, Map<String, Object> config) {
        try {
            return doTrans(new FileInputStream(file), buildFormat(filePath), config);
        } catch (Exception e) {
            return State.errorResource(null);
        }
    }

    private State doTrans(InputStream inputStream, DocumentFormat sourceType, Map<String, Object> config) {
        State state = new State(true, "");
        int openofficePort = getOpenofficePort(config);
        String openofficeHost = getOpenofficeHost(config);
        String pdfOutputFile = getTempDir(config) + System.currentTimeMillis() + ".pdf";
        OpenOfficeConnection connection = new SocketOpenOfficeConnection(openofficeHost, openofficePort);
        OutputStream dist = null;
        try {
            makeTempDir(config);
            connection.connect();
            dist = new FileOutputStream(pdfOutputFile);
            DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
            converter.convert(inputStream, sourceType, dist, buildFormat(pdfOutputFile));
            state.putInfo(RESULT_INFO_KEY_PDF, pdfOutputFile);
        } catch (Exception e) {
            logger.error("open office converting ERROR, maybe it's not running with port" + openofficePort, e);
            // 删除临时文件
            new File(pdfOutputFile).deleteOnExit();
            return State.errorResource("open office converting occur an ERROR");
        } finally {
            connection.disconnect();
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(dist);
        }
        return state;
    }

    private DocumentFormat buildFormat(String filePath) {
        String extension = FilenameUtils.getExtension(filePath);
        DefaultDocumentFormatRegistry defaultDocumentFormatRegistry = new DefaultDocumentFormatRegistry();
        DocumentFormat format = defaultDocumentFormatRegistry.getFormatByFileExtension(extension);
        return format;
    }

    private int getOpenofficePort(Map<String, Object> config) {
        Object port = config.get(CONF_KEY_OPEN_OFFICE_PORT);
        if (port == null) {
            return 8100;
        }
        return (int) port;
    }

    private String getOpenofficeHost(Map<String, Object> config) {
        Object host = config.get(CONF_KEY_OPEN_OFFICE_HOST);
        if (host == null) {
            return "localhost";
        }
        return (String) host;
    }

    @Override
    public String getTempDir(Map<String, Object> config) {
        return config.get(CONF_KEY_TEMP_DIR) + "/doc/";
    }

}

这里的具体实现就没什么可说的了,使用openoffice提供的jar(jodconverter-2.2.2.jar)即可,有一个坑需要指出的是:jar包版本2.2.2才支持docx、pptx,但是maven中央仓库没有这个版本,笔者的解决方案是从官网下载jar包之后上传至maven私服,此jar包还有很多其他依赖,所以还需要上传pom文件。

pdf转图片

public class Pdf2ImgConverter extends AbstractResourcesConverter {
    public static final String CONF_KEY_DPI = "pdf.dpi";
    public static final String RESULT_INFO_KEY_DIST = "dist";

    @Override
    public State doTransHttpResource(InputStream inputStream, URL url, Map<String, Object> config) {
        try {
            PDDocument document = PDDocument.load(inputStream);
            PDFRenderer renderer = new PDFRenderer(document);
            PdfReader pdfReader = new PdfReader(url);
            return doTrans(renderer, pdfReader, config);
        } catch (Exception e) {
            return State.errorResource(null);
        }
    }

    @Override
    public State doTransFileResource(File file, String filePath, Map<String, Object> config) {
        try {
            PDDocument document = PDDocument.load(file);
            PDFRenderer renderer = new PDFRenderer(document);
            PdfReader pdfReader = new PdfReader(filePath);
            return doTrans(renderer, pdfReader, config);
        } catch (Exception e) {
            return State.errorResource(null);
        }
    }

    @Override
    public String getTempDir(Map<String, Object> config) {
        return config.get(CONF_KEY_TEMP_DIR) + "/pdf/";
    }

    protected State doTrans(PDFRenderer renderer, PdfReader pdfReader, Map<String, Object> config) {
        State state = new State(true, "");
        try {
            int pageCount = pdfReader.getNumberOfPages();
            int maxPageSize = getMaxPageSize(config);
            if (pageCount > maxPageSize) {
                return State.pageSizeLimit();
            }
            makeTempDir(config);
            List<String> distFiles = new ArrayList<>();
            for (int i = 0; i < pageCount; i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, getDPIFromConfig(config));
                File distFile = new File(getTempDir(config) + System.currentTimeMillis() + ".png");
                ImageIO.write(image, "png", distFile);
                distFiles.add(save(distFile, i));
            }
            state.putInfo(RESULT_INFO_KEY_DIST, StringUtils.join(distFiles, ","));
        } catch (Exception e) {
            return new State(false, "转换失败");
        }
        return state;
    }

    protected float getDPIFromConfig(Map<String, Object> config) {
        Object dpi = config.get(CONF_KEY_DPI);
        if (dpi == null) {
            return 100F;
        }
        return (float) dpi;
    }


    @Override
    public boolean support(String resourceUri) {
        return StringUtils.endsWith(resourceUri, ".pdf");
    }

}

最后需要在Linux安装openoffice才能真正进行转换

官网下载安装包

tar -zxvf Apache_OpenOffice_4.x.x _Linux_x86-64_install-rpm_zh-CN.tar.gz

解压之后会在当前目录生成 zh-CN 文件夹,进入zh-CN里面的RPMS

cd ./zh-CN/RPMS

yum localinstall *.rpm

成功之后,会在当前目录生成desktop-integration文件夹,进入到此文件夹

cd ./desktop-integration

yum localinstall openoffice_4.x.x-redhat-menus-x.x.x.noarch.rpm

成功安装后会在 /opt目录下生成oppenoffice4文件夹

启动

nohup /opt/openoffice4/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard &

可能出现的问题

 error while loading shared libraries: libXext.so.6

 解决:
  yum install libXext.x86_64

  no suitable windowing system found, exiting

  解决:
  yum groupinstall "X Window System" 

查看服务是否成功启动

ps -ef | grep openoffice

netstat -lntp | grep 8100

环境准备完成后可能需要的问题就是中文乱码,这是因为Linux没有win上的字体文件,需要安装字体文件,安装完成后需要重启openoffice,openoffice提供了一个客户端jar,可以借用此jar包测试转换是否完好(需要将其所依赖的所有jar包放在同一目录下才能运行,官网下载后在lib目录下有所有jar包)

java -jar jodconverter-cli-2.2.2.jar source.doc tagert.pdf
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

文档在线预览解决方案——openoffice转换 的相关文章

  • CoAP数据包的大小是多少?

    我是这项技术的新手 有人可以帮助我了解一些疑问吗 Q 1 CoAP数据包的大小是多少 我知道有 4 字节固定标头 但是包括标头 选项和负载在内的最大大小限制是多少 Q 2 有像MQTT那样的Keep Alive的概念吗 它在UDP上工作 它
  • 内核模式下的线程(和进程)与用户模式下的线程(和进程)有什么区别?

    我的问题 1 书中现代操作系统 它说线程和进程可以处于内核模式或用户模式 但没有明确说明它们之间有什么区别 2 为什么内核态线程和进程的切换比用户态线程和进程的切换花费更多 3 现在 我正在学习Linux 我想知道如何在LINUX系统中分别
  • 我不明白 execlp() 在 Linux 中如何工作

    过去两天我一直在试图理解execlp 系统调用 但我还在这里 让我直奔主题 The man pageexeclp 将系统调用声明为int execlp const char file const char arg 与描述 execl exe
  • 确定我可以向文件句柄写入多少内容;将数据从一个 FH 复制到另一个 FH

    如何确定是否可以将给定数量的字节写入文件句柄 实际上是套接字 或者 如何 取消读取 我从其他文件句柄读取的数据 我想要类似的东西 n how much can I write w handle n read r handle buf n a
  • 快速像素绘图库

    我的应用程序以每像素的方式生成 动画 因此我需要有效地绘制它们 我尝试过不同的策略 库 但结果并不令人满意 尤其是在更高分辨率的情况下 这是我尝试过的 SDL 好的 但是慢 OpenGL 像素操作效率低下 xlib 更好 但仍然太慢 svg
  • NUMA 在虚拟内存中是如何表示的?

    有许多资源 https en wikipedia org wiki Non uniform memory access从硬件角度描述NUMA的架构性能影响 http practical tech com infrastructure num
  • 如何允许应用程序声明“https”方案 URI? (即如何从 https URL 打开桌面应用程序?)

    目前我正在尝试为 OAuth 2 0 授权流程创建一个客户端 实际上是一个本机应用程序 并且在规范中就在这儿 https www rfc editor org rfc rfc8252 section 7 2据说有 3 种方法来处理重定向 U
  • 在 unix 中编译 dhrystone 时出错

    我是使用基准测试和 makefile 的新手 我已经从下面的链接下载了 Dhrystone 基准测试 我正在尝试编译它 但我遇到了奇怪的错误 我尝试解决它 但没有成功 有人可以帮助我运行 dhrystone 基准测试吗 以下是我尝试编译的两
  • 从 systemd bash 内联脚本创建 filename_$(date %Y-%m-%d)

    我正在尝试执行systemd计时器并希望将执行脚本的输出保存在每个日期的文件中 这是我的ExecStart脚本中的 service file ExecStart bin bash c echo date Y m d gt gt home u
  • 如何从 PROC 获取有关子进程的信息

    我正在尝试编写一个以几个进程作为参数的程序 然后父进程执行每个子进程并打印出一些相关的统计信息 示例 generate ls l 将生成一个程序 打印出有关 ls l 的一些统计信息 特别是其系统时间 用户时间和上下文切换次数 我不想使用
  • vagrant ssh -c 并在连接关闭后保持后台进程运行

    我正在编写一个脚本来启动和后台流浪机器内的进程 似乎每次脚本结束和 ssh 会话结束时 后台进程也会结束 这是我正在运行的命令 vagrant ssh c cd vagrant src nohup python hello py gt he
  • Fedora dnf 更新不起作用?

    当我尝试使用 update 命令更新 Fedora 22 时 sudo dnf update 我收到以下错误 错误 无法同步存储库 更新 的缓存 无法准备内部镜像列表 Curl 错误 6 无法解析主机名 无法解析主机 mirrors fed
  • 在 Ubuntu 中找不到 X11/Xlib.h

    我试图在 Linux 上使用 open gl 编写一个相当简单的程序 但在编译时它说 编译拇指 egl 我对 GL 完全陌生 不知道出了什么问题 快速搜索使用 apt search Xlib h 打开 libx11 dev 包 但纯 Ope
  • Raspberry 交叉编译 - 执行程序以“分段错误”结束

    我有一个自己编写的程序 我想从我的 x86 机器上为 Raspberry Pi 构建它 我正在使用 eclipse 生成的 makefile 并且无法更改此内容 我已经阅读了 CC for raspi 的教程 Hackaday 链接 htt
  • X11 模式对话框

    如何使用 Xlib 在 X11 中创建模式对话框 模态对话框是一个位于应用程序其他窗口之上的窗口 就像瞬态窗口一样 并且拒绝将焦点给予应用程序的其他窗口 在 Windows 中 当试图从模态窗口夺取焦点时 模态也会通过闪 烁模态窗口的标题栏
  • PyPI 上的轮子平台约束有什么限制吗?

    是否有任何地方 PEP 或其他地方 声明关于 Linux 轮子上传范围的限制 PyPI http pypi io 应该有 具体来说 上传是否被认为是可接受的做法linux x86 64轮子到 PyPI 而不是manylinux1 x86 6
  • 使用 inotify 的正确方法是什么?

    我想使用inotifyLinux 上的机制 我希望我的应用程序知道文件何时aaa被改变了 您能给我提供一个如何做到这一点的示例吗 文档 来自监视文件系统活动 inotify https developer ibm com tutorials
  • 配置:错误:无法运行C编译的程序

    我正在尝试使用 Debian Wheezy 操作系统在我的 Raspberry Pi 上安装不同的软件 当我运行尝试配置软件时 我尝试安装我得到此输出 checking for C compiler default output file
  • 如何使用 echo 写入非 ASCII 字符?

    如何写非ASCII http en wikipedia org wiki ASCII使用 echo 的字符 是否有转义序列 例如 012或类似的东西 我想使用以下方法将 ASCII 字符附加到文件中 echo gt gt file 如果您关
  • dlopen 或 dlclose 未调用信号处理程序

    我在随机时间内收到分段错误 我注册了信号 但发生分段错误时未调用信号处理程序 include

随机推荐

  • Csharp:The .dat File using BinaryReader and BinaryWriter Convert to DataTable

  • Css3透明、background-size 属性

    background size length percentage cover contain 值 描述 测试 length 设置背景图像的高度和宽度 第一个值设置宽度 第二个值设置高度 如果只设置一个值 则第二个值会被设置为 auto 测
  • Python小知识点总结

    1 super 在类的继承中 如果重定义某个方法 该方法会覆盖父类的同名方法 但有时 我们希望能同时实现父类的功能 这时 我们就需要调用父类的方法了 可通过使用 super 来实现 class Animal object def init
  • ipv6 socket bind 失败 - accept_dad

    file proc sys net ipv6 conf interface accept dad variable net ipv6 conf interface accept dad Official reference Whether
  • 阿里一面:讲一讲 Spring、SpringMVC、SpringBoot、SpringCloud 之间的关系?

    大家好 我是Tom哥 搞后端开发的同学 对 Spring 家族一定不陌生 Spring 全家桶了为了解决不同场景的问题 逐渐演化出多套生态环框 如 Spring SpringMVC SpringBoot SpringCloud 它们之间的关
  • 区块链原理与应用(一)

    1 什么是区块链 mermaid svg z62l3eavYilXMK3m label font family trebuchet ms verdana arial font family var mermaid font family f
  • 【ML】2023 年面向初学者的十大机器学习项目

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • elementUI 的 table 表格改变数据不更新问题

    预期效果 点击输入框旁边的图标 输入框变为可输入状态 这里控制输入的 editable 字段不是 data 原有的属性 也不是 data 赋值时就存在的字段 问题原因 在 Vue 实例创建时 以及 data 赋值时 editable 并未声
  • 随机从数组或集合中抽取一个值或 从list集合中随机抽几个值 或算权重

    直接上代码 package cn itcast jk util import java util Arrays import java util HashSet import java util Map import java util S
  • Xilinx-FPGA关于BUFFER(时钟/普通IO信号)的使用总结

    目录 前言 一 时钟BUFFER使用总结 二 普通IO输出时钟信号时的推荐方法 使用ODDR 前言 Xilinx FPGA开发过程中 关于时钟信号和普通IO信号引入FPGA内部需要遵循一定的使用方法 现在自己一年多使用过的内容做一个总结 也
  • springmvc request与response获取

    request与response是Tomcat服务器在收到客户端请求后自己生成的 无需我们自己创建 但是在使用的时候可以有以下三种方式去获取 1 Controller直接使用 方法上直接使用 通过DispatcherServlet将参数传到
  • linux 查看当前系统时间,并实时刷新

    使用watch命令 周期性的执行一个命令 并全屏显示 watch n 1 date即可 每1秒刷新date命令 格式 watch option command watch n 1 date n interval 表示每n秒执行date n最
  • nodejs中使用数据库逆向映射字段

    安装依赖 在搭建项目时 一般都是先创建数据库 在使用服务去访问数据 但是由于数据库的映射非常麻烦 导致需要书写大量的映射文件 对于nodejs 这里提出一个逆向工程 本文使用的是mysql数据库 首先安装 npm init 先初始化项目 n
  • java实现具有修饰的完美圣诞树

    A 有咋样的实力可以写出这个代码 B 会for循环就好 A 只要会for就好 B 还有一点点逻辑能力和算法 package 海绵hong import java util Scanner public class text9 public
  • Linux下errno所代表的含义

    errno记录系统的最后一次错误代码 是一个int型 在errno h中定义 以下程序用于输出errno所代表的含义 0 133有意义 其余的属于未定义 include
  • 向ftp上传文件失败的可能原因

    1 文件编码问题引起 现象 上传含有中文符号的文件会上传失败 解决方法 将文件名中的中文符号修改为英文符号即可上传成功 如果上传的文件名中没有中文符号也失败 可以试试将文件名修改为短一点的 如 11 待上传成功后再修改文件名称 2 ftp服
  • 油猴脚本编写教程

    油猴脚本 Tampermonkey 是一个非常流行的浏览器扩展 它可以运行由广大社区编写的扩展脚本 来实现各式各样的功能 常见的去广告 修改样式文件 甚至是下载视频 今天我们就来看看如何编写自己的油猴脚本 当然为了运行油猴脚本 你应该在浏览
  • 在web项目启动时,执行某个方法

    在web项目启动时 执行某个方法 在web项目中有很多时候需要在项目启动时就执行一些方法 而且只需要执行一次 比如 加载解析自定义的配置文件 初始化数据库信息等等 在项目启动时就直接执行一些方法 可以减少很多繁琐的操作 在工作中遇到了项目初
  • 【计算机网络】物理层:数据通信系统模型

    一个数据通信系统可划分三大部分 发送方 源系统 发送端 传输系统 传输网络 接受方 接受端 目的系统 发送方包括 源点 源点设备产生要传输的数据 发送器 源点生成的数字比特流要通过发送器编码后才能够在传输系统中传输 典型的发送器是调制器 接
  • 文档在线预览解决方案——openoffice转换

    文档在线预览是一个复杂功能 文档格式的繁复更加增加了难度 虽然office给出了在线预览功能 https products office com en us office online view office documents onlin