由于上一遍文章
springboot实现上传doc&docx文件格式转pdf在线预览使用documents4j依赖只能在windows服务器(远程API)或者安装了offices实现效果。部署到linux服务器中就会转换失败。这篇文章介绍如何解决这个问题。我们可以把doc & docx文件格式转成html文件,然后以流的方式输出到浏览器就可以实现在线预览doc和docx文件的效果。话不多说,上才艺
doc&docx转html的依赖
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>3.15version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-scratchpadartifactId>
<version>3.15version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.15version>
dependency>
<dependency>
<groupId>fr.opensagres.xdocreportgroupId>
<artifactId>xdocreportartifactId>
<version>1.0.6version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxml-schemasartifactId>
<version>3.15version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>ooxml-schemasartifactId>
<version>1.3version>
dependency>
根据不同的系统环境区分不同的上传目录,Windows环境为\
,而Linux环境为/
,这里主要根据程序当前路径创建了upload/document
目录存放文档和upload/images
目录存放图片
@Slf4j
public class UploadPathUtil {
public static ArrayList<String> getSystemRoot() {
/**
* 存放doc,docx,pdf 文件上传路径
* 存放images 文件上传路径
*/
ArrayList<String> list = new ArrayList<>(2);
String osName = System.getProperty("os.name");
// windows目录
String windowsUpload = System.getProperty("user.dir")+"\\upload\\";
// linux目录
String linuxUpload = System.getProperty("user.dir")+"/upload/";
log.info("当前系统为: "+osName);
if (osName.toLowerCase().startsWith("linux")) {
list.add(linuxUpload+"document/");
list.add(linuxUpload+"images/");
return list;
} else if (osName.toLowerCase().startsWith("windows")) {
list.add(windowsUpload+"document\\");
list.add(windowsUpload+"images\\");
return list;
} else {
throw new RuntimeException("错误目录");
}
}
public static void main(String[] args) {
ArrayList<String> systemRoot = UploadPathUtil.getSystemRoot();
System.out.println(systemRoot);
}
}
application.yml中添加静态资源路径
在spring mvc配置类中使用@Value获取配置文件的值,并把静态资源路径和虚拟路径映射关联。
前端访问/api/static/document/**
,**
表示任意目录文件。虚拟映射到程序当前路径的upload
目录的document
目录下。
前端访问/api/static/images/**
,**
表示任意目录文件。虚拟映射到程序当前路径的upload
目录的images
目录下。
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 文档上传路径
@Value("${file.staticAccessDocumentPath}")
private String staticAccessDocumentPath;
// 图片上传路径
@Value("${file.staticAccessImagesPath}")
private String staticAccessImagesPath;
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 登录请求访问登录页面
registry.addViewController("/toLogin").setViewName("login.html");
}
/**
* 虚拟路径映射
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
ArrayList<String> systemRoot = UploadPathUtil.getSystemRoot();
log.info("systemRoot: "+systemRoot);
// 在yml配置文件中读取路径
registry.addResourceHandler(staticAccessDocumentPath).addResourceLocations("file:"+systemRoot.get(0));
registry.addResourceHandler(staticAccessImagesPath).addResourceLocations("file:"+systemRoot.get(1));
}
}
public class Doc2Html {
// 上传路径
public static final ArrayList<String> uploadPath = UploadPathUtil.getSystemRoot();
/**
* doc转换为html
* @param fileName docx文件路径(如果你的是存到文件服务器的,直接用路径,如果是直接将文件流存到数据库的,转为InputStream )
* @param outPutFile html输出文件路径
* @throws TransformerException
* @throws IOException
* @throws ParserConfigurationException
*/
public static void doc2Html(String fileName, String outPutFile)
throws TransformerException, IOException, ParserConfigurationException {
long startTime = System.currentTimeMillis();
// HWPFDocument wordDocument = new HWPFDocument(fileName);
HWPFDocument wordDocument = new HWPFDocument(new FileInputStream(fileName));
WordToHtmlConverter wordToHtmlConverter = new WordToHtmlConverter(
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
wordToHtmlConverter.setPicturesManager(new PicturesManager() {
public String savePicture(byte[] content, PictureType pictureType, String suggestedName, float widthInches,
float heightInches) {
// 设置虚拟访问路径
return "/api/static/images/" + suggestedName;
}
});
wordToHtmlConverter.processDocument(wordDocument);
// 保存图片
List<Picture> pics = wordDocument.getPicturesTable().getAllPictures();
if (pics != null) {
for (int i = 0; i < pics.size(); i++) {
Picture pic = (Picture) pics.get(i);
String suggestFullFileName = pic.suggestFullFileName();
System.out.println(suggestFullFileName);
try {
File windowsImageUpload = new File(uploadPath.get(1));
if (!windowsImageUpload.exists()){
windowsImageUpload.mkdirs();
}
pic.writeImageContent(new FileOutputStream(windowsImageUpload +"\\"+ pic.suggestFullFileName()));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
Document htmlDocument = wordToHtmlConverter.getDocument();
ByteArrayOutputStream out = new ByteArrayOutputStream();
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(out);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
out.close();
writeFile(new String(out.toByteArray()), outPutFile);
System.out.println("Generate " + outPutFile + " with " + (System.currentTimeMillis() - startTime) + " ms.");
}
/**
* 写文件
*
* @param content
* @param path
*/
public static void writeFile(String content, String path) {
FileOutputStream fos = null;
BufferedWriter bw = null;
try {
File file = new File(path);
fos = new FileOutputStream(file);
bw = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"));
bw.write(content);
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
if (bw != null)
bw.close();
if (fos != null)
fos.close();
} catch (IOException ie) {
}
}
}
/**
* docx格式word转换为html
*
* @param fileName
* docx文件路径(如果你的是存到文件服务器的,直接用路径,如果是直接将文件流存到数据库的,转为InputStream )
* @param outPutFile
* html输出文件路径
* @throws TransformerException
* @throws IOException
* @throws ParserConfigurationException
*/
public static void docx2Html(String fileName, String outPutFile)
throws TransformerException, IOException, ParserConfigurationException {
String fileOutName = outPutFile;
long startTime = System.currentTimeMillis();
XWPFDocument document = new XWPFDocument(new FileInputStream(fileName));
//XWPFDocument document = new XWPFDocument(fileName);
XHTMLOptions options = XHTMLOptions.create().indent(4);
// 导出图片
File imageFolder = new File(uploadPath.get(1));
options.setExtractor(new FileImageExtractor(imageFolder));
// URI resolver
//options.URIResolver(new FileURIResolver(imageFolder));
// 设置虚拟访问路径 docx导出图片会另外创建/word/media目录不影响虚拟访问
options.URIResolver(new BasicURIResolver("/api/static/images/"));
File outFile = new File(fileOutName);
outFile.getParentFile().mkdirs();
OutputStream out = new FileOutputStream(outFile);
XHTMLConverter.getInstance().convert(document, out, options);
System.out.println("Generate " + fileOutName + " with " + (System.currentTimeMillis() - startTime) + " ms.");
}
public static void main(String[] args) throws Exception {
Doc2Html.docx2Html("F:\\2017-2020级学生2021-2022学年缴费通知.docx","F:\\cs.html");
}
}
@Data
@TableName("tb_interface_doc")
public class InterfaceDoc extends SuperEntity{
@TableField("url_address")
private String urlAddress;
@TableField("doc_name")
private String docName;
@TableField("suffix")
private String suffix;
@TableField("project_id")
private Integer projectId;
}
我这里是直接把构建生成的randomNumber + extension
和randomNumber.html
保存到了数据库中,然后在前端以超链接打开一个窗口的方式显示,以实现pdf和doc&docx
在线阅读的效果。当然还可以以流的方式输出到浏览器直接显示。根据自己需求选择
/**
* 上传接口文档
* @param multipartFile
* @param req
* @param id
* @return
*/
@Override
public Boolean uploadInterfaceWord(MultipartFile multipartFile, HttpServletRequest req, Integer id) throws Exception {
//使用UUID生成唯一标识文件名
String randomNumber = UUID.randomUUID().toString().replace("-", "");
//获取文件的原始名
String oldFilename = multipartFile.getOriginalFilename();
//获取文件后缀 .pdf/.docx/.doc
String extension = oldFilename.substring(oldFilename.lastIndexOf("."));
//生成新的文件名
String newFileName = randomNumber + extension;
// 文档上传路径
File documentUpload = new File(systemRoot.get(0));
if (!documentUpload.exists()) {
//判断目录是否存在,不存在则直接创建
documentUpload.mkdirs();
}
try {
multipartFile.transferTo(new File(documentUpload, newFileName));
} catch (IOException e) {
e.printStackTrace();
}
// InputStream input = null;
// OutputStream out = null;
// 虚拟路径
String invented_address = null;
// 文件后缀,方便前端判断
String fileSuffix = null;
// 构建doc和docx新的文件名
String docAndDocxNewFileName = randomNumber + ".html";
if (extension.equals(".pdf")){
// 上传文件为.pdf后缀的虚拟访问路径
invented_address = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort() + "/api/static/document/" + newFileName;
// 添加后缀
fileSuffix = extension.substring(extension.indexOf(".") + 1);
} else
// 在线预览
//获取文件路径
if (extension.equals(".docx")) {
Doc2Html.docx2Html(systemRoot.get(0) + newFileName, systemRoot.get(0) + docAndDocxNewFileName);
// 上传文件为.pdf后缀的虚拟访问路径
invented_address = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort() + "/api/static/document/" + docAndDocxNewFileName;
// 添加后缀
fileSuffix = extension.substring(extension.indexOf(".") + 1);
// input = new FileInputStream(systemRoot.get(0) + docAndDocxNewFileName);
// response.setContentType("text/html;charset=UTF-8");// 解决页面显示乱码
// out = response.getOutputStream();
} else if (extension.equals(".doc")) {
Doc2Html.doc2Html(systemRoot.get(0) + newFileName, systemRoot.get(0) + docAndDocxNewFileName);
// 上传文件为.pdf后缀的虚拟访问路径
invented_address = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort() + "/api/static/document/" + docAndDocxNewFileName;
// 添加后缀
fileSuffix = extension.substring(extension.indexOf(".") + 1);
// input = new FileInputStream(systemRoot.get(0) + docAndDocxNewFileName);
// response.setContentType("text/html;charset=UTF-8");// 解决页面显示乱码
// out = response.getOutputStream();
}
InterfaceDoc interfaceDoc = new InterfaceDoc();
interfaceDoc.setProjectId(id);
interfaceDoc.setUrlAddress(invented_address);
interfaceDoc.setSuffix(fileSuffix);
interfaceDoc.setDocName(oldFilename);
interfaceDocMapper.insert(interfaceDoc);
return true;
// 以流的方式输出到浏览器
// if (extension.equals(".docx") || extension.equals(".doc")) {
// FileInputStream fis = new FileInputStream(systemRoot.get(0) + docAndDocxNewFileName);
// byte[] b = new byte[fis.available()];
// if (out != null) {
// if (input != null) {
// input.read(b);
// out.write(b);
// } else {
// System.out.println("InputStream为空。。。");
// }
// } else {
// System.out.println("OutputStream为空。。。");
// }
//
// out.flush();
// input.close();
// out.close();
// }
}
@PostMapping("/project/upload/{id}")
public ResultVO<?> fileupload(MultipartFile file, HttpServletRequest req,@PathVariable Integer id) throws FileNotFoundException {
Boolean bool = projectService.uploadInterfaceWord(file, req, id);
return bool ? ResultVO.ok("上传成功") : ResultVO.fail("上传失败");
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>查看接口文档title>
<link rel="stylesheet" th:href="@{/component/pear/css/pear.css}" />
head>
<body class="pear-container">
<div>
<div class="layui-row layui-col-space10">
<div class="layui-col-xs6 layui-col-md3" th:each="item:${InterfaceDocList}">
<div class="layui-card top-panel">
<div class="layui-card-header">[[${item.docName}]]div>
<div class="layui-card-body">
<div class="layui-row layui-col-space5">
<div class="layui-col-xs8 layui-col-md8 top-panel-number" style="color: #28333E;">
<a th:href="@{${item.urlAddress}}" target="_blank">
<img th:src="@{'/admin/images/'+${item.suffix}+'.png'}" alt="查看详情" style="width: 100px;height: 100px;border-radius: 5px;" />
a>
div>
div>
div>
div>
div>
div>
div>
body>
<script th:src="@{/component/layui/layui.js}">script>
<script th:src="@{/component/pear/pear.js}">script>
html>