本文使用OpenOffice+pdf.js实现

最近时间太少 格式没有排好 这种解决方案也实在没有办法 个人非常不满意 有好的解决方案必定更新

花了一周的时间实现此功能 感觉是网络太少实用的教程 或是自己能力不行吧 话不多说 内容如下

网络上现在(2019年5月)实现文件预览的几种方式

  • poi转换Office文件为pdf 据说效果不好
  • OpenOffice转换Office文件为pdf 大部分教程都在使用
  • pdf.js对pdf进行显示
  • swfTools将pdf文件转换成swf 通过FlexPaper显示 依赖Flash

被github迷惑 https://github.com/flexpaper/flexpaper 不知为什么是as 不是js

被官网迷惑 网络教程大部分都是官网改变前 直接访问http://flexpaper.devaldi.com/download/就可下载

现在访问 http://flexpaper.devaldi.com/download/ 直接跳转到 https://flowpaper.com/download/ 此页面 而且主页是exe执行文件下载 但是翻译+Google还是在现在的官网找到下载路径 下载页面如下

java swing 展示pdf文件 java预览pdf文件_操作系统


但是为时已晚发现下载地址时 我的pdf.js已经做好 有机会在用FlexPaper实现一次 FlesPaper实现请参考其它文章比如

swftools下载 http://www.swftools.org/download.html

OpenOffice使用分为Windows和Linux 通过此链接下载 http://www.openoffice.org/zh-cn/download/

Windows

使用cmd进入 C:\Program Files\OpenOffice4\program 执行如下命令启动 任务管理器会有soffice的进程

soffice -headless -accept=“socket,host=127.0.0.1,port=8100;urp;” -nofirststartwizard

可使用telnet localhost 8100 进行测试

Linux

# 下载npm版本 如Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz
tar -zxvf Apache_OpenOffice_4.1.5_Linux_x86-64_install-rpm_zh-CN.tar.gz
# 解压后进入zh-CN/RPMS 安装所有rpm
cd zh-CN/RPMS
rpm -ivh *.rpm
# 进入desktop-integration目录 安装openoffice4.1.5-redhat-menus-4.1.5-9789.noarch.rpm
cd desktop-integration
rpm -ivh openoffice4.1.5-redhat-menus-4.1.5-9789.noarch.rpm



# 启动openOffice 后面加'&'为永久启动
/opt/openoffice4/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard&
# 关闭就使用kill命令 如下
ps -ef|grep soffice 或 ps -ef|grep openoffice
kill - 9  进程编号

# 卸载openoffice
rpm -e `rpm -qa |grep openoffice` `rpm -qa |grep ooobasis`

还需要依赖jar包 maven如下 中央仓库现在(2019年5月)只有有2.21版本 不支持docx pptx xlsx的格式 但是官网可以下载 2.2.2的jar包 使用maven命令手动引入即可 2.2.2下载地址
https://sourceforge.net/projects/jodconverter/files/

<!-- jodconverter文档转换类 maven仓库只有2.2.1 2.2.2为手动安装 -->
<dependency>
    <groupId>com.artofsolving</groupId>
    <artifactId>jodconverter</artifactId>
    <version>2.2.2</version>
</dependency>

java代码如下 使用file类的方式 暂时不会使用流的方式转换 造成现在程序只能和预览文件放在一起
如下代码为上传文件的同时判断是否为office文件和pdf文件 如为office文件则生成预览pdf文件 为pdf文件则把此pdf文件复制到预览文件夹

/** 上传文件 */
    @PostMapping("/upload")
    @ResponseBody
    public Map<String, Object> uploadFile(MultipartFile file) {

        Map<String, Object> res = new HashMap<>();

        Map<String, Object> resUrl = new HashMap<>();

        boolean flag = false; // 是否生成文件预览文件

            // 取得当前上传文件的文件名称
            String fileName = file.getOriginalFilename();

            // 如果名称不为"" 说明该文件存在 否则说明该文件不存在
            if ("" != fileName.trim()) {
                String fileExtName = fileName.substring(fileName.lastIndexOf("."));
                String serverFileName = null;
                String newFileName = null;
                ChannelSftp sftp = null;

                String[] types = {".doc",".docx",".xls",".xlsx",".ppt",".pptx",".pdf"};

                for (String type:types) {

                if(type.equals(fileExtName)) {

                try {

                    // 打印文件名称
                    LOGGER.info(">>> fileName " + fileName +" <<<");

                    // 获取扩展名

                    // 没有'-'的uuid
                    serverFileName = UUID.randomUUID().toString().replace("-", "");

                    // 文件新名称
                    newFileName = serverFileName + fileExtName;

                    InputStream is = file.getInputStream();

                    // linux服务器地址 账号 密码
                    sftp = getChannel("192.168.110.110", 22, "root", "root", 60000);

                    final String folderName = "/usr/local/upload-files";

                    // 进入linux服务器文件目录
                    sftp.cd(folderName);

                    // 判断子目录文件夹是否存在 不存在即创建
                    SftpATTRS attrs = sftp.stat(folderName);

                    if (attrs == null) {
                        sftp.mkdir(folderName);
                        LOGGER.info("创建目录 : " + folderName);
                    }

                    // 把文件流命名成文件名称推送到linux
                    sftp.put(is, newFileName);
                    closeChannel(); // 关闭连接
                } catch (IOException e) {
                    e.printStackTrace();
                    LOGGER.error("上传文件失败 请检查连接参数", e);
                } catch (JSchException e) {
                    e.printStackTrace();
                    LOGGER.error("上传文件失败 请检查连接参数", e);
                } catch (SftpException e) {
                    e.printStackTrace();
                    LOGGER.error("上传文件失败 请检查连接参数", e);
                }

                // -----生成文件预览开始
                String[] PictureTypes = {".pdf"};
                for (String PictureType:PictureTypes) {
                    if (fileExtName.equals(PictureType)) {
                        flag = false; // 禁止生成文件预览文件
                        try {
                            int bytesum = 0;
                            int byteread = 0;
                            InputStream inStream = new FileInputStream("/usr/local/upload-files/" + newFileName); // 读入原文件
                            FileOutputStream fs = new FileOutputStream("/usr/local/upload-files/preview_pdf/" + newFileName);
                            byte[] buffer = new byte[1444];
                            while ( (byteread = inStream.read(buffer)) != -1) {
                                bytesum += byteread; //字节数 文件大小
                                System.out.println(bytesum);
                                fs.write(buffer, 0, byteread);
                            }
                            inStream.close();
                            fs.close();
                        }
                        catch (Exception e) {
                            System.out.println("复制单个文件操作出错");
                            e.printStackTrace();
                        }
                        resUrl.put("filePreviewAddress", newFileName);
                    }
                }

                // 来自微软官网 https://docs.microsoft.com/zh-cn/deployoffice/compat/office-file-format-reference
                String[] officeTypes = {".doc",".docx",".xls",".xlsx",".ppt",".pptx"};

                for (String officeType:officeTypes) {
                    if(fileExtName.equals(officeType)) flag = true;
                }

                // 判断文件扩展名是否在office的文件类型中
                if(flag) {
                    try {
                        // windows中启动OpenOffice的服务
                        // String command = "D:\\OpenOffice\\program" + "soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\"-nofirststartwizard";
                        // Process pro = Runtime.getRuntime().exec(command); // 执行exe文件

                        // 通过ip地址和端口号连接OpenOffice
                        OpenOfficeConnection connection = new SocketOpenOfficeConnection("127.0.0.1", 8100);
                        connection.connect();

                        // 需要转换的文件路径
                        File inFile = new File("/usr/local/upload-files/" + newFileName);

                        // 目标文件 准备存进哪里 转换后的文件路径
                        File outFile = new File("/usr/local/upload-files/preview_pdf/" + serverFileName + ".pdf");

                        if (!inFile.exists()) {
                            LOGGER.info(">>> 找不到源文件 <<<");
                        } else {
                            LOGGER.info(">>> 预览源文件名称 " + inFile + " <<<");
                        }

                        // 转换
                        DocumentConverter converter = new OpenOfficeDocumentConverter(connection);

                        converter.convert(inFile, outFile);

                        // 关闭连接资源
                        connection.disconnect();
                        // 关闭OpenOffice服务的进程
                        //pro.destroy();
                        LOGGER.info("转换为pdf成功" + outFile);

                        resUrl.put("filePreviewAddress", serverFileName + ".pdf");

                        // -----生成文件预览开始结束
                    } catch (ConnectException e) {
                    e.printStackTrace();
                    LOGGER.error("生成预览文件失败 请检查连接参数", e);
                    }
                }
                resUrl.put("fileRecordAddress", newFileName);
                res.put("data", resUrl);
                return res;
            }

            }
        }

        res.put("code", 1);
        res.put("msg", "");
        LOGGER.info(res.toString());
        return res;
    }

前端页面

pdf.js下载 https://mozilla.github.io/pdf.js/ 点击下载 选择稳定版Stable(现在为v2.0.943)下载

把官网的web 和 build文件导入到项目

  1. pdf.js 需要使用 localhost:8080/viewer.html?file=localhost:8080/test.pdf方式进行指定fdf位置
  2. 当然可以简写为localhost:8080/viewer.html?file=/test.pdf pdf文件是不能跨越的 且文件暴露在地址栏 可通过Nginx解决
  3. 网络上的解决方案是在外面套一个iframe 可以看看我的解决方案
  4. 默认的pdf路径viewer.js的4347行 但是此路径并没有相应的方法动态修改 没有办法 想要隐藏地址栏pdf路径并不使用iframe 只能在此变量的最近的方法中进行Ajax了
var defaultOptions = {
  cursorToolOnLoad: {
    value: 0,
    kind: OptionKind.VIEWER
  },
  defaultUrl: {
    value: '/web/test.pdf', // 默认路径
    kind: OptionKind.VIEWER
  },

Ajax如下 但是此Ajax会请求10次 定义boolean变量在Ajax执行后赋值为false也无法阻止 每次都相当于从新执行此方法 可在后台使用缓存解决此问题 大概在viewer.js的4500多行加入Ajax

_createClass(AppOptions, null, [{
    key: 'get',
    value: function get(name) {
      var defaultOption = defaultOptions[name],
          userOption = userOptions[name];
      if (userOption !== undefined) {
        return userOption;
      }
        // -----Ajax开始
        // 构造一个含有目标参数的正则表达式对象
        var reg = new RegExp("(^|&)" + "file_id" + "=([^&]*)(&|$)");
        // 匹配目标参数
        var r = window.location.search.substr(1).match(reg);
        // 返回参数值
        if(r != null) {
            var file_id = decodeURI(r[2]);
        }

        if (window.XMLHttpRequest) {
            var xhr = new XMLHttpRequest();
        } else {
            //IE5 & IE6 浏览器专属对象
            var xhr = new ActivexObject("Microsoft.XMLHTTP");
        }

        xhr.open("GET", "/file/preview_url?file_id=" + file_id, true);
        xhr.send();
        xhr.onreadystatechange = function() {
            //readyState = 4 读取请求完成响应就绪
            if (xhr.readyState == 4) {
                console.log("读取请求完成响应就绪");
                //status = 200 正常响应 页面可获得数据
                if (xhr.status == 200) {
                    defaultOptions.defaultUrl.value = xhr.responseText;
                    console.log(">>> 响应成功 preview pdf <<<" + defaultOptions.defaultUrl.value);
                } else {
                    console.log(xhr.status); // 打印状态码
                }
            }
        }
      // -----Ajax结束
      return defaultOption !== undefined ? defaultOption.value : undefined;
    }
  },

此时浏览器请求pdf为 localhost:8080/viewer.html?file_id=1
对应后端为

/** 文件预览 查看pdf */
    @GetMapping("preview_url")
    @ResponseBody
    public String previewUrl(Integer file_id) {
        // 获取redis中的数据
        String filePreviewUrl = (String) redisTemplate.boundHashOps("filePreviewUrl").get(file_id);

        if (null == filePreviewUrl || "".equals(filePreviewUrl.trim())) {

            String preFile = fileService.selectPreFile(file_id);

            if (null != preFile && !"".equals(preFile.trim())) {
                filePreviewUrl = "/usr/local/upload-files/preview_pdf/" + preFile;
                redisTemplate.boundHashOps("filePreviewUrl").put(file_id, filePreviewUrl); // 放入缓存
                LOGGER.info("数据已放到redis中 --- " + filePreviewUrl);
            }
        }
        return filePreviewUrl;
    }

pdf.js禁止打印和下载如下 但是Ctrl+S也可保存此页面但是保存的只是空html 大概在viewer.js的11400多行 注释的为原方法

items.print.addEventListener('click', function () {
  // eventBus.dispatch('print', { source: self });
    alert("禁止打印")
});
items.download.addEventListener('click', function () {
  // eventBus.dispatch('download', { source: self });
    alert("禁止下载")
});

pdf.js水印功能如下 大概在viewer.js中10000多行

var textLayer = null;
      if (this.textLayerMode !== _ui_utils.TextLayerMode.DISABLE && this.textLayerFactory) {
        var textLayerDiv = document.createElement('div');
        textLayerDiv.className = 'textLayer';
        textLayerDiv.style.width = canvasWrapper.style.width;
        textLayerDiv.style.height = canvasWrapper.style.height;
          // -----开始
          var cover = document.createElement('div');
          cover.className = "cover";

          var defaultSettings = {
              watermark_txt: "金荣翔",
              watermark_x: 0,//水印起始位置x轴坐标
              watermark_y: 0,//水印起始位置Y轴坐标
              watermark_rows: 30,//水印行数
              watermark_cols: 100,//水印列数
              watermark_x_space: 10,//水印x轴间隔
              watermark_y_space: 10,//水印y轴间隔
              watermark_color: 'red',//水印字体颜色
              watermark_alpha: 0.3,//水印透明度
              watermark_fontsize: '20px',//水印字体大小
              watermark_font: '微软雅黑',//水印字体
              watermark_width: 120,//水印宽度
              watermark_height: 80,//水印长度
              watermark_angle: 45//水印倾斜度数
          };
          var oTemp = document.createDocumentFragment();

          //获取页面最大宽度
          var page_width = parseInt(canvasWrapper.style.width);
          //获取页面最大长度
          var page_height = parseInt(canvasWrapper.style.height);
          // 如果将水印列数设置为0,或水印列数设置过大,超过页面最大宽度,则重新计算水印列数和水印x轴间隔
          if (defaultSettings.watermark_cols == 0 || (parseInt(defaultSettings.watermark_x + defaultSettings.watermark_width * defaultSettings.watermark_cols + defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1)) > page_width)) {
              defaultSettings.watermark_cols = parseInt((page_width - defaultSettings.watermark_x + defaultSettings.watermark_x_space) / (defaultSettings.watermark_width + defaultSettings.watermark_x_space));
              defaultSettings.watermark_x_space = parseInt((page_width - defaultSettings.watermark_x - defaultSettings.watermark_width * defaultSettings.watermark_cols) / (defaultSettings.watermark_cols - 1));
          }
          // 如果将水印行数设置为0,或水印行数设置过大,超过页面最大长度,则重新计算水印行数和水印y轴间隔
          if (defaultSettings.watermark_rows == 0 || (parseInt(defaultSettings.watermark_y + defaultSettings.watermark_height * defaultSettings.watermark_rows + defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1)) > page_height)) {
              defaultSettings.watermark_rows = parseInt((defaultSettings.watermark_y_space + page_height - defaultSettings.watermark_y) / (defaultSettings.watermark_height + defaultSettings.watermark_y_space));
              defaultSettings.watermark_y_space = parseInt(((page_height - defaultSettings.watermark_y) - defaultSettings.watermark_height * defaultSettings.watermark_rows) / (defaultSettings.watermark_rows - 1));
          }
          var x;
          var y;
          for (var i = 0; i < defaultSettings.watermark_rows; i++) {
              y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
              for (var j = 0; j < defaultSettings.watermark_cols; j++) {
                  x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;

                  var mask_div = document.createElement('div');
                  mask_div.id = 'mask_div' + i + j;
                  mask_div.className = 'mask_div';
                  mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
                  // 设置水印div倾斜显示
                  mask_div.style.webkitTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                  mask_div.style.MozTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                  mask_div.style.msTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                  mask_div.style.OTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                  mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)";
                  mask_div.style.visibility = "";
                  mask_div.style.position = "absolute";
                  // 奇偶行错开,这样水印就不对齐,显的不呆板
                  if (i % 2 != 0) {
                      mask_div.style.left = x + 100 + 'px';
                  } else {
                      mask_div.style.left = x + 'px';
                  }
                  mask_div.style.top = y + 'px';
                  mask_div.style.overflow = "hidden";
                  mask_div.style.opacity = defaultSettings.watermark_alpha;
                  mask_div.style.fontSize = defaultSettings.watermark_fontsize;
                  mask_div.style.fontFamily = defaultSettings.watermark_font;
                  mask_div.style.color = defaultSettings.watermark_color;
                  mask_div.style.textAlign = "center";
                  mask_div.style.width = canvasWrapper.style.width + 'px';
                  mask_div.style.height = canvasWrapper.style.height + 'px';
                  mask_div.style.display = "block";
                  oTemp.appendChild(mask_div);
              }
          }
          cover.appendChild(oTemp);
          // -----结束
        if (this.annotationLayer && this.annotationLayer.div) {
          div.insertBefore(textLayerDiv, this.annotationLayer.div);
          // -----开始
          div.appendChild(cover); // 追加
          // -----结束
        } else {
          div.appendChild(textLayerDiv);
            // -----开始
            div.appendChild(cover); // 追加
            // -----结束
           }
        textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, this.id - 1, this.viewport, this.textLayerMode === _ui_utils.TextLayerMode.ENABLE_ENHANCE);
        // -----开始
          var cover = document.getElementsByClassName('cover');
          for (var i = 0, len = cover.length; i < len; i++) {
              cover[i].style.width = canvasWrapper.style.width;
              cover[i].style.height = canvasWrapper.style.height;
          }
        // -----结束
      }

openoffice安装参考 https://fireinjava.iteye.com/blog/1712283 pdf.js添加水印参考 https://www.jianshu.com/p/9f349e41a7b2