一. Linux搭建好Minio服务后,springboot项目中配置好minio相关信息,详见主页其它文章;
二. 传文件前的相关Minio基础类信息 & 转换工具类code如下:
1. Minio相关配置类:
创建minio工具类接口:
package com.geb.bpgms.sheet.infrastructure.client.oss;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
/**
* @author songkz
*/
public interface OssClient {
/**
* 上传文件,并获取文件的URL链接
*/
String uploadFile(MultipartFile file, String fileName, String type) throws Exception;
void delFile(String fileName) throws Exception;
InputStream downloadFile(String filename) throws Exception;
/** 预览文件链接 */
String presignedObjectUrl(String url) throws Exception;
}
Minio实现类:
package com.geb.bpgms.sheet.infrastructure.client.oss;
import com.geb.bpgms.document.infrastructure.client.config.MinioClientConfig;
import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
/**
* @author songkz
*/
@Service
@Slf4j
public class FinancialMinioOssClient implements OssClient {
private final MinioClient financialMinioClient;
private final MinioClient previewPubMinioClient;
private final MinioClientConfig.MinioProperties minioProperties;
public FinancialMinioOssClient(MinioClientConfig.MinioProperties minioProperties,
@Qualifier(value = "financialMinioClient") MinioClient financialMinioClient,
@Qualifier(value = "previewPubMinioClient") MinioClient previewPubMinioClient) {
this.minioProperties = minioProperties;
this.financialMinioClient = financialMinioClient;
this.previewPubMinioClient = previewPubMinioClient;
}
@Override
public String uploadFile(MultipartFile file, String uniqueFileName, String type) throws Exception {
String uniqueFileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
try (InputStream inputStream = file.getInputStream()) {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(minioProperties.getFinancialBucketName())
.object(uniqueFileName)
.stream(inputStream, file.getSize(), -1)
.contentType(type)
.build();
financialMinioClient.putObject(putObjectArgs);
return uniqueFileName;
} catch (Exception e) {
log.error("上传minio失败,{}", e.getMessage());
throw new Exception("Error occurred while uploading file: " + e.getMessage(), e);
}
}
@Override
public String presignedObjectUrl(String url) throws Exception{
return previewPubMinioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET) // 请求方法
.bucket(minioProperties.getFinancialBucketName())
.object(url)
.expiry(1, TimeUnit.DAYS)
.build()
);
}
@Override
public void delFile(String fileName) throws Exception {
financialMinioClient.removeObject(RemoveObjectArgs.builder().bucket(minioProperties.getRagBucketName()).object(fileName).build());
}
@Override
public InputStream downloadFile(String filename) throws Exception {
try {
return financialMinioClient.getObject(
GetObjectArgs.builder()
.bucket(minioProperties.getFinancialBucketName())
.object(filename)
.build()
);
} catch (Exception e) {
throw new Exception("Error occurred while downloading file: " + e.getMessage(), e);
}
}
}
Minio相关配置类:
package com.geb.bpgms.document.infrastructure.client.config;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author songkz
*/
@Configuration
@EnableConfigurationProperties(MinioClientConfig.MinioProperties.class)
public class MinioClientConfig {
@Value("${minio.pubPointUrl}")
private String pubPointUrl;
private final MinioProperties properties;
public MinioClientConfig(MinioProperties properties) {
this.properties = properties;
}
@Bean(name = "ragMinioClient")
public MinioClient ragMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(properties.getUrl())
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
// Check if the bucket already exists, and create it if it doesn't
try {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getRagBucketName()).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getRagBucketName()).build());
}
} catch (Exception e) {
throw new RuntimeException("Error checking or creating bucket: " + e.getMessage(), e);
}
return minioClient;
}
@Bean(name = "financialMinioClient")
public MinioClient financialMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(properties.getUrl())
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
// Check if the bucket already exists, and create it if it doesn't
try {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getFinancialBucketName()).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getFinancialBucketName()).build());
}
} catch (Exception e) {
throw new RuntimeException("Error checking or creating bucket: " + e.getMessage(), e);
}
return minioClient;
}
@Bean(name = "previewPubMinioClient")
public MinioClient previewPubMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(pubPointUrl)
.credentials(properties.getAccessKey(), properties.getSecretKey())
.build();
// Check if the bucket already exists, and create it if it doesn't
try {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(properties.getFinancialBucketName()).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(properties.getFinancialBucketName()).build());
}
} catch (Exception e) {
throw new RuntimeException("Error checking or creating bucket: " + e.getMessage(), e);
}
return minioClient;
}
@ConfigurationProperties(prefix = "minio")
@Getter
@Setter
public static class MinioProperties {
private String url;
private String accessKey;
private String secretKey;
private String ragBucketName;
private String financialBucketName;
}
}
Minio配置详细信息:
这里的配置信息我们放到了nacos上,也可直接在yaml中配置
minio:
url: ${minio.host}
pubPointUrl: ${minio.pubHost}
access-key: ${minio.accessKey}
secret-key: ${minio.secretKey}
rag-bucket-name: minio-test
financial-bucket-name: minio-finalcial-test
2. Word转Pdf工具类:
package com.geb.bpgms.util;
import com.aspose.words.Shape;
import com.aspose.words.*;
import org.springframework.core.io.ClassPathResource;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
//work转pdf工具类
public class WordToPdfUtil {
private static boolean license = false;
public static void main(String[] args) {
WordToPdfUtil wordToPdfUtil = new WordToPdfUtil();
try {
String wordFile = "/Users/unifig/work/GEB/code/GEB/wisoss/sql/API信息调研(14).docx";
String pdfFile = "/Users/unifig/work/GEB/code/GEB/wisoss/sql/e17.pdf";
wordToPdfUtil.wordToPdf(wordFile,pdfFile);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 将Word文档转换为PDF格式-无水印
* 使用Apose库实现文档转换功能,需要先加载License以激活无水印转换。
*
* @param wordPath Word文档的路径,包括文件名和扩展名。
* @param pdfPath 生成的PDF文档的路径,包括文件名和扩展名。
* @throws Exception 如果转换过程中发生错误,将抛出异常。
* @return 转换失败时返回null,成功时返回非null值。
*/
public static String wordToPdf(String wordPath, String pdfPath) throws Exception {
FileOutputStream os = null;
try {
// 从classpath中加载Apose的License文件,以启用无水印转换。
// 凭证 不然切换后有水印
InputStream is = new ClassPathResource("/lc.xml").getInputStream();
License aposeLic = new License();
aposeLic.setLicense(is);
license = true;
// 检查License是否成功加载,如果未成功,则输出错误信息并中止转换。
if (!license) {
System.out.println("License验证不通过...");
return null;
}
// 创建一个空的PDF文件,准备写入转换后的内容。
//生成一个空的PDF文件
File file = new File(pdfPath);
os = new FileOutputStream(file);
// 创建一个Document对象,指定要转换的Word文档路径。
//要转换的word文件
Document doc = new Document(wordPath);
doc.save(os, SaveFormat.PDF);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保关闭文件输出流。
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 将指定文件转换为PDF格式,并添加水印。
*
* @param toFilePath 目标文件夹路径,用于保存转换后的PDF文件。
* @param fileName 原始文件名。
* @param type 原始文件类型,支持".doc"和".docx"。
* @return 转换后的PDF文件名,如果类型不支持则返回null。
* @throws Exception 如果转换过程中发生错误,则抛出异常。
*/
public static String file2pdf(String toFilePath, String fileName, String type ) throws Exception {
String htmFileName;
// 根据原始文件类型确定转换后的PDF文件名。
//获取转换成PDF之后文件名
if(".doc".equals(type)){
htmFileName = fileName+".pdf";
}else if(".docx".equals(type)){
htmFileName = fileName+".pdf";
}else{
// 如果不支持的文件类型,则返回null。
return null;
}
// 创建转换后的PDF文件对象。
//通过转换之后的PDF文件名,创建PDF文件
File htmlOutputFile = new File(toFilePath + File.separatorChar + htmFileName);
// 获取文件输出流,用于写入转换后的PDF文件。
//获取文件输出流
FileOutputStream os = new FileOutputStream(htmlOutputFile);
// 创建Doc文档对象模型,用于读取原始文档并进行转换。
//获取Doc文档对象模型
Document doc = new Document(toFilePath+ File.separatorChar + fileName+type);
// 为文档添加水印文本。
//为doc文档添加水印
insertWatermarkText(doc, "");//这里是水印内容
// 将文档保存为PDF格式,并写入到输出流中。
//将doc文旦转换成PDF文件并输出到之前创建好的pdf文件中
doc.save(os, SaveFormat.PDF);
// 关闭文件输出流。
//关闭输出流
if(os!=null){
os.close();
}
// 返回转换后的PDF文件名。
return htmFileName;
}
/**
* 为Word文档添加文本水印。
*
* @param doc 要添加水印的Word文档对象。
* @param watermarkText 水印文本内容。
* @throws Exception 如果操作文档过程中发生错误,则抛出异常。
*/
private static void insertWatermarkText(Document doc, String watermarkText) throws Exception {
// 创建一个文本形状对象作为水印
Shape watermark = new Shape(doc, ShapeType.TEXT_PLAIN_TEXT);
// 设置水印文本内容
watermark.getTextPath().setText(watermarkText);
// 设置水印字体及大小
watermark.getTextPath().setFontFamily("宋体");
// 设置水印的宽度和高度
watermark.setWidth(500);
watermark.setHeight(100);
// 设置水印的旋转角度
watermark.setRotation(-40);
// 设置水印的颜色
watermark.getFill().setColor(Color.lightGray);
watermark.setStrokeColor(Color.lightGray);
// 设置水印的位置属性
watermark.setRelativeHorizontalPosition(RelativeHorizontalPosition.PAGE);
watermark.setRelativeVerticalPosition(RelativeVerticalPosition.PAGE);
watermark.setWrapType(WrapType.NONE);
watermark.setVerticalAlignment(VerticalAlignment.CENTER);
watermark.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 创建一个段落对象,用于包含水印形状
Paragraph watermarkPara = new Paragraph(doc);
watermarkPara.appendChild(watermark);
// 遍历文档中的每个节(section),在每个节的主头、首个头和偶数头中插入水印
for (Section sect : doc.getSections())
{
// 在指定类型的头部插入水印
insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_PRIMARY);
insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_FIRST);
insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_EVEN);
}
// 提示水印添加完成
System.out.println("Watermark Set");
}
/**
* 在指定的页面头部插入水印。
* <p>
* 本方法用于向指定的页面头部插入一个水印。如果指定类型的头部不存在,则会创建一个新的头部并添加到文档中。
* 使用深度克隆来确保水印段落在每个页面头部的独立性。
*
* @param watermarkPara 水印段落对象,包含水印的文本和格式信息。
* @param sect 目标节对象,水印将被插入到该节的头部。
* @param headerType 指定的头部类型,用于获取或创建相应类型的头部。
* @throws Exception 如果操作失败,抛出异常。
*/
private static void insertWatermarkIntoHeader(Paragraph watermarkPara, Section sect, int headerType) throws Exception{
// 根据指定的头部类型获取现有的头部对象,如果不存在则返回null。
HeaderFooter header = sect.getHeadersFooters().getByHeaderFooterType(headerType);
// 如果指定类型的头部不存在,则创建一个新的头部对象,并将其添加到节的头部集合中。
if (header == null)
{
header = new HeaderFooter(sect.getDocument(), headerType);
sect.getHeadersFooters().add(header);
}
// 将水印段落深度克隆,并添加到头部对象中,确保每个页面的水印都是独立的。
header.appendChild(watermarkPara.deepClone(true));
}
public static File wordToPdf(InputStream inputStream, String fileName) {
String pdfFile = "./"+fileName;
FileOutputStream os = null;
try {
// 从classpath中加载Apose的License文件,以启用无水印转换。
// 凭证 不然切换后有水印
InputStream is = new ClassPathResource("/lc.xml").getInputStream();
License aposeLic = new License();
aposeLic.setLicense(is);
license = true;
// 检查License是否成功加载,如果未成功,则输出错误信息并中止转换。
if (!license) {
System.out.println("License验证不通过...");
return null;
}
// 创建一个空的PDF文件,准备写入转换后的内容。
//生成一个空的PDF文件
// 创建一个Document对象,指定要转换的Word文档路径。
//要转换的word文件
Document doc = new Document(inputStream);
// 创建PDF保存选项
doc.save(pdfFile,SaveFormat.PDF);
File file = new File(pdfFile);
//os = new FileOutputStream(file);
return file;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 确保关闭文件输出流。
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
3. pom.xml配置
手动导入aspose的jar包放到项目的resources新建个lib的目录下
<dependency>
<groupId>com.aspose</groupId>
<artifactId>aspose-words</artifactId>
<version>15.12.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/aspose-words-19.5jdk.jar</systemPath>
</dependency>
在pom.xml中加上这段 Build的plugins中
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
4. 上传文件接口--多个文件上传(先上传源文件->docx格式的word文件->再异步转为pdf上传):
package com.geb.bpgms.sheet.controller;
import com.geb.bpgms.common.dto.CommonResponse;
import com.geb.bpgms.sheet.controller.response.FileResponse;
import com.geb.bpgms.sheet.domain.service.MergeTaskDataService;
import com.geb.bpgms.sheet.infrastructure.client.oss.FinancialMinioOssClient;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
@Tag(name = "文件处理模块")
@Slf4j
public class FileController {
@Autowired
FinancialMinioOssClient financialMinioOssClient;
@Autowired
private MergeTaskDataService mergeTaskDataService;
@PostMapping("/uploadFiles")
@Operation(summary = "上传多个文件")
public CommonResponse<List<FileResponse>> uploadFiles(@RequestParam("files") MultipartFile[] files) throws IOException {
if (files.length < 3) {
return CommonResponse.fail("Please select three file to upload.");
}
List<FileResponse> fileList = new ArrayList<>();
FileResponse fileResponse ;
List<String> fileNameList = new ArrayList<>();
for (MultipartFile file : files) {
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
String filePath = "";
try{
filePath = financialMinioOssClient.uploadFile(file, fileName, file.getContentType());
}catch (Exception e){
log.error("文件上传失败, {}", e.getMessage());
}
fileResponse = new FileResponse();
fileResponse.setName(fileName);
fileResponse.setFilePath(filePath);
fileList.add(fileResponse);
fileNameList.add(fileName);
}
// docx 文件格式转 pdf --> 用于预览
mergeTaskDataService.uploadWordToPDF(fileNameList);
return CommonResponse.success(fileList);
}
/**
* 预览文件
* @param url
* @return
*/
@GetMapping("/{url}")
public CommonResponse<String> viewPdf(@PathVariable String url) {
if(url.contains(".docx")){
url = url.replace(".docx", ".pdf");
}
String filePath = null;
try{
filePath = financialMinioOssClient.presignedObjectUrl(url);
}catch (Exception e){
log.error("文件预览失败, {}", e.getMessage());
}
return CommonResponse.success(filePath);
}
}
异步上传pdf实现类:
package com.geb.bpgms.sheet.domain.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.geb.bpgms.sheet.infrastructure.client.oss.FinancialMinioOssClient;
import com.geb.bpgms.util.WordToPdfUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.List;
import java.util.Set;
/**
* @author songkz
*/
@Service
@Slf4j
public class MergeTaskDataService {
@Autowired
FinancialMinioOssClient financialMinioOssClient;
/**
* 异步上传pdf
* @param fileNameList
*/
@Async
public void uploadWordToPDF(List<String> fileNameList){
for (String fileName : fileNameList ) {
if(fileName.contains(".docx")){
String newFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".pdf";
try{
// minio上下载docx文件
InputStream inputStream = financialMinioOssClient.downloadFile(fileName);
File fileTemp = WordToPdfUtil.wordToPdf(inputStream, newFileName);
// 创建 MultipartFile 对象
assert fileTemp != null;
MultipartFile file = new MockMultipartFile(newFileName, newFileName,"application/pdf", new FileInputStream(fileTemp));
financialMinioOssClient.uploadFile(file, newFileName, "application/pdf");
}catch (Exception e){
log.error("文件上传失败, {}", e.getMessage());
}
}
}
}
}
PS:由于Word转为pdf过程需要时间,多个文件上传后可异步转换执行,启动类上记得加上
@EnableAsync注解,以此@Async注解才能生效。
最后PS:如果部署到服务器上传后,pdf预览需要设置下中文语言 启动命令内,请自行搜索下~
有问题可留言,多多交流~