【Spring Boot】多种存储文件(FastDFS / MinIO / 阿里云 OSS)接入设计说明(附源码)

Spring Boot 多种存储文件接入设计说明

本地文件系统 / FastDFS / MinIO / 阿里云 OSS** 四种存储的统一接入方式。


1. 总体思路

把"文件存哪里"这件事从业务代码里彻底剥离:

  • 业务代码(Controller)只依赖一个接口 IFileOperator,不感知具体存储;
  • 每种存储各写一个实现类,用 @Component("类型名") 注册成 Spring Bean;
  • 由 FileOperatorFactory 根据类型字符串取出对应实现;
  • 类型字符串来自两个地方:
    1. 当前默认存储 :Nacos 配置 sysFileSource.json 里的 fileSystem 字段(决定新上传的文件存哪);
    2. 文件自身的存储来源 :数据库 sys_file.file_system_ 字段(决定历史文件去哪读,保证切换存储后老文件仍可下载)。

因此"换存储"只改一行 Nacos 配置即可,代码零改动。

支持的存储类型

type(getType 返回值 / Bean 名) 实现类 说明
file FileOperator 本地磁盘(默认实现)
fastdfs FastDfsOperator 通过 FastClient 封装 fastdfs-client
minio MinioOperator MinIO Java SDK 8.x
aliyunOss AliyunOssOperator 阿里云 OSS SDK 3.10.2

2. 设计模式分析

这套代码同时用了 4 种模式/惯用法 ,核心是策略模式 + 简单工厂。

2.1 策略模式(Strategy)------核心

IFileOperator 是策略接口,把"上传 / 下载 / 删除 / 转 PDF / 取 base64"这一族可互换的算法抽象出来;FileOperator、FastDfsOperator、MinioOperator、AliyunOssOperator 是 4 个具体策略。它们在运行时被替换,调用方(SysFileController)代码完全一致:

java 复制代码
IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
operator.createFile(newFileName, bytes);   // 存哪由 operator 决定
operator.downFile(response, sysFile, transPdf, isScale, true);

2.2 简单工厂(Factory)

FileOperatorFactory 持有 Map<String, IFileOperator>,对外只暴露 getByType(type),把"选实现"的逻辑收敛到一处,避免在业务代码里写 if ("minio".equals(type)) ... else if ...。

2.3 模板方法(Template Method,部分使用)

BaseFileOperator 把公共逻辑固化在父类:

  • getRealPath():统一生成 账号/yyyyMM 或 yyyyMMdd 相对目录;
  • addHeader():统一处理 Content-Disposition(inline/attachment + URLEncoder);
  • convertPdf():提供一份基于 OpenOfficeUtil 的默认实现。

子类只实现差异化部分(createFile/downFile/delFile),需要时再覆盖 convertPdf(如 FileOperator 就重写了)。

2.4 Spring 惯用法:Map 注入,Bean 名即策略键

java 复制代码
@Autowired
private final Map<String, IFileOperator> fileOperatorMap = new ConcurrentHashMap<>();

Spring 对 Map<String, 接口类型> 的注入语义是:key = Bean 名称,value = 该类型的所有 Bean 。而每个实现类都用 @Component("类型名") 注册:

java 复制代码
@Component("minio")        → key = "minio"      (MinioOperator.getType() = "minio")
@Component("fastdfs")      → key = "fastdfs"
@Component("aliyunOss")    → key = "aliyunOss"
@Component("file")         → key = "file"

做法约定:Bean 名称必须等于 getType() 的返回值,这样工厂里连注册表都不用维护,新增实现自动进 Map。

2.5 适配器(Adapter)意味

每个 Operator 内部都在把各厂商 SDK 的差异磨平成同一套接口:

实现 上传 下载 删除
FileOperator FileUtil.writeByte FileUtil.downLoad(File) File.delete()
FastDfsOperator storageClient.upload_file storageClient.download_file storageClient.delete_file
MinioOperator minioClient.putObject minioClient.getObject minioClient.removeObject
AliyunOssOperator ossClient.putObject ossClient.getObject ossClient.deleteObject

2.6 开闭原则体现

新增一种存储(例如腾讯云 COS)只需:

  1. 新建 XxxOperator extends BaseFileOperator,@Component("xxx"),实现 getType() 返回 "xxx" 和 5 个方法;
  2. 加 SDK 依赖;
  3. 配置里把 fileSystem 改成 xxx。

FileOperatorFactory、SysFileController 一行都不用改。

2.7 各角色职责

类 角色 职责
IFileOperator 策略接口 定义 5 个文件操作 + 2 个元信息方法
BaseFileOperator 抽象策略 公共路径/响应头/默认转 PDF 实现
FileOperator 具体策略 本地磁盘,相对路径 账号/yyyyMM,PDF 与源文件同目录
FastDfsOperator 具体策略 走 FastClient,路径格式 group#filePath
MinioOperator 具体策略 bucket 不存在自动创建,路径即对象名
AliyunOssOperator 具体策略 支持 uploadPath 前缀,捕获 NoSuchKey 判断源文件缺失
FileOperatorFactory 工厂 Bean 名 → 实现的查表
SysFileController 客户端 上传/下载/预览/删除/打包下载入口
SysFileUtil 工具 读 Nacos 配置、拼路径、判断 office 文档
FastClient 工具 fastdfs-client 的封装(init/upload/del/download)
SysFile 数据 文件元数据,file_system_ 记录存储来源

3. 详细实现步骤

Step 1:引入依赖

在 pom.xml 中引入三种存储 SDK(本地文件无需额外依赖):

xml 复制代码
<!-- 阿里云 OSS -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

<!-- FastDFS -->
<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client</artifactId>
    <version>1.2.7</version>
</dependency>

<!-- MinIO -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.1</version>
</dependency>

Step 2:准备存储配置(Nacos)

配置文件常量定义在 SysFileUtil:

java 复制代码
public static final String FILE_CONFIG   = "sysFileSource.json";
public static final String DEFAULT_GROUP = "DEFAULT_GROUP";
public static final String UPLOAD_PATH   = "uploadPath";
public static final String PDF           = "pdf";

在 Nacos 中新建 Data Id = sysFileSource.json、Group = DEFAULT_GROUP 的JSON 配置 ,fileSystem 决定当前用哪种存储:

json 复制代码
{
  "fileSystem": "minio",
  "uploadPath": "/opt/apps/upload/",
  "pdfTmpPath": "/opt/apps/pdfTmp/",

  "endpoint": "http://192.168.1.10:9000",
  "accessKey": "minioadmin",
  "secretKey": "minioadmin",
  "bucketName": "jpaas",

  "accessKeyId": "LTAI5t******",
  "accessKeySecret": "******",

  "tracker_servers": "192.168.1.10:22122",
  "connect_timeout": "2",
  "network_timeout": "30",
  "charset": "UTF-8",
  "http_tracker_http_port": "8888",
  "http_anti_steal_token": "false",
  "http_secret_key": ""
}

minio 配置:

json 复制代码
{
    "fileSystem": "minio",
    "endpoint": "http://192.168.4.178:9000",
    "bucketName": "xxx",
    "accessKey": "xxx",
    "secretKey": "xxx"
}

内部配置:

json 复制代码
{
    "fileSystem":"file",
    "converterUploadPath":"/opt/temp/",
    "uploadPath":"/opt/temp/"
}

各键用途:

配置键 作用 读取位置
fileSystem 当前默认存储类型 SysFileUtil.getConfigKey("fileSystem")
uploadPath 本地存储根目录 FileOperator、SysFileUtil.getUploadPath()
pdfTmpPath Office 转 PDF 的临时目录 BaseFileOperator.PDF_TEMP_PATH
endpoint 服务地址(MinIO / OSS 共用键名) MinioOperator / AliyunOssOperator
accessKey、secretKey MinIO 账号密码 MinioOperator.buildMinioClient
accessKeyId、accessKeySecret 阿里云 RAM 账号 AliyunOssOperator.buildOssClient
bucketName 存储桶 / Bucket MinIO、OSS
tracker_servers FastDFS tracker 地址 FastClient.init
connect_timeout、network_timeout、charset、http_tracker_http_port、http_anti_steal_token、http_secret_key FastDFS 客户端参数 FastClient.init

另有一个 thumbnailsize(缩略图边长)由 SysPropertiesUtil.getInt("thumbnailsize") 读取,放在普通 nacos 配置里即可。

Step 3:定义策略接口 IFileOperator

java 复制代码
public interface IFileOperator {
    String getTitle();                 // 展示名,如"minio文件系统"
    String getType();                  // 类型,必须与 Bean 名一致
    void downFile(HttpServletResponse response, SysFile sysFile,
                  boolean transPdf, boolean isScale, boolean isDownload);
    FileModel createFile(String fileName, byte[] bytes);
    int delFile(String path);
    ConverResult convertPdf(SysFile file);
    default String getFileBase64(SysFile file) { return null; }  // 可选能力,默认不支持
}

getFileBase64 用 default 方法给出"不支持"的默认实现,只有 MinioOperator、FileOperator 提供了实现(FastDFS、OSS 未提供,调用会返回 null)。

Step 4:抽象基类 BaseFileOperator

沉淀公共逻辑,减少子类重复代码:

java 复制代码
public abstract class BaseFileOperator implements IFileOperator {
    public static String PDF_TEMP_PATH = "pdfTmpPath";
    public static String CONTENT_TYPE_PDF = "application/pdf";

    /** 生成相对目录:有登录用户 → 账号/yyyyMM;否则 → yyyyMMdd */
    protected String getRealPath() {
        String tempPath = DateUtils.parseDateToStr("yyyyMM", new Date());
        IUser curUser = ContextUtil.getCurrentUser();
        if (curUser != null) {
            return curUser.getAccount() + "/" + tempPath;
        }
        return DateUtils.parseDateToStr("yyyyMMdd", new Date());
    }

    /** 统一响应头:inline(预览)/ attachment(下载) + 文件名 URL 编码 */
    protected void addHeader(String fileName, HttpServletResponse response, boolean attachment)
            throws UnsupportedEncodingException {
        fileName = URLEncoder.encode(fileName, "UTF-8");
        String type = attachment ? "attachment" : "inline";
        response.setHeader("Content-Disposition", type + ";filename=" + fileName);
    }

    /** 默认的 Office → PDF:写临时文件 → OpenOffice 转换 → 存回当前存储 → 删临时文件 */
    @Override
    public ConverResult convertPdf(SysFile sysFile) { ... }
}

Step 5:实现各存储策略

统一约定 :类上加 @Component("类型名"),getType() 返回同一个字符串。

5.1 本地磁盘 FileOperator
java 复制代码
@Component("file")
public class FileOperator extends BaseFileOperator {
    @Override public String getType() { return "file"; }
    // createFile:uploadPath + 账号/yyyyMM + /fileName
    // convertPdf:PDF 与源文件同目录(覆盖了父类默认实现)
    // getFileBase64:Base64FileUtil.fileToBase64
}
5.2 FastDFS FastDfsOperator

路径用 组名#文件路径 表示(FastClient.uploadFile 用 # 拼接),删除/下载时再 split("#"):

java 复制代码
@Component("fastdfs")
public class FastDfsOperator extends BaseFileOperator {
    @Resource private FastClient fastClient;

    @Override public String getType() { return "fastdfs"; }

    @Override public FileModel createFile(String fileName, byte[] bytes) {
        fastClient.init();                                  // 每次操作前初始化 ClientGlobal
        String path = fastClient.uploadFile(bytes, FileUtil.getFileExt(fileName));
        FileModel model = new FileModel();
        model.setFileName(fileName);
        model.setExtName(extName);
        model.setRelPath(path);                             // group1#M00/00/00/xxx
        return model;
    }
}
5.3 MinIO MinioOperator

每次操作按配置构建 MinioClient;上传前先 bucketExists,不存在则 makeBucket:

java 复制代码
@Component("minio")
public class MinioOperator extends BaseFileOperator {
    @Autowired private ConfigService configService;

    @Override public String getType() { return "minio"; }

    private MinioClient buildMinioClient(JSONObject sysFileStore) {
        return MinioClient.builder()
                .endpoint(sysFileStore.getString("endpoint"))
                .credentials(sysFileStore.getString("accessKey"), sysFileStore.getString("secretKey"))
                .build();
    }

    @Override public FileModel createFile(String fileName, byte[] bytes) {
        // 1. 读 Nacos 配置 2. bucketExists / makeBucket
        // 3. putObject(PutObjectArgs.builder().object(fileName)
        //        .stream(new ByteArrayInputStream(bytes), bytes.length, -1).build())
        // 4. relPath = fileName(对象名即路径)
    }
}

下载:

java 复制代码
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(filePath).build();
InputStream inputStream = minioClient.getObject(objectArgs);
FileUtil.downLoad(inputStream, response);
5.4 阿里云 OSS AliyunOssOperator

与 MinIO 结构一致,只是换成 OSS 客户端;对象键会拼上配置的 uploadPath 前缀,并在 convertPdf 中用 OSSException.getErrorCode() == "NoSuchKey" 判定"源文件不存在":

java 复制代码
@Component("aliyunOss")
public class AliyunOssOperator extends BaseFileOperator {
    @Autowired private ConfigService configService;

    @Override public String getType() { return "aliyunOss"; }

    private OSS buildOssClient(JSONObject sysFileStore) {
        return new OSSClientBuilder().build(
                sysFileStore.getString("endpoint"),
                sysFileStore.getString("accessKeyId"),
                sysFileStore.getString("accessKeySecret"));
    }
}

Step 6:工厂 FileOperatorFactory

java 复制代码
@Service
public class FileOperatorFactory {
    @Autowired
    private final Map<String, IFileOperator> fileOperatorMap = new ConcurrentHashMap<>();

    public IFileOperator getByType(String type) {
        if (fileOperatorMap.containsKey(type)) {
            return fileOperatorMap.get(type);
        }
        return null;
    }
}

不需要写任何注册代码------Spring 会把 4 个 IFileOperator Bean 按 Bean 名灌进 Map。

Step 7:Controller 接入

上传 (以 upload 为例):

java 复制代码
String fileSystem = SysFileUtil.getConfigKey("fileSystem");        // 当前默认存储
IFileOperator operator = fileOperatorFactory.getByType(fileSystem);

String fileId = IdGenerator.getIdStr();
String extName = FileUtil.getFileExt(oriFileName);
String newFileName = fileId + "." + extName;                        // 用 fileId 作新文件名,避免重名
byte[] bytes = FileUtil.input2byte(multipartFile.getInputStream());

FileModel fileModel = operator.createFile(newFileName, bytes);

SysFile file = new SysFile();
file.setFileId(fileId);
file.setPath(fileModel.getRelPath());      // 存储中的相对路径/对象名
file.setFileSystem(fileSystem);            // 关键:记住这个文件存在哪种存储
file.setFileName(oriFileName);             // 原始文件名用于下载时还原
file.setExt(extName);
file.setTotalBytes(bytes.length);
file.setDelStatus("undeleted");

if ("image".equals(type) || isImg(extName)) {                       // 图片生成缩略图
    handImage(extName, bytes, operator, file, fileModel.getRelPath());
}
sysFileServiceImpl.save(file);
convertOffice(temp);                                                // office 文档投递 MQ 异步转 PDF

下载 / 预览 (注意用的是文件自己的 fileSystem):

java 复制代码
SysFile sysFile = sysFileServiceImpl.get(fileId);
IFileOperator operator = fileOperatorFactory.getByType(sysFile.getFileSystem());
response.setHeader("blob", "true");
operator.downFile(response, sysFile, transPdf, isScale, true);

删除:

java 复制代码
SysFile sysFile = sysFileServiceImpl.get(fileId);
sysFileServiceImpl.removeById(fileId);        // 先删库记录
if (isDelFile) {
    operator.delFile(sysFile.getPath());      // 再删物理文件
}

Step 8:数据表 sys_file

策略与数据通过 file_system_ 字段解耦,关键字段:

字段 列名 说明
fileId FILE_ID_ 主键,同时用作存储中的新文件名
fileName FILE_NAME_ 原始文件名(下载时还原)
path PATH_ 存储相对路径 / MinIO 对象名 / group#path
thumbnail THUMBNAIL_ 图片缩略图路径
pdfPath PDF_PATH_ 转换出的 PDF 路径
fileSystem FILE_SYSTEM_ 该文件所属存储类型,下载/删除时路由用
coverStatus COVER_STATUS_ PDF 转换状态:NO/YES/FAIL
totalBytes TOTAL_BYTES_ 文件大小(字节)
ext EXT_ 扩展名
delStatus DEL_STATUS_ 删除标记
from / typeId FROM_ / TYPE_ID_ 附件来源与分类
fileContent、timestamp、createUser --- 非表字段,仅用于传输

Step 9:扩展新存储

  1. 新建 CosOperator extends BaseFileOperator,标 @Component("cos"),getType() 返回 "cos";
  2. 实现 createFile / downFile / delFile(convertPdf 可继承父类默认);
  3. pom 加对应 SDK;
  4. Nacos 配置里 fileSystem 改为 cos。

4. 完整源码

以下为本次涉及文件的完整内容,便于对照与迁移。

一、核心文件(本次涉及的 8 个)

IFileOperator(策略接口)

IFileOperator.java

java 复制代码
package com.jx.system.operator;

import com.jx.system.core.entity.SysFile;
import com.jx.system.util.FileModel;

import javax.servlet.http.HttpServletResponse;

/**
 * 文件操作接口。
 */
public interface IFileOperator {
	
	String getTitle();
	
	String getType();

	/**
	 * 下载文件
	 * @param response
	 * @param sysFile
	 * @param transPdf
	 * @param isScale
     * @param isDownload
	 * @return
	 */
	void downFile(HttpServletResponse response, SysFile sysFile, boolean transPdf, boolean isScale,boolean isDownload);

	/**
	 * 创建文件,传入扩展名和文件内容写入文件。
	 * @param bytes
	 * @return
	 */
	FileModel createFile(String fileName, byte[] bytes);
	/**
	 * 删除文件。
	 * @param path
	 * @return
	 */
	int delFile(String path);


	/**
	 * 将文件转换成PDF。
	 * 返回ConverResult。
	 * @param file
	 */
	ConverResult convertPdf(SysFile file);

	/**
	 * 获取文件的base64编码 20230112
	 * @param file
	 * @return
	 */
	default String getFileBase64(SysFile file){
		return null;
	}
}

BaseFileOperator(抽象基类)

BaseFileOperator.java

java 复制代码
package com.jx.system.operator.impl;

import com.alibaba.fastjson.JSONObject;
import com.jx.common.base.entity.IUser;
import com.jx.common.tool.DateUtils;
import com.jx.common.utils.ContextUtil;
import com.jx.common.utils.FileUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.operator.ConverResult;
import com.jx.system.operator.IFileOperator;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.UUID;

@Slf4j
public abstract class BaseFileOperator implements IFileOperator {

    public static String PDF_TEMP_PATH="pdfTmpPath";

    public static String CONTENT_TYPE_PDF="application/pdf";

    /**
     * 构建路径
     * 1.当上下文有用户的情况返回
     * admin/202105 这样的路径
     * 2.否则返回 20210508的路径。
     * @return
     */
    protected String getRealPath(){
        // 上传的相对路径
        String tempPath = DateUtils.parseDateToStr("yyyyMM", new Date());
        String relFilePath = tempPath;
        IUser curUser = ContextUtil.getCurrentUser();
        if (curUser != null) {
            String account = curUser.getAccount();
            relFilePath = account + "/" + tempPath;
        } else {
            relFilePath =  DateUtils.parseDateToStr("yyyyMMdd", new Date());
        }
        return relFilePath;
    }

    protected void addHeader(String fileName , HttpServletResponse response,boolean attachment) throws UnsupportedEncodingException {
        fileName = URLEncoder.encode(fileName,"UTF-8");

        String type=attachment?"attachment":"inline";
        response.setHeader("Content-Disposition",  type+";filename=" +fileName);
    }





    @Override
    public ConverResult convertPdf(SysFile sysFile) {

        String tmpId= UUID.randomUUID().toString();
        String pdfTmpPath=SysFileUtil.getConfigKey(PDF_TEMP_PATH);

        pdfTmpPath=pdfTmpPath.replaceAll("\\\\","/");

        if(!pdfTmpPath.endsWith("/")){
            pdfTmpPath=pdfTmpPath +"/";
        }

        String filePath=pdfTmpPath + tmpId + "." + sysFile.getExt();
        String pdfPath=pdfTmpPath + tmpId +".pdf";

        FileUtil.writeByte(filePath, sysFile.getFileContent());
        JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, pdfPath);

        byte[] bytes= FileUtil.readByte(pdfPath);

        FileModel model= createFile(tmpId +".pdf", bytes);

        FileUtil.deleteFile(filePath);
        FileUtil.deleteFile(pdfPath);

        ConverResult result = new ConverResult();
        result.setSuccess(jsonObject.getBoolean("success"));
        result.setFileNotFind(jsonObject.getBoolean("fileNotFind"));
        result.setPath(model.getRelPath());

        return result;

    }

    public static void main(String[] args) {
        String fileName="a.docx";

        fileName =fileName.substring(0,fileName.indexOf(".")) + ".pdf";
        System.err.println(fileName);
    }
}

FileOperator(本地磁盘)

FileOperator.java

java 复制代码
package com.jx.system.operator.impl;

import com.alibaba.fastjson.JSONObject;
import com.jx.common.constant.MBoolean;
import com.jx.common.utils.FileUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.core.service.SysFileServiceImpl;
import com.jx.system.operator.ConverResult;
import com.jx.system.util.Base64FileUtil;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletResponse;
import java.io.File;

/**
 * 文件操作。
 *
 */
@Slf4j
@Component("file")
public class FileOperator extends BaseFileOperator {

	@Override
	public String getTitle() {
		return "文件系统";
	}

	@Override
	public String getType() {
		return "file";
	}

	@Autowired
	SysFileServiceImpl sysFileService;


	@Override
	public void downFile(HttpServletResponse response, SysFile sysFile, boolean transPdf, boolean isScale,boolean isDownload){
		try {
			// 创建file对象

			String fileName=sysFile.getFileName().toLowerCase();
			boolean isOffice= SysFileUtil.isOfficeDoc(sysFile.getExt());

			String	path= SysFileUtil.getFilePath(sysFile, isScale, transPdf);

			if(transPdf){
				fileName=SysFileUtil.replaceExt(sysFile.getFileName(),"pdf");
			}

			File file = new File(path);

            String ext=new MimetypesFileTypeMap().getContentType(file);
            response.setContentType(ext);

			addHeader(fileName,response,isDownload);

			if(transPdf){
				response.setContentType(CONTENT_TYPE_PDF);

				//如果没有转换则下载转换一次。
				boolean notConverted= MBoolean.NO.val.equals( sysFile.getCoverStatus());
				if(isOffice && notConverted){
					ConverResult result = convertPdf(sysFile);
					if(result.getSuccess()){
						String pdfPath = result.getPath();
						sysFile.setCoverStatus(MBoolean.YES.val);
						sysFile.setPdfPath(pdfPath);
						sysFileService.update(sysFile);
					}else if(result.getFileNotFind()){
						//源文件不存在将转换状态设置成FAIL
						sysFile.setCoverStatus(SysFile.FAIL);
						sysFileService.update(sysFile);
					}

				}
			}
			FileUtil.downLoad(file,response);

		}catch (Exception e){
			log.error("------FileOperator.downFile is error :"+e.getMessage());
		}
	}

	@Override
	public int delFile(String path) {
		String fullPath = SysFileUtil.getUploadPath()+ path;
		File file=new File(fullPath);
		file.delete();
		return 0;
	}





	@Override
	public FileModel createFile(String fileName, byte[] bytes) {
		String extName=FileUtil.getFileExt(fileName);

		String	relFilePath=getRealPath();

		
		String fullPath = SysFileUtil.getUploadPath()  + relFilePath;
		
		File dirFile = new File(fullPath);
		if (!dirFile.exists()) {
			dirFile.mkdirs();
		}
		relFilePath+="/" +fileName;
		String filePath=fullPath + "/" +fileName;
		try {
			FileUtil.writeByte(filePath, bytes);
		} catch (Exception e) {
			log.error("------FileOperator.createFile is error :"+e.getMessage());
		}
		
		FileModel model=new FileModel();
		model.setFileName(fileName);
		model.setExtName(extName);
		model.setRelPath(relFilePath);
		return model;
	}



	@Override
	public ConverResult convertPdf(SysFile file) {

		String pdfPath=SysFileUtil.replaceExt(file.getPath() ,"pdf");

		String uploadPath=SysFileUtil.getUploadPath();

		String filePath= uploadPath + file.getPath();

		String fullPdfPath=uploadPath +pdfPath;

		JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, fullPdfPath );
		ConverResult result = new ConverResult();
		result.setSuccess(jsonObject.getBoolean("success"));
		result.setFileNotFind(jsonObject.getBoolean("fileNotFind"));
		result.setReason(jsonObject.getString("reason"));
		if(jsonObject.getBoolean("success")){
			result.setPath(pdfPath);
		}

		return result;
	}

	/**
	 * 获取文件的base64编码 20230112
	 */
	@Override
	public String getFileBase64(SysFile sysFile) {
		try {
			String filePath = SysFileUtil.getFilePath(sysFile, false, false);
			return Base64FileUtil.fileToBase64(filePath);
		} catch (Exception e) {
			log.error("------MinioOperator.downFile is error :" + e.getMessage());
		}
		return null;
	}

}

FastDfsOperator(FastDFS)

FastDfsOperator.java

java 复制代码
package com.jx.system.operator.impl;

import com.alibaba.fastjson.JSONObject;
import com.jx.common.utils.FileUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.operator.ConverResult;
import com.jx.system.util.FastClient;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.csource.common.MyException;
import org.springframework.stereotype.Component;

import javax.activation.MimetypesFileTypeMap;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

/**
 * fast dfs操作。
 *
 */
@Slf4j
@Component("fastdfs")
public class FastDfsOperator extends BaseFileOperator {
	@Resource
	private FastClient fastClient;

	@Override
	public void downFile(HttpServletResponse response, SysFile sysFile, boolean transPdf, boolean isScale,boolean isDownload){
		try {
			String fileName= SysFileUtil.getDownloadName(sysFile,transPdf);
			// 创建file对象
			String path=getFilePath(sysFile,isScale,transPdf);

			byte[]	bytes=getFile(path);

            String ext=new MimetypesFileTypeMap().getContentType(fileName);
            response.setContentType(ext);

            addHeader(fileName,response,isDownload);

			if(transPdf){
				response.setContentType(CONTENT_TYPE_PDF);
			}

			response.getOutputStream().write(bytes);
		}catch (Exception e){
			log.error("------FastDfsOperator.downFile is error :"+e.getMessage());
		}
	}

	private static String getFilePath(SysFile sysFile,boolean isScale,boolean isPdf) throws Exception {

		String fullPath;
		if(isPdf){
			if(SysFileUtil.PDF.equals(sysFile.getExt()) ){
				fullPath =  sysFile.getPath();
			}
			else {
				fullPath =  sysFile.getPdfPath();
			}
		}
		else{
			if (isScale){
				fullPath =  sysFile.getThumbnail();
			}else {
				fullPath = sysFile.getPath();
			}
		}

		return fullPath;
	}



	@Override
	public int delFile(String path) {
		try {
			fastClient.init();
			int rtn=fastClient.delFile(path);
			return rtn;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;
	}


	private byte[] getFile(String path) {
		try {
			fastClient.init();
			byte[] bytes=fastClient.getFile(path);
			return bytes;
		} catch (Exception e) {
			log.error("------FastDfsOperator.getFile is error :"+e.getMessage());
		}
		return null;
	}



	@Override
	public FileModel createFile(String fileName, byte[] bytes) {
		String extName = FileUtil.getFileExt(fileName);
		String path="";
		try {
			fastClient.init();
			path=fastClient.uploadFile(bytes, extName);
		} catch (IOException | MyException e) {
			log.error("------FastDfsOperator.createFile is error :"+e.getMessage());
		}

		FileModel model=new FileModel();
		model.setFileName(fileName);
		model.setExtName(extName);
		model.setRelPath(path);

		return model;
	}
	
	@Override
	public String getTitle() {
		return "fast分布式文件系统";
	}

	@Override
	public String getType() {
		return "fastdfs";
	}


	@Override
	public ConverResult convertPdf(SysFile sysFile) {
		ConverResult result = new ConverResult();
		try {
			String tmpId= UUID.randomUUID().toString();
			String pdfTmpPath=SysFileUtil.getConfigKey(PDF_TEMP_PATH);

			pdfTmpPath=pdfTmpPath.replaceAll("\\\\","/");

			if(!pdfTmpPath.endsWith("/")){
				pdfTmpPath=pdfTmpPath +"/";
			}

			String filePath=  pdfTmpPath + tmpId + "." + sysFile.getExt();
			String pdfPath=  pdfTmpPath + tmpId +".pdf";

			// 创建file对象
			String path=getFilePath(sysFile,false,false);
			byte[]	bytes=getFile(path);

			if(bytes == null){
				result.setSuccess(false);
				result.setFileNotFind(true);
				result.setReason("找不到源文件");

				return result;
			}

			FileUtil.writeByte(filePath, bytes);
			JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, pdfPath);

			FileModel model= createFile(tmpId +".pdf", FileUtil.readByte(pdfPath));

			FileUtil.deleteFile(filePath);
			FileUtil.deleteFile(pdfPath);

			result.setSuccess(jsonObject.getBoolean("success"));
			if(jsonObject.getBoolean("success")){
				result.setPath(model.getRelPath());
				return result;
			}else{
				return  result;
			}


		}catch (Exception e){
			log.error("------FastDfsOperator.convertPdf is error :"+e.getMessage());
			result.setSuccess(false);
			result.setFileNotFind(false);
			result.setReason("转换异常");

			return result;
		}


	}



}

MinioOperator(MinIO)

MinioOperator.java

java 复制代码
package com.jx.system.operator.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.jx.common.utils.ExceptionUtil;
import com.jx.common.utils.FileUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.operator.ConverResult;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.UUID;

/**
 * minio操作。
 *
 */
@Slf4j
@Component("minio")
public class MinioOperator extends BaseFileOperator {

    @Override
    public String getTitle() {
        return "minio文件系统";
    }

    @Override
    public String getType() {
        return "minio";
    }

    @Autowired
    private ConfigService configService;

    @Override
    public void downFile(HttpServletResponse response, SysFile sysFile, boolean transPdf, boolean isScale, boolean isDownload) {
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);
            String bucketName = sysFileStore.getString("bucketName");

            String fileName = SysFileUtil.getDownloadName(sysFile, transPdf);
            String ext = new MimetypesFileTypeMap().getContentType(fileName);
            response.setContentType(ext);
            addHeader(fileName, response, isDownload);
            String filePath = sysFile.getPath();
            if (transPdf) {
                response.setContentType(CONTENT_TYPE_PDF);
            }
            if(!"pdf".equals(sysFile.getExt()) && transPdf){
                filePath = sysFile.getPdfPath();
            }
            // 创建minioClient实例。
            MinioClient minioClient = buildMinioClient(sysFileStore);
            //sysFile.getPath():存储桶里的对象名称
            GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(filePath).build();
            // 获取minio文件inputStream对象
            InputStream inputStream = minioClient.getObject(objectArgs);

            FileUtil.downLoad(inputStream, response);
        } catch (Exception e) {
            log.error("------MinioOperator.downFile is error :" + e.getMessage());
        }
    }

    /**
     * 构造minio客户端。
     *
     * @param sysFileStore
     * @return
     */
    private MinioClient buildMinioClient(JSONObject sysFileStore) {
        //连接地址
        String endpoint = sysFileStore.getString("endpoint");
        //账号
        String accessKey = sysFileStore.getString("accessKey");
        //密码
        String secretKey = sysFileStore.getString("secretKey");

        MinioClient minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();


        return minioClient;
    }


    @Override
    public int delFile(String path) {

        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);

            //存储桶名称
            String bucketName = sysFileStore.getString("bucketName");

            // 创建OSSClient实例。
            MinioClient minioClient = buildMinioClient(sysFileStore);

            //path:存储桶里的对象名称
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path).build());

        } catch (Exception ex) {
            log.error("------MinioOperator.delFile is error :" + ExceptionUtil.getExceptionMessage(ex));
        }

        return 0;
    }


    @Override
    public FileModel createFile(String fileName, byte[] bytes) {
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);
            String bucketName = sysFileStore.getString("bucketName");

            // 创建MinioClient实例
            MinioClient minioClient = buildMinioClient(sysFileStore);

            BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build();
            boolean flag = minioClient.bucketExists(bucketExistsArgs);
            if (!flag) {
                MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
                minioClient.makeBucket(makeBucketArgs);
            }

            //fileName:存储桶里的对象名称
            PutObjectArgs putObjectArgs = PutObjectArgs.builder().bucket(bucketName).object(fileName)
                    .stream(new ByteArrayInputStream(bytes), bytes.length, -1).build();

            // 上传到minio
            minioClient.putObject(putObjectArgs);

        } catch (Exception e) {
            log.error("------MinioOperator.createFile is error :" + e.getMessage());
            e.printStackTrace();
        }

        FileModel model = new FileModel();
        model.setFileName(fileName);
        model.setExtName(FileUtil.getFileExt(fileName));
        //存储桶里的对象名称
        model.setRelPath(fileName);
        return model;

    }


    @Override
    public ConverResult convertPdf(SysFile file) {
        ConverResult result = new ConverResult();
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);

            String bucketName = sysFileStore.getString("bucketName");

            // 创建minioClient实例。
            MinioClient minioClient = buildMinioClient(sysFileStore);
            //file.getPath():存储桶里的对象名称
            GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(file.getPath()).build();
            // 获取minio文件inputStream对象
            InputStream inputStream = minioClient.getObject(objectArgs);

            byte[] bytes = FileUtil.input2byte(inputStream);

            String tmpId = UUID.randomUUID().toString();
            String pdfTmpPath = SysFileUtil.getConfigKey(PDF_TEMP_PATH);

            pdfTmpPath = pdfTmpPath.replaceAll("\\\\", "/");

            if (!pdfTmpPath.endsWith("/")) {
                pdfTmpPath = pdfTmpPath + "/";
            }

            String filePath = pdfTmpPath + tmpId + "." + file.getExt();
            String pdfPath = pdfTmpPath + tmpId + ".pdf";

            FileUtil.writeByte(filePath, bytes);

            JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, pdfPath);

            FileModel model = createFile(tmpId + ".pdf", FileUtil.readByte(pdfPath));

            FileUtil.deleteFile(filePath);
            FileUtil.deleteFile(pdfPath);

            result.setSuccess(jsonObject.getBoolean("success"));
            if (jsonObject.getBoolean("success")) {
                result.setPath(model.getRelPath());
                return result;
            } else {
                return result;
            }

        } catch (Exception e) {
            log.error("------MinioOperator.convertPdf is error :" + e.getMessage());
            result.setSuccess(false);
            result.setFileNotFind(false);
            result.setReason("转换异常");
            return result;
        }
    }

    /**
     * 获取文件的base64编码 20230112
     */
    @Override
    public String getFileBase64(SysFile sysFile) {
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);
            String bucketName = sysFileStore.getString("bucketName");

            String strBase64 = "";
            InputStream is = null;
            ByteArrayOutputStream os = null;
            byte[] buff = new byte[1024];
            int len = 0;
            try {
                // 创建minioClient实例。
                MinioClient minioClient = buildMinioClient(sysFileStore);
                //sysFile.getPath():存储桶里的对象名称
                GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(sysFile.getPath()).build();
                // 获取minio文件inputStream对象
                is = minioClient.getObject(objectArgs);

                os = new ByteArrayOutputStream();
                while ((len = is.read(buff)) != -1) {
                    os.write(buff, 0, len);
                }
                os.flush();
                os.toByteArray();
                strBase64 = Base64.getEncoder().encodeToString(os.toByteArray());
            } catch (Exception e) {
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (Exception e) {
                    }
                }
                if (os != null) {
                    try {
                        os.close();
                    } catch (Exception e) {
                    }
                }
            }
            return strBase64;
        } catch (Exception e) {
            log.error("------MinioOperator.downFile is error :" + e.getMessage());
        }
        return null;
    }
}

AliyunOssOperator(阿里云 OSS)

AliyunOssOperator.java

java 复制代码
package com.jx.system.operator.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.OSSObject;
import com.jx.common.tool.StringUtils;
import com.jx.common.utils.ExceptionUtil;
import com.jx.common.utils.FileUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.operator.ConverResult;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.UUID;

/**
 * fast dfs操作。
 *
 */
@Slf4j
@Component("aliyunOss")
public class AliyunOssOperator extends BaseFileOperator{

    @Override
    public String getTitle() {
        return "阿里云Oss文件系统";
    }

    @Override
    public String getType() {
        return "aliyunOss";
    }

    @Autowired
    private ConfigService configService;

    @Override
    public void downFile(HttpServletResponse response, SysFile sysFile, boolean transPdf, boolean isScale,boolean isDownload){
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);

            String bucketName = sysFileStore.getString("bucketName");

            // 创建OSSClient实例。
            OSS ossClient = buildOssClient(sysFileStore);

            String fullPath =SysFileUtil.getFilePath(sysFile,isScale,transPdf);

            String fileName=SysFileUtil.getDownloadName(sysFile,transPdf);

            String ext=new MimetypesFileTypeMap().getContentType(fileName);
            response.setContentType(ext);

            addHeader(fileName,response,isDownload);

            if(transPdf){
                response.setContentType(CONTENT_TYPE_PDF);
            }

            // 获取阿里云OSS文件file对象
            OSSObject object = ossClient.getObject(bucketName, fullPath);

            FileUtil.downLoad(object.getObjectContent(), response);

            // 关闭OSSClient。
            ossClient.shutdown();

        }catch (Exception e){
            log.error("------AliyunOssOperator.downFile is error :"+e.getMessage());
        }
    }

    /**
     * 构造阿里云客户端。
     * @param sysFileStore
     * @return
     * @throws NacosException
     */
    private OSS buildOssClient(JSONObject sysFileStore) throws NacosException {
        // Endpoint以杭州为例,其它Region请按实际情况填写。
        String endpoint = sysFileStore.getString("endpoint");
        // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录RAM控制台创建RAM账号。
        String accessKeyId = sysFileStore.getString("accessKeyId");
        String accessKeySecret = sysFileStore.getString("accessKeySecret");

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        return  ossClient;
    }


    @Override
    public int delFile(String path) {

        try{
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);

            String bucketName = sysFileStore.getString("bucketName");

            // 创建OSSClient实例。
            OSS ossClient = buildOssClient(sysFileStore);

            ossClient.deleteObject(bucketName,path);

            ossClient.shutdown();
        }
        catch (Exception ex){
            log.error("------AliyunOssOperator.delFile is error :"+ ExceptionUtil.getExceptionMessage(ex));
        }

        return 0;
    }


    @Override
    public FileModel createFile(String fileName, byte[] bytes) {
        String extName = FileUtil.getFileExt(fileName);

        String relFilePath=getRealPath();

        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);


            String fullPath = relFilePath+"/" +fileName;
            String uploadPath = sysFileStore.getString(SysFileUtil.UPLOAD_PATH);
            if(StringUtils.isNotEmpty(uploadPath)){
                fullPath=uploadPath+fullPath;
            }
            relFilePath+="/" +fileName;


            // 创建OSSClient实例。
            OSS ossClient = buildOssClient(sysFileStore);

            String bucketName = sysFileStore.getString("bucketName");
            // 上传文件到指定的存储空间(bucketName)并将其保存为指定的文件名称(objectName)。
            ossClient.putObject(bucketName, fullPath, new ByteArrayInputStream(bytes));
            // 关闭OSSClient。
            ossClient.shutdown();
        } catch (Exception e) {
            log.error("------AliyunOssOperator.createFile is error :"+e.getMessage());
        }

        FileModel model = new FileModel();
        model.setFileName(fileName);
        model.setExtName(extName);
        model.setRelPath(relFilePath);
        return model;
    }


    @Override
    public ConverResult convertPdf(SysFile file) {
        ConverResult result = new ConverResult();
        try {
            String config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);
            JSONObject sysFileStore = JSONObject.parseObject(config);

            String bucketName = sysFileStore.getString("bucketName");

            // 创建OSSClient实例。
            OSS ossClient = buildOssClient(sysFileStore);

            String fullPath =SysFileUtil.getFilePath(file,false,false);
            // 获取阿里云OSS文件file对象
            OSSObject object = ossClient.getObject(bucketName, fullPath);
            byte[] bytes =  FileUtil.input2byte(object.getObjectContent());

            String tmpId= UUID.randomUUID().toString();
            String pdfTmpPath=SysFileUtil.getConfigKey(PDF_TEMP_PATH);

            pdfTmpPath=pdfTmpPath.replaceAll("\\\\","/");

            if(!pdfTmpPath.endsWith("/")){
                pdfTmpPath=pdfTmpPath +"/";
            }

            String filePath= pdfTmpPath + tmpId + "." + file.getExt();
            String pdfPath= pdfTmpPath + tmpId +".pdf";

            FileUtil.writeByte(filePath, bytes);

            JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, pdfPath);

            FileModel model= createFile(tmpId +".pdf", FileUtil.readByte(pdfPath));

            FileUtil.deleteFile(filePath);
            FileUtil.deleteFile(pdfPath);

            // 关闭OSSClient。
            ossClient.shutdown();

            result.setSuccess(jsonObject.getBoolean("success"));
            if(jsonObject.getBoolean("success")){
                result.setPath( model.getRelPath());
                return result;
            }else{
                return  result;
            }


        }catch (OSSException e){
            log.error("------AliyunOssOperator.convertPdf is error :"+e.getMessage());
            result.setSuccess(false);
            result.setFileNotFind(false);
            result.setReason(e.getErrorMessage());
            //源文件不存在
            if("NoSuchKey".equals(e.getErrorCode())){
                result.setFileNotFind(true);
                result.setReason("找不到源文件");
            }
            return result;
        }catch (Exception e){
            log.error("------AliyunOssOperator.convertPdf is error :"+e.getMessage());
            result.setSuccess(false);
            result.setFileNotFind(false);
            result.setReason("转换异常");

            return result;
        }


    }



}

FileOperatorFactory(工厂)

FileOperatorFactory.java

java 复制代码
package com.jx.system.operator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class FileOperatorFactory {

	@Autowired
	private final Map<String ,IFileOperator> fileOperatorMap= new ConcurrentHashMap<>();
	
	/**
	 * 根据类型获取文件操作对象。
	 * @param type
	 * @return
	 */
	public IFileOperator getByType(String type){
		if(fileOperatorMap.containsKey(type)){
			return fileOperatorMap.get(type);
		}
		return null;
	}
	

}

SysFileController(上传下载入口)

SysFileController.java

java 复制代码
package com.jx.system.core.controller;


import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.jx.common.annotation.ClassDefine;
import com.jx.common.annotation.MethodDefine;
import com.jx.common.annotation.ParamDefine;
import com.jx.common.base.db.BaseService;
import com.jx.common.base.entity.JsonResult;
import com.jx.common.constant.HttpMethodConstants;
import com.jx.common.constant.MBoolean;
import com.jx.common.model.JPaasUser;
import com.jx.common.tool.BeanUtil;
import com.jx.common.tool.IdGenerator;
import com.jx.common.tool.StringUtils;
import com.jx.common.utils.*;
import com.jx.dto.sys.SysFileDto;
import com.jx.log.annotation.AuditLog;
import com.jx.log.model.Audit;
import com.jx.log.model.LogContext;
import com.jx.system.core.entity.SysFile;
import com.jx.system.core.service.SysFileServiceImpl;
import com.jx.system.mq.SysInputOutput;
import com.jx.system.operator.FileOperatorFactory;
import com.jx.system.operator.IFileOperator;
import com.jx.system.util.Base64FileUtil;
import com.jx.system.util.FileModel;
import com.jx.system.util.OpenOfficeUtil;
import com.jx.system.util.SysFileUtil;
import com.jx.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.tika.mime.MimeType;
import org.apache.tika.mime.MimeTypes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;


@Slf4j
@RestController
@RequestMapping("/system/core/sysFile/")
@ClassDefine(title = "系统附件", alias = "sysFileController", path = "/system/core/sysFile", packages = "core", packageName = "系统管理")
@Api(tags = "系统附件")
public class SysFileController extends BaseController<SysFile> {

    private static Pattern iconRegex = Pattern.compile("\\.(icon-[\\w-]*?):\\s*before\\s*\\{.*?\\}", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE | Pattern.DOTALL);

    @Autowired
    SysFileServiceImpl sysFileServiceImpl;
    @Autowired
    FileOperatorFactory fileOperatorFactory;

    @Autowired
    SysInputOutput sysInputOutput;

    @Autowired
    private ConfigService configService;

    @Override
    public BaseService getBaseService() {
        return sysFileServiceImpl;
    }

    @Override
    public String getComment() {
        return "系统附件";
    }

    @MethodDefine(title = "根据文件id查询文件", path = "/getByFileId", method = HttpMethodConstants.GET,
            params = {@ParamDefine(title = "文件ID", varName = "fileId")})
    @ApiOperation("根据文件id查询文件")
    @GetMapping("/getByFileId")
    public SysFileDto getByFileId(@ApiParam @RequestParam String fileId) {
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        if (sysFile == null) {
            return null;
        }
        SysFileDto sysFileDto = new SysFileDto();
        BeanUtil.copyProperties(sysFileDto, sysFile);
        return sysFileDto;
    }

    @ApiOperation("文件上传")
    @MethodDefine(title = "文件上传", path = "/upload", method = HttpMethodConstants.POST,
            params = {@ParamDefine(title = "上传文件", varName = "request")})
    @AuditLog(operation = "上传文件")
    @RequestMapping("upload")
    public JsonResult upload(MultipartHttpServletRequest request) throws Exception {

        JsonResult jsonResult = new JsonResult();

        JPaasUser curUser = (JPaasUser) ContextUtil.getCurrentUser();

        String timestamp = request.getParameter("time");
        Map<String, MultipartFile> multiFileMap = request.getFileMap();
        Collection<MultipartFile> files= multiFileMap.values();

        Iterator<MultipartFile> it = files.iterator();
        List<SysFile> fileList = new ArrayList<SysFile>();

        String fileSystem = SysFileUtil.getConfigKey("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);

        StringBuilder sb = new StringBuilder();
        sb.append("上传文件:");

        while (it.hasNext()) {
            String fileId = IdGenerator.getIdStr();
            SysFile file = new SysFile();
            file.setFileId(fileId);
            MultipartFile multipartFile = it.next();
            String oriFileName = multipartFile.getOriginalFilename();
            String contentType = multipartFile.getContentType();
            String[] split = contentType.split("/");
            String type = split[0];
            String extName = FileUtil.getFileExt(oriFileName);
            // 新文件名
            String newFileName = fileId + "." + extName;

            sb.append(fileId + ",");

            InputStream is = multipartFile.getInputStream();
            byte[] bytes = FileUtil.input2byte(is);
            FileModel fileModel = operator.createFile(newFileName, bytes);
            file.setPath(fileModel.getRelPath());
            file.setFileSystem(fileSystem);
            file.setFileName(oriFileName);
            file.setTotalBytes(bytes.length);
            file.setExt(extName);
            file.setDelStatus("undeleted");
            if(curUser==null){
                file.setCreateUser("system");
            }
            else{
                file.setCreateUser(curUser.getFullName());
            }



            // 如果为图片,则生成图片的缩略图
            if ("image".equals(type) || isImg(extName)) {
                handImage(extName, bytes, operator, file, fileModel.getRelPath());
            }

            //接收前端from和typeId,方便前端区别附件来源和分类 20220318
            file.setFrom(request.getParameter("from"));
            file.setTypeId(request.getParameter("typeId"));

            sysFileServiceImpl.save(file);

            file.setTimestamp(timestamp);
            fileList.add(file);
            file.setFileContent(bytes);

            //发送文件转pdf消息中间件处理
            SysFile temp = new SysFile();
            temp.setFileId(file.getFileId());
            temp.setPath(file.getPath());
            temp.setFileSystem(file.getFileSystem());
            temp.setExt(file.getExt());
            convertOffice(temp);
        }
        jsonResult.setSuccess(true);
        for (SysFile file : fileList) {
            file.setFileContent(null);
        }
        jsonResult.setData(fileList);
        jsonResult.setMessage("成功上传!");
        jsonResult.setShow(false);

        return jsonResult;
    }

    private void handImage(String extName, byte[] bytes, IFileOperator operator, SysFile file, String relPath) {
        String fileId = IdGenerator.getIdStr();
        String imgName = fileId + "." + extName;
        int thumbnailsize = SysPropertiesUtil.getInt("thumbnailsize");
        byte[] imgBytes = ImageUtil.thumbnailImage(bytes, thumbnailsize, thumbnailsize, extName);
        if (imgBytes == null) {
            file.setThumbnail(relPath);
        } else {
            FileModel imgModel = operator.createFile(imgName, imgBytes);
            String thumbnailPath = imgModel.getRelPath();
            file.setThumbnail(thumbnailPath);
        }
    }

    /**
     * 将office文件置入转换队列
     *
     * @param file
     * @throws Exception
     */
    private void convertOffice(SysFile file) throws Exception {
        String ext = file.getExt();
        if (!SysFileUtil.isOfficeDoc(ext)) {
            return;
        }
        boolean enableOpenOffice = OpenOfficeUtil.isOpenOfficeEnabled();
        if (!enableOpenOffice) {
            return;
        }
        //用消息队列处理doc转换成pdf
        sysInputOutput.filedOutput().send(MessageBuilder.withPayload(file).build());
    }

    public static String formatDate(Date date, String format) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
        return dateFormat.format(date);
    }

    @ApiOperation("文件下载或打开")
    @RequestMapping("download/{fileId}")
    public void downloadOne(@PathVariable("fileId") String fileId) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        download(response, fileId, false, false);
    }

    //打开缩放图
    @ApiOperation("打开缩放图")
    @GetMapping("downloadScale/{fileId}")
    public void downloadScale(@PathVariable("fileId") String fileId) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        download(response, fileId, false, true);
    }

    /**
     * 下载文件
     *
     * @param response
     * @param fileId
     * @param transPdf
     * @param isScale
     * @throws Exception
     */
    private void download(HttpServletResponse response, String fileId, boolean transPdf, boolean isScale) throws Exception {
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        String fileSystem = sysFile.getFileSystem();
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        response.setHeader("blob", "true");// 有些前端会根据这个来判断是否下载文件,20251114 jqp
        operator.downFile(response, sysFile, transPdf, isScale, true);
    }

    @ApiOperation("获取附件或图片路径")
    @GetMapping("getFilePath")
    public JsonResult  getFilePath(@ApiParam(name = "系统文件ID") @RequestParam(value = "fileId") String fileId,
                               @ApiParam(name = "是否取缩略图") @RequestParam(value = "isScale") boolean isScale) throws  Exception{
        JsonResult result=JsonResult.Fail("获取出错");
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        String fileSystem = sysFile.getFileSystem();
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        if(!"file".equals(operator.getType())){
            return result;
        }
        String	path= SysFileUtil.getFilePath(sysFile, isScale, false);
        result.setSuccess(true);
        result.setMessage(path);
        result.setData(path);
        return  result;
    }

    /**
     * 文件预览
     *
     * @param fileId
     * @throws Exception
     */
    @ApiOperation("文件预览")
    @GetMapping("previewFile")
    public void previewFile(@RequestParam String fileId) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        // 创建file对象
        IFileOperator operator = fileOperatorFactory.getByType(sysFile.getFileSystem());

        operator.downFile(response, sysFile, false, false, false);
    }

    /**
     * 图片预览
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @ApiOperation("图片预览")
    @GetMapping("previewImg")
    public void previewImg(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String fileId = request.getParameter("fileId");
        boolean isScale = RequestUtil.getBoolean(request, "isScale", false);
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        IFileOperator operator = fileOperatorFactory.getByType(sysFile.getFileSystem());
        operator.downFile(response, sysFile, false, isScale, false);
    }

    /**
     * 根据文件的后缀名判断是否为图片
     *
     * @param ext
     * @return
     */
    private boolean isImg(String ext) {
        if ("jpg".equals(ext) || "jpeg".equals(ext) || "png".equals(ext)
                || "gif".equals(ext) || "bmp".equals(ext)) {
            return true;
        }
        return false;
    }


    @MethodDefine(title = "移动端图标选择对话框", path = "/mobileIconSelectDialog", method = HttpMethodConstants.GET)
    @ApiOperation("移动端图标选择对话框")
    @GetMapping("/mobileIconSelectDialog")
    public Object mobileIconSelectDialog() throws IOException {
        List<String> iconList = new ArrayList<String>();
        ClassPathResource classPathResource = new ClassPathResource("mobileIcon/iconfont.css");
        InputStream is = classPathResource.getInputStream();
        String icon = FileUtil.readFile(is);
        Matcher regexMatcher = iconRegex.matcher(icon);
        while (regexMatcher.find()) {
            iconList.add(regexMatcher.group(1));
        }
        return iconList;
    }

    @ApiOperation("图片上传")
    @MethodDefine(title = "图片上传", path = "/uploadPicture", method = HttpMethodConstants.POST,
            params = {@ParamDefine(title = "图片链接", varName = "urlStr")})
    @PostMapping("uploadPicture")
    public String uploadPicture(@RequestParam(value = "urlStr") String urlStr) throws Exception {
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //设置超时间为3秒
        conn.setConnectTimeout(3 * 1000);

        InputStream inputStream = conn.getInputStream();
        String contentType = conn.getContentType();

        String fileSystem = SysPropertiesUtil.getString("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        String fileId = IdGenerator.getIdStr();
        SysFile file = new SysFile();

        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[2048];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        inputStream.close();
        byte[] bytes = outStream.toByteArray();

        MimeTypes allTypes = MimeTypes.getDefaultMimeTypes();
        MimeType jpeg = allTypes.forName(contentType);
        String extName = jpeg.getExtension();
        if (StringUtils.isNotEmpty(extName)) {
            extName = extName.substring(1);
        }
        //生成缩略图
        String fileName = fileId + "." + extName;
        FileModel fileModel = operator.createFile(fileName, bytes);
        if (isImg(extName)) {
            int thumbNailSize = SysPropertiesUtil.getInt("thumbnailsize");
            byte[] imgBytes = ImageUtil.thumbnailImage(bytes, thumbNailSize, thumbNailSize, extName);
            fileName = IdGenerator.getIdStr() + "." + extName;
            FileModel imgModel = operator.createFile(fileName, imgBytes);
            String thumbnailPath = imgModel.getRelPath();
            file.setThumbnail(thumbnailPath);
        }

        file.setFileId(fileId);
        file.setPath(fileModel.getRelPath());
        file.setFileSystem(fileSystem);
        file.setFileName(fileName);

        file.setExt(extName);
        file.setDelStatus("undeleted");

        sysFileServiceImpl.save(file);

        return fileId;
    }

    /**
     * 预览PDF文件
     *
     * @param fileId
     * @throws Exception
     */
    @MethodDefine(title = "预览PDF文件", path = "/previewPdf", method = HttpMethodConstants.GET)
    @ApiOperation("预览PDF文件")
    @GetMapping("previewPdf")
    public void previewPdf(@RequestParam String fileId) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        response.setCharacterEncoding("utf-8");
        SysFile sysFile = sysFileServiceImpl.get(fileId);

        if (SysFileUtil.isOfficeDoc(sysFile.getExt())) {
            if (!sysFile.getCoverStatus().equals(MBoolean.YES.val)) {
                //文件不存在,无法转换成PDF文件
                if (sysFile.getCoverStatus().equals(SysFile.FAIL)) {
                    response.getWriter().println("文件不存在,无法转换成PDF文件!");
                    return;
                }

                //发送文件转pdf消息中间件处理
                SysFile temp = new SysFile();
                temp.setFileId(sysFile.getFileId());
                temp.setPath(sysFile.getPath());
                temp.setFileSystem(sysFile.getFileSystem());
                temp.setExt(sysFile.getExt());
                convertOffice(temp);
//
//                response.getWriter().println("PDF文件还没有转换完成,请稍后再试!");
//                return;

//                String pdfPath = sysFile.getPdfPath();
                //office转换没有启用
                boolean officeEnabled = OpenOfficeUtil.isOpenOfficeEnabled();
                if (!officeEnabled) {
                    response.getWriter().println("系统还没有启用OPENOFFICE转换,请先在系统中配置!");
                    return;
                }
            }
        }
        IFileOperator fileOperator = fileOperatorFactory.getByType(sysFile.getFileSystem());
        fileOperator.downFile(response, sysFile, true, false, false);
    }

    /**
     * 下载文件(将文件打成压缩包)
     * @param fileIdStr 逗号隔开
     * @throws Exception
     */
    @MethodDefine(title = "下载文件(将文件打成压缩包)", path = "/downloadZip", method = HttpMethodConstants.GET)
    @ApiOperation("下载文件(将文件打成压缩包)")
    @GetMapping("downloadZip/{fileIdStr}")
    public void downloadZip( @PathVariable("fileIdStr") String fileIdStr) throws Exception {
        if (StringUtils.isNotEmpty(fileIdStr)){
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = attributes.getResponse();
            String[] fileIds = fileIdStr.split(",");
            List<File> fileList = new ArrayList<>();
            String path = SysFileUtil.getConfigKey("uploadPath");
            for (int i = 0; i < fileIds.length; i++) {
                SysFile sysFile = sysFileServiceImpl.get(fileIds[i]);
                if(StringUtils.isNotEmpty(sysFile.getPath())){
                    fileList.add(new File(path+sysFile.getPath()));
                }
            }
            String fileName= IdGenerator.getIdStr()+".zip";
            FileUtil.compressedDownload(response,fileList,path,fileName);
        }
    }

    /**
     * 下载Excel错误信息 20220318
     */
    @ApiOperation("下载Excel错误信息")
    @GetMapping("download/downloadExcelFailFile/{fileName}")
    public void downloadExcelFailFileOne(@PathVariable("fileName") String fileName) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        String fileSystem = SysFileUtil.getUploadPath() + File.separator + "excelError" + File.separator + fileName + ".xlsx";
        File tempFile = new File(fileSystem);
        if (tempFile.exists()) {
            fileName = URLEncoder.encode(fileName + ".xlsx", "UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
            FileUtil.downLoad(tempFile, response);
        } else {
            response.getWriter().println("Excel文件不存在!");
        }
    }

    /**
     * feign文件下载或打开 20220318
     */
    @ApiOperation("feign文件下载或打开")
    @GetMapping("/feign/download/{fileId}")
    public void feigndownloadOne(@PathVariable("fileId") String fileId) throws Exception {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        String fileSystem = sysFile.getFileSystem();
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        operator.downFile(response, sysFile, false, false, true);
    }

    /**
     * base64文件上传 20221111
     */
    @ApiOperation("文件上传")
    @MethodDefine(title = "文件上传", path = "/uploadBase64", method = HttpMethodConstants.POST,
            params = {@ParamDefine(title = "上传文件", varName = "request")})
    @AuditLog(operation = "上传文件")
    @PostMapping("uploadBase64")
    public JsonResult uploadBase64(String base64, String fileName) throws Exception {
        JsonResult jsonResult = new JsonResult();
        JPaasUser curUser = (JPaasUser) ContextUtil.getCurrentUser();
        String fileSystem = SysFileUtil.getConfigKey("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);

        String fileId = IdGenerator.getIdStr();
        SysFile file = new SysFile();
        file.setFileId(fileId);
        String extName = FileUtil.getFileExt(fileName);
        // 新文件名
        String newFileName = fileId + "." + extName;
        // base64转图片
        FileModel fileModel = Base64FileUtil.base64ToFile(base64, newFileName);
        file.setPath(fileModel.getRelPath());
        file.setFileSystem(fileSystem);
        file.setFileName(fileName);
        file.setTotalBytes(fileModel.getBytes().length);
        file.setExt(extName);
        file.setDelStatus("undeleted");
        if (curUser == null) {
            file.setCreateUser("system");
        } else {
            file.setCreateUser(curUser.getFullName());
        }
        sysFileServiceImpl.save(file);
        jsonResult.setSuccess(true);
        jsonResult.setData(file);
        jsonResult.setMessage("成功上传!");
        jsonResult.setShow(false);
        return jsonResult;
    }

    /**
     * base64文件下载 20221111
     */
    @ApiOperation("文件下载或打开")
    @GetMapping("downloadBase64/{fileId}")
    public JsonResult downloadBase64(@PathVariable("fileId") String fileId) throws Exception {
        JsonResult jsonResult = new JsonResult();
        jsonResult.setSuccess(false);
        jsonResult.setMessage("下载失败!");
        jsonResult.setShow(false);
        SysFile sysFile = sysFileServiceImpl.get(fileId);
        if (BeanUtil.isNotEmpty(sysFile)) {
            String fileSystem = sysFile.getFileSystem();
            IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
            String base64File = operator.getFileBase64(sysFile);
            if (!StringUtils.isEmpty(base64File)) {
                JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(sysFile));
                jsonObject.put("base64", base64File);
                jsonResult.setSuccess(true);
                jsonResult.setMessage("下载成功!");
                jsonResult.setShow(false);
                jsonResult.setData(jsonObject);
            }
        }
        return jsonResult;
    }

    /**
     * 下载文件(将文件打成压缩包)支持使用原来文件名 20230111
     * @param fileIdStr 逗号隔开
     * @throws Exception
     */
    @MethodDefine(title = "下载文件(将文件打成压缩包)", path = "/downloadZips", method = HttpMethodConstants.GET)
    @ApiOperation("下载文件(将文件打成压缩包)")
    @GetMapping("downloadZips/{fileIdStr}")
    public void downloadZips( @PathVariable("fileIdStr") String fileIdStr) throws Exception {
        if (StringUtils.isNotEmpty(fileIdStr)){
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = attributes.getResponse();
            String[] fileIds = fileIdStr.split(",");
            List<SysFile> fileList = new ArrayList<>();
            String path = SysFileUtil.getConfigKey("uploadPath");
            for (int i = 0; i < fileIds.length; i++) {
                SysFile sysFile = sysFileServiceImpl.get(fileIds[i]);
                if(StringUtils.isNotEmpty(sysFile.getPath())){
                    fileList.add(sysFile);
                }
            }
            String fileName= IdGenerator.getIdStr()+".zip";
            if(fileList.size()>0){
                String zipName = fileName;
                response.setContentType("application/zip");
                FileOutputStream fos2 = new FileOutputStream(new File(path+zipName));
                long start = System.currentTimeMillis();
                ZipOutputStream zos = null ;
                try {
                    zos = new ZipOutputStream(fos2);
                    for (SysFile sysFile : fileList) {
                        File srcFile = new File(path+sysFile.getPath());
                        byte[] buf = new byte[2 * 1024];
                        String fileExt = FileUtil.getFileExt(sysFile.getFileName());
                        String oldName = sysFile.getFileName().replace("."+fileExt,"");
                        zos.putNextEntry(new ZipEntry(oldName+"-"+sysFile.getFileId()+"."+fileExt));
                        int len;
                        FileInputStream in = new FileInputStream(srcFile);
                        while ((len = in.read(buf)) != -1){
                            zos.write(buf, 0, len);
                        }
                        zos.closeEntry();
                        in.close();
                    }
                    long end = System.currentTimeMillis();
                    System.out.println("压缩完成,耗时:" + (end - start) +" ms");
                } catch (Exception e) {
                    throw new RuntimeException("zip error from ZipUtils",e);
                }finally{
                    if(zos != null){
                        try {
                            zos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                // 创建file对象
                File file = new File(path+zipName);
                String ext=new MimetypesFileTypeMap().getContentType(file);
                response.setContentType(ext);
                zipName = URLEncoder.encode(zipName,"UTF-8");
                response.setHeader("Content-Disposition",  "attachment;filename=" +zipName);
                FileInputStream fis = new FileInputStream(file);
                BufferedInputStream buff = null;
                OutputStream out = null;
                try {
                    buff = new BufferedInputStream(fis);
                    out = response.getOutputStream();
                    IOUtils.copy(buff, out);
                    FileUtil.deleteFile(path+zipName);
                } finally {
                    out.flush();
                    fis.close();
                    buff.close();
                    out.close();
                }
            }

        }
    }

    /**
     * uploadOnePreFileId文件上传 20230227
     */
    @ApiOperation("单个文件上传,可以预设文件id,如果不设置文件id,默认自动生成一个文件id")
    @MethodDefine(title = "文件上传", path = "/uploadOnePreFileId", method = HttpMethodConstants.POST,
            params = {@ParamDefine(title = "上传文件", varName = "request")})
    @AuditLog(operation = "上传文件")
    @RequestMapping("uploadOnePreFileId")
    public JsonResult uploadOnePreFileId(MultipartHttpServletRequest request) throws Exception {
        JsonResult jsonResult = new JsonResult();
        JPaasUser curUser = (JPaasUser) ContextUtil.getCurrentUser();
        String timestamp = request.getParameter("time");
        String fileId = request.getParameter("fileId");
        Map<String, MultipartFile> multiFileMap = request.getFileMap();
        Collection<MultipartFile> files= multiFileMap.values();
        Iterator<MultipartFile> it = files.iterator();
        List<SysFile> fileList = new ArrayList<SysFile>();
        String fileSystem = SysFileUtil.getConfigKey("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        StringBuilder sb = new StringBuilder();
        sb.append("上传文件:");
        if (it.hasNext()) {
            //String fileId = IdGenerator.getIdStr();
            if(StringUtils.isBlank(fileId)){//如果没有传文件id,默认生成一个文件id
                fileId = IdGenerator.getIdStr();
            }
            SysFile file = new SysFile();
            file.setFileId(fileId);
            MultipartFile multipartFile = it.next();
            String oriFileName = multipartFile.getOriginalFilename();
            String contentType = multipartFile.getContentType();
            String[] split = contentType.split("/");
            String type = split[0];
            String extName = FileUtil.getFileExt(oriFileName);
            // 新文件名
            String newFileName = fileId + "." + extName;
            sb.append(fileId + ",");
            InputStream is = multipartFile.getInputStream();
            byte[] bytes = FileUtil.input2byte(is);
            FileModel fileModel = operator.createFile(newFileName, bytes);
            file.setPath(fileModel.getRelPath());
            file.setFileSystem(fileSystem);
            file.setFileName(oriFileName);
            file.setTotalBytes(bytes.length);
            file.setExt(extName);
            file.setDelStatus("undeleted");
            if(curUser==null){
                file.setCreateUser("system");
            }
            else{
                file.setCreateUser(curUser.getFullName());
            }
            // 如果为图片,则生成图片的缩略图
            if ("image".equals(type) || isImg(extName)) {
                handImage(extName, bytes, operator, file, fileModel.getRelPath());
            }
            //接收前端from和typeId,方便前端区别附件来源和分类 20220318
            file.setFrom(request.getParameter("from"));
            file.setTypeId(request.getParameter("typeId"));
            sysFileServiceImpl.save(file);
            file.setTimestamp(timestamp);
            fileList.add(file);
            file.setFileContent(bytes);
            //发送文件转pdf消息中间件处理
            SysFile temp = new SysFile();
            temp.setFileId(file.getFileId());
            temp.setPath(file.getPath());
            temp.setFileSystem(file.getFileSystem());
            temp.setExt(file.getExt());
            convertOffice(temp);
        }
        jsonResult.setSuccess(true);
        for (SysFile file : fileList) {
            file.setFileContent(null);
        }
        jsonResult.setData(fileList);
        jsonResult.setMessage("成功上传!");
        jsonResult.setShow(false);
        return jsonResult;
    }

    /**
     * base64文件上传 20230227
     * @param base64 文件
     * @param fileName 文件名称
     * @param fileId 文件
     * @return
     * @throws Exception
     */
    @ApiOperation("base64文件上传")
    @MethodDefine(title = "base64文件上传", path = "/uploadBase64PreFileId", method = HttpMethodConstants.POST,
            params = {@ParamDefine(title = "base64上传文件", varName = "request")})
    @AuditLog(operation = "base64上传文件")
    @PostMapping("uploadBase64PreFileId")
    public JsonResult uploadBase64PreFileId(String base64, String fileName,String fileId) throws Exception {
        JsonResult jsonResult = new JsonResult();
        JPaasUser curUser = (JPaasUser) ContextUtil.getCurrentUser();
        String fileSystem = SysFileUtil.getConfigKey("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        if(StringUtils.isBlank(fileId)) {
            fileId = IdGenerator.getIdStr();
        }
        SysFile file = new SysFile();
        file.setFileId(fileId);
        String extName = FileUtil.getFileExt(fileName);
        // 新文件名
        String newFileName = fileId + "." + extName;
        // base64转图片
        FileModel fileModel = Base64FileUtil.base64ToFile(base64, newFileName);
        file.setPath(fileModel.getRelPath());
        file.setFileSystem(fileSystem);
        file.setFileName(fileName);
        file.setTotalBytes(fileModel.getBytes().length);
        file.setExt(extName);
        file.setDelStatus("undeleted");
        if (curUser == null) {
            file.setCreateUser("system");
        } else {
            file.setCreateUser(curUser.getFullName());
        }
        sysFileServiceImpl.save(file);
        jsonResult.setSuccess(true);
        jsonResult.setData(file);
        jsonResult.setMessage("成功上传!");
        jsonResult.setShow(false);
        return jsonResult;
    }

    /**
     * 删除文件 (20240304 jqp)
     * @param fileIds 文件id 多个逗号隔开
     * @param isDelFile  是否把文件也删除 true删除,false不删除
     * @return
     */
    @ApiOperation("删除系统附件")
    @MethodDefine(title = "删除系统附件", path = "/delFile", method = HttpMethodConstants.POST,
            params = {
                    @ParamDefine(title = "文件id,逗号隔开", varName = "fileIds"),
                    @ParamDefine(title = "是否把文件也删除", varName = "isDelFile")
            })
    @AuditLog(operation = "删除系统附件")
    @PostMapping("/delFile")
    public JsonResult delFile(@RequestParam String fileIds,@RequestParam(defaultValue = "false") boolean isDelFile){
        String[] fileIdList = fileIds.split(",");
        //最多一次只能删除50个文件
        if(fileIdList.length>=50){
            return JsonResult.Fail("参数检验不通过");
        }

        String detail="删除"+this.getComment()+":" + fileIds + ",参数:{isDelFile:"+isDelFile+"}";
        LogContext.put(Audit.DETAIL,detail);

        String fileSystem = SysFileUtil.getConfigKey("fileSystem");
        IFileOperator operator = fileOperatorFactory.getByType(fileSystem);
        for (int i = 0; i < fileIdList.length; i++) {
            SysFile sysFile = sysFileServiceImpl.get(fileIdList[i]);
            if(sysFile == null) continue;

            //删除数据库记录
            if(!sysFileServiceImpl.removeById(fileIdList[i])){
                continue;
            }

            //删除文件
            if(isDelFile){
                if(StringUtils.isNotEmpty(sysFile.getPath())){
                    String filePath = sysFile.getPath();
                    operator.delFile(filePath);
                }
            }
        }
        return JsonResult.Success("成功删除");
    }
}

二、配套支撑类

SysFileUtil(配置读取与路径工具)

SysFileUtil.java

java 复制代码
package com.jx.system.util;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.ConfigService;
import com.jx.common.tool.StringUtils;
import com.jx.common.utils.SpringUtil;
import com.jx.system.core.entity.SysFile;
import com.jx.system.core.service.SysFileServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 文件上传下载。
 *
 */
@Slf4j
@Component
public class SysFileUtil {
	public static final String FILE_CONFIG = "sysFileSource.json";
	public static final String DEFAULT_GROUP = "DEFAULT_GROUP";
	public static final String UPLOAD_PATH = "uploadPath";
	public static final String PDF = "pdf";


	/**
	 * 替换扩展名
	 * @param fileName
	 * @param ext
	 * @return
	 */
	public static String replaceExt(String fileName,String ext){
		fileName =fileName.substring(0,fileName.lastIndexOf(".")) + "."+ ext;
		return  fileName;
	}

	public static String getDownloadName(SysFile sysFile,boolean isPdf){
		String fileName=sysFile.getFileName();
		//扩展名不是PDF,并且需要下载PDF
		if(!PDF.equals(sysFile.getExt()) && isPdf){
			fileName=replaceExt(sysFile.getPdfPath(),PDF);
		}

		return  fileName;
	}

	public static String getUploadPath(){
		String uploadPath=getConfigKey(UPLOAD_PATH);
		uploadPath=uploadPath.replaceAll("\\\\","/");
		if(!uploadPath.endsWith("/")){
			uploadPath=uploadPath+"/";
		}
		return uploadPath;
	}

	/**
	 * 获取文件路径。
	 * @param sysFile
	 * @param isScale
	 * @param transPdf
	 * @return
	 * @throws Exception
	 */
	public static String getFilePath(SysFile sysFile,boolean isScale,boolean transPdf) throws Exception {
		String uploadPath=getUploadPath();
		String fullPath="";
		if(transPdf){
			if(PDF.equals(sysFile.getExt()) ){
				fullPath =  sysFile.getPath();
			}
			else {
				fullPath = SysFileUtil.replaceExt(sysFile.getPath(),"pdf")  ;
			}
		}
		else{
			if (isScale){
				fullPath =  sysFile.getThumbnail();
			}else {
				fullPath = sysFile.getPath();
			}
		}

		fullPath=uploadPath + fullPath;

		return fullPath;
	}



	/**
	 * 转换pdf
	 * @param sysFile
	 * @throws Exception
	 */
	public static void convertPdf(SysFile sysFile) throws Exception {
		String filePath= SysFileUtil.getUploadPath()+ sysFile.getPath();
		//是否启动openOffice,没有则启动
		OpenOfficeUtil.getConnectStatus(true);
		if("file".equals(sysFile.getFileSystem())){
			String pdfPath=filePath.substring(0, filePath.lastIndexOf(".")) +".pdf";
			JSONObject jsonObject = OpenOfficeUtil.coverFromOffice2Pdf(filePath, pdfPath);
			if(jsonObject.getBoolean("success")){
				//获取pdf保存路径
			 	sysFile.setPdfPath(pdfPath);
				//转换后将转换状态设置成YES
				sysFile.setCoverStatus("YES");
			}
		}
		SysFileServiceImpl sysFileServiceImpl= SpringUtil.getBean(SysFileServiceImpl.class);
		sysFileServiceImpl.update(sysFile);
	}

	public static boolean isOfficeDoc(String ext){
		if ("docx".equals(ext)||"doc".equals(ext)||"pptx".equals(ext)
				||"ppt".equals(ext)||"xlsx".equals(ext)||"xls".equals(ext)){
			return true;
		}
		return false;
	}

	/**
	 * 根据属性在NACOS配置中获取值。
	 * <pre>
	 *     1. 配置先从sysFileSource.json中获取。
	 *     2. 获取不到则从 nacos-config.properties 配置中获取。
	 * </pre>
	 * @param key
	 * @return
	 */
	public static String getConfigKey(String key){
		String keyValue ="";
		try {
			ConfigService configService=SpringUtil.getBean(ConfigService.class);
			String config =configService.getConfig(FILE_CONFIG, DEFAULT_GROUP, 0L);
			if(StringUtils.isNotEmpty(config)){
				JSONObject sysFileStore = JSONObject.parseObject(config);
				keyValue = sysFileStore.getString(key);
			}
		}catch (Exception e){
			log.error("----获取config失败!");
		}

		return keyValue;
	}


	/**
	 * 配置先从JSON中获取,获取不到则到 nacos-config.properties 中获取。
	 * @param config
	 * @param key
	 * @return
	 */
	public static String getConfigKey(String config,String key){
		String keyValue ="";
		try {
			if(StringUtils.isNotEmpty(config)){
				JSONObject sysFileStore = JSONObject.parseObject(config);
				keyValue = sysFileStore.getString(key);
			}
		}catch (Exception e){

		}

		return keyValue;
	}
	public static void main(String[] args) {
		List<String> list = Arrays.asList("1","2","3","4");
		String join = StringUtils.join(list, ",");
		System.out.println(join);
	}
}

FastClient(FastDFS 客户端封装)

FastClient.java

java 复制代码
package com.jx.system.util;

import com.alibaba.nacos.api.config.ConfigService;
import com.jx.common.utils.ExceptionUtil;
import lombok.extern.slf4j.Slf4j;
import org.csource.common.MyException;
import org.csource.fastdfs.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Properties;

/**
 * FASTDFS 文件上传下载。
 *
 */
@Component
@Slf4j
public class FastClient {

	@Autowired
	ConfigService configService;

	public void init() throws IOException, MyException{
		Properties properties=new Properties();
		String config ="";
		try {

			config = configService.getConfig(SysFileUtil.FILE_CONFIG, SysFileUtil.DEFAULT_GROUP, 0L);

		}catch (Exception e){
			log.error(ExceptionUtil.getExceptionMessage(e));
		}
		properties.put("fastdfs.connect_timeout_in_seconds", SysFileUtil.getConfigKey(config,"connect_timeout"));
		properties.put("fastdfs.network_timeout_in_seconds",SysFileUtil.getConfigKey(config,"network_timeout"));
		properties.put("fastdfs.charset",SysFileUtil.getConfigKey(config,"charset"));
		properties.put("fastdfs.http_tracker_http_port",SysFileUtil.getConfigKey(config,"http_tracker_http_port"));
		properties.put("fastdfs.http_anti_steal_token",SysFileUtil.getConfigKey(config,"http_anti_steal_token"));
		properties.put("fastdfs.http_secret_key",SysFileUtil.getConfigKey(config,"http_secret_key"));
		properties.put("fastdfs.tracker_servers",SysFileUtil.getConfigKey(config,"tracker_servers"));

		ClientGlobal.initByProperties(properties);
	}

	/**
	 * 上传文件
	 * @param bytes		文件内容
	 * @param extName	扩展名
	 * @return
	 * @throws IOException
	 * @throws MyException
	 */
	public String uploadFile(byte[] bytes,String extName) throws IOException, MyException{

        TrackerClient tracker = new TrackerClient(); 
        TrackerServer trackerServer = tracker.getConnection(); 
        StorageServer storageServer = null;

        StorageClient storageClient = new StorageClient(trackerServer, storageServer); 
        
        String fileIds[] = storageClient.upload_file(bytes, extName, null);
        
        return fileIds[0] +"#" + fileIds[1];
	}
	
	/**
	 * 根据文件路径删除文件。
	 * path 组#文件路径
	 * @param path
	 * @return
	 * @throws IOException
	 * @throws MyException
	 */
	public int delFile(String path) throws IOException, MyException{
		String[] ary=path.split("#");
		 TrackerClient tracker = new TrackerClient(); 
         TrackerServer trackerServer = tracker.getConnection(); 
         StorageServer storageServer = null;

         StorageClient storageClient = new StorageClient(trackerServer, 
                 storageServer); 
         int i = storageClient.delete_file(ary[0], ary[1]); 
         return i;
	}
	
	/**
	 * 根据文件的组和路径获取文件
	 * path 组#文件路径
	 * @param path
	 * @return
	 * @throws IOException
	 * @throws MyException
	 */
	public byte[] getFile(String path) throws IOException, MyException{
		String[] ary=path.split("#");
		 TrackerClient tracker = new TrackerClient(); 
         TrackerServer trackerServer = tracker.getConnection(); 
         StorageServer storageServer = null;
         
         StorageClient storageClient = new StorageClient(trackerServer, storageServer); 
         byte[] bytes = storageClient.download_file(ary[0], ary[1]);

       
         return bytes;
	}

}

FileModel(上传结果对象)

FileModel.java

java 复制代码
package com.jx.system.util;

/**
 * 上传文件对象。
 *
 */
public class FileModel {
	
	private String relPath="";
	
	private String fileName="";
	
	private String extName="";
	
	private byte[] bytes;

	public String getRelPath() {
		return relPath;
	}

	public void setRelPath(String relPath) {
		this.relPath = relPath;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getExtName() {
		return extName;
	}

	public void setExtName(String extName) {
		this.extName = extName;
	}

	public byte[] getBytes() {
		return bytes;
	}

	public void setBytes(byte[] bytes) {
		this.bytes = bytes;
	}
	
	

}

ConverResult(PDF 转换结果对象)

ConverResult.java

java 复制代码
package com.jx.system.operator;

import lombok.Getter;
import lombok.Setter;

/**
 * PDF转换结果对象
 */
@Getter
@Setter
public class ConverResult {

    /**
     * 转换是否成功,true是,false否
     */
    private  Boolean success = false;

    /**
     * 源文件是否存在,true是,false否
     */
    private  Boolean fileNotFind = false;

    /**
     * 原因,如转换失败的原因
     */
    private  String reason;

    /**
     * PDF文件地址
     */
    private String  path;

}

SysFile(文件元数据实体)

SysFile.java

java 复制代码
package com.jx.system.core.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.jx.common.base.entity.BaseExtEntity;
import com.jx.common.constant.MBoolean;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

@Setter
@Getter
@Accessors(chain = true)
@TableName(value = "sys_file")
public class SysFile  extends BaseExtEntity<java.lang.String> {

    @JsonCreator
    public SysFile() {
    }

    //FILE_ID_
    @TableId(value = "FILE_ID_",type = IdType.INPUT)
	private String fileId;

    //分类ID
    @TableField(value = "TYPE_ID_")
    private String typeId;
    //文件名
    @TableField(value = "FILE_NAME_")
    private String fileName;
    //新文件名
    @TableField(value = "NEW_FNAME_")
    private String newFname;
    //文件路径
    @TableField(value = "PATH_")
    private String path;
    //图片缩略图
    @TableField(value = "THUMBNAIL_")
    private String thumbnail;
    //扩展名
    @TableField(value = "EXT_")
    private String ext;
    //附件类型
    @TableField(value = "MINE_TYPE_")
    private String mineType;
    //说明
    @TableField(value = "DESC_")
    private String desc;
    //总字节数
    @TableField(value = "TOTAL_BYTES_")
    private Integer totalBytes;
    //删除标识
    @TableField(value = "DEL_STATUS_")
    private String delStatus;

    @TableField(value = "FROM_")
    private String from;
    //生成PDF状态
    @TableField(value = "COVER_STATUS_")
    private String coverStatus= MBoolean.NO.val;
    //文件系统
    @TableField(value = "FILE_SYSTEM_")
    private String fileSystem;
    //PDF文件路径
    @TableField(value = "PDF_PATH_")
    private String pdfPath;

    @TableField(exist = false)
    private String createUser;


    @TableField(exist = false)
    private byte[] fileContent;
    @TableField(exist = false)
    private String timestamp;

    @Override
    public String getPkId() {
        return fileId;
    }

    @Override
    public void setPkId(String pkId) {
        this.fileId=pkId;
    }


    /**
     * 文件不存在,无法转换成PDF文件
     */
    public static String FAIL = "FAIL";
}
相关推荐
ShineWinsu3 小时前
对于Redis:string类型的解析
java·c++·redis·分布式·缓存·面试·string
光依旧5 小时前
MCP实战手记(八):从“能跑“到“能上线“——无状态MCP Server的生产落地清单
java·人工智能·spring boot·架构·ai agent·mcp
MZ_ZXD0016 小时前
【关注可白嫖源码】--课程设计--毕业设计-- 29210 Vue.js 3+Springboot中药材基本信息与图片查询系统
javascript·spring boot·课程设计·毕设·大作业·程序开发·毕设代做
Cheney Pan7 小时前
第4篇 Spring Boot 应用监控:Micrometer 与 JVM 指标
jvm·spring boot·后端·prometheus
仍然.7 小时前
RabbitMQ---可靠性传输
网络·分布式·rabbitmq
小蒜学长8 小时前
基于Spring Boot+Vue的“禾源”农产品销售系统设计与实现(代码+数据库+LW)
java·spring boot·后端·农产品销售系统·产品溯源
墨鱼老师8 小时前
SpringBoot+Vue 毕设项目开发踩坑记录(前后端分离)
vue.js·spring boot·毕业设计·课程设计·前后端分离·java 毕设
灯澜忆梦8 小时前
【RabbitMQ #13】 | 延迟消息
分布式·rabbitmq·ruby
Wx-bishekaifayuan9 小时前
springboot房屋租赁系统11574-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·sql·spring·课程设计