libreoffice在Windows和Linux环境的安装和结合Springboot使用教程
前言:
在公司做开发时,遇到一个需求,要求上传的文件有图片,也有word和pdf。预览信息时,既要求能水印展示出来,又要求能大图水印预览。思索许久,我决定采取全部打水印然后转成图片Base64,但是后面突然发现word不能直接转为图片,强制转换会有内容丢失,于是我打算先把word转为pdf,然后再把pdf转为图片Base64,思路堪称完美。
思路有了,那该选什么工具好呢?网上的教程参差不齐,有的工具也能用,但是转出来的效果不尽人意,在寻找许久后我选择了document4j,这工具在转换方面堪称完美,但美中不足的是只能在Windows上使用,最后我选择了libreoffice。
libreoffice既兼容Windows,同时又兼容Linux,转换出来的效果也不错,于是我的工具问题也就迎刃而解了。
环境:
IntelliJ IDEA 2024.1.4
Linux服务器
Windows笔记本
Oracle数据库(Mysql数据也行,我这里测试用的Mysql)
LibreOffice 24.2.4 下载地址:下载 LibreOffice | LibreOffice 简体中文官方网站 - 自由免费的办公套件
1.安装教程:
我们需要下载两个LibreOffice 24.2.4,一个是Windows的,一个是Linux的。另外下载的话选择国内镜像下载,比较快:
Window下载完成后安装在D盘即可(选择),安装没有特别需要注意的,安装完自己记得路径就行。对于Linux安装,安装如下:
1.1解压压缩包
上传到linux指定目录下或者用wget直接下载到linux指定目录下后(我上传到的是/opt目录下),使用下面命令解压:
cd /opttar -xvf LibreOffice_24.2.4_Linux_x86-64_rpm.tar.gz
1.2安装libreoffice
执行下面命令开始安装,等待安装完成即可:
sudo yum install ./LibreOffice_24.2.4_Linux_x86-64_rpm/RPMS/*.rpm
1.3启动libreoffice
Windows的话cmd执行下面命令启动,其中D:\\Program Files\\LibreOffice就是安装的路径:
D:\\Program Files\\LibreOffice\\program\\soffice.exe --headless --invisible --nologo --nodefault --nofirststartwizard --accept=\"socket,host=127.0.0.1,port=8100;urp;\"
Linux的话执行下面命令启动,同样的/opt/libreoffice24.2就是安装的路径:
/opt/libreoffice24.2/program/soffice --headless --invisible --nologo --nodefault --nofirststartwizard --accept=\"socket,host=127.0.0.1,port=8100;urp;\"
Linux的话还可以使用docker部署,这样的话就可以不用安装并启动得那么麻烦,使用的镜像是libreoffice/online,但是要把这个安装的libreoffice映射出来,我这里就不详细介绍了,感兴趣的可以自己去试一试。
到此,Windows的安装启动就结束了,但是Linux还没有结束,因为Linux缺少中文字体的缘故,导致转出来的中文会是如下的效果:
为此,我们要给Linux装上中文字体:
1.4Linux安装中文字体
首先安装fontconfig:
yum -y install fontconfig
进入/usr/share目录下,执行ls会发现这两个目录,则说明安装成功:
cd /usr/sharels
接着,打开这个fonts文件夹,新建一个叫chinese文件夹:
cd fontsmkdir chinese
然后将Windows系统的字体文件全部拷进去(这里直接复制是不允许的,需要复制C:\\Windows下的Fonts目录到桌面,然后再从桌面双击进入Fonts文件夹,Ctrl+A全选,上传到服务器的这个chinese目录下):
接着给这个目录赋予权限,执行命令:
chmod -R 755 /usr/share/fonts/chinese
安装ttmkfdir,编辑配置文件,执行命令:
yum -y install ttmkfdirttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dirvi /etc/fonts/fonts.conf
在Font directory list配置项下,把这个中文字体目录加进去,添加如下代码:
/usr/share/fonts/chinese
保存退出后,执行下面命令刷新一下缓存
fc-cache
到此,中文字体安装完成。转出的效果也变成了如下图,中文字体也能正常显示了:
2.Springboot集成libreoffice
环境安装好了,解下来,我们就开始写代码了。
2.1引入pom依赖
首先创建完Springboot项目后,安装必要的依赖,其中重点就是jodconverter-spring-boot-starter和jodconverter-local,其他的看需要引入:
org.jodconverter jodconverter-spring-boot-starter 4.4.7 org.jodconverter jodconverter-local 4.4.7 org.apache.pdfbox pdfbox 2.0.24 cn.hutoolhutool-all5.8.25 org.apache.commons commons-collections4 4.4 commons-io commons-io 2.16.1 org.apache.commons commons-lang3 3.12.0 org.apache.poi poi-ooxml 5.2.5 com.itextpdf itextpdf 5.5.13.4
2.2修改yaml配置文件
修改application.yaml,添加如下配置:
spring: profiles: active: local# 共享配置jodconverter: local: enabled: true portNumbers: 8100 maxTasksPerProcess: 100 taskQueueTimeout: 60file: path: windows: \'D:/temp/file\' linux: \'/file\'---spring: config: activate: on-profile: localjodconverter: local: officeHome: \'D:\\Program Files\\LibreOffice\'---spring: config: activate: on-profile: serverjodconverter: local: officeHome: \'/opt/libreoffice24.2\'
其中我用spring: profiles: active: 来区分是Windows启动的项目还是Linux,若是Windows则改为local,若是Linux的话改成server即可;
file:path:是文件上传后保存的目录。若是Linux环境确保启动该项目的用户对该目录有足够的读写权限,否则上传文件会报错500。
2.3control层代码
FileControl.java完整代码如下:
package com.example.testdemo.test.control;import com.example.testdemo.test.model.DownloadFile;import com.example.testdemo.test.service.FileService;import com.example.testdemo.test.utils.Result;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;import javax.servlet.http.HttpServletResponse;/** * 项目名称: testDemo * 作者: zhaojs * 创建日期: 2024年07月04日 * 文件说明: 文件控制层 */@RestControllerpublic class FileControl { /** * 文件服务 */ @Resource private FileService fileService; /** * 文件上传 * @param files * @return */ @PostMapping(\"/file/upload\") public Result uploadFile(@RequestParam(\"files\") MultipartFile[] files) { return fileService.uploadFile(files); } /** * 获取文件列表 * @param ids * @return */ @GetMapping(\"/file/getFile/{ids}\") public Result getFile(@PathVariable(\"ids\") String ids) { return Result.success(fileService.getFile(ids)); } /** * 附件转为图片的base64编码 * 支持附件类型:doc、docx、pdf、png、jpeg * @param downloadFile */ @PostMapping(\"/file/switchAttachment/imageBase64\") public Result switchAttachmentImageBase64(@RequestBody DownloadFile downloadFile) { return fileService.switchAttachmentImageBase64(downloadFile); }}
2.4service层代码
FileService.java完整代码如下:
package com.example.testdemo.test.service;import com.example.testdemo.test.model.Attachment;import com.example.testdemo.test.model.DownloadFile;import com.example.testdemo.test.utils.Result;import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;import java.util.List;/** * 项目名称: testDemo * 创建日期: 2024年07月04日 * @author zhaojs * 文件说明: 见类描述 */public interface FileService { /** * 上传文件 * @param files * @return */ Result uploadFile(MultipartFile[] files); /** * 获取文件列表 * @param ids * @return */ List getFile(String ids); /** * 附件转为图片的base64编码 * @param downloadFile * @return */ Result switchAttachmentImageBase64(DownloadFile downloadFile);}
FileServiceImpl.java完整代码如下:
package com.example.testdemo.test.service.Impl;import com.example.testdemo.test.dao.AttachmentDao;import com.example.testdemo.test.model.Attachment;import com.example.testdemo.test.model.DownloadFile;import com.example.testdemo.test.service.FileService;import com.example.testdemo.test.utils.Result;import com.example.testdemo.test.utils.WordWaterMarker;import com.itextpdf.text.Element;import com.itextpdf.text.pdf.BaseFont;import com.itextpdf.text.pdf.PdfContentByte;import com.itextpdf.text.pdf.PdfGState;import com.itextpdf.text.pdf.PdfReader;import com.itextpdf.text.pdf.PdfStamper;import org.apache.commons.collections4.CollectionUtils;import org.apache.commons.collections4.MapUtils;import org.apache.commons.io.FilenameUtils;import org.apache.commons.io.IOUtils;import org.apache.commons.lang3.StringUtils;import org.apache.pdfbox.pdmodel.PDDocument;import org.apache.pdfbox.rendering.ImageType;import org.apache.pdfbox.rendering.PDFRenderer;import org.apache.poi.xwpf.usermodel.XWPFDocument;import org.jodconverter.core.DocumentConverter;import org.jodconverter.core.document.DefaultDocumentFormatRegistry;import org.jodconverter.core.document.DocumentFormat;import org.jodconverter.core.office.OfficeException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.FileSystemResource;import org.springframework.stereotype.Service;import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;import javax.imageio.ImageIO;import javax.imageio.stream.ImageOutputStream;import javax.servlet.http.HttpServletResponse;import java.awt.*;import java.awt.geom.AffineTransform;import java.awt.image.BufferedImage;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.nio.file.StandardCopyOption;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.ZoneId;import java.time.format.DateTimeFormatter;import java.util.ArrayList;import java.util.Arrays;import java.util.Base64;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import java.util.UUID;import java.util.concurrent.Callable;import java.util.concurrent.ConcurrentSkipListSet;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeUnit;import java.util.stream.Collectors;import java.util.zip.ZipEntry;import java.util.zip.ZipOutputStream;@Servicepublic class FileServiceImpl implements FileService { /** * Windows文件路径 */ @Value(\"${file.path.windows}\") private String filePathWindows; /** * Linux文件路径 */ @Value(\"${file.path.linux}\") private String filePathLinux; /** * 文档转换器 */ @Resource private DocumentConverter documentConverter; /** * 附件表Dao */ @Resource private AttachmentDao attachmentDao; /** * 日期时间格式 */ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(\"yyyyMMddHHmmssSSS\"); /** * 并发处理任务的数量 */ private static final int MAX_CONCURRENT_TASKS = 5; /** * 日志 */ private static final Logger logger = LoggerFactory.getLogger(FileServiceImpl.class); /** * 上传文件 * * @param files * @return */ @Override public Result uploadFile(MultipartFile[] files) { try { // 遍历文件数组并处理每个文件 for (MultipartFile file : files) { if (! file.isEmpty()) { // 处理文件名 String originalFilename = file.getOriginalFilename(); String extension = FilenameUtils.getExtension(originalFilename); String uniqueFilename = UUID.randomUUID().toString() + (extension != null && ! extension.isEmpty() ? \".\" + extension : \"\"); // 获取当前日期并格式化为年月目录格式 LocalDate now = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy/MM\"); String yearMonthDir = now.format(formatter); // 指定保存的根目录,对于Windows可以是D盘的某个路径,对于Linux通常是/home/user或特定的目录 String rootDir = System.getProperty(\"os.name\").toLowerCase().startsWith(\"win\") ? filePathWindows : filePathLinux; // 构建保存文件的完整路径,使用Paths.get()自动处理路径分隔符 Path directoryPath = Paths.get(rootDir, yearMonthDir); // 确保目录存在 Files.createDirectories(directoryPath); // 文件保存路径 Path filePath = directoryPath.resolve(uniqueFilename); // 将文件输入流中的数据复制到目标路径,如果目标文件已存在,则覆盖原有文件 Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); // 插入附件表 Attachment attachment = new Attachment(); attachment.setAttachmentId(UUID.randomUUID().toString()); attachment.setFileId(uniqueFilename); attachment.setFileName(originalFilename); attachment.setFileType(file.getContentType()); attachment.setFileSize(Double.valueOf(file.getSize())); attachment.setFilePath(filePath.toString()); attachment.setCreateTime(LocalDateTime.now()); attachmentDao.insert(attachment); } else { return Result.fail(\"文件为空\"); } } return Result.success(\"文件上传成功\"); } catch (IOException e) { return Result.fail(\"文件上传失败\"); } } /** * 获取文件列表 * * @param ids * @return */ @Override public List getFile(String ids) { if (StringUtils.isNotBlank(ids)) { String[] attachmentIds = ids.split(\",\"); List attachmentList = attachmentDao.selectBatchIds(Arrays.asList(attachmentIds)); return attachmentList; } return null; } /** * 附件转为图片的base64编码 * * @param downloadFile * @return */ @Override public Result switchAttachmentImageBase64(DownloadFile downloadFile) { List<Map> fileList = new ArrayList(); List attachmentList = getFile(downloadFile.getAttachmentIds()); attachmentList.forEach(attachment -> { Map map = new HashMap(); map.put(\"fileName\", attachment.getFileName()); map.put(\"filePath\", attachment.getFilePath()); fileList.add(map); }); //附件归类 List imageFilePathList = new ArrayList(); List pdfFilePathList = new ArrayList(); List wordFilePathList = new ArrayList(); fileList.forEach(map -> { // 附件名称 String fileName = MapUtils.getString(map, \"fileName\"); // 判断附件类型 String filePath = MapUtils.getString(map, \"filePath\"); if (fileName.endsWith(\".jpg\") || fileName.endsWith(\".jpeg\") || fileName.endsWith(\".png\")) { imageFilePathList.add(filePath); } else if (fileName.endsWith(\".pdf\")) { pdfFilePathList.add(filePath); } else if (fileName.endsWith(\".doc\") || fileName.endsWith(\".docx\")) { wordFilePathList.add(filePath); } }); List imageBase64List = new ArrayList(); //先把word转为图片base64编码 if (CollectionUtils.isNotEmpty(wordFilePathList)) { List imageBase64 = wordFileToImageBase64(wordFilePathList, downloadFile.getWatermarkText()); if (imageBase64 != null && ! imageBase64.isEmpty()) { imageBase64List.addAll(imageBase64); } } //再把pdf转为图片base64编码 if (CollectionUtils.isNotEmpty(pdfFilePathList)) { List imageBase64 = pdfFileToImageBase64(pdfFilePathList, downloadFile.getWatermarkText()); if (imageBase64 != null && ! imageBase64.isEmpty()) { imageBase64List.addAll(imageBase64); } } //最后把图片直接转为base64编码 if (CollectionUtils.isNotEmpty(imageFilePathList)) { List imageBase64 = imageFileToBase64(imageFilePathList, downloadFile.getWatermarkText()); if (imageBase64 != null && ! imageBase64.isEmpty()) { imageBase64List.addAll(imageBase64); } } return Result.success(imageBase64List); } /** * word转为图片base64编码 * * @param wordFilePathList * @param watermarkText * @return */ public List wordFileToImageBase64(List wordFilePathList, String watermarkText) { List imageBase64List = new ArrayList(); ExecutorService executor = Executors.newFixedThreadPool(MAX_CONCURRENT_TASKS); try { List<Future<List>> futures = new ArrayList(); // 首先,将Word文件转换为PDF字节流,并为每个字节流创建一个转换任务 for (String filePath : wordFilePathList) { // 将Word转换为PDF字节流 ByteArrayOutputStream pdfByteStream = wordToPdf(filePath); // 将PDF字节流转换为图片Base64 Callable<List> task = () -> pdfByteToImageBase64Single(pdfByteStream.toByteArray(), watermarkText); futures.add(executor.submit(task)); } // 收集所有任务的结果 for (Future<List> future : futures) { try { // 合并每个任务返回的Base64编码图像字符串列表 imageBase64List.addAll(future.get()); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); logger.error(\"处理PDF字节流时发生错误\", e); } } } finally { executor.shutdown(); try { if (! executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); logger.error(\"关闭线程池时发生中断\", e); } } return imageBase64List; } /** * word转pdf * * @param wordFilePath * @return * @throws Exception */ public ByteArrayOutputStream wordToPdf(String wordFilePath) { ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); try (InputStream docxInputStream = new FileInputStream(wordFilePath)) { DocumentFormat type = \"docx\".equals(getFileType(new File(wordFilePath))) ? DefaultDocumentFormatRegistry.DOCX : DefaultDocumentFormatRegistry.DOC; documentConverter.convert(docxInputStream) .as(type) .to(pdfOutputStream) .as(DefaultDocumentFormatRegistry.PDF) .execute(); } catch (IOException | OfficeException e) { logger.error(\"转换Word文件为PDF时发生错误\", e); throw new RuntimeException(e); } return pdfOutputStream; } /** * pdfBytes转图片Base64(单个字节流处理) * * @param pdfBytes * @param watermarkText * @return */ private List pdfByteToImageBase64Single(byte[] pdfBytes, String watermarkText) { List imageBase64List = new ArrayList(); try (PDDocument document = PDDocument.load(pdfBytes)) { PDFRenderer pdfRenderer = new PDFRenderer(document); for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300, ImageType.RGB); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, \"png\", baos); baos.flush(); byte[] imageBytes = baos.toByteArray(); baos.close(); if (StringUtils.isNotBlank(watermarkText)) { try (InputStream watermarkedInputStream = addImageWaterMark(new ByteArrayInputStream(imageBytes), watermarkText)) { byte[] watermarkedBytes = watermarkedInputStream.readAllBytes(); watermarkedInputStream.close(); String format = \"png\"; String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(watermarkedBytes); imageBase64List.add(fullEncodedImage); } catch (IOException e) { logger.error(\"添加水印时发生错误\", e); } } else { String format = \"png\"; String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(imageBytes); imageBase64List.add(fullEncodedImage); } } } catch (IOException e) { logger.error(\"处理PDF字节流时发生错误\", e); } return imageBase64List; } /** * pdf转图片Base64 * * @param pdfFilePathList * @param watermarkText * @return */ public List pdfFileToImageBase64(List pdfFilePathList, String watermarkText) { List imageBase64List = new ArrayList(); ExecutorService executor = Executors.newFixedThreadPool(MAX_CONCURRENT_TASKS); try { // 提交任务到线程池 List<Future<List>> futures = new ArrayList(); for (String filePath : pdfFilePathList) { Callable<List> task = () -> convertSinglePdfToImageBase64(filePath, watermarkText); futures.add(executor.submit(task)); } // 收集所有任务的结果 for (Future<List> future : futures) { try { // 获取并合并每个任务的结果 imageBase64List.addAll(future.get()); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); logger.error(\"处理PDF文件时发生错误\", e); } } } finally { executor.shutdown(); try { if (! executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); logger.error(\"关闭线程池时发生中断\", e); } } return imageBase64List; } /** * 单个PDF转图片Base64 * * @param filePath * @param watermarkText * @return */ private List convertSinglePdfToImageBase64(String filePath, String watermarkText) { List singleFileImageBase64List = new ArrayList(); try (PDDocument document = PDDocument.load(new FileSystemResource(filePath).getFile())) { PDFRenderer pdfRenderer = new PDFRenderer(document); for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 300, ImageType.RGB); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(image, \"png\", baos); baos.flush(); byte[] imageBytes = baos.toByteArray(); baos.close(); if (StringUtils.isNotBlank(watermarkText)) { try (InputStream watermarkedInputStream = addImageWaterMark(new ByteArrayInputStream(imageBytes), watermarkText)) { byte[] watermarkedBytes = watermarkedInputStream.readAllBytes(); String format = \"png\"; String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(watermarkedBytes); singleFileImageBase64List.add(fullEncodedImage); } catch (IOException e) { logger.error(\"添加水印时发生错误\", e); } } else { String format = \"png\"; String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(imageBytes); singleFileImageBase64List.add(fullEncodedImage); } } } catch (IOException e) { logger.error(\"加载PDF文档时发生错误\", e); } return singleFileImageBase64List; } /** * 图片转为base64编码 * * @param imageFilePathList * @param watermarkText * @return */ public List imageFileToBase64(List imageFilePathList, String watermarkText) { List imageBase64List = new ArrayList(); ExecutorService executor = Executors.newFixedThreadPool(MAX_CONCURRENT_TASKS); try { // 提交任务到线程池 List<Future> futures = imageFilePathList.stream() .map(path -> executor.submit(() -> convertSingleImageToBase64(path, watermarkText))) .collect(Collectors.toList()); // 收集所有任务的结果 for (Future future : futures) { try { // 添加每个任务返回的Base64编码字符串 imageBase64List.add(future.get()); } catch (InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); logger.error(\"处理图像文件时发生错误\", e); } } } finally { executor.shutdown(); try { if (! executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); logger.error(\"关闭线程池时发生中断\", e); } } return imageBase64List; } /** * 单个图像文件转换为Base64字符串 * * @param imagePath 图像文件路径 * @param watermarkText * @return Base64编码的字符串 */ private String convertSingleImageToBase64(String imagePath, String watermarkText) { try { File file = new File(imagePath); InputStream originalInputStream = new FileInputStream(file); if (StringUtils.isNotBlank(watermarkText)) { try (InputStream watermarkedInputStream = addImageWaterMark(originalInputStream, watermarkText)) { byte[] bytes = watermarkedInputStream.readAllBytes(); String format = getFileExtension(file.getName()).toLowerCase(); String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(bytes); return fullEncodedImage; } } else { String format = getFileExtension(file.getName()).toLowerCase(); String fullEncodedImage = \"data:image/\" + format + \";base64,\" + Base64.getEncoder().encodeToString(originalInputStream.readAllBytes()); return fullEncodedImage; } } catch (Exception e) { logger.error(\"转换图像文件为Base64时出错: \" + imagePath, e); return null; } } /** * 获取文件扩展名 * * @param fileName * @return */ private static String getFileExtension(String fileName) { int lastIndexOfDot = fileName.lastIndexOf(\'.\'); if (lastIndexOfDot == - 1) { // 没有找到扩展名的情况 return \"\"; } return fileName.substring(lastIndexOfDot + 1); } /** * 判断word是doc还是docx * * @param file * @return */ public String getFileType(File file) { String fileName = file.getName(); int dotIndex = fileName.lastIndexOf(\'.\'); if (dotIndex > 0 && dotIndex < fileName.length() - 1) { String extension = fileName.substring(dotIndex + 1).toLowerCase(); if (\"docx\".equals(extension)) { return \"docx\"; } else if (\"doc\".equals(extension)) { return \"doc\"; } } return null; } /** * 给照片添加水印 * * @param inputStream * @param waterMarkText * @return */ public InputStream addImageWaterMark(InputStream inputStream, String waterMarkText) throws IOException { if (waterMarkText == null || waterMarkText == \"\") { return inputStream; } BufferedImage image; try { image = ImageIO.read(inputStream); } catch (IOException e) { throw e; } int width = image.getWidth(); int height = image.getHeight(); BufferedImage watermarkedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = watermarkedImage.createGraphics(); // 绘制原始图像到新图像 g2d.drawImage(image, 0, 0, width, height, null); // 设置抗锯齿 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // 设置水印的字体和颜色(包括透明度) Font font = new Font(\"STSong-Light\", Font.BOLD, 30); // 可以调整字体大小 g2d.setFont(font); g2d.setColor(new Color(0, 0, 0, 255)); // 红色,透明度为 25% g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.2f)); // 创建旋转变换 AffineTransform originalTransform = g2d.getTransform(); AffineTransform rotateTransform = new AffineTransform(); rotateTransform.rotate(Math.toRadians(- 30), 0, 0); g2d.setTransform(rotateTransform); // 获取字体度量信息 FontMetrics fontMetrics = g2d.getFontMetrics(); int watermarkWidth = fontMetrics.stringWidth(waterMarkText); int watermarkHeight = fontMetrics.getHeight(); // 将图像用水印铺满 for (int x = - width; x < width * 2; x += watermarkWidth + 100) { for (int y = - height; y < height * 2; y += watermarkHeight + 100) { g2d.drawString(waterMarkText, x, y); } } // 恢复原始变换 g2d.setTransform(originalTransform); g2d.dispose(); // 将带有水印的图像写入ByteArrayOutputStream ByteArrayOutputStream os = new ByteArrayOutputStream(); try (ImageOutputStream ios = ImageIO.createImageOutputStream(os)) { if (! ImageIO.write(watermarkedImage, \"jpg\", ios)) { throw new IOException(\"Failed to write watermarked image to output stream\"); } } catch (IOException e) { throw e; } finally { try { os.close(); } catch (IOException e) { throw e; } } // 返回处理后的图像流 return new ByteArrayInputStream(os.toByteArray()); } /** * 给PDF添加水印 * * @param inputStream * @param waterMarkText * @return */ public InputStream addPdfWaterMark(InputStream inputStream, String waterMarkText) { if (waterMarkText == null || waterMarkText == \"\") { return inputStream; } OutputStream outputStream = null; PdfReader pdfReader = null; PdfStamper pdfStamper = null; try { // 水印的高和宽 int waterMarkHeight = 30; int watermarkWeight = 60; // 水印间隔距离 int waterMarkInterval = 100; outputStream = new ByteArrayOutputStream(); // 读取PDF文件流 pdfReader = new PdfReader(inputStream); // 创建PDF文件的模板,可以对模板的内容修改,重新生成新PDF文件 pdfStamper = new PdfStamper(pdfReader, outputStream); // 设置水印字体 BaseFont baseFont = BaseFont.createFont(\"STSong-Light\", \"UniGB-UCS2-H\", BaseFont.EMBEDDED); // 设置PDF内容的Graphic State 图形状态 PdfGState pdfGraPhicState = new PdfGState(); // 填充透明度 pdfGraPhicState.setFillOpacity(0.2f); // 轮廓不透明度 pdfGraPhicState.setStrokeOpacity(0.4f); // PDF页数 int pdfPageNum = pdfReader.getNumberOfPages() + 1; // PDF文件内容字节 PdfContentByte pdfContent; // PDF页面矩形区域 com.itextpdf.text.Rectangle pageRectangle; for (int i = 1; i < pdfPageNum; i++) { // 获取当前页面矩形区域 pageRectangle = pdfReader.getPageSizeWithRotation(i); // 获取当前页内容,getOverContent表示之后会在页面内容的上方加水印 pdfContent = pdfStamper.getOverContent(i); // 获取当前页内容,getOverContent表示之后会在页面内容的下方加水印 // pdfContent = pdfStamper.getUnderContent(i); pdfContent.saveState(); // 设置水印透明度 pdfContent.setGState(pdfGraPhicState); // 开启写入文本 pdfContent.beginText(); // 设置字体 pdfContent.setFontAndSize(baseFont, 15); // 在高度和宽度维度每隔waterMarkInterval距离添加一个水印 for (int height = waterMarkHeight; height < pageRectangle.getHeight(); height = height + waterMarkInterval) { for (int width = watermarkWeight; width < pageRectangle.getWidth() + watermarkWeight; width = width + waterMarkInterval) { // 添加水印文字并旋转30度角 pdfContent.showTextAligned(Element.ALIGN_LEFT, waterMarkText, width - watermarkWeight, height - waterMarkHeight, 30); } } // 停止写入文本 pdfContent.endText(); } pdfStamper.close(); pdfReader.close(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (pdfReader != null) { pdfReader.close(); } if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.flush(); outputStream.close(); } } catch (IOException e) { throw new RuntimeException(e); } } return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray()); } /** * 给word添加水印 * * @param inputStream * @param waterMarkText * @return */ public InputStream addWordWaterMark(InputStream inputStream, String waterMarkText) { if (waterMarkText == null || waterMarkText == \"\") { return inputStream; } try { XWPFDocument doc = new XWPFDocument(inputStream); WordWaterMarker.makeFullWaterMarkByWordArt(doc, waterMarkText, \"#d8d8d8\", \"0.5pt\", \"-30\"); ByteArrayOutputStream os = new ByteArrayOutputStream(); doc.write(os); return new ByteArrayInputStream(os.toByteArray()); } catch (IOException e) { e.printStackTrace(); } return null; }}
2.5model层代码
DownloadFile.java完整代码如下:
package com.example.testdemo.test.model;import lombok.Data;import java.util.List;/** * 项目名称: testDemo * 作者: zhaojs * 创建日期: 2024年07月04日 * 文件说明: 见类描述 */@Datapublic class DownloadFile { /** * 附件id,多个用\',\'隔开 */ private String attachmentIds; /** * 文件压缩包名 */ private String fileZipName; /** * 水印文字 */ private String watermarkText;}
Attachment.java完整代码如下:
package com.example.testdemo.test.model;import java.time.LocalDateTime;import java.util.Date;import java.io.Serializable;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.Data;/** * 附件表实体类 * @author zhaojs * @date 2024-07-04 */@Data@TableName(\"ATTACHMENT\")public class Attachment implements Serializable { /** * 序列化变量 */ private static final long serialVersionUID = 697303854047803428L; /** * 主键id */ @TableId private String attachmentId; /** * 文件id */ private String fileId; /** * 文件名 */ private String fileName; /** * 文件类型 */ private String fileType; /** * 文件大小 */ private Double fileSize; /** * 文件路径 */ private String filePath; /** * 创建人id */ private String createUserId; /** * 创建人姓名 */ private Integer createUserName; /** * 创建时间 */ private LocalDateTime createTime; /** * 更新人id */ private String updateUserId; /** * 更新人姓名 */ private String updateUserName; /** * 更新时间 */ private LocalDateTime updateTime; /** * 文件过期时间 */ private LocalDateTime expireTime;}
2.6dao层代码
AttachmentDao.java完整代码如下,对应的mapper文件自己映射就好,这里就不给出了:
package com.example.testdemo.test.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.testdemo.test.model.Attachment;import org.apache.ibatis.annotations.Mapper;import org.springframework.stereotype.Repository;/** * 附件表Dao接口 * @author zhaojs * @date 2024-07-04 */public interface AttachmentDao extends BaseMapper {}
2.6说明
(1)代码内的数据库表自己创建,参考Attachment.java即可。
(2)到此,就可以使用了,Linux和Windows都可以,启动项目前记得把libreoffice也启起来,也就是1.3启动libreoffice。
(3)若是转换过程发现报错Cause: java.sql.SQLException: Incorrect string value: \'\\xE6\\xB5\\x8B\\xE8\\xAF\\x95...\' for column \'FILE_NAME\' at row 1
; uncategorized SQLException; SQL state [HY000]; error code [1366]; Incorrect string value: \'\\xE6\\xB5\\x8B\\xE8\\xAF\\x95...\' for column \'FILE_NAME\' at row 1; nested exception is java.sql.SQLException: Incorrect string value: \'\\xE6\\xB5\\x8B\\xE8\\xAF\\x95...\' for column \'FILE_NAME\' at row 1] with root cause
则是字符集编码引起的,执行下面命令修改mysql字符集即可:
ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;ALTER DATABASE 数据库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;