提交 f3739005 作者: ZhangJingKun

在线编辑 zhangjingkun

上级 a25d5ae9
...@@ -160,6 +160,18 @@ ...@@ -160,6 +160,18 @@
<artifactId>htmlcleaner</artifactId> <artifactId>htmlcleaner</artifactId>
<version>2.25</version> <version>2.25</version>
</dependency> </dependency>
<dependency>
<groupId>org.hashids</groupId>
<artifactId>hashids</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
package com.zzsn.knowbase.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";
}
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
* *
*/ */
package com.zzsn.knowbase.enums; package com.zzsn.knowbase.constant;
public enum DocumentType { public enum DocumentType {
word, word,
......
package com.zzsn.knowbase.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() + " |");
});
}
}
package com.zzsn.knowbase.controller;
import com.zzsn.knowbase.service.ILocalFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Version 1.0
* @Author: ZhangJingKun
* @Date: 2024/1/10 10:18
* @Content:
*/
@RestController
@RequestMapping("/api/file")
public class KbFileController {
@Autowired
private ILocalFileService localFileService;
/**
* 下载文档接口
* @param fileName
* @param filePath
* @param response
*/
@GetMapping("/download")
public void download(String fileName, String filePath, HttpServletResponse response) {
localFileService.download(fileName, filePath, response);
}
/**
* 编辑文档接口
* @param fileName
* @param filePath
* @param userName
* @param model
* @return
*/
@GetMapping("/edit")
public String editDocFile(String fileName, String filePath, String userName, Model model) {
return localFileService.editDocFile(fileName,filePath,userName,model);
}
/**
* 编辑文档时的回调接口
* @param request
* @param response
* @throws IOException
*/
@RequestMapping("/callback")
public void saveDocumentFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
//处理编辑回调逻辑
localFileService.callBack(request, response);
}
}
package com.zzsn.knowbase.entity;
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;
/**
* 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;
}
package com.zzsn.knowbase.entity;
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;
}
}
package com.zzsn.knowbase.service;
import com.zzsn.knowbase.entity.Document;
import com.zzsn.knowbase.entity.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);
}
...@@ -4,8 +4,12 @@ import com.zzsn.knowbase.entity.KnowFile; ...@@ -4,8 +4,12 @@ import com.zzsn.knowbase.entity.KnowFile;
import com.zzsn.knowbase.vo.Result; import com.zzsn.knowbase.vo.Result;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -33,11 +37,32 @@ public interface ILocalFileService { ...@@ -33,11 +37,32 @@ public interface ILocalFileService {
/** /**
* 文件下载 * 文件下载
* @param
* @param
* @return
*/
//ResponseEntity<Resource> download(String fileName, String filePath);
void download(String fileName, String filePath, HttpServletResponse response);
/**
* 编辑文档
* @param fileName * @param fileName
* @param filePath * @param filePath
* @param userName
* @param model
* @return * @return
*/ */
ResponseEntity<Resource> download(String fileName, String filePath); String editDocFile(String fileName, String filePath, String userName, Model model);
/**
* 编辑文档时的回调接口
* @param request
* @param response
* @throws IOException
*/
void callBack(HttpServletRequest request, HttpServletResponse response) throws IOException;
} }
package com.zzsn.knowbase.service.impl;
import com.alibaba.fastjson.JSON;
import com.zzsn.knowbase.constant.DocumentConstants;
import com.zzsn.knowbase.constant.ErrorCodeEnum;
import com.zzsn.knowbase.entity.Document;
import com.zzsn.knowbase.entity.DocumentEditParam;
import com.zzsn.knowbase.service.DocumentService;
import com.zzsn.knowbase.util.DocumentException;
import com.zzsn.knowbase.util.file.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
*/
@Value("${document.file-size.limit:10485760}")
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());
}
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);
}
}
}
package com.zzsn.knowbase.service.impl; package com.zzsn.knowbase.service.impl;
import com.zzsn.knowbase.constant.Constants;
import com.zzsn.knowbase.entity.Document;
import com.zzsn.knowbase.entity.KnowFile; import com.zzsn.knowbase.entity.KnowFile;
import com.zzsn.knowbase.service.DocumentService;
import com.zzsn.knowbase.service.ILocalFileService; import com.zzsn.knowbase.service.ILocalFileService;
import com.zzsn.knowbase.util.CodeGenerateUtil;
import com.zzsn.knowbase.util.file.FileUtility; import com.zzsn.knowbase.util.file.FileUtility;
import com.zzsn.knowbase.vo.Result; import com.zzsn.knowbase.vo.Result;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource; import org.springframework.core.io.UrlResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.ui.Model;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/** /**
* @Version 1.0 * @Version 1.0
...@@ -36,7 +44,13 @@ import java.util.UUID; ...@@ -36,7 +44,13 @@ import java.util.UUID;
public class LocalFileServiceImpl implements ILocalFileService { public class LocalFileServiceImpl implements ILocalFileService {
@Autowired @Autowired
private FileUtility fileUtility; private FileUtility fileUtility;//文件工具类
@Autowired
private CodeGenerateUtil codeGenerateUtil; //生成唯一id
@Autowired
private DocumentService documentService;
@Value("${files.storage}") @Value("${files.storage}")
String filesStorage; String filesStorage;
...@@ -44,8 +58,9 @@ public class LocalFileServiceImpl implements ILocalFileService { ...@@ -44,8 +58,9 @@ public class LocalFileServiceImpl implements ILocalFileService {
@Override @Override
public Result<KnowFile> upload(MultipartFile file) { public Result<KnowFile> upload(MultipartFile file) {
try { try {
String fullFileName = file.getOriginalFilename(); // get file name String fileName = file.getOriginalFilename(); // 获取文件名称
String fileExtension = fileUtility.getFileExtension(fullFileName); // get file extension String fileExtension = fileUtility.getFileExtension(fileName); // 获取文件扩展名
String fileType = fileUtility.getFileType(fileName); //获取文件类型
long fileSize = file.getSize(); // get file size long fileSize = file.getSize(); // get file size
// check if the file size exceeds the maximum file size or is less than 0 // check if the file size exceeds the maximum file size or is less than 0
...@@ -59,21 +74,17 @@ public class LocalFileServiceImpl implements ILocalFileService { ...@@ -59,21 +74,17 @@ public class LocalFileServiceImpl implements ILocalFileService {
return result; return result;
} }
String fileName = file.getOriginalFilename(); String fileId = codeGenerateUtil.geneIdNo(Constants.FINANCE, 8);
String fileSuffix = getFileSuffix(fileName); String filePath = getFilePath() + fileId + fileExtension;
//byte[] bytes = file.getBytes(); // get file in bytes
String uid = UUID.randomUUID().toString();
String filePath = getFilePath() + uid + "." + fileSuffix;
byte[] bytes = file.getBytes(); // get file in bytes
//Files.write(Paths.get(filePath), bytes); //Files.write(Paths.get(filePath), bytes);
file.transferTo(new File(filePath)); file.transferTo(new File(filesStorage + filePath));
KnowFile knowFile = new KnowFile(); KnowFile knowFile = new KnowFile();
knowFile.setFileId(uid); knowFile.setFileId(fileId);
knowFile.setFileName(fileName); knowFile.setFileName(fileName);
knowFile.setFilePath(filePath); knowFile.setFilePath(filePath);
knowFile.setFileType(fileSuffix); knowFile.setFileType(fileType);
knowFile.setFileSize(fileSize); knowFile.setFileSize(fileSize);
Result result = Result.OK(knowFile); Result result = Result.OK(knowFile);
return result; // create user metadata and return it return result; // create user metadata and return it
...@@ -96,11 +107,163 @@ public class LocalFileServiceImpl implements ILocalFileService { ...@@ -96,11 +107,163 @@ public class LocalFileServiceImpl implements ILocalFileService {
return list; return list;
} }
@Override
public void download(String fileName, String filePath, HttpServletResponse response) {
// path是指想要下载的文件的路径
File file = new File(filesStorage + filePath);
try {
// 将文件写入输入流
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();
} catch (Exception e) {
e.printStackTrace();
}
}
//edit
@Override
public String editDocFile(String fileName, String filePath, String userName, Model model) {
String path = filePath;
Document document = documentService.getDocument(documentService.buildDocument(path, fileName));
model.addAttribute("document", document);
// 如果该格式不支持编辑,则返回预览页面
if (!documentService.canEdit(document)) {
return "/demo";
}
model.addAttribute("documentEditParam", documentService.buildDocumentEditParam(userName, userName,fileName));
return "/editor";
}
//编辑文档时回调接口
@Override
public void callBack(HttpServletRequest request, HttpServletResponse response) throws IOException{
PrintWriter writer = null;
JSONObject jsonObj = null;
System.out.println("===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);
System.out.println(jsonObj);
System.out.println("===saveeditedfile:" + jsonObj.get("status"));
/*
0-找不到具有密钥标识符的文档,
1-文档正在编辑,
2-文档已准备好保存,
3-发生文档保存错误,
4-文档已关闭,没有任何更改,
6-文档正在编辑,但当前文档状态已保存,
7-强制保存文档时发生错误。
*/
if ((long) jsonObj.get("status") == 2) {
callBackSaveDocument(jsonObj,request, response);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/*
* status = 1,我们给onlyoffice的服务返回{"error":"0"}的信息,这样onlyoffice会认为回调接口是没问题的,这样就可以在线编辑文档了,否则的话会弹出窗口说明
* 在线编辑还没有关闭,前端有人下载文档时,强制保存最新内容 当status 是6时说明有人在编辑时下载文档
* */
System.out.println(jsonObj.get("status"));
if ((long) jsonObj.get("status") == 6) {
//处理当文档正在编辑为关闭时,下载文档
if (((String)jsonObj.get("userdata")).equals("sample userdata")){
callBackSaveDocument(jsonObj,request, response);
}
System.out.println("====保存失败:");
writer.write("{\"error\":1}");
} else {
//执行删除编辑时下载保存的文件:
deleteTempFile(request.getParameter("fileName"));
writer.write("{\"error\":0}");
}
}
/**
* 编辑以后保存文件
* @param jsonObj
* @param request
* @param response
* @throws IOException
*/
public void callBackSaveDocument(JSONObject jsonObj, 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);
java.net.HttpURLConnection connection = (java.net.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(filesStorage + fileName);
if (null!=((String) jsonObj.get("userdata"))&&((String) jsonObj.get("userdata")).equals("sample userdata")) {
savedFile = new File(filesStorage + "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();
}
public void deleteTempFile(String fileName) {
//因为临时存储的文件都添加了v1前缀所以删除文件时需要在文件名测前边加一个v1
File file = new File(filesStorage + "v1" + fileName);
if (file.exists()) {
file.delete();
}
}
/** /**
* 文件下载 * 文件下载
*/ */
/*
@Override @Override
public ResponseEntity<Resource> download(String fileName, String filePath) { public ResponseEntity<Resource> download(String fileName, String filePath) {
Path path = Paths.get(filePath); Path path = Paths.get(filePath);
...@@ -118,15 +281,20 @@ public class LocalFileServiceImpl implements ILocalFileService { ...@@ -118,15 +281,20 @@ public class LocalFileServiceImpl implements ILocalFileService {
throw new RuntimeException("文件读取失败"); throw new RuntimeException("文件读取失败");
} }
} }
*/
//获取文件夹路径
//生成文件夹路径
private String getFilePath(){ private String getFilePath(){
LocalDate currentDate = LocalDate.now(); LocalDate currentDate = LocalDate.now();
//System.out.println("当前日期: " + currentDate); String current = currentDate.toString().replace("-", "");
String filePath = filesStorage + currentDate + "/"; String filePath = current + "/";
//判断文件夹是否存在,不存在创建 //判断文件夹是否存在,不存在创建
Path directory = Paths.get(filePath); Path directory = Paths.get(filesStorage + filePath);
if (!Files.exists(directory)) { if (!Files.exists(directory)) {
try { try {
Files.createDirectories(directory); Files.createDirectories(directory);
...@@ -138,14 +306,5 @@ public class LocalFileServiceImpl implements ILocalFileService { ...@@ -138,14 +306,5 @@ public class LocalFileServiceImpl implements ILocalFileService {
} }
return filePath; return filePath;
} }
//获取文件后缀
private String getFileSuffix(String fileName){
int lastIndexOfDot = fileName.lastIndexOf('.');
String fileExtension = "";
if (lastIndexOfDot != -1) {
fileExtension = fileName.substring(lastIndexOfDot + 1);
}
return fileExtension;
}
} }
package com.zzsn.knowbase.util;
import com.zzsn.knowbase.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;
}
}
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
package com.zzsn.knowbase.util.file; package com.zzsn.knowbase.util.file;
import com.zzsn.knowbase.constant.Constants; import com.zzsn.knowbase.constant.Constants;
import com.zzsn.knowbase.enums.DocumentType; import com.zzsn.knowbase.constant.DocumentType;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -123,6 +123,15 @@ public class DefaultFileUtility implements FileUtility { ...@@ -123,6 +123,15 @@ public class DefaultFileUtility implements FileUtility {
String fileExt = fileName.substring(fileName.lastIndexOf(".")); String fileExt = fileName.substring(fileName.lastIndexOf("."));
return fileExt.toLowerCase(); return fileExt.toLowerCase();
} }
//get file type from URL
public String getFileType(final String url) {
String fileName = getFileName(url);
if (fileName == null) {
return null;
}
String fileExt = fileName.substring(fileName.lastIndexOf("."));
return fileExt.toLowerCase();
}
// get an editor internal extension // get an editor internal extension
public String getInternalExtension(final DocumentType type) { public String getInternalExtension(final DocumentType type) {
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
package com.zzsn.knowbase.util.file; package com.zzsn.knowbase.util.file;
import com.zzsn.knowbase.enums.DocumentType; import com.zzsn.knowbase.constant.DocumentType;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
...@@ -29,6 +29,7 @@ public interface FileUtility { ...@@ -29,6 +29,7 @@ public interface FileUtility {
String getFileName(String url); // get file name from its URL String getFileName(String url); // get file name from its URL
String getFileNameWithoutExtension(String url); // get file name without extension String getFileNameWithoutExtension(String url); // get file name without extension
String getFileExtension(String url); // get file extension from URL String getFileExtension(String url); // get file extension from URL
String getFileType(String url); // get file type from URL
String getInternalExtension(DocumentType type); // get an editor internal extension String getInternalExtension(DocumentType type); // get an editor internal extension
List<String> getFileExts(); // get all the supported file extensions List<String> getFileExts(); // get all the supported file extensions
List<String> getFillExts(); // get file extensions that can be filled List<String> getFillExts(); // get file extensions that can be filled
......
package com.zzsn.knowbase.util.file;
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);
}
}
...@@ -50,9 +50,18 @@ know: ...@@ -50,9 +50,18 @@ know:
checkuserurl: http://127.0.0.1:9988/sys/checkToken checkuserurl: http://127.0.0.1:9988/sys/checkToken
getusersurl: http://127.0.0.1:9988/sys/user/thirdparty getusersurl: http://127.0.0.1:9988/sys/user/thirdparty
document:
server:
host: http://gq55rd.natappfree.cc
files: files:
storage: E:/aaa/ storage: D:/know/
docservice: docservice:
url:
site: http://114.116.116.241:80/
converter: ConvertService.ashx
command: coauthoring/CommandService.ashx
api: web-apps/apps/api/documents/api.js
preloader: web-apps/apps/api/documents/cache-scripts.html
fillforms-docs: .docx|.oform fillforms-docs: .docx|.oform
viewed-docs: .djvu|.oxps|.pdf|.xps viewed-docs: .djvu|.oxps|.pdf|.xps
edited-docs: .csv|.docm|.docx|.docxf|.dotm|.dotx|.epub|.fb2|.html|.odp|.ods|.odt|.otp|.ots|.ott|.potm|.potx|.ppsm|.ppsx|.pptm|.pptx|.rtf|.txt|.xlsm|.xlsx|.xltm|.xltx edited-docs: .csv|.docm|.docx|.docxf|.dotm|.dotx|.epub|.fb2|.html|.odp|.ods|.odt|.otp|.ots|.ott|.potm|.potx|.ppsm|.ppsx|.pptm|.pptx|.rtf|.txt|.xlsm|.xlsx|.xltm|.xltx
......
...@@ -22,10 +22,10 @@ class KnowBaseApplicationTests { ...@@ -22,10 +22,10 @@ class KnowBaseApplicationTests {
@Test @Test
void contextLoads() throws IOException { void contextLoads() throws IOException {
ResponseEntity<Resource> re = localFileService.download("abc.docx", "E:/aaa/abc.docx"); // ResponseEntity<Resource> re = localFileService.download("abc.docx", "E:/aaa/abc.docx");
File file = re.getBody().getFile(); // File file = re.getBody().getFile();
//
System.out.println(re); // System.out.println(re);
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论