Browse Source

初始化代码

陈长荣 3 months ago
parent
commit
dd80c135ba
45 changed files with 2888 additions and 0 deletions
  1. 18 0
      README.md
  2. 150 0
      pom.xml
  3. 15 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/DocDealApplication.java
  4. 50 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/Document.java
  5. 95 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditCallback.java
  6. 25 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditCallbackResponse.java
  7. 26 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditParam.java
  8. 63 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentResponse.java
  9. 14 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/File.java
  10. 25 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/FileHistoryLog.java
  11. 36 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/FileUpload.java
  12. 38 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/base/BaseEntity.java
  13. 30 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/config/Crosconfig.java
  14. 72 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/config/LogAspect.java
  15. 33 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/config/MvcConfiguration.java
  16. 45 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/config/OssAutoConfiguration.java
  17. 51 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/DocumentConstants.java
  18. 48 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/DocumentStatus.java
  19. 75 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/ErrorCodeEnum.java
  20. 30 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/OpenModeEnum.java
  21. 325 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/controller/FileController.java
  22. 26 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/exception/DocumentException.java
  23. 33 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/exception/WebExceptionHandler.java
  24. 8 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/mapper/FileUploadMapper.java
  25. 26 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/OssProperties.java
  26. 84 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/http/OssEndpoint.java
  27. 142 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/service/OssTemplate.java
  28. 71 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/service/DocumentService.java
  29. 14 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/service/FileUploadService.java
  30. 306 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/service/impl/DocumentServiceImpl.java
  31. 23 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/service/impl/FileUploadServiceImpl.java
  32. 187 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/utils/FileUtil.java
  33. 74 0
      src/main/java/com/github/jfcloud/excel/editor/docdeal/utils/Md5Utils.java
  34. 24 0
      src/main/resources/application-dbconfig.yml
  35. 100 0
      src/main/resources/application.yml
  36. 6 0
      src/main/resources/mapper/FileUploadMapper.xml
  37. 26 0
      src/main/resources/static/css/viewer.css
  38. BIN
      src/main/resources/static/favicon.ico
  39. 176 0
      src/main/resources/static/js/editor.js
  40. 1 0
      src/main/resources/static/js/jquery-2.1.3.min.js
  41. 117 0
      src/main/resources/static/js/viewer.js
  42. 114 0
      src/main/resources/static/js/viewerExcel.js
  43. 22 0
      src/main/resources/templates/editor.html
  44. 22 0
      src/main/resources/templates/viewer.html
  45. 22 0
      src/main/resources/templates/viewerExcel.html

+ 18 - 0
README.md

@@ -0,0 +1,18 @@
+# 部署服务器onlyoffice镜像
+```
+9.1 导入镜像文件
+docker load -i /home/chineseOnlyoffice.tar
+9.2 创建容器
+docker run -i -t -d -p 20053:80 --name chineseonlyoffice/documentserver --privileged=true onlyoffice/documentserver:5.4.2.46 /usr/sbin/init
+测试onlyoffice是否启动成功
+浏览器访问
+http://192.168.209.129:20053/web-apps/apps/api/documents/api.js
+```
+```
+   部署后访问js地址
+   http://ip:20053/web-apps/apps/api/documents/api.js
+部署该服务jar(port=20054)
+ 部署后预览与编辑示例:
+http://ip:20054/review/?name=01050625685446bd9597985d94753587.xlsx&userName=zhangjian&userId=1472848120668229634
+http://ip:20054/edit/?name=01050625685446bd9597985d94753587.xlsx&userName=zhangjian&userId=1472848120668229634
+```

+ 150 - 0
pom.xml

@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.github</groupId>
+    <artifactId>jfcloud-excel-edit</artifactId>
+    <version>k6</version>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.5.3</version>
+    </parent>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <jedis.version>2.9.0</jedis.version>
+        <log4jdbc.version>1.16</log4jdbc.version>
+        <swagger.version>2.9.2</swagger.version>
+        <fastjson.version>1.2.54</fastjson.version>
+        <commons-pool2.version>2.5.0</commons-pool2.version>
+        <mapstruct.version>1.2.0.Final</mapstruct.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <!--io常用工具类 -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.2</version>
+        </dependency>
+
+        <!--文件上传工具类 -->
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.41</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.4.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>1.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-thymeleaf</artifactId>
+            <version>2.1.1.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.14</version>
+        </dependency>
+        <dependency>
+            <groupId>org.hashids</groupId>
+            <artifactId>hashids</artifactId>
+            <version>1.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.json-simple</groupId>
+            <artifactId>json-simple</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <!-- swagger2 依赖 -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>2.7.0</version>
+        </dependency>
+        <!-- Swagger第三方ui依赖 -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+            <version>1.9.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+            <version>1.11.543</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjrt</artifactId>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>3.0.2</version>
+            </plugin>
+        </plugins>
+
+    </build>
+
+</project>

+ 15 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/DocDealApplication.java

@@ -0,0 +1,15 @@
+package com.github.jfcloud.excel.editor.docdeal;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+//启动类
+@SpringBootApplication
+@MapperScan("com.github.jfcloud.excel.editor.docdeal.mapper")
+public class DocDealApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(DocDealApplication.class,args);
+    }
+}

+ 50 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/Document.java

@@ -0,0 +1,50 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * onlyoffice定义的文档对象
+ * @author: zhangcx
+ * @date: 2019/8/7 16:30
+ */
+@ApiModel("文档实体")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Document implements Serializable {
+    /** 【必需】文件唯一标识 */
+    @ApiModelProperty(value = "文档 key", example="xYz123")
+    private String key;
+    /** 【必需】文档名称 */
+    @ApiModelProperty(value = "文档标题", example="test.doc")
+    private String title;
+    /** 【必需】文档后缀 */
+    @ApiModelProperty(value = "文档类型", example="doc")
+    private String fileType;
+    /** mimeType 应该先校验文件是否可以打开(非api必须字段) */
+    //private String mimeType;
+    /** 文件实体在服务器硬盘存储位置 */
+    @ApiModelProperty(value = "文档物理存储位置", example="/temp/test.doc")
+    private String storage;
+    /** 【必需】文件实体下载地址 */
+    @ApiModelProperty(value = "文档获取url", example="http://192.168.0.58:20053/api/file/xYz123")
+    private String url;
+    /** 打开文件预览/编辑的链接 */
+    //private String refrence;
+
+    /** 文档打开方式 {@link OpenModeEnum} */
+    //private String mode;
+
+    private Map<String,Object> permissions;
+
+}

+ 95 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditCallback.java

@@ -0,0 +1,95 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 文档编辑时的回调实体
+ *
+ * <p>
+ * 每个用户与文档共同编辑的连接或断开都接收状态 1 。
+ * 文档关闭后10秒钟收到状态 2 ( 3 ),其中包含最后一次将更改发送到文档编辑服务的用户的id。
+ * 在文档关闭进行编辑后收到状态 4 ,最后一个用户不做任何更改。
+ * 当执行强制保存请求时,接收状态 6 ( 7 )。
+ * </p>
+ * @author: zhangcx
+ * @date: 2019/8/23 13:48
+ */
+@Data
+public class DocumentEditCallback implements Serializable {
+    /** 连接到文档的新用户共同编辑或与其断开连接的操作对象信息 */
+    private List<ActionsBean> actions;
+    /** 用于跟踪和显示文档更改历史记录的文档编辑数据 链接 */
+    private String changesurl;
+    /** 执行强制保存请求时 启动器的类型 */
+    /**
+     0 - 对命令服务执行强制保存请求,
+     1 - 每次保存时执行强制保存请求(例如,单击“ 保存”按钮),仅当forcesave选项设置为true时才可用。
+     2 - 使用服务器配置中的设置由计时器执行强制保存请求。
+     当状态值仅等于6或7时,存在类型。
+     */
+    private int forcesavetype;
+    /** 文档更改历史记录 */
+    private HistoryBean history;
+    /** 【必需】文档key */
+    private String key;
+    /**
+     *  【必需】文档的状态 {@link DocumentStatus}
+     *  <ul>
+     *  <li>每个用户与文档共同编辑的连接或断开都接收状态 1</li>
+     *  <li>文档关闭后10秒钟收到状态 2 ( 3 ),其中包含最后一次将更改发送到文档编辑服务的用户的id</li>
+     *  <li>最后一个用户不做任何更改,在文档关闭后收到状态 4 </li>
+     *  <li>当执行强制保存请求时,接收状态 6 ( 7 )</li>
+     *  </ul>
+     */
+    private int status;
+    /** 文档存储服务中缓存的已编辑的文档的链接 */
+    private String url;
+    /** 发送到命令服务的自定义信息 */
+    private String userdata;
+    /** 同时打开文档进行编辑的用户id列表; 当文档被更改保存时,用户将返回最后一个编辑文档的用户id(状态2和状态6时) */
+    private List<String> users;
+
+    /**
+     * 文档更改历史记录Bean
+     */
+    @Data
+    public static class HistoryBean {
+        /**
+         * 修改记录(json字符串形式)
+         <pre>
+         [{
+                "created": "2019-08-28 05:25:23",
+                "user": {
+                    "name": "用户456",
+                    "id": "456"
+                }
+            }, {
+                "created": "2019-08-28 05:25:33",
+                "user": {
+                    "name": "用户789",
+                    "id": "789"
+                }
+            }
+         ]
+         </pre>
+         */
+        private String changes;
+        /** onlyoffice服务端版本 **/
+        private String serverVersion;
+    }
+
+    /**
+     * 连接到文档的新用户共同编辑或与其断开连接的操作对象信息Bean
+     */
+    @Data
+    public static class ActionsBean {
+        /** 1:建立编辑连接 0:断开连接 */
+        private int type;
+        /** 共同编辑文档时 连接或断开连接的用户id */
+        private String userid;
+    }
+}

+ 25 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditCallbackResponse.java

@@ -0,0 +1,25 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 文档响应实体类
+ * @author: zhangcx
+ * @date: 2019/8/12 13:29
+ */
+@Data
+@AllArgsConstructor
+public class DocumentEditCallbackResponse implements Serializable {
+    private int error;
+
+    public static DocumentEditCallbackResponse success(){
+        return new DocumentEditCallbackResponse(0);
+    }
+
+    public static DocumentEditCallbackResponse failue(){
+        return new DocumentEditCallbackResponse(1);
+    }
+}

+ 26 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentEditParam.java

@@ -0,0 +1,26 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 文档编辑时参数 实体
+ * @author: zhangcx
+ * @date: 2019/8/26 14:50
+ */
+@Data
+@Builder
+public class DocumentEditParam {
+    /** 当前打开编辑页面的用户信息 */
+    private UserBean user;
+    /** onlyoffice在编辑时请求的回调地址,必选项 */
+    private String callbackUrl;
+
+    @Data
+    @Builder
+    public static class UserBean {
+        /** 用户id */
+        private String id;
+        /** 用户姓名 */
+        private String name;
+    }
+}

+ 63 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/DocumentResponse.java

@@ -0,0 +1,63 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+
+import com.github.jfcloud.excel.editor.docdeal.constant.ErrorCodeEnum;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 文档响应实体类
+ * @author: zhangcx
+ * @date: 2019/8/12 13:29
+ */
+@Data
+@ApiModel("文档响应实体")
+public class DocumentResponse<T> implements Serializable {
+    @ApiModelProperty(value = "错误代码(参考:ErrorCodeEnum)", example="\"0\"")
+    private String code;
+    @ApiModelProperty("错误信息")
+    private String msg;
+    @ApiModelProperty("返回的对象")
+    private T data;
+    @ApiModelProperty("返回的列表")
+    private List<T> dataList;
+
+    private DocumentResponse<T> code(String code){
+        this.code = code;
+        return this;
+    }
+
+    private DocumentResponse<T> msg(String msg){
+        this.msg = msg;
+        return this;
+    }
+
+    private DocumentResponse<T> data(T data){
+        this.data = data;
+        return this;
+    }
+
+    private DocumentResponse<T> dataList(List<T> dataList){
+        this.dataList = dataList;
+        return this;
+    }
+
+    public static <T>DocumentResponse<T> success(T data){
+        DocumentResponse<T> dr = new DocumentResponse<>();
+        return dr.code(ErrorCodeEnum.SUCCESS.getCode()).msg(ErrorCodeEnum.SUCCESS.getMsg()).data(data);
+    }
+
+    public static <T>DocumentResponse<T> success(List<T> dataList){
+        DocumentResponse<T> dr = new DocumentResponse<>();
+        return dr.code(ErrorCodeEnum.SUCCESS.getCode()).msg(ErrorCodeEnum.SUCCESS.getMsg()).dataList(dataList);
+    }
+
+    public static DocumentResponse failue(ErrorCodeEnum errorCodeEnum) {
+        DocumentResponse dr = new DocumentResponse<>();
+        return dr.code(errorCodeEnum.getCode()).msg(errorCodeEnum.getMsg());
+    }
+}

+ 14 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/File.java

@@ -0,0 +1,14 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+import java.io.Serializable;
+
+public class  File implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private String fileId;
+    private String fileName;
+    private String crateTime;
+    private String updateTime;
+    private String createBy;
+    private String updateBy;
+    private String isDel;
+}

+ 25 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/FileHistoryLog.java

@@ -0,0 +1,25 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+
+import com.github.jfcloud.excel.editor.docdeal.bean.base.BaseEntity;
+
+public class FileHistoryLog extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+    private String historyFileId;
+    private String fileId;
+    private String lastSave; //最后保存事件
+    private String notmodified; //最后不修改
+    private String changesUrl;//修改记录zip包路径
+    private String serverVersion; //服务器版本
+    private String changes; //用户修改记录json数组
+    private String actions;//共同编辑用户json数组
+    private String key; //当前版本号
+    private String url; //当前最新记录
+    private String users; //用户数组字符串
+    private String stauts;
+    private String realUrl;  //最新版本文件下载路径
+    private String isDel;
+    private String previousKey;  //之前版本的key
+    private String previousUrl; //之前版本的url
+
+}

+ 36 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/FileUpload.java

@@ -0,0 +1,36 @@
+package com.github.jfcloud.excel.editor.docdeal.bean;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.github.jfcloud.excel.editor.docdeal.bean.base.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString
+@Accessors(chain = true)
+@TableName("onlyoffice_file_info")
+public class FileUpload  extends BaseEntity {
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+    @TableField("file_name")
+    private String file_name;
+    @TableField(value = "file_size")
+    private Long file_size;
+    @TableField(value = "file_type")
+    private String file_type;
+    @TableField(value = "file_path")
+    private String file_path;
+    @TableField(value = "upload_date")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
+    private Date upload_date;
+}

+ 38 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/bean/base/BaseEntity.java

@@ -0,0 +1,38 @@
+package com.github.jfcloud.excel.editor.docdeal.bean.base;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * 基础实体类:用于自动生成数据库表实体的公共字段
+ *
+ * @author wgb
+ * @date 2020/3/26 11:47
+ */
+@Getter
+@Setter
+@Accessors(chain = true)
+public class BaseEntity implements Serializable {
+
+
+    /**
+//     * 创建时间,插入数据时自动填充
+//     */
+
+
+//    /**
+//     * 修改时间,插入、更新数据时自动填充
+//     */
+//    @TableField(value = "modified_time", fill = FieldFill.INSERT_UPDATE)
+//    private LocalDateTime modifiedTime;
+//    /**
+//     * 删除状态:插入数据时自动填充
+//     */
+//    @TableField(value = "delete_status", fill = FieldFill.INSERT)
+//    @TableLogic
+//    private boolean deleteStatus;
+
+}

+ 30 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/config/Crosconfig.java

@@ -0,0 +1,30 @@
+package com.github.jfcloud.excel.editor.docdeal.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class Crosconfig implements WebMvcConfigurer {
+
+
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+//                .allowedOrigins("*")
+                .allowedOriginPatterns("*")
+                .allowCredentials(true)
+                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
+                .maxAge(3600);
+    }
+
+    /**
+     * 配置restTemplate工具类
+     * @return
+     */
+    @Bean
+    public RestTemplate restTemplate(){
+        return new RestTemplate();
+    }
+}

+ 72 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/config/LogAspect.java

@@ -0,0 +1,72 @@
+package com.github.jfcloud.excel.editor.docdeal.config;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 接口日志切面配置
+ *
+ * @author zj
+ * @date 2022/6/7
+ **/
+@Aspect
+@Component
+@Order(0)
+@Slf4j
+@RequiredArgsConstructor
+public class LogAspect {
+
+    /**
+     * 操作版本号
+     */
+    private final String operVer ="ONLY.12";
+
+    /**
+     * 设置web日志切入点
+     */
+    @Pointcut("execution(public * com.github.jfcloud.excel.editor.docdeal.controller.*.*(..))")
+    public void webLog() {
+    }
+
+    @Before("webLog()")
+    public void deBefore(JoinPoint joinPoint) {
+        // 接收到请求,记录请求内容
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
+        // 记录下请求内容
+        log.info("=======================================================================");
+        log.info("VERSION : " + operVer);
+        log.info("URL : " + request.getRequestURL().toString());
+        log.info("HTTP_METHOD : " + request.getMethod());
+        //网关代理,Ip始终是zuul网关ip
+        log.info("IP : " + request.getRemoteAddr());
+        log.info("方法调用位置 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
+        String param = Arrays.toString(joinPoint.getArgs());
+        log.info("参数 : " + param);
+    }
+
+    @AfterReturning(returning = "ret", pointcut = "webLog()")
+    public void doAfterReturning(Object ret) {
+        // 处理完请求,返回内容
+        log.info("方法的返回值 : " + JSON.toJSONString(ret));
+    }
+
+}

+ 33 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/config/MvcConfiguration.java

@@ -0,0 +1,33 @@
+package com.github.jfcloud.excel.editor.docdeal.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.thymeleaf.spring5.view.ThymeleafViewResolver;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class MvcConfiguration implements WebMvcConfigurer {
+
+    @Resource(name="thymeleafViewResolver")
+    private ThymeleafViewResolver thymeleafViewResolver;
+    @Value("${files.docservice.url.site}")
+    private String documentServerHost;
+    @Value("${files.docservice.url.api}")
+    private String documentServerApiJs;
+    @Override
+    public void configureViewResolvers(ViewResolverRegistry registry) {
+        if (thymeleafViewResolver != null) {
+            Map<String, Object> vars = new HashMap<>(8);
+            vars.put("documentServerApiJs", documentServerHost+documentServerApiJs);
+//            System.out.println( String.format(documentServerApiJs, documentServerHost));
+//            System.out.println( String.format(documentServerApiJs, documentServerHost));
+            // 静态参数,只取一次值
+            thymeleafViewResolver.setStaticVariables(vars);
+        }
+    }
+}

+ 45 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/config/OssAutoConfiguration.java

@@ -0,0 +1,45 @@
+package com.github.jfcloud.excel.editor.docdeal.config;
+
+import com.github.jfcloud.excel.editor.docdeal.oss.OssProperties;
+import com.github.jfcloud.excel.editor.docdeal.oss.http.OssEndpoint;
+import com.github.jfcloud.excel.editor.docdeal.oss.service.OssTemplate;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author zj
+ * @date 2023-4-25
+ */
+@Configuration
+@EnableConfigurationProperties({OssProperties.class})
+public class OssAutoConfiguration {
+    private final OssProperties properties;
+
+    @Bean
+    @ConditionalOnMissingBean({OssTemplate.class})
+    @ConditionalOnProperty(
+            name = {"oss.enable"},
+            havingValue = "true",
+            matchIfMissing = true
+    )
+    public OssTemplate ossTemplate() {
+        return new OssTemplate(this.properties);
+    }
+
+    @Bean
+    @ConditionalOnProperty(
+            name = {"oss.info"},
+            havingValue = "true"
+    )
+    public OssEndpoint ossEndpoint(OssTemplate template) {
+        return new OssEndpoint(template);
+    }
+
+    public OssAutoConfiguration(OssProperties properties) {
+        this.properties = properties;
+    }
+}
+

+ 51 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/DocumentConstants.java

@@ -0,0 +1,51 @@
+package com.github.jfcloud.excel.editor.docdeal.constant;
+
+import java.time.Duration;
+
+/**
+ * @author: zhangcx
+ * @date: 2019/8/7 17:23
+ */
+public class DocumentConstants {
+    public static final String HTTP_SCHEME = "http";
+    /**
+     * 支持的文档类型
+     */
+    public static final String[] FILE_TYPE_SUPPORT_VIEW = {"doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "pdf"};
+
+    /**
+     * 不支持编辑的类型
+     */
+    public static final String[] FILE_TYPE_UNSUPPORT_EDIT = {"pdf"};
+
+    /**
+     * 文档文件下载接口地址
+     */
+    public static final String OFFICE_API_DOC_FILE = "%s/download%s";
+    /**
+     * 文档信息获取地址
+     */
+    public static final String OFFICE_API_DOC = "%s/api/doc/%s";
+    /**
+     * 编辑回调地址
+     */
+    public static final String OFFICE_API_CALLBACK = "%s/callback";
+    /**
+     * 预览地址
+     */
+    public static final String OFFICE_VIEWER = "%s/viewer/%s";
+    /**
+     * 编辑地址
+     */
+    public static final String OFFICE_EDITOR = "%s/editor/%s";
+    /**
+     * 文档redis缓存前缀 格式化
+     */
+    public static final String DOCUMENT_REDIS_KEY_PREFIX_FORMAT = "onlyoffice:document:%s";
+    /**
+     * 缓存过期时间: 1天
+     */
+    public static final Duration CACHE_DURATION = Duration.ofDays(1);
+
+    public static final String HASH_KEY = "lezhixing";
+}

+ 48 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/DocumentStatus.java

@@ -0,0 +1,48 @@
+package com.github.jfcloud.excel.editor.docdeal.constant;
+
+/**
+ * 文档的状态:
+ *
+ 0 - 找不到带有密钥标识符的文档,
+ 1 - 正在编辑文档,
+ 2 - 文件已准备好保存,
+ 3 - 发生了文档保存错误,
+ 4 - 文件关闭,没有变化,
+ 6 - 正在编辑文档,但保存当前文档状态,
+ 7 - 强制保存文档时发生错误。
+ * @author: zhangcx
+ * @date: 2019/8/23 14:04
+ */
+public enum DocumentStatus {
+    NOT_FOUND(0, "找不到带有密钥标识符的文档"),
+    BEING_EDITED(1, "正在编辑文档"),
+    READY_FOR_SAVING(2, "文件已准备好保存"),
+    SAVING_ERROR(3, "发生了文档保存错误"),
+    CLOSED_WITH_NO_CHANGES(4, "文件关闭,没有变化"),
+    BEING_EDITED_STATE_SAVED(6, "正在编辑文档,但保存当前文档状态"),
+    FORCE_SAVING_ERROR(7, "强制保存文档时发生错误");
+
+    private int code;
+    private String msg;
+
+    DocumentStatus(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    @Override
+    public String toString() {
+        return "DocumentStatus{" +
+                "code='" + code + '\'' +
+                ", msg='" + msg + '\'' +
+                '}';
+    }
+}

+ 75 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/ErrorCodeEnum.java

@@ -0,0 +1,75 @@
+package com.github.jfcloud.excel.editor.docdeal.constant;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+
+/**
+ * 错误码枚举
+ * 约定 code 为 0 表示操作成功,
+ * 1 或 2 等正数表示软件错误,
+ * -1, -2 等负数表示系统错误.
+ * @author: zhangcx
+ * @date: 2019/8/12 13:18
+ */
+public enum ErrorCodeEnum {
+    SUCCESS("0", "success"),
+
+    DOC_FILE_NOT_EXISTS("1001", "目标文档不存在"),
+    DOC_FILE_EMPTY("1002", "目标文档是目录或空文件"),
+    DOC_FILE_UNREADABLE("1003", "目标文档不可读"),
+    DOC_FILE_OVERSIZE("1004", "目标文档大小超过限制"),
+    DOC_FILE_TYPE_UNSUPPORTED("1005", "目标文档格式不正确"),
+    DOC_FILE_MD5_ERROR("1006", "目标文档md5校验失败"),
+    DOC_FILE_MIME_ERROR("1007", "目标文档MIME检查失败"),
+    DOC_FILE_NO_EXTENSION("1008", "文件路径不包含扩展名"),
+    DOC_FILE_EXTENSION_NOT_MATCH("1009", "文件路径和名称后缀不匹配"),
+    DOC_FILE_KEY_ERROR("1010", "目标文档key计算失败"),
+
+    DOC_CACHE_ERROR("1101", "文档信息缓存失败"),
+    DOC_CACHE_NOT_EXISTS("1102", "从缓存中获取文档信息失败"),
+
+    UNSUPPORTED_REQUEST_METHOD("1201", "不支持的请求类型"),
+
+    SYSTEM_UNKNOWN_ERROR("-1", "系统繁忙,请稍后再试...");
+
+    private String code;
+    private String msg;
+
+    ErrorCodeEnum(String code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    @Override
+    public String toString() {
+        return "ErrorCodeEnum{" +
+                "code='" + code + '\'' +
+                ", msg='" + msg + '\'' +
+                '}';
+    }
+
+    public boolean isSuccessful() {
+        return this.code == ErrorCodeEnum.SUCCESS.getCode();
+    }
+
+    public boolean isFailed() {
+        return !isSuccessful();
+    }
+
+    public static void main(String[] args) {
+        System.out.println("| 代码  | 描述   |");
+        System.out.println("| ---- | ---- |");
+        Arrays.stream(ErrorCodeEnum.values()).forEach((ce) -> {
+            System.out.println("| " + StringUtils.rightPad(ce.getCode(), 4) + " | " + ce.getMsg() + " |");
+        });
+    }
+}

+ 30 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/constant/OpenModeEnum.java

@@ -0,0 +1,30 @@
+package com.github.jfcloud.excel.editor.docdeal.constant;
+
+/**
+ * 文档打开方式 枚举
+ * @author: zhangcx
+ * @date: 2019/8/7 16:41
+ */
+public enum OpenModeEnum {
+    /** 拒绝打开(无法打开、不支持类型等) */
+    REFUSE(0),
+    /** 预览 */
+    VIEW(1),
+    /** 编辑 */
+    EDIT(2);
+
+    private int id;
+
+    OpenModeEnum(int id) {
+        this.id = id;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return this.name().toLowerCase();
+    }
+}

+ 325 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/controller/FileController.java

@@ -0,0 +1,325 @@
+package com.github.jfcloud.excel.editor.docdeal.controller;
+
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.github.jfcloud.excel.editor.docdeal.bean.Document;
+import com.github.jfcloud.excel.editor.docdeal.bean.FileUpload;
+import com.github.jfcloud.excel.editor.docdeal.constant.DocumentConstants;
+import com.github.jfcloud.excel.editor.docdeal.constant.ErrorCodeEnum;
+import com.github.jfcloud.excel.editor.docdeal.exception.DocumentException;
+import com.github.jfcloud.excel.editor.docdeal.oss.OssProperties;
+import com.github.jfcloud.excel.editor.docdeal.oss.service.OssTemplate;
+import com.github.jfcloud.excel.editor.docdeal.service.DocumentService;
+import com.github.jfcloud.excel.editor.docdeal.service.FileUploadService;
+import com.github.jfcloud.excel.editor.docdeal.utils.FileUtil;
+import com.github.jfcloud.excel.editor.docdeal.utils.Md5Utils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.hashids.Hashids;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+@Slf4j
+@Controller
+public class FileController {
+    @Value("${files.savePath}")
+    private String filePath;
+    @Value("${files.docservice.url.site}")
+    private String officeUrl;
+    @Value("${files.docservice.url.command}")
+    private String officeCommand;
+    @Autowired
+    private DocumentService documentService;
+    @Autowired
+    private FileUploadService uploadService;
+    @Autowired
+    RestTemplate restTemplate;
+    @Autowired
+    OssTemplate ossTemplate;
+    @Autowired
+    OssProperties ossProperties;
+
+
+    @ResponseBody
+    @GetMapping("/syncOss")
+    @Transactional(rollbackFor = Exception.class)
+    public ResponseEntity<Object> syncOss(@RequestParam(required = false) String bucket,@RequestParam String objectName) {
+        int count = uploadService.count(new LambdaQueryWrapper<FileUpload>()
+                .eq(FileUpload::getFile_name, objectName));
+        if(count>=1){
+            return new ResponseEntity<>(objectName, HttpStatus.OK);
+        }
+        String fileName = FilenameUtils.getName(objectName);
+        if(new File(filePath+fileName).exists()){
+            fileName = FilenameUtils.getBaseName(objectName)+"_"+System.currentTimeMillis()+"."+FilenameUtils.getExtension(objectName);
+        }
+        try(InputStream is = ossTemplate.getObject(StringUtils.isNotBlank(bucket)?bucket:ossProperties.getBucketName(),objectName).getObjectContent();
+            OutputStream ous=new FileOutputStream(filePath+ fileName)) {
+            IOUtils.copy(is,ous);
+            FileUpload upload = new FileUpload();
+            upload.setUpload_date(new Date());
+            upload.setFile_type(fileName.substring(fileName.indexOf(".")));
+            upload.setFile_path(filePath);
+            upload.setFile_name(fileName);
+            upload.setFile_size(0L);
+            uploadService.save(upload);
+            return new ResponseEntity<>(upload, HttpStatus.OK);
+        }catch (Exception e){
+            log.error("文件同步失败",e);
+            throw new RuntimeException();
+        }
+    }
+
+
+
+    @ResponseBody
+    @PostMapping(value = "upload")
+    public ResponseEntity<Object> upload(MultipartFile file, HttpServletRequest request) throws Exception {
+        if (file.isEmpty()) {
+            throw new Exception("上传文件不能为空");
+        }
+        FileUpload upload = new FileUpload();
+        String fileName = file.getOriginalFilename();
+//        if (!fileName.endsWith("xls") && !fileName.endsWith("xlsx")) {
+//            throw new Exception("请上传Excel文件");
+//        }
+        //更新保存文件信息到数据库
+        try(InputStream is = file.getInputStream();OutputStream osm = new FileOutputStream(filePath + file.getOriginalFilename());){
+            FileUtil.saveFile(file.getInputStream(), osm);
+        }
+//        log.info(fileName);
+        upload.setUpload_date(new Date());
+        log.info("{}",".".indexOf(fileName));
+        log.info("{}",fileName.length());
+        upload.setFile_type(fileName.substring(fileName.indexOf(".")));
+        upload.setFile_path(filePath);
+        upload.setFile_name(file.getOriginalFilename());
+        upload.setFile_size(file.getSize());
+        uploadService.save(upload);
+        //操作人
+//        String operator=request.getAttribute(StrUtil.USER_WORKNUMBER).toString();
+//        xxxService.saveUploadCkdExecl(file,operator);
+
+        return new ResponseEntity<Object>("上传成功", HttpStatus.OK);
+
+    }
+
+    /**
+     * \
+     * 查询所有上传文档信息接口
+     *
+     * @return
+     */
+    @GetMapping("/filelist")
+    public ResponseEntity<Object> listFile() {
+
+        return new ResponseEntity<Object>(uploadService.list(), HttpStatus.OK);
+    }
+
+    /**
+     * 下载文档接口
+     * @param name
+     * @param response
+     */
+    @GetMapping("/download")
+    public void download(String name, HttpServletResponse response) {
+        try {
+            FileUtil.downLoadFile(name,filePath,response);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @GetMapping("/review")
+    public String reviewDocFile(@RequestParam(required = false) String bucket, @RequestParam(required = false) String title,
+                                @RequestParam String name, String userName, String userId, Model model) {
+        syncOss(bucket, name);
+        String path = filePath + name;
+//        String name = "cc.docx";
+        Document document = documentService.getDocument(documentService.buildDocument(path, name));
+        if (org.springframework.util.StringUtils.hasLength(title)) {
+            document.setTitle(title);
+        }
+        Map<String, Object> map = new HashMap<>();
+        map.put("chat", true);
+        map.put("comment", true);
+        map.put("copy", true);
+        map.put("download", true);
+        map.put("edit", false);
+        map.put("fillForms", false);
+        map.put("modifyContentControl", true);
+        map.put("modifyFilter", true);
+        map.put("print", true);
+        map.put("review", false);
+        map.put("reviewGroups", null);
+        map.put("commentGroups", null);
+        map.put("userInfoGroups", null);
+        document.setPermissions(map);
+        model.addAttribute("document", document);
+        // 如果该格式不支持编辑,则返回预览页面
+        if (!documentService.canEdit(document)) {
+            return "/demo";
+        }
+        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userId, userName, name));
+        return "/viewer";
+    }
+
+    @GetMapping("/reviewExcel")
+    public String reviewExcel(@RequestParam(required = false) String bucket, @RequestParam(required = false) String title,
+                              @RequestParam String name, String userName, String userId, Model model) {
+        syncOss(bucket, name);
+        String path = filePath + name;
+//        String name = "cc.docx";
+        Document document = documentService.getDocument(documentService.buildDocument(path, name));
+        if (org.springframework.util.StringUtils.hasLength(title)) {
+            document.setTitle(title);
+        }
+        model.addAttribute("document", document);
+        // 如果该格式不支持编辑,则返回预览页面
+        if (!documentService.canEdit(document)) {
+            return "/demo";
+        }
+        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userId, userName, name));
+        return "/viewerExcel";
+    }
+
+    @GetMapping("/edit")
+    public String editDocFile(@RequestParam(required = false) String bucket, @RequestParam(required = false) String title,
+                              @RequestParam String name, String userName, String userId, Model model) {
+        syncOss(bucket, name);
+        String path = filePath + name;
+//        String name = "cc.docx";
+        Document document = documentService.getDocument(documentService.buildDocument(path, name));
+        if (org.springframework.util.StringUtils.hasLength(title)) {
+            document.setTitle(title);
+        }
+        model.addAttribute("document", document);
+        // 如果该格式不支持编辑,则返回预览页面
+        if (!documentService.canEdit(document)) {
+            return "/demo";
+        }
+        model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userId, userName, name));
+        return "/editor";
+    }
+
+
+    /**
+     * 编辑文档时回调接口
+     * @param request
+     * @param response
+     * @throws IOException
+     */
+    @RequestMapping("/callback")
+    public void saveDocumentFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        //处理编辑回调逻辑
+        callBack(request, response);
+        String fileName = request.getParameter("fileName");
+        try(InputStream is = new FileInputStream(filePath+ fileName)) {
+            ossTemplate.putObject(ossProperties.getBucketName(),fileName,is);
+            log.info("修改文件{}同步至oss成功",fileName);
+        }catch (Exception e){
+            log.error("文件同步至oss失败",e);
+            throw new RuntimeException();
+        }
+    }
+
+    /**
+     *
+     * @return
+     */
+    @GetMapping("/editStatus")
+    public  ResponseEntity<Object> getDoucmentEditStatus(String name) throws ParseException {
+        String url = officeUrl+officeCommand;
+        Map<String,String>  map = new HashMap<String,String>();
+        map.put("c", "forcesave");
+        String docFileMd5 = Md5Utils.getFileMd5(new File(filePath+name));
+        if (StringUtils.isBlank(docFileMd5)) {
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_MD5_ERROR);
+        }
+        String pathShortMd5 = Md5Utils.md5(filePath + name);
+        String nameShortMd5 = Md5Utils.md5(name);
+        Hashids hashids = new Hashids(DocumentConstants.HASH_KEY);
+        // (将路径字符串短md5值 + 名称字符串短md5值) ==> 再转成短id形式 ==> 作为文档的key(暂且认为是不会重复的)
+        String key = hashids.encodeHex(String.format("%s%s%s", docFileMd5,pathShortMd5, nameShortMd5));
+        map.put("key", key);
+        map.put("userdata", "sample userdata");
+        JSONObject obj = (JSONObject) new JSONParser().parse(FileUtil.editStatus(url, JSON.toJSONString(map)));
+        return new ResponseEntity<Object>(obj, HttpStatus.OK);
+
+    }
+    /**
+     * 处理在线编辑文档的逻辑
+     * @param request
+     * @param response
+     * @throws IOException
+     */
+    private void callBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        PrintWriter writer = null;
+        JSONObject jsonObj = null;
+        log.info("===saveeditedfile------------");
+
+        try {
+            writer = response.getWriter();
+            Scanner scanner = new Scanner(request.getInputStream()).useDelimiter("\\A");
+            String body = scanner.hasNext() ? scanner.next() : "";
+            jsonObj = (JSONObject) new JSONParser().parse(body);
+            log.info("{}",jsonObj);
+            log.info("===saveeditedfile:" + jsonObj.get("status"));
+	            /*
+	                0 - no document with the key identifier could be found,
+	                1 - document is being edited,
+	                2 - document is ready for saving,
+	                3 - document saving error has occurred,
+	                4 - document is closed with no changes,
+	                6 - document is being edited, but the current document state is saved,
+	                7 - error has occurred while force saving the document.
+	             * */
+            if ((long) jsonObj.get("status") == 2) {
+                FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
+            }
+        } catch (IOException | ParseException e) {
+            e.printStackTrace();
+        }
+        /*
+         * status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明
+         * 在线编辑还没有关闭,前端有人下载文档时,强制保存最新内容  当status 是6时说明有人在编辑时下载文档
+         * */
+        log.info("{}",jsonObj.get("status"));
+        if ((long) jsonObj.get("status") == 6) {
+            //处理当文档正在编辑为关闭时,下载文档
+            if (((String)jsonObj.get("userdata")).equals("sample userdata")){
+                FileUtil.callBackSaveDocument(jsonObj,filePath,request, response);
+            }
+
+            log.info("====保存失败:");
+            writer.write("{\"error\":1}");
+        } else {
+            //执行删除编辑时下载保存的文件:
+            FileUtil.deleteTempFile(filePath,request.getParameter("fileName"));
+            writer.write("{\"error\":0}");
+        }
+    }
+
+}

+ 26 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/exception/DocumentException.java

@@ -0,0 +1,26 @@
+package com.github.jfcloud.excel.editor.docdeal.exception;
+
+
+import com.github.jfcloud.excel.editor.docdeal.constant.ErrorCodeEnum;
+
+/**
+ * 文档异常封装类
+ * @author zhangcx
+ */
+public final class DocumentException extends RuntimeException {
+    private ErrorCodeEnum errorCode;
+
+    public DocumentException(ErrorCodeEnum errorCode) {
+        super(errorCode.getMsg());
+        this.errorCode = errorCode;
+    }
+
+    public DocumentException(ErrorCodeEnum errorCode, Throwable t) {
+        super(errorCode.getMsg(), t);
+        this.errorCode = errorCode;
+    }
+
+    public ErrorCodeEnum getErrorCode() {
+        return errorCode;
+    }
+}

+ 33 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/exception/WebExceptionHandler.java

@@ -0,0 +1,33 @@
+package com.github.jfcloud.excel.editor.docdeal.exception;
+
+
+import com.github.jfcloud.excel.editor.docdeal.bean.DocumentResponse;
+import com.github.jfcloud.excel.editor.docdeal.constant.ErrorCodeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+@ControllerAdvice
+@ResponseBody
+@Slf4j
+public class WebExceptionHandler {
+    @ExceptionHandler(DocumentException.class)
+    public DocumentResponse documentException(DocumentException e) {
+        log.error("$$$ 文档异常~~", e);
+        return DocumentResponse.failue(e.getErrorCode());
+   }
+
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public DocumentResponse requestException(HttpRequestMethodNotSupportedException e) {
+        log.error("$$$ 不支持的请求类型~~", e);
+        return DocumentResponse.failue(ErrorCodeEnum.UNSUPPORTED_REQUEST_METHOD);
+    }
+
+    @ExceptionHandler(Exception.class)
+    public DocumentResponse unknownException(Exception e) {
+        log.error("$$$ 未知异常~~", e);
+        return DocumentResponse.failue(ErrorCodeEnum.SYSTEM_UNKNOWN_ERROR);
+    }
+}

+ 8 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/mapper/FileUploadMapper.java

@@ -0,0 +1,8 @@
+package com.github.jfcloud.excel.editor.docdeal.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.github.jfcloud.excel.editor.docdeal.bean.FileUpload;
+
+public interface FileUploadMapper extends  BaseMapper<FileUpload>{
+
+}

+ 26 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/OssProperties.java

@@ -0,0 +1,26 @@
+package com.github.jfcloud.excel.editor.docdeal.oss;
+
+/**
+ * @author zj
+ * @date 2023-4-25
+ */
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "oss")
+@Data
+public class OssProperties {
+    private String endpoint;
+    private String customDomain;
+    private Boolean pathStyleAccess = true;
+    private String appId;
+    private String region;
+    private String accessKey;
+    private String secretKey;
+    private String bucketName = "payerp";
+    private Integer maxConnections = 100;
+
+    public String toString() {
+        return "OssProperties(endpoint=" + this.getEndpoint() + ", customDomain=" + this.getCustomDomain() + ", pathStyleAccess=" + this.getPathStyleAccess() + ", appId=" + this.getAppId() + ", region=" + this.getRegion() + ", accessKey=" + this.getAccessKey() + ", secretKey=" + this.getSecretKey() + ", bucketName=" + this.getBucketName() + ", maxConnections=" + this.getMaxConnections() + ")";
+    }
+}

+ 84 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/http/OssEndpoint.java

@@ -0,0 +1,84 @@
+package com.github.jfcloud.excel.editor.docdeal.oss.http;
+
+import com.amazonaws.services.s3.model.Bucket;
+import com.amazonaws.services.s3.model.S3Object;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+import com.github.jfcloud.excel.editor.docdeal.oss.service.OssTemplate;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author zj
+ * @date 2023-4-25
+ */
+@RestController
+@RequestMapping({"/oss"})
+public class OssEndpoint {
+    private final OssTemplate template;
+
+    @PostMapping({"/bucket/{bucketName}"})
+    public Bucket createBucker(@PathVariable String bucketName) {
+        this.template.createBucket(bucketName);
+        return this.template.getBucket(bucketName).get();
+    }
+
+    @GetMapping({"/bucket"})
+    public List<Bucket> getBuckets() {
+        return this.template.getAllBuckets();
+    }
+
+    @GetMapping({"/bucket/{bucketName}"})
+    public Bucket getBucket(@PathVariable String bucketName) {
+        return this.template.getBucket(bucketName).orElseThrow(() -> new IllegalArgumentException("Bucket Name not found!"));
+    }
+
+    @DeleteMapping({"/bucket/{bucketName}"})
+    @ResponseStatus(HttpStatus.ACCEPTED)
+    public void deleteBucket(@PathVariable String bucketName) {
+        this.template.removeBucket(bucketName);
+    }
+
+    @PostMapping({"/object/{bucketName}"})
+    public S3Object createObject(@RequestBody MultipartFile object, @PathVariable String bucketName) throws Exception {
+        String name = object.getOriginalFilename();
+        this.template.putObject(bucketName, name, object.getInputStream(), object.getSize(), object.getContentType());
+        return this.template.getObjectInfo(bucketName, name);
+    }
+
+    @PostMapping({"/object/{bucketName}/{objectName}"})
+    public S3Object createObject(@RequestBody MultipartFile object, @PathVariable String bucketName, @PathVariable String objectName) throws Exception {
+        this.template.putObject(bucketName, objectName, object.getInputStream(), object.getSize(), object.getContentType());
+        return this.template.getObjectInfo(bucketName, objectName);
+    }
+
+    @GetMapping({"/object/{bucketName}/{objectName}"})
+    public List<S3ObjectSummary> filterObject(@PathVariable String bucketName, @PathVariable String objectName) {
+        return this.template.getAllObjectsByPrefix(bucketName, objectName, true);
+    }
+
+    @GetMapping({"/object/{bucketName}/{objectName}/{expires}"})
+    public Map<String, Object> getObject(@PathVariable String bucketName, @PathVariable String objectName, @PathVariable Integer expires) {
+        Map<String, Object> responseBody = new HashMap(8);
+        responseBody.put("bucket", bucketName);
+        responseBody.put("object", objectName);
+        responseBody.put("url", this.template.getObjectURL(bucketName, objectName, expires));
+        responseBody.put("expires", expires);
+        return responseBody;
+    }
+
+    @ResponseStatus(HttpStatus.ACCEPTED)
+    @DeleteMapping({"/object/{bucketName}/{objectName}/"})
+    public void deleteObject(@PathVariable String bucketName, @PathVariable String objectName) throws Exception {
+        this.template.removeObject(bucketName, objectName);
+    }
+
+    public OssEndpoint(OssTemplate template) {
+        this.template = template;
+    }
+}

+ 142 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/oss/service/OssTemplate.java

@@ -0,0 +1,142 @@
+package com.github.jfcloud.excel.editor.docdeal.oss.service;
+
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import com.amazonaws.services.s3.model.*;
+import com.amazonaws.util.IOUtils;
+import com.github.jfcloud.excel.editor.docdeal.oss.OssProperties;
+import org.springframework.beans.factory.InitializingBean;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * @author zj
+ * @date 2023-4-25
+ */
+public class OssTemplate implements InitializingBean {
+    private final OssProperties ossProperties;
+    private AmazonS3 amazonS3;
+
+    public void createBucket(String bucketName) {
+        try {
+            if (!this.amazonS3.doesBucketExistV2(bucketName)) {
+                this.amazonS3.createBucket(bucketName);
+            }
+
+        } catch (Throwable var3) {
+            throw var3;
+        }
+    }
+
+    public List<Bucket> getAllBuckets() {
+        try {
+            return this.amazonS3.listBuckets();
+        } catch (Throwable var2) {
+            throw var2;
+        }
+    }
+
+    public Optional<Bucket> getBucket(String bucketName) {
+        try {
+            return this.amazonS3.listBuckets().stream().filter((b) -> {
+                return b.getName().equals(bucketName);
+            }).findFirst();
+        } catch (Throwable var3) {
+            throw var3;
+        }
+    }
+
+    public void removeBucket(String bucketName) {
+        try {
+            this.amazonS3.deleteBucket(bucketName);
+        } catch (Throwable var3) {
+            throw var3;
+        }
+    }
+
+    public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
+        try {
+            ObjectListing objectListing = this.amazonS3.listObjects(bucketName, prefix);
+            return new ArrayList(objectListing.getObjectSummaries());
+        } catch (Throwable var5) {
+            throw var5;
+        }
+    }
+
+    public String getObjectURL(String bucketName, String objectName, Integer expires) {
+        try {
+            Date date = new Date();
+            Calendar calendar = new GregorianCalendar();
+            calendar.setTime(date);
+            calendar.add(5, expires);
+            URL url = this.amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
+            return url.toString();
+        } catch (Throwable var7) {
+            throw var7;
+        }
+    }
+
+    public S3Object getObject(String bucketName, String objectName) {
+        try {
+            return this.amazonS3.getObject(bucketName, objectName);
+        } catch (Throwable var4) {
+            throw var4;
+        }
+    }
+
+    public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
+        this.putObject(bucketName, objectName, stream, (long)stream.available(), "application/octet-stream");
+    }
+
+    public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception {
+        byte[] bytes = IOUtils.toByteArray(stream);
+        ObjectMetadata objectMetadata = new ObjectMetadata();
+        objectMetadata.setContentLength(size);
+        objectMetadata.setContentType(contextType);
+        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
+        return this.amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);
+    }
+
+    public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {
+        S3Object object = this.amazonS3.getObject(bucketName, objectName);
+
+        S3Object var4;
+        try {
+            var4 = object;
+        } finally {
+            if (Collections.singletonList(object).get(0) != null) {
+                object.close();
+            }
+
+        }
+
+        return var4;
+    }
+
+    public void removeObject(String bucketName, String objectName) throws Exception {
+        this.amazonS3.deleteObject(bucketName, objectName);
+    }
+
+    public void afterPropertiesSet() {
+        ClientConfiguration clientConfiguration = new ClientConfiguration();
+        clientConfiguration.setMaxConnections(this.ossProperties.getMaxConnections());
+        AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(this.ossProperties.getEndpoint(), this.ossProperties.getRegion());
+        AWSCredentials awsCredentials = new BasicAWSCredentials(this.ossProperties.getAccessKey(), this.ossProperties.getSecretKey());
+        AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
+        this.amazonS3 = (AmazonS3)((AmazonS3ClientBuilder)((AmazonS3ClientBuilder)((AmazonS3ClientBuilder)((AmazonS3ClientBuilder)((AmazonS3ClientBuilder) AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)).withClientConfiguration(clientConfiguration)).withCredentials(awsCredentialsProvider)).disableChunkedEncoding()).withPathStyleAccessEnabled(this.ossProperties.getPathStyleAccess())).build();
+    }
+
+    public OssTemplate(OssProperties ossProperties) {
+        this.ossProperties = ossProperties;
+    }
+}

+ 71 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/service/DocumentService.java

@@ -0,0 +1,71 @@
+package com.github.jfcloud.excel.editor.docdeal.service;
+
+
+
+import com.github.jfcloud.excel.editor.docdeal.bean.Document;
+import com.github.jfcloud.excel.editor.docdeal.bean.DocumentEditParam;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 文档业务 接口
+ * @author: zhangcx
+ * @date: 2019/8/7 16:30
+ */
+public interface DocumentService {
+    /**
+     * 构建文档对象
+     * @param filePath
+     * @param fileName
+     * @return documentKey 文档key
+     */
+    String buildDocument(String filePath, String fileName);
+
+    /**
+     * 从缓从中获取文档信息
+     * @param documentKey
+     * @return
+     */
+    Document getDocument(String documentKey);
+
+    /**
+     * 下载文档实体文件
+     * @param documentKey
+     * @param request
+     * @param response
+     * @throws IOException
+     */
+    void downloadDocumentFile(String documentKey, HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+    /**
+     * 构建文档编辑参数 对象
+     *
+     * @param userId
+     * @param userName
+     * @return
+     */
+    DocumentEditParam buildDocumentEditParam(String userId, String userName, String fileName);
+
+    /**
+     * 编辑后保存文档实体文件
+     * @param documentKey
+     * @param downloadUrl
+     * @throws IOException
+     */
+    boolean saveDocumentFile(String documentKey, String downloadUrl) throws IOException;
+
+    /**
+     * 获取服务暴露的host(包含端口)
+     * @return
+     */
+    Object getServerHost();
+
+    /**
+     * 文档是否支持编辑
+     * @param document
+     * @return
+     */
+    boolean canEdit(Document document);
+}

+ 14 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/service/FileUploadService.java

@@ -0,0 +1,14 @@
+package com.github.jfcloud.excel.editor.docdeal.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.jfcloud.excel.editor.docdeal.bean.FileUpload;
+
+import java.util.List;
+
+public interface FileUploadService extends IService<FileUpload> {
+
+
+    List<FileUpload> list();
+
+
+}

+ 306 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/service/impl/DocumentServiceImpl.java

@@ -0,0 +1,306 @@
+package com.github.jfcloud.excel.editor.docdeal.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.github.jfcloud.excel.editor.docdeal.bean.Document;
+import com.github.jfcloud.excel.editor.docdeal.bean.DocumentEditParam;
+import com.github.jfcloud.excel.editor.docdeal.constant.DocumentConstants;
+import com.github.jfcloud.excel.editor.docdeal.constant.ErrorCodeEnum;
+import com.github.jfcloud.excel.editor.docdeal.exception.DocumentException;
+import com.github.jfcloud.excel.editor.docdeal.service.DocumentService;
+import com.github.jfcloud.excel.editor.docdeal.utils.Md5Utils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.hashids.Hashids;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+/**
+ * 文档相关业务方法
+ *
+ * @author: zhangcx
+ * @date: @date: 2019/8/7 16:30
+ */
+@Slf4j
+@Service
+public class DocumentServiceImpl implements DocumentService {
+    @Autowired
+    Environment environment;
+    @Value("${document.server.host}")
+    private String serverHost ;
+    /**
+     * 大小限制,默认10M,单位kb
+     */
+    @Value("${document.file-size.limit:1048576000}")
+    private Long docFileSizeLimit;
+    @Value("${files.docservice.url.site}")
+    private String documentServerHost;
+    @Value("${files.docservice.url.api}")
+    private String documentServerApiJs;
+   // @Autowired
+    //private DocumentCacheService cacheService;
+
+    @Override
+    public String buildDocument(String filePath, String fileName) {
+        if (StringUtils.isBlank(filePath)) {
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_NOT_EXISTS);
+        }
+        filePath = FilenameUtils.normalize(filePath);
+        String fileType = StringUtils.lowerCase(FilenameUtils.getExtension(filePath));
+        if (StringUtils.isBlank(fileType)) {
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_NO_EXTENSION);
+        }
+        // 如果指定了文件名,则需要校验和实体文件格式是否一致
+        if (StringUtils.isNotBlank(fileName) && !fileType.equalsIgnoreCase(FilenameUtils.getExtension(fileName))) {
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_EXTENSION_NOT_MATCH);
+        }
+        File docFile = new File(filePath);
+        // 校验文件实体
+        preFileCheck(docFile);
+        fileName = StringUtils.isNotBlank(fileName) ? fileName : docFile.getName();
+        String fileKey = this.fileKey(docFile, fileName);
+        Document document = Document.builder()
+                                .fileType(fileType)
+                                .title(fileName)
+                                .storage(filePath)
+                                .build();
+        boolean cached = false;
+//        try {
+//            cached = cacheService.put(fileKey, document);
+//        } catch (Exception e) {
+//            log.error("$$$ 缓存失败~~", e);
+//        }
+//        if (!cached) {
+//            throw new DocumentException(ErrorCodeEnum.DOC_CACHE_ERROR);
+//        }
+        document.setKey(fileKey);
+        return JSON.toJSONString(document);
+    }
+
+    @Override
+    public Document getDocument(String documentKey) {
+        Document doc = null;
+
+        try {
+            doc = JSON.parseObject(documentKey,Document.class);
+        } catch (Exception e) {
+            log.error("$$$ 获取缓存失败~~", e);
+        }
+        if (doc == null) {
+            throw new DocumentException(ErrorCodeEnum.DOC_CACHE_NOT_EXISTS);
+        }
+        // 从缓存中取出后,再绑定非必需缓存字段(节省缓存大小)
+//        doc.setKey(documentKey);
+        doc.setUrl(fileUrl(doc.getTitle()));
+        if (log.isDebugEnabled()) {
+            log.info(doc.toString());
+        }
+        log.info(doc.toString());
+        return doc;
+    }
+
+    /**
+     * 计算文件key值: 文件md5值+路径的短md5值+名称的短md5值
+     * @param docFile
+     * @param name 生成协作时文档的docuemnt.key的值
+     * @return
+     */
+    public String fileKey(File docFile, String name) {
+        String docFileMd5 = Md5Utils.getFileMd5(docFile);
+        if (StringUtils.isBlank(docFileMd5)) {
+            log.error("$$$ 构建文件信息失败!计算文件 md5 失败!");
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_MD5_ERROR);
+        }
+        String pathShortMd5 = Md5Utils.md5(docFile.getAbsolutePath());
+        String nameShortMd5 = Md5Utils.md5(name);
+        Hashids hashids = new Hashids(DocumentConstants.HASH_KEY);
+        // (将路径字符串短md5值 + 名称字符串短md5值) ==> 再转成短id形式 ==> 作为文档的key(暂且认为是不会重复的)
+        String key = hashids.encodeHex(String.format("%s%s%s", docFileMd5,pathShortMd5, nameShortMd5));
+        if (StringUtils.isBlank(key)) {
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_KEY_ERROR);
+        }
+        return key;
+    }
+
+    /**
+     * 文件key值
+     * @param fileType
+     * @param docCrc32
+     * @return
+     */
+    private String fileKey(String fileType, String docCrc32) {
+        return String.format("%s_%s", fileType, docCrc32);
+    }
+
+    /**
+     * 文件url地址
+     * @param
+     * @return
+     */
+    private String fileUrl(String filename) {
+        return String.format(DocumentConstants.OFFICE_API_DOC_FILE, getServerHost(), "?name="+filename);
+//        return  "http://192.168.0.58:20053/download?name="+filename;
+    }
+
+    /**
+     * 根据文档信息下载文档文件
+     * @param documentKey
+     * @param request
+     * @param response
+     * @throws IOException
+     */
+    @Override
+    public void downloadDocumentFile(String documentKey, HttpServletRequest request, HttpServletResponse response) throws IOException {
+        Document doc = this.getDocument(documentKey);
+        File file = new File(doc.getStorage());
+        try (InputStream reader = new FileInputStream(file);
+             OutputStream out = response.getOutputStream()) {
+            byte[] buf = new byte[(int) FileUtils.ONE_KB];
+            int len = 0;
+            //response.setContentType(mimeType(file));
+            while ((len = reader.read(buf)) != -1) {
+                out.write(buf, 0, len);
+            }
+            out.flush();
+        } catch (Exception e) {
+            log.error("下载失败!读取文件[" + doc.getStorage() + "]报错~~", e);
+        }
+    }
+
+    @Override
+    public DocumentEditParam buildDocumentEditParam(String userId, String userName, String fileName) {
+        return DocumentEditParam.builder()
+                .callbackUrl(callbackUrl(fileName))
+                .user(DocumentEditParam.UserBean.builder()
+                        .id(userId)
+                        .name(userName)
+                        .build())
+                .build();
+    }
+
+    private String callbackUrl(String fileName) {
+        String format = String.format(DocumentConstants.OFFICE_API_CALLBACK, getServerHost());
+        format=format+"?fileName="+fileName;
+        return format;
+    }
+
+    /**
+     * 上传文档实体文件
+     * @param documentKey
+     * @param downloadUrl
+     * @throws IOException
+     */
+    @Override
+    public boolean saveDocumentFile(String documentKey, String downloadUrl) {
+        if (log.isInfoEnabled()) {
+            log.info(downloadUrl);
+        }
+        // TODO 默认覆盖源文件,如果调用者指定,则存到临时目录?
+        boolean isCover = true;
+        Document doc = this.getDocument(documentKey);
+        String saveFilePath = doc.getStorage();
+//        if (!isCover) {
+//            String baseDir = environment.getProperty("java.io.tmpdir");
+//            saveFilePath = String.format("%s/office-api/%s/%s.%s", baseDir, documentKey, System.currentTimeMillis(), doc.getFileType());
+//        }
+        File saveFile = new File(saveFilePath);
+        boolean success = false;
+        try {
+            FileUtils.copyURLToFile(new URL(downloadUrl), saveFile);
+            if (saveFile.exists() && saveFile.length() > 0) {
+                success = true;
+            }
+        } catch (IOException e) {
+            log.error("$$$ 保存文档失败!", e);
+        }
+        return success;
+        //TODO 编辑成功后,应该删除之前的编辑状态缓存
+    }
+
+    @Override
+    public Object getServerHost() {
+        if (StringUtils.startsWith(serverHost, DocumentConstants.HTTP_SCHEME)) {
+            return serverHost;
+        }
+        return String.format("http://%s", serverHost);
+    }
+
+    @Override
+    public boolean canEdit(Document document) {
+        if (ArrayUtils.contains(DocumentConstants.FILE_TYPE_UNSUPPORT_EDIT, document.getFileType())) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取文档信息api地址
+     * @param docId
+     * @return
+     */
+    private String docInfoUrl(String docId) {
+        return String.format(DocumentConstants.OFFICE_API_DOC, getServerHost(), docId);
+    }
+
+    /**
+     * 获取文件的 mimetype
+     * @param file
+     * @deprecated
+     * @return
+     */
+    @Deprecated
+    private String mimeType(File file) {
+        try {
+            return Files.probeContentType(Paths.get(file.toURI()));
+        } catch (IOException e) {
+            log.error("$$$ 获取文件mimeType错误!", e);
+        }
+        return null;
+    }
+
+    /**
+     * 先校验文档文件
+     * @param docFile
+     * @return
+     */
+    private void preFileCheck(File docFile) {
+        if (log.isDebugEnabled()) {
+            log.debug("### 开始校验文档:[{}]", docFile.getAbsolutePath());
+        }
+        if (docFile == null || !docFile.exists()) {
+            log.error("$$$ 目标文档不存在,无法打开!");
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_NOT_EXISTS);
+        }
+        if (docFile.isDirectory() || docFile.length() <= 0) {
+            log.error("$$$ 目标文档[{}]是目录或空文件,无法打开!", docFile.getAbsolutePath());
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_EMPTY);
+        }
+        if (!docFile.canRead()) {
+            log.error("$$$ 目标文档[{}]不可读,无法打开!", docFile.getAbsolutePath());
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_UNREADABLE);
+        }
+        if (docFile.length() > docFileSizeLimit) {
+            log.error("$$$ 目标文档大小超过限制({}B > {}B),无法打开!", docFile.length(), docFileSizeLimit);
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_OVERSIZE);
+        }
+        String ext = StringUtils.lowerCase(FilenameUtils.getExtension(docFile.getName()));
+        if (!ArrayUtils.contains(DocumentConstants.FILE_TYPE_SUPPORT_VIEW, ext)) {
+            log.error("$$$ 目标文档格式[{}]不正确,无法打开!(只支持:{})",
+                    ext, StringUtils.join(DocumentConstants.FILE_TYPE_SUPPORT_VIEW, ","));
+            throw new DocumentException(ErrorCodeEnum.DOC_FILE_TYPE_UNSUPPORTED);
+        }
+    }
+
+
+}

+ 23 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/service/impl/FileUploadServiceImpl.java

@@ -0,0 +1,23 @@
+package com.github.jfcloud.excel.editor.docdeal.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.jfcloud.excel.editor.docdeal.bean.FileUpload;
+import com.github.jfcloud.excel.editor.docdeal.mapper.FileUploadMapper;
+import com.github.jfcloud.excel.editor.docdeal.service.FileUploadService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class FileUploadServiceImpl extends ServiceImpl<FileUploadMapper, FileUpload> implements FileUploadService {
+    @Autowired
+    FileUploadMapper  uploadMapper;
+
+    @Override
+    public List<FileUpload> list() {
+        return uploadMapper.selectList(null);
+    }
+
+
+}

+ 187 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/utils/FileUtil.java

@@ -0,0 +1,187 @@
+package com.github.jfcloud.excel.editor.docdeal.utils;
+
+import org.apache.commons.io.IOUtils;
+import org.json.simple.JSONObject;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+
+public class FileUtil {
+    /**
+     * 保存文件方法
+     * @param in
+     * @throws Exception
+     */
+    public static void saveFile(InputStream in, OutputStream osm) throws Exception {
+//        FileChannel in = new FileInputStream("src/demo20/data.txt").getChannel(),
+//                out = new FileOutputStream("src/demo20/data2.txt").getChannel();
+//        in.transferTo(0, in.size(), out);
+
+        IOUtils.copy(in, osm);
+
+    }
+
+
+
+    /**
+     * 前端下载文件方法
+     * @param name
+     * @param filePath
+     * @param response
+     * @throws IOException
+     */
+    public static void downLoadFile(String name, String filePath, HttpServletResponse response) throws IOException {
+
+        String path = filePath + name;
+        // path是指想要下载的文件的路径
+        File file = new File(path);
+        //log.info(file.getPath());
+        // 获取文件名
+        String filename = file.getName();
+        // 获取文件后缀名
+        String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
+        System.out.println("文件后缀名:" + ext);
+
+        // 将文件写入输入流
+        FileInputStream fileInputStream = new FileInputStream(file);
+        InputStream fis = new BufferedInputStream(fileInputStream);
+        byte[] buffer = new byte[fis.available()];
+        fis.read(buffer);
+        fis.close();
+
+        // 清空response
+        response.reset();
+        // 设置response的Header
+        response.setCharacterEncoding("UTF-8");
+        //Content-Disposition的作用:告知浏览器以何种方式显示响应返回的文件,用浏览器打开还是以附件的形式下载到本地保存
+        //attachment表示以附件方式下载   inline表示在线打开   "Content-Disposition: inline; filename=文件名.mp3"
+        // filename表示文件的默认名称,因为网络传输只支持URL编码的相关支付,因此需要将文件名URL编码后进行传输,前端收到后需要反编码才能获取到真正的名称
+        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
+        // 告知浏览器文件的大小
+        response.addHeader("Content-Length", "" + file.length());
+        OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
+        response.setContentType("application/octet-stream");
+        outputStream.write(buffer);
+        outputStream.flush();
+    }
+
+    /**
+     * 编辑以后保存文件
+     * @param jsonObj
+     * @param filePath
+     * @param request
+     * @param response
+     * @throws IOException
+     */
+    public static void callBackSaveDocument(JSONObject jsonObj, String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
+        /*
+         * 当我们关闭编辑窗口后,十秒钟左右onlyoffice会将它存储的我们的编辑后的文件,,此时status = 2,通过request发给我们,我们需要做的就是接收到文件然后回写该文件。
+         * */
+        /*
+         * 定义要与文档存储服务保存的编辑文档的链接。当状态值仅等于2或3时,存在链路。
+         * */
+        String downloadUri = (String) jsonObj.get("url");
+        System.out.println("====文档编辑完成,现在开始保存编辑后的文档,其下载地址为:" + downloadUri);
+        //解析得出文件名
+        //String fileName = downloadUri.substring(downloadUri.lastIndexOf('/')+1);
+        String fileName = request.getParameter("fileName");
+        System.out.println("====下载的文件名:" + fileName);
+
+        URL url = new URL(downloadUri);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        InputStream stream = connection.getInputStream();
+        //更换为实际的路径F:\DataOfHongQuanzheng\java\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\Java Example\\app_data\192.168.56.1\
+        //File savedFile = new File("F:\\DataOfHongQuanzheng\\onlyoffice_data\\app_data\\"+fileName);
+        File savedFile = new File(filePath + fileName);
+        if (null!=((String) jsonObj.get("userdata"))&&((String) jsonObj.get("userdata")).equals("sample userdata")) {
+            savedFile = new File(filePath + "v1" + fileName);
+        }
+
+
+        try (FileOutputStream out = new FileOutputStream(savedFile)) {
+            int read;
+            final byte[] bytes = new byte[1024];
+            while ((read = stream.read(bytes)) != -1) {
+                out.write(bytes, 0, read);
+            }
+            out.flush();
+        }
+        connection.disconnect();
+    }
+
+    /**
+     * 发送网路请求查看是否正在编辑
+     * @param path
+     * @param params
+     * @return
+     */
+    public static String editStatus(String path, String params) {
+        OutputStreamWriter out = null;
+        BufferedReader in = null;
+        StringBuilder result = new StringBuilder();
+        HttpURLConnection conn = null;
+        try {
+            URL url = new URL(path);
+            conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");
+            //发送POST请求必须设置为true
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            //设置连接超时时间和读取超时时间
+            conn.setConnectTimeout(30000);
+            conn.setReadTimeout(10000);
+            conn.setRequestProperty("Content-Type", "application/json");
+            conn.setRequestProperty("Accept", "application/json");
+            //获取输出流
+            out = new OutputStreamWriter(conn.getOutputStream());
+//            String jsonStr = "{\"c\":\"forcesave\", \"key\":\"WpP7m85eNQSEOoepp31oIYVG2oJyJJcvkLdoywgvs1k3ywm3Omuxk4\",\"userdata\":\"sample userdata\"}";
+            out.write(params);
+            out.flush();
+            out.close();
+            //取得输入流,并使用Reader读取
+            if (200 == conn.getResponseCode()) {
+
+                return IOUtils.toString(conn.getInputStream());
+
+
+            } else {
+                System.out.println("ResponseCode is an error code:" + conn.getResponseCode());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ioe) {
+                ioe.printStackTrace();
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 当最后关闭编辑界面后,将编辑时下载的文件删除
+     *
+     * @param path
+     * @param fileName
+     */
+    public static void deleteTempFile(String path, String fileName) {
+        //因为临时存储的文件都添加了v1前缀所以删除文件时需要在文件名测前边加一个v1
+        File file = new File(path + "v1" + fileName);
+        if (file.exists()) {
+            file.delete();
+        }
+
+    }
+}

+ 74 - 0
src/main/java/com/github/jfcloud/excel/editor/docdeal/utils/Md5Utils.java

@@ -0,0 +1,74 @@
+package com.github.jfcloud.excel.editor.docdeal.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Md5 工具类
+ * need  commons-codec-1.6.jar +
+ * @author zhangcx
+ * @date 2019-7-23
+ */
+@Slf4j
+public class Md5Utils {
+    private static MessageDigest MD5 = null;
+
+    static {
+        try {
+            MD5 = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException ne) {
+            ne.printStackTrace();
+        }
+    }
+
+    public static String getFileMd5(String filePath) {
+        if (StringUtils.isBlank(filePath)) {
+            return null;
+        }
+        return getFileMd5(new File(filePath));
+    }
+
+    /**
+     * 对一个文件获取md5值
+     */
+    public static String getFileMd5(File file) {
+        FileInputStream fileInputStream = null;
+        try {
+            fileInputStream = new FileInputStream(file);
+            byte[] buffer = new byte[8192];
+            int length;
+            while ((length = fileInputStream.read(buffer)) != -1) {
+                MD5.update(buffer, 0, length);
+            }
+            return new String(Hex.encodeHex(MD5.digest()));
+        } catch (IOException e) {
+            log.error("$$$ 获取文件md5失败!", e);
+            return null;
+        } finally {
+            try {
+                if (fileInputStream != null) {
+                    fileInputStream.close();
+                }
+            } catch (IOException e) {
+                log.error("关闭文件流出错!", e);
+            }
+        }
+    }
+
+    /**
+     * 计算字符串的md5值
+     * @param target 字符串
+     * @return md5 value
+     */
+    public static String md5(final String target) {
+        return DigestUtils.md5Hex(target);
+    }
+}

+ 24 - 0
src/main/resources/application-dbconfig.yml

@@ -0,0 +1,24 @@
+# 数据源配置
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    druid:
+      driver-class-name: com.mysql.cj.jdbc.Driver
+      username: gimi
+      password: Gimi!2021
+      url: jdbc:mysql://jfcloud-k6-mysql:3306/lacms_animal?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
+      stat-view-servlet:
+        enabled: false
+        allow: ""
+        url-pattern: /druid/*
+        #login-username: admin
+        #login-password: admin
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true
+          slow-sql-millis: 10000
+          merge-sql: false
+        wall:
+          config:
+            multi-statement-allow: true

+ 100 - 0
src/main/resources/application.yml

@@ -0,0 +1,100 @@
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 20054
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # tomcat最大线程数,默认为200
+    max-threads: 800
+    # Tomcat启动初始化的线程数,默认值25
+    min-spare-threads: 30
+
+# 文件系统 (minio测试环境,不可当生产使用)
+#oss:
+#  endpoint: http://jfcloud-v4-oss:9002
+#  access-key: jfcloud
+#  secret-key: jfcloudjfcloud
+#  bucket-name: jfcloud
+
+#oss:
+#  endpoint: http://10.0.1.200:9090
+#  access-key: jfcloud
+#  secret-key: jfcloudjfcloud
+#  bucket-name: report
+
+oss:
+  endpoint: http://192.168.66.228:9002
+  access-key: jfcloud
+  secret-key: jfcloudjfcloud
+  bucket-name: report
+
+# 日志配置
+logging:
+  level:
+    com.ruoyi: debug
+    org.springframework: warn
+
+# Spring配置
+spring:
+  # 资源信息
+  messages:
+    # 国际化资源文件路径
+    basename: i18n/messages
+  profiles:
+    active: dbconfig
+  # 文件上传
+  servlet:
+     multipart:
+       # 单个文件大小
+       max-file-size:  500MB
+       # 设置总上传的文件大小
+       max-request-size:  500MB
+  thymeleaf:
+    prefix: classpath:/templates
+
+# MyBatisPlus配置
+mybatis-plus:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    map-underscore-to-camel-case: false
+  type-aliases-package: com.github.jfcloud.excel.editor.docdeal.bean
+  global-config:
+    db-config:
+      # 全局默认主键类型
+      id-type: assign_id
+      # 表名、是否使用下划线命名,默认数据库表使用下划线命名
+      table-underline: true
+      # 逻辑已删除值(默认为 1)
+      logic-delete-value: true
+      # 逻辑未删除值(默认为 0)
+      logic-not-delete-value: false
+  mapper-locations:
+    - /mapper/*.xml
+pagehelper:
+  helper-dialect: mysql
+  reasonable: true
+  support-methods-arguments: true
+  params: count=countsql
+
+document:
+  server:
+    host: 192.168.66.228:20054
+files:
+  savePath: onlyoffice/
+  #savePath: D:\onlyoffice\
+  docservice:
+    secret: cThavmwXYSEExEtM54hBpqu0vggVv7MP
+    convert-docs: .docm|.dotx|.dotm|.dot|.doc|.odt|.fodt|.ott|.xlsm|.xltx|.xltm|.xlt|.xls|.ods|.fods|.ots|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.rtf|.mht|.html|.htm|.xml|.epub|.fb2
+    edited-docs: .docx|.xlsx|.csv|.pptx|.txtls
+    viewed-docs: .pdf|.djvu|.xps
+    url:
+      site: http://192.168.66.228:20053/
+      converter: ConvertService.ashx
+      command: coauthoring/CommandService.ashx
+      api: web-apps/apps/api/documents/api.js
+      preloader: web-apps/apps/api/documents/cache-scripts.html
+

+ 6 - 0
src/main/resources/mapper/FileUploadMapper.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.lc.docdeal.mapper">
+
+
+</mapper>

+ 26 - 0
src/main/resources/static/css/viewer.css

@@ -0,0 +1,26 @@
+html {
+    height: 100%;
+    width: 100%;
+}
+
+body {
+    background: #fff;
+    color: #333;
+    font-family: Arial, Tahoma, sans-serif;
+    font-size: 12px;
+    font-weight: normal;
+    height: 100%;
+    margin: 0;
+    overflow-y: hidden;
+    padding: 0;
+    text-decoration: none;
+}
+
+.form {
+    height: 100%;
+}
+
+div {
+    margin: 0;
+    padding: 0;
+}

BIN
src/main/resources/static/favicon.ico


+ 176 - 0
src/main/resources/static/js/editor.js

@@ -0,0 +1,176 @@
+var Editor = function () {
+    var docEditor;
+
+    var innerAlert = function (message) {
+        if (console && console.log)
+            console.log(message);
+    };
+
+    var onAppReady = function () {
+        innerAlert("文档编辑已就绪~");
+    };
+
+    var onDocumentStateChange = function (event) {
+        var title = document.title.replace(/\*$/g, "");
+        document.title = title + (event.data ? "*" : "");
+    };
+
+    var onError = function (event) {
+        if (event) {
+            innerAlert(event.data);
+        }
+    };
+
+    var onOutdatedVersion = function (event) {
+        //TODO
+        location.reload(true);
+    };
+    var onAppReady = function() {
+        console.log("ONLYOFFICE Document Editor is ready");
+    };
+    var onCollaborativeChanges = function () {
+        console.log("The document changed by collaborative user");
+    };
+    var onDocumentReady = function() {
+        console.log("Document is loaded");
+    };
+    var onDocumentStateChange = function (event) {
+        if (event.data) {
+            console.log("The document changed");
+            // docEditor.downloadAs();
+        } else {
+            console.log("Changes are collected on document editing service");
+            //
+        }
+    };
+    var onDownloadAs = function (event) {
+        console.log("ONLYOFFICE Document Editor create file: " + event.data);
+        window.top.postMessage(event.data);
+        createAndDownloadFile("test.docx",event.data)
+    };
+    window.addEventListener('message',function(e){
+        console.log(e.data)
+        if (e.data=="downloadAs") {
+            docEditor.downloadAs();
+        }
+    },false)
+
+    $("#insertImage").click(function(event) {
+        console.log("ONLYOFFICE Document Editor insertImage: "+ event.data);
+        docEditor.insertImage({
+            "fileType": "png",
+            "url": "http://192.168.0.58:20056/attachment/20190728测试上传文件名修改/2020January/1580363537940306800_small.png"
+        });
+    })
+
+    var onRequestInsertImage = function(event) {
+        console.log("ONLYOFFICE Document Editor insertImage" + event.data);
+        docEditor.insertImage({
+            "fileType": "png",
+            "url": "http://192.168.0.58:20056/attachment/20190728测试上传文件名修改/2020January/1580363537940306800_small.png"
+        });
+    };
+
+    var onError = function (event) {
+        console.log("ONLYOFFICE Document Editor reports an error: code " + event.data.errorCode + ", description " + event.data.errorDescription);
+    };
+    var onOutdatedVersion = function () {
+        location.reload(true);
+    };
+    var onRequestEditRights = function () {
+        console.log("ONLYOFFICE Document Editor requests editing rights");
+        // document.location.reload();
+        var he=location.href.replace("view","edit");
+        location.href=he;
+    };
+
+    //历史版本保留1个月。比如Unix时间戳(Unix timestamp)expires=1524547423
+    var onRequestHistory = function() {
+    };
+
+    var onRequestHistoryClose = function() {
+        document.location.reload();
+    };
+    var getUrlParam = function (name) {
+        //构造一个含有目标参数的正则表达式对象
+        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+        //匹配目标参数
+        var r = window.location.search.substr(1).match(reg);
+        //返回参数值
+        if (r != null) {
+            return decodeURI(r[2]);
+        }
+        return null;
+    };
+
+    var getDocumentConfig = function (document) {
+        if (document) {
+            return {
+                "document": document
+            };
+        }
+        innerAlert("文档未指定!");
+        return null;
+    };
+    var onRequestHistoryData = function() {}
+    var connectEditor = function (document, documentEditParam) {
+
+        var config = getDocumentConfig(document);
+        console.log(document)
+        console.log(documentEditParam)
+        config.width = "100%";
+        config.height = "100%";
+        config.events = {
+            "onAppReady": onAppReady,
+            "onDocumentStateChange": onDocumentStateChange,
+            "onError": onError,
+            "onOutdatedVersion": onOutdatedVersion
+        };
+        //config.documentType = ""+document.fileType
+        config.editorConfig = {
+            "lang": "zh-CN",
+            "mode": "edit",
+            "recent": [],
+            // 自定义一些配置
+            "customization": {
+                "features": {
+                    "spellcheck": {
+                        "mode": false
+                    }
+                },
+            },
+            // "customization": {
+            //     "chat": true, // 禁用聊天菜单按钮
+            //     "commentAuthorOnly": false, // 仅能编辑和删除其注释
+            //     "comments": false, // 隐藏文档注释菜单按钮
+            //     "compactHeader": false, // 隐藏附加操作按钮
+            //     "compactToolbar": false, // 完整工具栏(true代表紧凑工具栏)
+            //     "feedback": {
+            //         "visible": true // 隐藏反馈按钮
+            //     },
+            //     "forcesave": false, // true 表示强制文件保存请求添加到回调处理程序
+            //     "goback": false,/*{
+            //                 "blank": true, // 转到文档时,在新窗口打开网站(false表示当前窗口打开)
+            //                 "text": "转到文档位置(可以考虑放文档打开源页面)",
+            //                 // 文档打开失败时的跳转也是该地址
+            //                 "url": "http://www.lezhixing.com.cn"
+            //             },*/
+            //     "help": false, // 隐藏帮助按钮
+            //     "hideRightMenu": false, // 首次加载时隐藏右侧菜单(true 为显示)
+            //     "showReviewChanges": false, // 加载编辑器时自动显示/隐藏审阅更改面板(true显示 false隐藏)
+            //     "toolbarNoTabs": false, // 清楚地显示顶部工具栏选项卡(true 代表仅突出显示以查看选择了哪一个)
+            //     "zoom": 100, // 定义文档显示缩放百分比值,
+            // },
+        };
+        $.extend(config.editorConfig, documentEditParam);
+        console.log('文档参数',config)
+        docEditor = new DocsAPI.DocEditor("iframeEditor",config);
+    };
+
+
+    return {
+        init: function (document, documentEditParam) {
+            connectEditor(document, documentEditParam);
+        }
+    }
+}();

File diff suppressed because it is too large
+ 1 - 0
src/main/resources/static/js/jquery-2.1.3.min.js


+ 117 - 0
src/main/resources/static/js/viewer.js

@@ -0,0 +1,117 @@
+var Viewer = function() {
+    var docEditor;
+
+    var innerAlert = function (message) {
+        if (console && console.log)
+            console.log(message);
+    };
+
+    var onAppReady = function () {
+        innerAlert("文档查看已就绪~");
+    };
+
+    var onDocumentStateChange = function (event) {
+        var title = document.title.replace(/\*$/g, "");
+        document.title = title + (event.data ? "*" : "");
+    };
+
+    var onError = function (event) {
+        if (event) {
+            innerAlert(event.data);
+        }
+    };
+
+    var onOutdatedVersion = function (event) {
+        location.reload(true);
+    };
+
+    var getUrlParam = function (name) {
+        //构造一个含有目标参数的正则表达式对象
+        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+        //匹配目标参数
+        var r = window.location.search.substr(1).match(reg);
+        //返回参数值
+        if (r != null) {
+            return decodeURI(r[2]);
+        }
+        return null;
+    };
+
+    var getDocumentConfig = function (document) {
+        if (document) {
+            return {
+                "document": document
+            };
+        }
+        innerAlert("文档未指定!");
+        return null;
+    };
+
+    var connectEditor = function (document, documentEditParam) {
+        var config = getDocumentConfig(document);
+        config.width = "100%";
+        config.height = "100%";
+        config.type = "desktop";
+        config.events = {
+            "onAppReady": onAppReady,
+            "onDocumentStateChange": onDocumentStateChange,
+            "onError": onError,
+            "onOutdatedVersion": onOutdatedVersion
+        };
+        config.editorConfig = {
+            "lang": "zh-CN",
+            "mode": "edit",
+            "recent": [],
+            // 自定义一些配置
+            "customization": {
+                "about": true,
+                "comments": true,
+                "feedback": true,
+                "forcesave": false,
+                "goback": false,
+                "submitForm": false,
+                "features": {
+                    "spellcheck": false
+                },
+            }
+            //自定义一些配置
+           /* "customization": {
+                "chat": false, // 禁用聊天菜单按钮
+                "commentAuthorOnly": true, // 仅能编辑和删除其注释
+                "comments": true, // 隐藏文档注释菜单按钮
+                "compactHeader": false, // 隐藏附加操作按钮
+                "compactToolbar": false, // 完整工具栏(true代表紧凑工具栏)
+                "about": true,
+                "feedback": true,
+                "forcesave": false, // true 表示强制文件保存请求添加到回调处理程序
+                "goback": false,/!*{
+                            "blank": true, // 转到文档时,在新窗口打开网站(false表示当前窗口打开)
+                            "text": "转到文档位置(可以考虑放文档打开源页面)",
+                            // 文档打开失败时的跳转也是该地址
+                            "url": "http://www.lezhixing.com.cn"
+                        },*!/
+                "features": {
+                    "spellcheck": {
+                        "mode": true
+                    }
+                },
+                "help": false, // 隐藏帮助按钮
+                "hideRightMenu": false, // 首次加载时隐藏右侧菜单(true 为显示)
+                "showReviewChanges": false, // 加载编辑器时自动显示/隐藏审阅更改面板(true显示 false隐藏)
+                "toolbarNoTabs": false, // 清楚地显示顶部工具栏选项卡(true 代表仅突出显示以查看选择了哪一个)
+                "zoom": 100 // 定义文档显示缩放百分比值
+            }*/
+
+        };
+        $.extend(config.editorConfig, documentEditParam);
+        console.log('文档参数',config)
+        console.log('documentEditParam文档参数',documentEditParam)
+        docEditor = new DocsAPI.DocEditor("iframeEditor", config);
+    };
+
+    return {
+        init : function(document, documentEditParam) {
+            connectEditor(document,documentEditParam);
+        }
+    }
+}();

+ 114 - 0
src/main/resources/static/js/viewerExcel.js

@@ -0,0 +1,114 @@
+var Viewer = function() {
+    var docEditor;
+
+    var innerAlert = function (message) {
+        if (console && console.log)
+            console.log(message);
+    };
+
+    var onAppReady = function () {
+        innerAlert("文档查看已就绪~");
+    };
+
+    var onDocumentStateChange = function (event) {
+        var title = document.title.replace(/\*$/g, "");
+        document.title = title + (event.data ? "*" : "");
+    };
+
+    var onError = function (event) {
+        if (event) {
+            innerAlert(event.data);
+        }
+    };
+
+    var onOutdatedVersion = function (event) {
+        location.reload(true);
+    };
+
+    var getUrlParam = function (name) {
+        //构造一个含有目标参数的正则表达式对象
+        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
+        //匹配目标参数
+        var r = window.location.search.substr(1).match(reg);
+        //返回参数值
+        if (r != null) {
+            return decodeURI(r[2]);
+        }
+        return null;
+    };
+
+    var getDocumentConfig = function (document) {
+        if (document) {
+            return {
+                "document": document
+            };
+        }
+        innerAlert("文档未指定!");
+        return null;
+    };
+
+    var connectEditor = function (document, documentEditParam) {
+        var config = getDocumentConfig(document);
+        config.width = "100%";
+        config.height = "100%";
+        config.type = "desktop";
+        config.events = {
+            "onAppReady": onAppReady,
+            "onDocumentStateChange": onDocumentStateChange,
+            "onError": onError,
+            "onOutdatedVersion": onOutdatedVersion
+        };
+        config.editorConfig = {
+            "lang": "zh-CN",
+            "mode": "view",
+            "recent": [],
+            // 自定义一些配置
+            "customization": {
+                "about": true,
+                "comments": true,
+                "feedback": true,
+                "forcesave": false,
+                "goback": false,
+                "submitForm": false,
+            }
+            //自定义一些配置
+           /* "customization": {
+                "chat": false, // 禁用聊天菜单按钮
+                "commentAuthorOnly": true, // 仅能编辑和删除其注释
+                "comments": true, // 隐藏文档注释菜单按钮
+                "compactHeader": false, // 隐藏附加操作按钮
+                "compactToolbar": false, // 完整工具栏(true代表紧凑工具栏)
+                "about": true,
+                "feedback": true,
+                "forcesave": false, // true 表示强制文件保存请求添加到回调处理程序
+                "goback": false,/!*{
+                            "blank": true, // 转到文档时,在新窗口打开网站(false表示当前窗口打开)
+                            "text": "转到文档位置(可以考虑放文档打开源页面)",
+                            // 文档打开失败时的跳转也是该地址
+                            "url": "http://www.lezhixing.com.cn"
+                        },*!/
+                "features": {
+                    "spellcheck": {
+                        "mode": true
+                    }
+                },
+                "help": false, // 隐藏帮助按钮
+                "hideRightMenu": false, // 首次加载时隐藏右侧菜单(true 为显示)
+                "showReviewChanges": false, // 加载编辑器时自动显示/隐藏审阅更改面板(true显示 false隐藏)
+                "toolbarNoTabs": false, // 清楚地显示顶部工具栏选项卡(true 代表仅突出显示以查看选择了哪一个)
+                "zoom": 100 // 定义文档显示缩放百分比值
+            }*/
+
+        };
+        $.extend(config.editorConfig, documentEditParam);
+        console.log('文档参数',config)
+        console.log('documentEditParam文档参数',documentEditParam)
+        docEditor = new DocsAPI.DocEditor("iframeEditor", config);
+    };
+
+    return {
+        init : function(document, documentEditParam) {
+            connectEditor(document,documentEditParam);
+        }
+    }
+}();

+ 22 - 0
src/main/resources/templates/editor.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
+<head>
+    <title th:text="${document.title}"></title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" type="text/css" th:href="@{/css/viewer.css}">
+</head>
+<body>
+    <div class="form">
+        <div id="iframeEditor"></div>
+    </div>
+    <script type="text/javascript" th:src="@{/js/jquery-2.1.3.min.js}"></script>
+    <script type="text/javascript" th:src="@{${documentServerApiJs}}"></script>
+    <script type="text/javascript" th:src="@{/js/editor.js}"></script>
+    <!-- 先通过 th:inline=“javascript” 添加到标签,这样js代码即可访问model中的属性 -->
+    <script th:inline="javascript">
+        // js 中可以通过“[[${xxx}]]” 格式获得实际的值
+        Editor.init([[${document}]], [[${documentEditParam}]]);
+    </script>
+</body>
+</html>

+ 22 - 0
src/main/resources/templates/viewer.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
+<head>
+    <title th:text="${document.title}"></title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" type="text/css" th:href="@{/css/viewer.css}">
+</head>
+<body>
+    <div class="form">
+        <div id="iframeEditor"></div>
+    </div>
+    <script type="text/javascript" th:src="@{/js/jquery-2.1.3.min.js}"></script>
+    <script type="text/javascript" th:src="@{${documentServerApiJs}}"></script>
+    <script type="text/javascript" th:src="@{/js/viewer.js}"></script>
+    <!-- 先通过 th:inline=“javascript” 添加到标签,这样js代码即可访问model中的属性 -->
+    <script th:inline="javascript">
+        // js 中可以通过“[[${xxx}]]” 格式获得实际的值
+        Viewer.init([[${document}]], [[${documentEditParam}]]);
+    </script>
+</body>
+</html>

+ 22 - 0
src/main/resources/templates/viewerExcel.html

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
+<head>
+    <title th:text="${document.title}"></title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" type="text/css" th:href="@{/css/viewer.css}">
+</head>
+<body>
+    <div class="form">
+        <div id="iframeEditor"></div>
+    </div>
+    <script type="text/javascript" th:src="@{/js/jquery-2.1.3.min.js}"></script>
+    <script type="text/javascript" th:src="@{${documentServerApiJs}}"></script>
+    <script type="text/javascript" th:src="@{/js/viewerExcel.js}"></script>
+    <!-- 先通过 th:inline=“javascript” 添加到标签,这样js代码即可访问model中的属性 -->
+    <script th:inline="javascript">
+        // js 中可以通过“[[${xxx}]]” 格式获得实际的值
+        Viewer.init([[${document}]], [[${documentEditParam}]]);
+    </script>
+</body>
+</html>

Some files were not shown because too many files changed in this diff