Răsfoiți Sursa

!51 实现预览加密的(受密码保护)office文件
* 1. 修复getCorsFile接口高危安全漏洞
* 1. 优化密码错误提示(“密码错误,请重新输入密码。”)
* 1. 修复PPT重复预览bug,此bug导致ppt每次预览会执行两次转换(请求两次onlinePreview接口),在大文件尤其耗时(双倍时…
* 1. 【加密office预览】优化受密码保护的office文件检查逻辑,提升旧文件格式的兼容性
* 1. 【加密office预览】优化office文件是否受密码保护判断逻辑,避免兼容性误判
* 1. 【加密office预览】优化重新输入密码提示。
* 1. 【加密office预览】优化当密码输入错误后,不是抛出异常,而是提示用户重新输入
* 1. 优化prompt提示框的输入密码提示样式
* 1. 实现基于userToken缓存加密文件,没有userToken的加密文件不缓存
* 1. 优化docker构建方案,使用分层构建方式,采用层级缓存解决构建慢发布慢等问题。从原本5分钟左右缩短至几秒
* 1. 加密文件暂时不缓存(后续基于用户token实现,基于用户缓存)
* 1. 优化office文件下载逻辑,跳过重复下载(大量节约带宽与磁盘空间)。
* 1. 修复预览不同类型的加密office文件bug
* 实现预览加密的(受密码保护)office文件

yl-yue 3 ani în urmă
părinte
comite
acffcbfe98
26 a modificat fișierele cu 761 adăugiri și 508 ștergeri
  1. 1 38
      Dockerfile
  2. 38 0
      docker/kkfileview-jdk/Dockerfile
  3. 2 0
      docker/kkfileview-jdk/docker build.txt
  4. 0 0
      docker/kkfileview-jdk/fonts/.gitkeep
  5. 3 14
      office-plugin/pom.xml
  6. 10 12
      office-plugin/src/main/java/org/artofsolving/jodconverter/AbstractConversionTask.java
  7. 16 8
      office-plugin/src/main/java/org/artofsolving/jodconverter/OfficeDocumentConverter.java
  8. 34 0
      office-plugin/src/main/java/org/artofsolving/jodconverter/model/FileProperties.java
  9. 60 55
      server/pom.xml
  10. 2 0
      server/src/main/config/freemarker_implicit.ftl
  11. 1 1
      server/src/main/java/cn/keking/config/ConfigConstants.java
  12. 28 0
      server/src/main/java/cn/keking/model/FileAttribute.java
  13. 11 0
      server/src/main/java/cn/keking/service/FileHandlerService.java
  14. 9 7
      server/src/main/java/cn/keking/service/OfficeToPdfService.java
  15. 73 20
      server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java
  16. 7 0
      server/src/main/java/cn/keking/utils/DownloadUtils.java
  17. 62 0
      server/src/main/java/cn/keking/utils/OfficeUtils.java
  18. 9 2
      server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java
  19. 5 0
      server/src/main/resources/static/js/bootbox.min.js
  20. 199 199
      server/src/main/resources/static/pptx/ppt.js
  21. 47 4
      server/src/main/resources/web/commonHeader.ftl
  22. 21 17
      server/src/main/resources/web/fileNotSupported.ftl
  23. 14 8
      server/src/main/resources/web/html.ftl
  24. 3 2
      server/src/main/resources/web/index.ftl
  25. 2 1
      server/src/main/resources/web/pdf.ftl
  26. 104 120
      server/src/main/resources/web/ppt.ftl

+ 1 - 38
Dockerfile

@@ -1,42 +1,5 @@
-FROM ubuntu:20.04
+FROM keking/kkfileview-jdk:4.1.1
 MAINTAINER chenjh "842761733@qq.com"
 ADD server/target/kkFileView-*.tar.gz /opt/
-COPY fonts/* /usr/share/fonts/chinese/
-RUN echo "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse" > /etc/apt/sources.list &&\
-	apt-get clean && apt-get update &&\
-	apt-get install -y locales && apt-get install -y language-pack-zh-hans &&\
-	localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 && locale-gen zh_CN.UTF-8 &&\
-    export DEBIAN_FRONTEND=noninteractive &&\
-	apt-get install -y tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
-	apt-get install -y libxrender1 && apt-get install -y libxt6 && apt-get install -y libxext-dev && apt-get install -y libfreetype6-dev &&\
-	apt-get install -y wget && apt-get install -y ttf-mscorefonts-installer && apt-get install -y fontconfig &&\
-	apt-get install ttf-wqy-microhei &&\
-	apt-get install ttf-wqy-zenhei &&\
-	apt-get install xfonts-wqy &&\
-    cd /tmp &&\
-	wget https://kkfileview.keking.cn/server-jre-8u251-linux-x64.tar.gz &&\
-	tar -zxf /tmp/server-jre-8u251-linux-x64.tar.gz && mv /tmp/jdk1.8.0_251 /usr/local/ &&\
-
-#	安装 OpenOffice
-#	wget https://kkfileview.keking.cn/Apache_OpenOffice_4.1.6_Linux_x86-64_install-deb_zh-CN.tar.gz -cO openoffice_deb.tar.gz &&\
-#	tar -zxf /tmp/openoffice_deb.tar.gz && cd /tmp/zh-CN/DEBS &&\
-#	dpkg -i *.deb && dpkg -i desktop-integration/openoffice4.1-debian-menus_4.1.6-9790_all.deb &&\
-
-#	安装 libreoffice
-    apt-get install -y libxinerama1 libcairo2 libcups2 libx11-xcb1 &&\
-    wget https://kkfileview.keking.cn/LibreOffice_7.1.4_Linux_x86-64_deb.tar.gz -cO libreoffice_deb.tar.gz &&\
-    tar -zxf /tmp/libreoffice_deb.tar.gz && cd /tmp/LibreOffice_7.1.4.2_Linux_x86-64_deb/DEBS &&\
-    dpkg -i *.deb &&\
-
-	rm -rf /tmp/* && rm -rf /var/lib/apt/lists/* &&\
-    cd /usr/share/fonts/chinese &&\
-    mkfontscale &&\
-    mkfontdir &&\
-    fc-cache -fv
-ENV JAVA_HOME /usr/local/jdk1.8.0_251
-ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
-ENV PATH $PATH:$JAVA_HOME/bin
-ENV LANG zh_CN.UTF-8
-ENV LC_ALL zh_CN.UTF-8
 ENV KKFILEVIEW_BIN_FOLDER /opt/kkFileView-4.1.0-SNAPSHOT/bin
 ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=/opt/kkFileView-4.1.0-SNAPSHOT/config/application.properties","-jar","/opt/kkFileView-4.1.0-SNAPSHOT/bin/kkFileView-4.1.0-SNAPSHOT.jar"]

+ 38 - 0
docker/kkfileview-jdk/Dockerfile

@@ -0,0 +1,38 @@
+FROM ubuntu:20.04
+MAINTAINER chenjh "842761733@qq.com"
+# 内置一些常用的中文字体,避免普遍性乱码
+COPY fonts/* /usr/share/fonts/chinese/
+RUN echo "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse" > /etc/apt/sources.list &&\
+	apt-get clean && apt-get update &&\
+	apt-get install -y locales && apt-get install -y language-pack-zh-hans &&\
+	localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 && locale-gen zh_CN.UTF-8 &&\
+    export DEBIAN_FRONTEND=noninteractive &&\
+	apt-get install -y tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
+	apt-get install -y libxrender1 && apt-get install -y libxt6 && apt-get install -y libxext-dev && apt-get install -y libfreetype6-dev &&\
+	apt-get install -y wget && apt-get install -y ttf-mscorefonts-installer && apt-get install -y fontconfig &&\
+	apt-get install ttf-wqy-microhei &&\
+	apt-get install ttf-wqy-zenhei &&\
+	apt-get install xfonts-wqy &&\
+    cd /tmp &&\
+	wget https://kkfileview.keking.cn/server-jre-8u251-linux-x64.tar.gz &&\
+	tar -zxf /tmp/server-jre-8u251-linux-x64.tar.gz && mv /tmp/jdk1.8.0_251 /usr/local/ &&\
+
+#	安装 libreoffice
+    apt-get install -y libxinerama1 libcairo2 libcups2 libx11-xcb1 &&\
+    wget https://kkfileview.keking.cn/LibreOffice_7.1.4_Linux_x86-64_deb.tar.gz -cO libreoffice_deb.tar.gz &&\
+    tar -zxf /tmp/libreoffice_deb.tar.gz && cd /tmp/LibreOffice_7.1.4.2_Linux_x86-64_deb/DEBS &&\
+    dpkg -i *.deb &&\
+
+#   清理临时文件
+	rm -rf /tmp/* && rm -rf /var/lib/apt/lists/* &&\
+    cd /usr/share/fonts/chinese &&\
+    mkfontscale &&\
+    mkfontdir &&\
+    fc-cache -fv
+
+ENV JAVA_HOME /usr/local/jdk1.8.0_251
+ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
+ENV PATH $PATH:$JAVA_HOME/bin
+ENV LANG zh_CN.UTF-8
+ENV LC_ALL zh_CN.UTF-8
+ENTRYPOINT ["java","-version"]

+ 2 - 0
docker/kkfileview-jdk/docker build.txt

@@ -0,0 +1,2 @@
+# 执行如下命令构建基础镜像,加快kkfileview docker镜像构建与发布
+docker build --tag keking/kkfileview-jdk:4.1.1 .

+ 0 - 0
fonts/.gitkeep → docker/kkfileview-jdk/fonts/.gitkeep


+ 3 - 14
office-plugin/pom.xml

@@ -3,7 +3,6 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
-
     <parent>
         <artifactId>kkFileView-parent</artifactId>
         <groupId>cn.keking</groupId>
@@ -27,19 +26,9 @@
             <version>2.7</version>
         </dependency>
         <dependency>
-            <groupId>org.openoffice</groupId>
-            <artifactId>juh</artifactId>
-            <version>3.2.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.openoffice</groupId>
-            <artifactId>ridl</artifactId>
-            <version>3.2.1</version>
-        </dependency>
-        <dependency>
-            <groupId>org.openoffice</groupId>
-            <artifactId>unoil</artifactId>
-            <version>3.2.1</version>
+            <groupId>org.libreoffice</groupId>
+            <artifactId>libreoffice</artifactId>
+            <version>7.1.4</version>
         </dependency>
         <dependency>
             <!-- for the command line tool -->

+ 10 - 12
office-plugin/src/main/java/org/artofsolving/jodconverter/AbstractConversionTask.java

@@ -12,18 +12,6 @@
 //
 package org.artofsolving.jodconverter;
 
-import static org.artofsolving.jodconverter.office.OfficeUtils.SERVICE_DESKTOP;
-import static org.artofsolving.jodconverter.office.OfficeUtils.cast;
-import static org.artofsolving.jodconverter.office.OfficeUtils.toUnoProperties;
-import static org.artofsolving.jodconverter.office.OfficeUtils.toUrl;
-
-import java.io.File;
-import java.util.Map;
-
-import org.artofsolving.jodconverter.office.OfficeContext;
-import org.artofsolving.jodconverter.office.OfficeException;
-import org.artofsolving.jodconverter.office.OfficeTask;
-
 import com.sun.star.frame.XComponentLoader;
 import com.sun.star.frame.XStorable;
 import com.sun.star.io.IOException;
@@ -32,6 +20,14 @@ import com.sun.star.lang.XComponent;
 import com.sun.star.task.ErrorCodeIOException;
 import com.sun.star.util.CloseVetoException;
 import com.sun.star.util.XCloseable;
+import org.artofsolving.jodconverter.office.OfficeContext;
+import org.artofsolving.jodconverter.office.OfficeException;
+import org.artofsolving.jodconverter.office.OfficeTask;
+
+import java.io.File;
+import java.util.Map;
+
+import static org.artofsolving.jodconverter.office.OfficeUtils.*;
 
 public abstract class AbstractConversionTask implements OfficeTask {
 
@@ -47,6 +43,7 @@ public abstract class AbstractConversionTask implements OfficeTask {
 
     protected abstract Map<String,?> getStoreProperties(File outputFile, XComponent document);
 
+    @Override
     public void execute(OfficeContext context) throws OfficeException {
         XComponent document = null;
         try {
@@ -79,6 +76,7 @@ public abstract class AbstractConversionTask implements OfficeTask {
         }
         XComponentLoader loader = cast(XComponentLoader.class, context.getService(SERVICE_DESKTOP));
         Map<String,?> loadProperties = getLoadProperties(inputFile);
+
         XComponent document = null;
         try {
             document = loader.loadComponentFromURL(toUrl(inputFile), "_blank", 0, toUnoProperties(loadProperties));

+ 16 - 8
office-plugin/src/main/java/org/artofsolving/jodconverter/OfficeDocumentConverter.java

@@ -12,18 +12,18 @@
 //
 package org.artofsolving.jodconverter;
 
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
+import com.sun.star.document.UpdateDocMode;
 import org.apache.commons.io.FilenameUtils;
 import org.artofsolving.jodconverter.document.DefaultDocumentFormatRegistry;
 import org.artofsolving.jodconverter.document.DocumentFormat;
 import org.artofsolving.jodconverter.document.DocumentFormatRegistry;
+import org.artofsolving.jodconverter.model.FileProperties;
 import org.artofsolving.jodconverter.office.OfficeException;
 import org.artofsolving.jodconverter.office.OfficeManager;
 
-import com.sun.star.document.UpdateDocMode;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
 
 public class OfficeDocumentConverter {
 
@@ -60,14 +60,22 @@ public class OfficeDocumentConverter {
     public void convert(File inputFile, File outputFile) throws OfficeException {
         String outputExtension = FilenameUtils.getExtension(outputFile.getName());
         DocumentFormat outputFormat = formatRegistry.getFormatByExtension(outputExtension);
-        convert(inputFile, outputFile, outputFormat);
+        convert(inputFile, outputFile, outputFormat, null);
+    }
+
+    public void convert(File inputFile, File outputFile, FileProperties fileProperties) throws OfficeException {
+        String outputExtension = FilenameUtils.getExtension(outputFile.getName());
+        DocumentFormat outputFormat = formatRegistry.getFormatByExtension(outputExtension);
+        convert(inputFile, outputFile, outputFormat, fileProperties);
     }
 
-    public void convert(File inputFile, File outputFile, DocumentFormat outputFormat) throws OfficeException {
+    public void convert(File inputFile, File outputFile, DocumentFormat outputFormat, FileProperties fileProperties) throws OfficeException {
         String inputExtension = FilenameUtils.getExtension(inputFile.getName());
         DocumentFormat inputFormat = formatRegistry.getFormatByExtension(inputExtension);
+        Map<String, Object> properties = fileProperties.toMap();
+        properties.putAll(defaultLoadProperties);
         StandardConversionTask conversionTask = new StandardConversionTask(inputFile, outputFile, outputFormat);
-        conversionTask.setDefaultLoadProperties(defaultLoadProperties);
+        conversionTask.setDefaultLoadProperties(properties);
         conversionTask.setInputFormat(inputFormat);
         officeManager.execute(conversionTask);
     }

+ 34 - 0
office-plugin/src/main/java/org/artofsolving/jodconverter/model/FileProperties.java

@@ -0,0 +1,34 @@
+package org.artofsolving.jodconverter.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by kl on 2018/1/17.
+ * Content :
+ */
+public class FileProperties {
+
+    private String filePassword;
+
+    public FileProperties() {
+    }
+
+    public Map<String, Object> toMap() {
+        Map<String, Object> map = new HashMap();
+        if (filePassword != null) {
+            map.put("Password", filePassword);
+        }
+
+        return map;
+    }
+
+    public String getFilePassword() {
+        return filePassword;
+    }
+
+    public void setFilePassword(String filePassword) {
+        this.filePassword = filePassword;
+    }
+
+}

+ 60 - 55
server/pom.xml

@@ -25,20 +25,12 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <!-- 对 rar5 的支持 和其他众多压缩支持 可参考 package net.sf.sevenzipjbinding.ArchiveFormat; -->
-        <dependency>
-            <groupId>net.sf.sevenzipjbinding</groupId>
-            <artifactId>sevenzipjbinding</artifactId>
-            <version>16.02-2.01</version>
-        </dependency>
-        <dependency>
-            <groupId>net.sf.sevenzipjbinding</groupId>
-            <artifactId>sevenzipjbinding-all-platforms</artifactId>
-            <version>16.02-2.01</version>
+            <groupId>cn.keking</groupId>
+            <artifactId>office-plugin</artifactId>
+            <version>${project.version}</version>
         </dependency>
+
+        <!-- web start -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
@@ -53,28 +45,13 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jetty</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>cn.keking</groupId>
-            <artifactId>office-plugin</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-lang3</artifactId>
-            <version>3.7</version>
-        </dependency>
-        <!-- REDISSON -->
-        <dependency>
-            <groupId>org.redisson</groupId>
-            <artifactId>redisson</artifactId>
-            <version>3.2.0</version>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
         </dependency>
+        <!-- web end -->
+
+        <!-- poi start -->
         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi</artifactId>
@@ -83,7 +60,12 @@
         <dependency>
             <groupId>org.apache.poi</groupId>
             <artifactId>poi-scratchpad</artifactId>
-            <version>3.12</version>
+            <version>3.17</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>3.17</version>
         </dependency>
         <dependency>
             <groupId>fr.opensagres.xdocreport</groupId>
@@ -106,6 +88,31 @@
             <artifactId>fr.opensagres.xdocreport.document</artifactId>
             <version>1.0.5</version>
         </dependency>
+        <!-- poi start -->
+
+        <!-- 对 rar5 的支持 和其他众多压缩支持 可参考 package net.sf.sevenzipjbinding.ArchiveFormat; -->
+        <dependency>
+            <groupId>net.sf.sevenzipjbinding</groupId>
+            <artifactId>sevenzipjbinding</artifactId>
+            <version>16.02-2.01</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.sevenzipjbinding</groupId>
+            <artifactId>sevenzipjbinding-all-platforms</artifactId>
+            <version>16.02-2.01</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.2.0</version>
+        </dependency>
+
         <!-- 解压(apache) -->
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -134,23 +141,12 @@
             <artifactId>antlr</artifactId>
             <version>2.7.7</version>
         </dependency>
-        <dependency>
-            <groupId>commons-httpclient</groupId>
-            <artifactId>commons-httpclient</artifactId>
-            <version>3.1</version>
-            <scope>test</scope>
-            <exclusions>
-                <exclusion>
-                    <artifactId>commons-logging</artifactId>
-                    <groupId>commons-logging</groupId>
-                </exclusion>
-            </exclusions>
-        </dependency>
         <dependency>
             <groupId>commons-cli</groupId>
             <artifactId>commons-cli</artifactId>
             <version>1.2</version>
         </dependency>
+
         <!-- FTP -->
         <dependency>
             <groupId>commons-net</groupId>
@@ -210,7 +206,6 @@
             <artifactId>javacv</artifactId>
             <version>1.5.2</version>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>javacpp</artifactId>
@@ -224,43 +219,36 @@
             <version>4.1.2-1.5.2</version>
             <classifier>linux-x86_64</classifier>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>opencv</artifactId>
             <version>4.1.2-1.5.2</version>
             <classifier>windows-x86_64</classifier>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>openblas</artifactId>
             <version>0.3.6-1.5.1</version>
             <classifier>linux-x86_64</classifier>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>openblas</artifactId>
             <version>0.3.6-1.5.1</version>
             <classifier>windows-x86_64</classifier>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>ffmpeg</artifactId>
             <version>4.2.1-1.5.2</version>
             <classifier>linux-x86_64</classifier>
         </dependency>
-
         <dependency>
             <groupId>org.bytedeco</groupId>
             <artifactId>ffmpeg</artifactId>
             <version>4.2.1-1.5.2</version>
             <classifier>windows-x86_64</classifier>
         </dependency>
-
-
         <dependency>
             <groupId>com.lowagie</groupId>
             <artifactId>itext</artifactId>
@@ -274,7 +262,6 @@
             <scope>system</scope>
             <systemPath>${basedir}/lib/jai_core-1.1.3.jar</systemPath>
         </dependency>
-
         <dependency>
             <groupId>javax.media</groupId>
             <artifactId>jai_codec</artifactId>
@@ -283,6 +270,25 @@
             <systemPath>${basedir}/lib/jai_codec-1.1.3.jar</systemPath>
         </dependency>
 
+        <!-- test dependency - start -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.1</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <artifactId>commons-logging</artifactId>
+                    <groupId>commons-logging</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- test dependency - end -->
     </dependencies>
 
     <build>
@@ -336,5 +342,4 @@
             </plugin>
         </plugins>
     </build>
-
 </project>

+ 2 - 0
server/src/main/config/freemarker_implicit.ftl

@@ -5,6 +5,8 @@
 [#-- @ftlvariable name="file" type="cn.keking.model.FileAttribute" --]
 [#-- @ftlvariable name="fileName" type="java.lang.String" --]
 [#-- @ftlvariable name="fileTree" type="java.lang.String" --]
+[#-- @ftlvariable name="needFilePassword" type="java.lang.Boolean" --]
+[#-- @ftlvariable name="filePasswordError" type="java.lang.Boolean" --]
 [#-- @ftlvariable name="baseUrl" type="java.lang.String" --]
 [#-- @ftlvariable name="imgUrls" type="String" --]
 [#-- @ftlvariable name="textData" type="java.lang.String" --]

+ 1 - 1
server/src/main/java/cn/keking/config/ConfigConstants.java

@@ -6,8 +6,8 @@ import org.springframework.stereotype.Component;
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * @author: chenjh

+ 28 - 0
server/src/main/java/cn/keking/model/FileAttribute.java

@@ -1,6 +1,7 @@
 package cn.keking.model;
 
 import cn.keking.config.ConfigConstants;
+import org.artofsolving.jodconverter.model.FileProperties;
 
 /**
  * Created by kl on 2018/1/17.
@@ -13,6 +14,8 @@ public class FileAttribute {
     private String name;
     private String url;
     private String fileKey;
+    private String filePassword;
+    private String userToken;
     private String officePreviewType = ConfigConstants.getOfficePreviewType();
     private String tifPreviewType;
     private Boolean skipDownLoad = false;
@@ -35,6 +38,12 @@ public class FileAttribute {
         this.officePreviewType = officePreviewType;
     }
 
+    public FileProperties toFileProperties() {
+        FileProperties fileProperties = new FileProperties();
+        fileProperties.setFilePassword(filePassword);
+        return fileProperties;
+    }
+
     public String getFileKey() {
         return fileKey;
     }
@@ -43,6 +52,22 @@ public class FileAttribute {
         this.fileKey = fileKey;
     }
 
+    public String getFilePassword() {
+        return filePassword;
+    }
+
+    public void setFilePassword(String filePassword) {
+        this.filePassword = filePassword;
+    }
+
+    public String getUserToken() {
+        return userToken;
+    }
+
+    public void setUserToken(String userToken) {
+        this.userToken = userToken;
+    }
+
     public String getOfficePreviewType() {
         return officePreviewType;
     }
@@ -82,6 +107,7 @@ public class FileAttribute {
     public void setUrl(String url) {
         this.url = url;
     }
+
     public Boolean getSkipDownLoad() {
         return skipDownLoad;
     }
@@ -89,6 +115,7 @@ public class FileAttribute {
     public void setSkipDownLoad(Boolean skipDownLoad) {
         this.skipDownLoad = skipDownLoad;
     }
+
     public String getTifPreviewType() {
         return tifPreviewType;
     }
@@ -96,4 +123,5 @@ public class FileAttribute {
     public void setTifPreviewType(String previewType) {
         this.tifPreviewType = previewType;
     }
+
 }

+ 11 - 0
server/src/main/java/cn/keking/service/FileHandlerService.java

@@ -292,7 +292,18 @@ public class FileHandlerService {
             if (StringUtils.hasText(tifPreviewType)) {
                 attribute.setTifPreviewType(tifPreviewType);
             }
+
+            String filePassword = req.getParameter("filePassword");
+            if (StringUtils.hasText(filePassword)) {
+                attribute.setFilePassword(filePassword);
+            }
+
+            String userToken = req.getParameter("userToken");
+            if (StringUtils.hasText(userToken)) {
+                attribute.setUserToken(userToken);
+            }
         }
+
         return attribute;
     }
 

+ 9 - 7
server/src/main/java/cn/keking/service/OfficeToPdfService.java

@@ -1,5 +1,6 @@
 package cn.keking.service;
 
+import cn.keking.model.FileAttribute;
 import org.artofsolving.jodconverter.OfficeDocumentConverter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -20,22 +21,23 @@ public class OfficeToPdfService {
         this.officePluginManager = officePluginManager;
     }
 
-    public void openOfficeToPDF(String inputFilePath, String outputFilePath) {
-        office2pdf(inputFilePath, outputFilePath);
+    public void openOfficeToPDF(String inputFilePath, String outputFilePath, FileAttribute fileAttribute) {
+        office2pdf(inputFilePath, outputFilePath, fileAttribute);
     }
 
 
-    public static void converterFile(File inputFile, String outputFilePath_end, OfficeDocumentConverter converter) {
+    public static void converterFile(File inputFile, String outputFilePath_end, OfficeDocumentConverter converter, FileAttribute fileAttribute) {
         File outputFile = new File(outputFilePath_end);
         // 假如目标路径不存在,则新建该路径
         if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
             logger.error("创建目录【{}】失败,请检查目录权限!",outputFilePath_end);
         }
-        converter.convert(inputFile, outputFile);
+
+        converter.convert(inputFile, outputFile, fileAttribute.toFileProperties());
     }
 
 
-    public void office2pdf(String inputFilePath, String outputFilePath) {
+    public void office2pdf(String inputFilePath, String outputFilePath, FileAttribute fileAttribute) {
         OfficeDocumentConverter converter = officePluginManager.getDocumentConverter();
         if (null != inputFilePath) {
             File inputFile = new File(inputFilePath);
@@ -45,12 +47,12 @@ public class OfficeToPdfService {
                 String outputFilePath_end = getOutputFilePath(inputFilePath);
                 if (inputFile.exists()) {
                     // 找不到源文件, 则返回
-                    converterFile(inputFile, outputFilePath_end,converter);
+                    converterFile(inputFile, outputFilePath_end, converter, fileAttribute);
                 }
             } else {
                 if (inputFile.exists()) {
                     // 找不到源文件, 则返回
-                    converterFile(inputFile, outputFilePath, converter);
+                    converterFile(inputFile, outputFilePath, converter, fileAttribute);
                 }
             }
         }

+ 73 - 20
server/src/main/java/cn/keking/service/impl/OfficeFilePreviewImpl.java

@@ -3,11 +3,13 @@ package cn.keking.service.impl;
 import cn.keking.config.ConfigConstants;
 import cn.keking.model.FileAttribute;
 import cn.keking.model.ReturnResponse;
-import cn.keking.service.FilePreview;
-import cn.keking.utils.DownloadUtils;
 import cn.keking.service.FileHandlerService;
+import cn.keking.service.FilePreview;
 import cn.keking.service.OfficeToPdfService;
+import cn.keking.utils.DownloadUtils;
+import cn.keking.utils.OfficeUtils;
 import cn.keking.web.filter.BaseUrlFilter;
+import org.artofsolving.jodconverter.office.OfficeException;
 import org.springframework.stereotype.Service;
 import org.springframework.ui.Model;
 import org.springframework.util.StringUtils;
@@ -42,33 +44,83 @@ public class OfficeFilePreviewImpl implements FilePreview {
         String baseUrl = BaseUrlFilter.getBaseUrl();
         String suffix = fileAttribute.getSuffix();
         String fileName = fileAttribute.getName();
+        String filePassword = fileAttribute.getFilePassword();
+        String userToken = fileAttribute.getUserToken();
         boolean isHtml = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx");
         String pdfName = fileName.substring(0, fileName.lastIndexOf(".") + 1) + (isHtml ? "html" : "pdf");
-        String outFilePath = FILE_DIR + pdfName;
-        // 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
-        if (!fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
-            String filePath;
-            ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
-            if (response.isFailure()) {
-                return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
+        String cacheFileName = userToken == null ? pdfName : userToken + "_" + pdfName;
+        String outFilePath = FILE_DIR + cacheFileName;
+
+        // 下载远程文件到本地,如果文件在本地已存在不会重复下载
+        ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
+        if (response.isFailure()) {
+            return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
+        }
+        String filePath = response.getContent();
+
+        /*
+         * 1. 缓存判断-如果文件已经进行转换过,就直接返回,否则执行转换
+         * 2. 缓存判断-加密文件基于userToken进行缓存,如果没有就不缓存
+         */
+        boolean isCached = false;
+        boolean isUseCached = false;
+        boolean isPwdProtectedOffice = false;
+        if (ConfigConstants.isCacheEnabled()) {
+            // 全局开启缓存
+            isUseCached = true;
+            if (fileHandlerService.listConvertedFiles().containsKey(cacheFileName)) {
+                // 存在缓存
+                isCached = true;
             }
-            filePath = response.getContent();
-            if (StringUtils.hasText(outFilePath)) {
-                officeToPdfService.openOfficeToPDF(filePath, outFilePath);
-                if (isHtml) {
-                    // 对转换后的文件进行操作(改变编码方式)
-                    fileHandlerService.doActionConvertedFile(outFilePath);
+            if (OfficeUtils.isPwdProtected(filePath)) {
+                isPwdProtectedOffice = true;
+                if (!StringUtils.hasLength(userToken)) {
+                    // 不缓存没有userToken的加密文件
+                    isUseCached = false;
                 }
-                if (ConfigConstants.isCacheEnabled()) {
-                    // 加入缓存
-                    fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
+            }
+        } else {
+            isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
+        }
+
+        if (isCached == false) {
+            // 没有缓存执行转换逻辑
+            if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
+                // 加密文件需要密码
+                model.addAttribute("needFilePassword", true);
+                return EXEL_FILE_PREVIEW_PAGE;
+            } else {
+                if (StringUtils.hasText(outFilePath)) {
+                    try {
+                        officeToPdfService.openOfficeToPDF(filePath, outFilePath, fileAttribute);
+                    } catch (OfficeException e) {
+                        if (isPwdProtectedOffice && OfficeUtils.isCompatible(filePath, filePassword) == false) {
+                            // 加密文件密码错误,提示重新输入
+                            model.addAttribute("needFilePassword", true);
+                            model.addAttribute("filePasswordError", true);
+                            return EXEL_FILE_PREVIEW_PAGE;
+                        }
+
+                        return otherFilePreview.notSupportedFile(model, fileAttribute, "抱歉,该文件版本不兼容,文件版本错误。");
+                    }
+
+                    if (isHtml) {
+                        // 对转换后的文件进行操作(改变编码方式)
+                        fileHandlerService.doActionConvertedFile(outFilePath);
+                    }
+                    if (isUseCached) {
+                        // 加入缓存
+                        fileHandlerService.addConvertedFile(cacheFileName, fileHandlerService.getRelativePath(outFilePath));
+                    }
                 }
             }
         }
+
         if (!isHtml && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
-            return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, pdfName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
+            return getPreviewType(model, fileAttribute, officePreviewType, baseUrl, cacheFileName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
         }
-        model.addAttribute("pdfUrl", pdfName);
+
+        model.addAttribute("pdfUrl", cacheFileName);
         return isHtml ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
     }
 
@@ -88,4 +140,5 @@ public class OfficeFilePreviewImpl implements FilePreview {
             return PICTURE_FILE_PREVIEW_PAGE;
         }
     }
+
 }

+ 7 - 0
server/src/main/java/cn/keking/utils/DownloadUtils.java

@@ -87,6 +87,13 @@ public class DownloadUtils {
         if (!dirFile.exists() && !dirFile.mkdirs()) {
             logger.error("创建目录【{}】失败,可能是权限不够,请检查", fileDir);
         }
+
+        // 文件已在本地存在,跳过文件下载
+        File realFile = new File(realPath);
+        if (realFile.exists()) {
+            fileAttribute.setSkipDownLoad(true);
+        }
+
         return realPath;
     }
 

+ 62 - 0
server/src/main/java/cn/keking/utils/OfficeUtils.java

@@ -0,0 +1,62 @@
+package cn.keking.utils;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.extractor.ExtractorFactory;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.springframework.lang.Nullable;
+
+import java.io.FileInputStream;
+
+/**
+ * Office工具类
+ *
+ * @author ylyue
+ * @since 2022/7/5
+ */
+public class OfficeUtils {
+
+    /**
+     * 判断office(word,excel,ppt)文件是否受密码保护
+     *
+     * @param path office文件路径
+     * @return 是否受密码保护
+     */
+    public static boolean isPwdProtected(String path) {
+        try {
+            ExtractorFactory.createExtractor(new FileInputStream(path));
+        } catch (EncryptedDocumentException e) {
+            return true;
+        } catch (Exception e) {
+            Throwable[] throwables = ExceptionUtils.getThrowables(e);
+            for (Throwable throwable : throwables) {
+                if (throwable instanceof EncryptedDocumentException) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 判断office文件是否可打开(兼容)
+     *
+     * @param path     office文件路径
+     * @param password 文件密码
+     * @return 是否可打开(兼容)
+     */
+    public static synchronized boolean isCompatible(String path, @Nullable String password) {
+        try {
+            Biff8EncryptionKey.setCurrentUserPassword(password);
+            ExtractorFactory.createExtractor(new FileInputStream(path));
+        } catch (Exception e) {
+            return false;
+        } finally {
+            Biff8EncryptionKey.setCurrentUserPassword(null);
+        }
+
+        return true;
+    }
+
+}

+ 9 - 2
server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java

@@ -19,6 +19,7 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.util.HtmlUtils;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -73,10 +74,13 @@ public class OnlinePreviewController {
         String fileUrls;
         try {
             fileUrls = new String(Base64.decodeBase64(urls));
+            // 防止XSS攻击
+            fileUrls = HtmlUtils.htmlEscape(fileUrls);
         } catch (Exception ex) {
             String errorMsg = String.format(BASE64_DECODE_ERROR_MSG, "urls");
             return otherFilePreview.notSupportedFile(model, errorMsg);
         }
+
         logger.info("预览文件url:{},urls:{}", fileUrls, urls);
         // 抽取文件并返回文件列表
         String[] images = fileUrls.split("\\|");
@@ -102,6 +106,11 @@ public class OnlinePreviewController {
      */
     @RequestMapping(value = "/getCorsFile", method = RequestMethod.GET)
     public void getCorsFile(String urlPath, HttpServletResponse response) {
+        if (urlPath == null || urlPath.toLowerCase().startsWith("file:") || urlPath.toLowerCase().startsWith("file%3") || !urlPath.toLowerCase().startsWith("http")) {
+            logger.info("读取跨域文件异常,可能存在非法访问,urlPath:{}", urlPath);
+            return;
+        }
+
         logger.info("下载跨域pdf文件url:{}", urlPath);
         try {
             URL url = WebUtils.normalizedURL(urlPath);
@@ -125,6 +134,4 @@ public class OnlinePreviewController {
         return "success";
     }
 
-
-
 }

Fișier diff suprimat deoarece este prea mare
+ 5 - 0
server/src/main/resources/static/js/bootbox.min.js


+ 199 - 199
server/src/main/resources/static/pptx/ppt.js

@@ -2,7 +2,6 @@
  * Copyright 2013 I Doc View
  * @author Godwin <I Doc View>
  */
-
 var ratio = 0.75;
 var pages;
 var slideUrls = new Array();
@@ -11,247 +10,248 @@ var curSlide = 1;
 var totalSize = 1;	// PPT当前获取到的总页数
 var slideCount = 1;	// PPT文件总页数
 var size = (!!$.url().param('size') ? $.url().param('size') : 0);
-$(document).ready(function() {
-	
-	// async method:
-	$.get('onlinePreview?' , params,  function(data, status) {
-		var data = JSON.parse(data);
-		var code = data.code;
-		if (1 == code) {
-			uuid = data.uuid;
-			pages = data.data;
-			totalSize = pages.length;
-			slideCount = data.totalSize;
-			
-			// title
-			$('.container-fluid:first .btn:first').after('<a class="brand lnk-file-title" style="text-decoration: none;" href="' + contextPath + '/doc/download/' + uuid + (!!queryStr ? '?' + queryStr : '') + '" title="' + data.name + '">' + data.name + '</a>');
-			document.title = data.name;
-			
-			// set ratio
-			ratio = pages[0].ratio;
-			
-			// reset all content
-			resetContent();
-
-			afterLoad();
-		} else {
-			$('.container-fluid .row-fluid').html('<section><div class="alert alert-error">' + data.desc + '</div></section>');
-		}
-
-		clearProgress();
-	});
-
-	// 是否显示全屏按钮
-	$('.fullscreen-link').toggle(screenfull.enabled);
-	// 全屏事件
-	$('.fullscreen-link').click(function(){
-		if (screenfull.enabled) {
-			screenfull.toggle($('.slide-img-container')[0]);
-		}
-	});
-	$(document).bind("fullscreenchange", function() {
-		if (screenfull.isFullscreen) {
-			$('.slide-img-container').css('background-color', 'black');
-			$('.slide-img-container').contextMenu(true);
-		} else {
-			$('.slide-img-container').css('background-color', '');
-			$('.slide-img-container').contextMenu(false);
-		}
-	});
-	
-	$('.select-page-selector').change(function() {
-		var selectNum = $(".select-page-selector option:selected").text();
-		gotoSlide(selectNum);
-	});
-	$('.slide-img-container .ppt-turn-left-mask').click(function () {
-		preSlide();
-	});
-	$('.slide-img-container .ppt-turn-right-mask').click(function () {
-		nextSlide();
-	});
-
-	// Right click (NOT supported in SOUGOU browser)
-	/*
-	$.contextMenu({
+
+$(document).ready(function () {
+    var data = resultData
+    var code = data.code;
+    if (1 == code) {
+        uuid = data.uuid;
+        pages = data.data;
+        totalSize = pages.length;
+        slideCount = data.totalSize;
+
+        // title
+        $('.container-fluid:first .btn:first').after('<a class="brand lnk-file-title" style="text-decoration: none;">' + data.name + '</a>');
+        document.title = data.name;
+
+        // set ratio
+        ratio = pages[0].ratio;
+
+        // reset all content
+        resetContent();
+
+        afterLoad();
+    } else {
+        $('.container-fluid .row-fluid').html('<section><div class="alert alert-error">' + data.desc + '</div></section>');
+    }
+    clearProgress();
+
+    // 是否显示全屏按钮
+    $('.fullscreen-link').toggle(screenfull.enabled);
+    // 全屏事件
+    $('.fullscreen-link').click(function () {
+        if (screenfull.enabled) {
+            screenfull.toggle($('.slide-img-container')[0]);
+        }
+    });
+    $(document).bind("fullscreenchange", function () {
+        if (screenfull.isFullscreen) {
+            $('.slide-img-container').css('background-color', 'black');
+            $('.slide-img-container').contextMenu(true);
+        } else {
+            $('.slide-img-container').css('background-color', '');
+            $('.slide-img-container').contextMenu(false);
+        }
+    });
+
+    $('.select-page-selector').change(function () {
+        var selectNum = $(".select-page-selector option:selected").text();
+        gotoSlide(selectNum);
+    });
+    $('.slide-img-container .ppt-turn-left-mask').click(function () {
+        preSlide();
+    });
+    $('.slide-img-container .ppt-turn-right-mask').click(function () {
+        nextSlide();
+    });
+
+    // Right click (NOT supported in SOUGOU browser)
+    /*
+    $.contextMenu({
         selector: '.slide-img-container',
         items: {
-        	"next": {
+            "next": {
                 name: "下一张",
                 callback: function(key, options) {
-                	nextSlide();
+                    nextSlide();
                 }
             },
             "previous": {
                 name: "上一张",
                 callback: function(key, options) {
-                	preSlide();
+                    preSlide();
                 }
             },
             "sep1": "---------",
             "exit": {
                 name: "结束放映",
                 callback: function(key, options) {
-                	$('.slide-img-container').fullScreen(false);
+                    $('.slide-img-container').fullScreen(false);
                 }
             },
         }
     });
     */
-	$('.slide-img-container').contextMenu(false);
-	
-	// Swipe method is NOT supported in IE6, so it should be the last one.
-	try {
-		$('.slide-img-container').swipeleft(function() { nextSlide(); });
-		$('.slide-img-container').swiperight(function() { preSlide(); });
-	} catch (err) {
-
-	}
+    $('.slide-img-container').contextMenu(false);
+
+    // Swipe method is NOT supported in IE6, so it should be the last one.
+    try {
+        $('.slide-img-container').swipeleft(function () {
+            nextSlide();
+        });
+        $('.slide-img-container').swiperight(function () {
+            preSlide();
+        });
+    } catch (err) {
+
+    }
 });
 
 var remainContentInterval;
-function checkRemainContent () {
-	clearInterval(remainContentInterval);
-	if (slideCount == totalSize) {
-		return;
-	}
+
+function checkRemainContent() {
+    clearInterval(remainContentInterval);
+    if (slideCount == totalSize) {
+        return;
+    }
 
 }
 
 function resetContent() {
-	remainContentInterval = setInterval(checkRemainContent, 8000);
-
-	// clear all content
-	$('.row-fluid .span2').empty();
-	$('.select-page-selector').empty();
-	$('.select-page-selector-sync').empty();
-	$('.slide-img-container img').remove();
-
-	// 限制预览页数开始
-	var viewCheck = authMap.view;
-	if (!!viewCheck && (viewCheck > 1) && (pages.length > viewCheck)) {
-		$('.navbar').after('<div class="alert alert-info" style="text-align: center; color: red;">试读结束,支付后阅读全文!</div>');
-		totalSize = viewCheck;
-		clearInterval(remainContentInterval);
-	}
-	// 限制预览页数结束
-
-	// pages
-	for (i = 0; i < totalSize; i++) {
-		var page = pages[i];
-		slideUrls[i] = page.url;
-		slideThumbUrls[i] = page.thumbUrl;
-		$('.row-fluid .span2').append('<div class="thumbnail" page="' + (i + 1) + '"><img src="' + page.thumbUrl + '"></div><div class="thumb-page-number-container">' + (i + 1) + '/' + slideCount + '</div>');
-		$('.select-page-selector').append('<option>' + (i + 1) + '</option>');
-		$('.select-page-selector-sync').append('<option>' + (i + 1) + '</option>');
-	}
-
-	// 未转换完成提示信息
-	if (totalSize < slideCount) {
-		$('.row-fluid .span2').prepend('<div style="color: red;">转换中(' + Math.floor((totalSize / slideCount) * 100) + '%),请稍候……</div>');
-	}
-
-	$('.slide-img-container').append('<img src="' + slideUrls[curSlide - 1] + '" class="img-polaroid" style="height: 100%;">');
-	var thumbnailWidth = $('.thumbnail:first').width();
-	var thumbnailHeight = thumbnailWidth * ratio;
-	$('.thumbnail').height(thumbnailHeight);
-	$('.thumbnail>img').width(thumbnailWidth).height(thumbnailHeight);
-
-	var slideImgContainerWidth = $('.slide-img-container:first').width();
-	var slideImgContainerHeight = slideImgContainerWidth * ratio;
-	$('.slide-img-container').height(slideImgContainerHeight);
-
-	resetImgSize();
-
-	var percent = Math.ceil((curSlide / slideUrls.length) * 100);
-	$('.thumbnail[page="' + curSlide + '"]').addClass('ppt-thumb-border');
-
-	// $('.thumbnail[page="' + curSlide + '"]').animate({scrollTop:($(window).height()/2)}, 'slow');
-
-	$('.select-page-selector').val(curSlide);
-	$('.bottom-paging-progress .bar').width('' + percent + '%');
-
-	$('.thumbnail').click(function () {
-		var page_num = $(this).attr('page');
-		gotoSlide(page_num);
-	});
+    remainContentInterval = setInterval(checkRemainContent, 8000);
+
+    // clear all content
+    $('.row-fluid .span2').empty();
+    $('.select-page-selector').empty();
+    $('.select-page-selector-sync').empty();
+    $('.slide-img-container img').remove();
+
+    // 限制预览页数开始
+    var viewCheck = authMap.view;
+    if (!!viewCheck && (viewCheck > 1) && (pages.length > viewCheck)) {
+        $('.navbar').after('<div class="alert alert-info" style="text-align: center; color: red;">试读结束,支付后阅读全文!</div>');
+        totalSize = viewCheck;
+        clearInterval(remainContentInterval);
+    }
+    // 限制预览页数结束
+
+    // pages
+    for (i = 0; i < totalSize; i++) {
+        var page = pages[i];
+        slideUrls[i] = page.url;
+        slideThumbUrls[i] = page.thumbUrl;
+        $('.row-fluid .span2').append('<div class="thumbnail" page="' + (i + 1) + '"><img src="' + page.thumbUrl + '"></div><div class="thumb-page-number-container">' + (i + 1) + '/' + slideCount + '</div>');
+        $('.select-page-selector').append('<option>' + (i + 1) + '</option>');
+        $('.select-page-selector-sync').append('<option>' + (i + 1) + '</option>');
+    }
+
+    // 未转换完成提示信息
+    if (totalSize < slideCount) {
+        $('.row-fluid .span2').prepend('<div style="color: red;">转换中(' + Math.floor((totalSize / slideCount) * 100) + '%),请稍候……</div>');
+    }
+
+    $('.slide-img-container').append('<img src="' + slideUrls[curSlide - 1] + '" class="img-polaroid" style="height: 100%;">');
+    var thumbnailWidth = $('.thumbnail:first').width();
+    var thumbnailHeight = thumbnailWidth * ratio;
+    $('.thumbnail').height(thumbnailHeight);
+    $('.thumbnail>img').width(thumbnailWidth).height(thumbnailHeight);
+
+    var slideImgContainerWidth = $('.slide-img-container:first').width();
+    var slideImgContainerHeight = slideImgContainerWidth * ratio;
+    $('.slide-img-container').height(slideImgContainerHeight);
+
+    resetImgSize();
+
+    var percent = Math.ceil((curSlide / slideUrls.length) * 100);
+    $('.thumbnail[page="' + curSlide + '"]').addClass('ppt-thumb-border');
+
+    // $('.thumbnail[page="' + curSlide + '"]').animate({scrollTop:($(window).height()/2)}, 'slow');
+
+    $('.select-page-selector').val(curSlide);
+    $('.bottom-paging-progress .bar').width('' + percent + '%');
+
+    $('.thumbnail').click(function () {
+        var page_num = $(this).attr('page');
+        gotoSlide(page_num);
+    });
 }
 
-$(window).resize(function() {
-	resetImgSize();
+$(window).resize(function () {
+    resetImgSize();
 });
 
 function resetImgSize() {
-	var leftW = $('.row-fluid .span2').width() + 40;
-	var windowW = $(window).width();
-	if (windowW < 768) {
-		leftW = -40;
-		$('.hidden-phone').css('display', 'none');
-		$('.span9').removeClass('offset2');
-	} else {
-		$('.hidden-phone').css('display', 'block');
-		$('.span9').addClass('offset2');
-	}
-	var ww = $(window).width() - 120 - leftW;
-	var wh = $(window).height() - 90;
-	if (screenfull.isFullscreen) {
-		ww = ww + 90 + leftW;
-		wh = wh + 80;
-	}
-	if (wh / ww < ratio) {
-		$('.slide-img-container').height(wh);
-		$('.slide-img-container').width(wh / ratio);
-	} else {
-		$('.slide-img-container').width(ww);
-		$('.slide-img-container').height(ww * ratio);
-	}
+    var leftW = $('.row-fluid .span2').width() + 40;
+    var windowW = $(window).width();
+    if (windowW < 768) {
+        leftW = -40;
+        $('.hidden-phone').css('display', 'none');
+        $('.span9').removeClass('offset2');
+    } else {
+        $('.hidden-phone').css('display', 'block');
+        $('.span9').addClass('offset2');
+    }
+    var ww = $(window).width() - 120 - leftW;
+    var wh = $(window).height() - 90;
+    if (screenfull.isFullscreen) {
+        ww = ww + 90 + leftW;
+        wh = wh + 80;
+    }
+    if (wh / ww < ratio) {
+        $('.slide-img-container').height(wh);
+        $('.slide-img-container').width(wh / ratio);
+    } else {
+        $('.slide-img-container').width(ww);
+        $('.slide-img-container').height(ww * ratio);
+    }
 }
 
-$(document).keydown(function(event){
-	if (event.keyCode == 37 || event.keyCode == 38 || event.keyCode == 33) {	// 37 left, 38 up, 33 pageUp
-		preSlide();
-	} else if (event.keyCode == 39 || event.keyCode == 40 || event.keyCode == 32 || event.keyCode == 34){	// 39 right, 40 down, 32 space, 34 pageDown
-		nextSlide();
-	} else if (event.keyCode == 13) {
-		screenfull.toggle($('.slide-img-container')[0]);
-	}
+$(document).keydown(function (event) {
+    if (event.keyCode == 37 || event.keyCode == 38 || event.keyCode == 33) {	// 37 left, 38 up, 33 pageUp
+        preSlide();
+    } else if (event.keyCode == 39 || event.keyCode == 40 || event.keyCode == 32 || event.keyCode == 34) {	// 39 right, 40 down, 32 space, 34 pageDown
+        nextSlide();
+    } else if (event.keyCode == 13) {
+        screenfull.toggle($('.slide-img-container')[0]);
+    }
 });
 
 function getCurSlide() {
-	return curSlide;
+    return curSlide;
 }
 
 function preSlide() {
-	var preSlide = eval(Number(getCurSlide()) - 1);
-	gotoSlide(preSlide);
+    var preSlide = eval(Number(getCurSlide()) - 1);
+    gotoSlide(preSlide);
 }
 
 function nextSlide() {
-	var nextSlide = eval(Number(getCurSlide()) + 1);
-	gotoSlide(nextSlide);
+    var nextSlide = eval(Number(getCurSlide()) + 1);
+    gotoSlide(nextSlide);
 }
 
 function gotoSlide(slide) {
-	var slideSum = slideUrls.length;
-	if (slide <= 0) {
-		slide = 1;
-	} else if (slideSum < slide) {
-		slide = slideSum;
-	}
-	curSlide = slide;
-	/*
-	$(".slide-img-container img").fadeOut(function() {
-		$(this).attr("src", slideUrls[slide - 1]).fadeIn();
-	});
-	*/
-	$(".slide-img-container img").attr("src", slideUrls[slide - 1]);
-	var percent = Math.ceil((curSlide / slideUrls.length) * 100);
-	$('.thumbnail').removeClass('ppt-thumb-border');
-	$('.thumbnail[page="' + slide + '"]').addClass('ppt-thumb-border');
-	var thumbTop = slide * ($('.thumbnail[page="' + 1 + '"]').height() + 10 + $('.thumb-page-number-container').height()) - ($(document).height() / 2);
-	$('.span2 ').animate({scrollTop:(thumbTop)}, 'slow');
-	$('.select-page-selector').val(slide);
-	$('.select-page-selector-sync').val(slide);
-	$('.bottom-paging-progress .bar').width('' + percent + '%');
-
-
-}
+    var slideSum = slideUrls.length;
+    if (slide <= 0) {
+        slide = 1;
+    } else if (slideSum < slide) {
+        slide = slideSum;
+    }
+    curSlide = slide;
+    /*
+    $(".slide-img-container img").fadeOut(function() {
+        $(this).attr("src", slideUrls[slide - 1]).fadeIn();
+    });
+    */
+    $(".slide-img-container img").attr("src", slideUrls[slide - 1]);
+    var percent = Math.ceil((curSlide / slideUrls.length) * 100);
+    $('.thumbnail').removeClass('ppt-thumb-border');
+    $('.thumbnail[page="' + slide + '"]').addClass('ppt-thumb-border');
+    var thumbTop = slide * ($('.thumbnail[page="' + 1 + '"]').height() + 10 + $('.thumb-page-number-container').height()) - ($(document).height() / 2);
+    $('.span2 ').animate({scrollTop: (thumbTop)}, 'slow');
+    $('.select-page-selector').val(slide);
+    $('.select-page-selector-sync').val(slide);
+    $('.bottom-paging-progress .bar').width('' + percent + '%');
+
+
+}

+ 47 - 4
server/src/main/resources/web/commonHeader.ftl

@@ -1,9 +1,9 @@
-
-
+<#setting classic_compatible=true>
 <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css"/>
 <script src="js/jquery-3.0.0.min.js" type="text/javascript"></script>
 <script src="js/jquery.form.min.js" type="text/javascript"></script>
 <script src="bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
+<script src="js/bootbox.min.js" type="text/javascript"></script>
 <script src="js/watermark.js" type="text/javascript"></script>
 <script src="js/base64.min.js" type="text/javascript"></script>
 
@@ -33,7 +33,52 @@
         }
     }
 
+    // 中文环境
+    var locale_zh_CN = {
+        OK: '确定',
+        CONFIRM: '确认',
+        CANCEL: '取消'
+    };
+    bootbox.addLocale('locale_zh_CN', locale_zh_CN);
+
+    /**
+     * 需要文件密码
+     */
+    function needFilePassword() {
+        if ('${needFilePassword}' == 'true') {
+            let promptTitle = "你正在预览加密文件,请输入文件密码。";
+            if ('${filePasswordError}' == 'true') {
+                promptTitle = "密码错误,请重新输入密码。";
+            }
+
+            bootbox.prompt({
+                title: promptTitle,
+                inputType: 'password',
+                centerVertical: true,
+                locale: 'locale_zh_CN',
+                callback: function (filePassword) {
+                    if (filePassword != null) {
+                        const locationHref = window.location.href;
+                        const isInclude = locationHref.includes("filePassword=");
+                        let redirectUrl = null;
+                        if (isInclude) {
+                            const url = new URL(locationHref);
+                            url.searchParams.set("filePassword", filePassword);
+                            redirectUrl = url.href;
+                        } else {
+                            redirectUrl = locationHref + '&filePassword=' + filePassword;
+                        }
+
+                        window.location.replace(redirectUrl);
+                    } else {
+                        location.reload();
+                    }
+                }
+            });
+        }
+    }
 </script>
+
 <style>
     * {
         margin: 0;
@@ -44,6 +89,4 @@
         height: 100%;
         width: 100%;
     }
-
 </style>
-

+ 21 - 17
server/src/main/resources/web/fileNotSupported.ftl

@@ -1,42 +1,46 @@
 <!DOCTYPE html>
-
 <html lang="en">
 <head>
-    <meta charset="utf-8" />
+    <meta charset="utf-8"/>
     <style type="text/css">
-        body{
+        body {
             margin: 0 auto;
-            width:900px;
+            width: 900px;
             background-color: #CCB;
         }
-        .container{
+
+        .container {
             width: 700px;
             height: 700px;
             margin: 0 auto;
         }
-        img{
-            width:auto;
-            height:auto;
-            max-width:100%;
-            max-height:100%;
+
+        img {
+            width: auto;
+            height: auto;
+            max-width: 100%;
+            max-height: 100%;
             padding-bottom: 36px;
         }
-        span{
+
+        span {
             display: block;
-            font-size:20px;
-            color:blue;
+            font-size: 20px;
+            color: blue;
         }
     </style>
 </head>
+
 <body>
 <div class="container">
-    <img src="images/sorry.jpg" />
+    <img src="images/sorry.jpg"/>
     <span>
-    该文件类型(${fileType})系统暂时不支持在线预览,<b>说明</b>
+        该(${fileType})文件,系统暂不支持在线预览,具体原因如下
         <p style="color: red;">${msg}</p>
-        有任何疑问,请加&nbsp;<a href="https://jq.qq.com/?_wv=1027&k=5c0UAtu">官方QQ群:613025121</a>&nbsp;咨询
     </span>
 </div>
+<script>
+    console.log(`有任何疑问,请加:<a href="https://jq.qq.com/?_wv=1027&k=5c0UAtu">官方QQ群:613025121</a> 咨询`);
+</script>
 </body>
-
 </html>

+ 14 - 8
server/src/main/resources/web/html.ftl

@@ -2,25 +2,31 @@
 
 <html lang="en">
 <head>
-    <meta charset="utf-8" />
+    <meta charset="utf-8"/>
     <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
+    <title>文件预览</title>
     <#include "*/commonHeader.ftl">
 </head>
 <body>
-    <iframe src="${pdfUrl}" width="100%" frameborder="0"></iframe>
+<iframe src="${pdfUrl}" width="100%" frameborder="0"></iframe>
 </body>
+
+<script type="text/javascript">
+    needFilePassword();
+</script>
+
 <script type="text/javascript">
-    document.getElementsByTagName('iframe')[0].height = document.documentElement.clientHeight-10;
+    document.getElementsByTagName('iframe')[0].height = document.documentElement.clientHeight - 10;
     /**
      * 页面变化调整高度
      */
-    window.onresize = function(){
+    window.onresize = function () {
         var fm = document.getElementsByTagName("iframe")[0];
-        fm.height = window.document.documentElement.clientHeight-10;
+        fm.height = window.document.documentElement.clientHeight - 10;
     }
     /*初始化水印*/
-    window.onload = function() {
-      initWaterMark();
+    window.onload = function () {
+        initWaterMark();
     }
 </script>
-</html>
+</html>

+ 3 - 2
server/src/main/resources/web/index.ftl

@@ -262,13 +262,14 @@
             }, {
                 field: 'action',
                 title: '操作'
-            },]
+            }]
         }).on('pre-body.bs.table', function (e, data) {
             // 每个data添加一列用来操作
             $(data).each(function (index, item) {
                 item.action = "<a class='btn btn-default' target='_blank' href='${baseUrl}onlinePreview?url=" + encodeURIComponent(Base64.encode('${baseUrl}' + item.fileName)) + "'>预览</a>" +
                     "<a class='btn btn-default' href='javascript:void(0);' onclick='deleteFile(\"" + item.fileName + "\")'>删除</a>";
             });
+
             return data;
         }).on('post-body.bs.table', function (e, data) {
             return data;
@@ -281,7 +282,6 @@
             urlField.val(b64Encoded);
         });
 
-
         function showLoadingDiv() {
             var height = window.document.documentElement.clientHeight - 1;
             $(".loading_container").css("height", height).show();
@@ -307,6 +307,7 @@
                 dataType: "json" /*设置返回值类型为文本*/
             });
         });
+
         var gitalk = new Gitalk({
             clientID: '525d7f16e17aab08cef5',
             clientSecret: 'd1154e3aee5c8f1cbdc918b5c97a4f4157e0bfd9',

+ 2 - 1
server/src/main/resources/web/pdf.ftl

@@ -1,5 +1,4 @@
 <!DOCTYPE html>
-
 <html lang="en">
 <head>
     <meta charset="utf-8"/>
@@ -7,6 +6,7 @@
     <title>PDF预览</title>
     <#include "*/commonHeader.ftl">
 </head>
+
 <body>
 <#if pdfUrl?contains("http://") || pdfUrl?contains("https://")>
     <#assign finalUrl="${pdfUrl}">
@@ -20,6 +20,7 @@
          onclick="goForImage()"/>
 </#if>
 </body>
+
 <script type="text/javascript">
     var url = '${finalUrl}';
     var baseUrl = '${baseUrl}'.endsWith('/') ? '${baseUrl}' : '${baseUrl}' + '/';

+ 104 - 120
server/src/main/resources/web/ppt.ftl

@@ -1,149 +1,136 @@
-<#if RequestParameters['name']??> 
-{
-	"code": 1,
-	"name": "PPT预览",
-	"totalSize": ${imgurls?size},
-	"curPage": 1,
-	"totalPage": 1,
-	"pageSize": 10,
-	"titles": null,
-	"data": [
-<#assign index = 0>
-<#list imgurls as img>
-<#if index != 0>,</#if>{
-		"uuid": null,
-		"title": null,
-		"content": null,
-		"text": null,
-		"url": "${img}",
-		"destFile": null,
-		"viewCount": 0,
-		"downloadCount": 0,
-		"ctime": null,
-		"thumbUrl": "${img}",
-		"largeUrl": null,
-		"ratio": 0.5625,
-		"note": null
-	}<#assign index = index + 1>
-</#list>],
-	"desc": "Success"
-}
-
-<#else>
-
 <!DOCTYPE html>
-
 <html lang="en">
- <head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-   
-
-    <!-- BOOTSTRAP STYLE start -->
-    <!-- Le styles -->
-    
-<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-<link href="pptx/bootstrap/css/bootstrap.min.css" rel="stylesheet">
-
-<link href="pptx/idocv/idocv_common.min.css" rel="stylesheet">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <link href="pptx/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="pptx/idocv/idocv_common.min.css" rel="stylesheet">
+    <link href="pptx/jquery.contextMenu.css" rel="stylesheet">
 
-<link href="pptx/jquery.contextMenu.css" rel="stylesheet">
-
-    <!-- BOOTSTRAP STYLE end -->
-    
+    <#--  手机端预览兼容  -->
     <script type="text/javascript">
-      var windowWidth = document.documentElement.clientWidth;
-      var searchStr = window.location.search.substr(1);
-      if ((windowWidth < 768 || (/micromessenger/.test(navigator.userAgent.toLowerCase()))) && (!searchStr || searchStr.indexOf('type=') < 0)) {
-          var redirectUrl = window.location.pathname + '?type=mobile' + (!!searchStr ? ('&' + searchStr) : '');
-          window.location.replace(redirectUrl);
-      }
+        var windowWidth = document.documentElement.clientWidth;
+        var searchStr = window.location.search.substr(1);
+        if ((windowWidth < 768 || (/micromessenger/.test(navigator.userAgent.toLowerCase()))) && (!searchStr || searchStr.indexOf('type=') < 0)) {
+            var redirectUrl = window.location.pathname + '?type=mobile' + (!!searchStr ? ('&' + searchStr) : '');
+            window.location.replace(redirectUrl);
+        }
     </script>
 
     <style type="text/css">
-      .thumbnail{
-        /*
-        max-width: 200px;
-        */
-        cursor: pointer;
-      }
+        .thumbnail {
+            /*
+            max-width: 200px;
+            */
+            cursor: pointer;
+        }
     </style>
-    
+
     <!--[if lt IE 9]>
-      <script src="/static/bootstrap/js/html5shiv.js"></script>
+    <script src="/static/bootstrap/js/html5shiv.js"></script>
     <![endif]-->
+</head>
 
-  </head>
-
-  <body onload="resetImgSize();" class="ppt-body">
+<body onload="resetImgSize();" class="ppt-body">
 
 <div class="loading-mask" style="display: block;">
     <div class="loading-zone">
         <div class="text"><img src="pptx/img/loader_indicator_lite.gif">加载中...</div>
     </div>
-  
 </div>
 
-    <div class="navbar navbar-inverse navbar-fixed-top">
-      <div class="navbar-inner">
+<div class="navbar navbar-inverse navbar-fixed-top">
+    <div class="navbar-inner">
         <div class="container-fluid">
-          <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
-            <span class="icon-bar"></span>
-            <span class="icon-bar"></span>
-            <span class="icon-bar"></span>
-          </button>
-          <!-- FILE NAME HERE -->
-          <!-- SIGN UP & SIGN IN -->
-          
-          <div class="nav-collapse collapse">
-            <p class="navbar-text pull-right">
-              <a href="#" title="全屏" class="fullscreen-link"><i class="icon-fullscreen icon-white"></i></a>
-            </p>
-          </div><!--/.nav-collapse -->
+            <button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+                <span class="icon-bar"></span>
+            </button>
+            <!-- FILE NAME HERE -->
+            <!-- SIGN UP & SIGN IN -->
+
+            <div class="nav-collapse collapse">
+                <p class="navbar-text pull-right">
+                    <a href="#" title="全屏" class="fullscreen-link"><i class="icon-fullscreen icon-white"></i></a>
+                </p>
+            </div><!--/.nav-collapse -->
         </div>
-      </div>
     </div>
+</div>
 
-    <div class="container-fluid" style="max-height: 100%;">
-      <div class="row-fluid">
-        <div class="span2 hidden-phone" style="position: fixed; top: 60px; left: 20px; bottom: 20px; padding-right: 10px; border-right: 3px solid #c8c8c8; max-height: 100%; overflow: auto; text-align: center;">
-          <!--Sidebar content-->
-          <!-- 
-          <div class="thumbnail">
-            <img src="">
-          </div>
-          1/20<br />
-          -->
+<div class="container-fluid" style="max-height: 100%;">
+    <div class="row-fluid">
+        <div class="span2 hidden-phone"
+             style="position: fixed; top: 60px; left: 20px; bottom: 20px; padding-right: 10px; border-right: 3px solid #c8c8c8; max-height: 100%; overflow: auto; text-align: center;">
+            <!--Sidebar content-->
+            <!--
+            <div class="thumbnail">
+              <img src="">
+            </div>
+            1/20<br />
+            -->
         </div>
         <div class="span9 offset2">
-          <div class="slide-img-container">
-            <div class="ppt-turn-left-mask"></div>
-            <div class="ppt-turn-right-mask"></div>
-            <!-- 
-            <img src="" class="img-polaroid" style="max-height: 100%;">
-             -->
-          </div>
-          <!-- ONLY AVAILABLE ON MOBILE -->
-          <div class="span12 visible-phone text-center" style="position: fixed; bottom: 10px; left: 0px; z-index: 1000;">
-            <select class="select-page-selector span1" style="width: 80px; margin-top: 10px;">
-              <!-- PAGE NUMBERS HERE -->
-            </select>
-          </div>
+            <div class="slide-img-container">
+                <div class="ppt-turn-left-mask"></div>
+                <div class="ppt-turn-right-mask"></div>
+                <!--
+                <img src="" class="img-polaroid" style="max-height: 100%;">
+                 -->
+            </div>
+            <!-- ONLY AVAILABLE ON MOBILE -->
+            <div class="span12 visible-phone text-center"
+                 style="position: fixed; bottom: 10px; left: 0px; z-index: 1000;">
+                <select class="select-page-selector span1" style="width: 80px; margin-top: 10px;">
+                    <!-- PAGE NUMBERS HERE -->
+                </select>
+            </div>
         </div>
-      </div>
     </div>
-    
-    <div class="progress progress-striped active bottom-paging-progress">
-      <div class="bar" style="width: 0%;"></div>
-    </div>
-    <!-- JavaSript
-    ================================================== -->
-    
+</div>
+
+<div class="progress progress-striped active bottom-paging-progress">
+    <div class="bar" style="width: 0%;"></div>
+</div>
+
+<!-- JavaSript ================================================== -->
 <script src="pptx/jquery-3.5.1.min.js"></script>
 <script src="pptx/jquery.contextMenu.js?v=11.2.5_20210128"></script>
 <script src="pptx/idocv/idocv_common.min.js"></script>
 
 <script>
+    var resultData = {
+        "code": 1,
+        "name": "PPT预览",
+        "totalSize": ${imgurls?size},
+        "curPage": 1,
+        "totalPage": 1,
+        "pageSize": 10,
+        "titles": null,
+        "data": [
+            <#assign index = 0>
+            <#list imgurls as img>
+            <#if index != 0>, </#if>{
+                "uuid": null,
+                "title": null,
+                "content": null,
+                "text": null,
+                "url": "${img}",
+                "destFile": null,
+                "viewCount": 0,
+                "downloadCount": 0,
+                "ctime": null,
+                "thumbUrl": "${img}",
+                "largeUrl": null,
+                "ratio": 0.5625,
+                "note": null
+            }<#assign index = index + 1>
+            </#list>],
+        "desc": "Success"
+    }
+
     var contextPath = '';
     var version = '12';
     // var urlObj = $.url($.url().attr('source').replace(contextPath, ''));
@@ -166,10 +153,7 @@
     }
 </script>
 <!-- 客户自定义JS -->
-    <script src="pptx/jquery.mobile-events.min.js"></script>
-    <script src="pptx/ppt.js"></script>
-</body>
-</html>
+<script src="pptx/jquery.mobile-events.min.js"></script>
+<script src="pptx/ppt.js"></script>
 </body>
 </html>
-</#if>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff