package org.dromara.x.file.storage.core;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.core.aspect.*;
import org.dromara.x.file.storage.core.constant.Constant;
import org.dromara.x.file.storage.core.copy.CopyPretreatment;
import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException;
import org.dromara.x.file.storage.core.file.FileWrapper;
import org.dromara.x.file.storage.core.file.FileWrapperAdapter;
import org.dromara.x.file.storage.core.file.HttpServletRequestFileWrapper;
import org.dromara.x.file.storage.core.file.MultipartFormDataReader;
import org.dromara.x.file.storage.core.get.GetFilePretreatment;
import org.dromara.x.file.storage.core.get.ListFilesPretreatment;
import org.dromara.x.file.storage.core.get.ListFilesSupportInfo;
import org.dromara.x.file.storage.core.get.RemoteFileInfo;
import org.dromara.x.file.storage.core.move.MovePretreatment;
import org.dromara.x.file.storage.core.platform.FileStorage;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlPretreatment;
import org.dromara.x.file.storage.core.presigned.GeneratePresignedUrlResult;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.tika.ContentTypeDetect;
import org.dromara.x.file.storage.core.upload.*;
import org.dromara.x.file.storage.core.upload.MultipartUploadSupportInfo;
import org.dromara.x.file.storage.core.upload.UploadPretreatment;
import org.dromara.x.file.storage.core.util.Tools;

/**
 * 用来处理文件存储，对接多个平台
 */
@Slf4j
@Getter
@Setter
public class FileStorageService {

    private FileStorageService self;
    private FileStorageProperties properties;
    private FileRecorder fileRecorder;
    private CopyOnWriteArrayList<FileStorage> fileStorageList;
    private CopyOnWriteArrayList<FileStorageAspect> aspectList;
    private CopyOnWriteArrayList<FileWrapperAdapter> fileWrapperAdapterList;
    private ContentTypeDetect contentTypeDetect;

    /**
     * 获取默认的存储平台，请使用 getProperties().getDefaultPlatform() 代替
     */
    @Deprecated
    public String getDefaultPlatform() {
        return properties.getDefaultPlatform();
    }

    /**
     * 缩略图后缀，例如【.min.jpg】【.png】，请使用 getProperties().getThumbnailSuffix() 代替
     */
    @Deprecated
    public String getThumbnailSuffix() {
        return properties.getThumbnailSuffix();
    }

    /**
     * 获取默认的存储平台
     */
    public <T extends FileStorage> T getFileStorage() {
        return self.getFileStorage(properties.getDefaultPlatform());
    }

    /**
     * 获取对应的存储平台
     */
    public <T extends FileStorage> T getFileStorage(String platform) {
        for (FileStorage fileStorage : fileStorageList) {
            if (fileStorage.getPlatform().equals(platform)) {
                return Tools.cast(fileStorage);
            }
        }
        return null;
    }

    /**
     * 获取对应的存储平台，如果存储平台不存在则抛出异常
     */
    public <T extends FileStorage> T getFileStorageVerify(FileInfo fileInfo) {
        return self.getFileStorageVerify(fileInfo.getPlatform());
    }

    /**
     * 获取对应的存储平台，如果存储平台不存在则抛出异常
     */
    public <T extends FileStorage> T getFileStorageVerify(String platform) {
        T fileStorage = self.getFileStorage(platform);
        if (fileStorage == null)
            throw new FileStorageRuntimeException(StrUtil.format("没有找到对应的存储平台！platform:{}", platform));
        return fileStorage;
    }

    /**
     * 上传文件，成功返回文件信息，失败返回 null，请使用 pre.upload() 代替
     */
    @Deprecated
    public FileInfo upload(UploadPretreatment pre) {
        return pre.upload();
    }

    /**
     * 根据 url 获取 FileInfo
     */
    public FileInfo getFileInfoByUrl(String url) {
        return fileRecorder.getByUrl(url);
    }

    /**
     * 根据 url 删除文件
     */
    public boolean delete(String url) {
        return self.delete(getFileInfoByUrl(url));
    }

    /**
     * 根据 url 删除文件
     */
    public boolean delete(String url, Predicate<FileInfo> predicate) {
        return self.delete(getFileInfoByUrl(url), predicate);
    }

    /**
     * 根据条件
     */
    public boolean delete(FileInfo fileInfo) {
        return self.delete(fileInfo, null);
    }

    /**
     * 根据条件删除文件
     */
    public boolean delete(FileInfo fileInfo, Predicate<FileInfo> predicate) {
        if (fileInfo == null) return true;
        if (predicate != null && !predicate.test(fileInfo)) return false;
        FileStorage fileStorage = self.getFileStorage(fileInfo.getPlatform());
        if (fileStorage == null) throw new FileStorageRuntimeException("没有找到对应的存储平台！");
        return self.delete(fileInfo, fileStorage, fileRecorder, aspectList);
    }

    /**
     * 删除文件，仅限内部使用
     */
    public boolean delete(
            FileInfo fileInfo, FileStorage fileStorage, FileRecorder fileRecorder, List<FileStorageAspect> aspectList) {
        return new DeleteAspectChain(aspectList, (_fileInfo, _fileStorage, _fileRecorder) -> {
                    if (_fileStorage.delete(_fileInfo)) { // 删除文件
                        return _fileRecorder.delete(_fileInfo.getUrl()); // 删除文件记录
                    }
                    return false;
                })
                .next(fileInfo, fileStorage, fileRecorder);
    }

    /**
     * 文件是否存在
     */
    public boolean exists(String url) {
        return self.exists(getFileInfoByUrl(url));
    }

    /**
     * 文件是否存在
     */
    public boolean exists(FileInfo fileInfo) {
        if (fileInfo == null) return false;
        return new ExistsAspectChain(aspectList, (_fileInfo, _fileStorage) -> _fileStorage.exists(_fileInfo))
                .next(fileInfo, getFileStorageVerify(fileInfo));
    }

    /**
     * 获取文件下载器
     */
    public Downloader download(FileInfo fileInfo) {
        return new Downloader(fileInfo, aspectList, getFileStorageVerify(fileInfo), Downloader.TARGET_FILE);
    }

    /**
     * 获取文件下载器
     */
    public Downloader download(String url) {
        return self.download(getFileInfoByUrl(url));
    }

    /**
     * 获取缩略图文件下载器
     */
    public Downloader downloadTh(FileInfo fileInfo) {
        return new Downloader(fileInfo, aspectList, getFileStorageVerify(fileInfo), Downloader.TARGET_TH_FILE);
    }

    /**
     * 获取缩略图文件下载器
     */
    public Downloader downloadTh(String url) {
        return self.downloadTh(getFileInfoByUrl(url));
    }

    /**
     * 是否支持对文件生成可以签名访问的 URL
     */
    public boolean isSupportPresignedUrl() {
        return self.isSupportPresignedUrl(properties.getDefaultPlatform());
    }

    /**
     * 是否支持对文件生成可以签名访问的 URL
     */
    public boolean isSupportPresignedUrl(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportPresignedUrl(storage);
    }

    /**
     * 是否支持对文件生成可以签名访问的 URL
     */
    public boolean isSupportPresignedUrl(FileStorage fileStorage) {
        if (fileStorage == null) return false;
        return new IsSupportPresignedUrlAspectChain(aspectList, FileStorage::isSupportPresignedUrl).next(fileStorage);
    }

    /**
     * 生成预签名 URL
     * @return 生成预签名 URL 预处理器
     */
    public GeneratePresignedUrlPretreatment generatePresignedUrl() {
        return new GeneratePresignedUrlPretreatment()
                .setFileStorageService(self)
                .setPlatform(properties.getDefaultPlatform());
    }

    /**
     * 对文件生成可以签名访问的 URL，无法生成则返回 null
     *
     * @param expiration 到期时间
     */
    public String generatePresignedUrl(FileInfo fileInfo, Date expiration) {
        if (fileInfo == null) return null;
        GeneratePresignedUrlResult result = generatePresignedUrl()
                .setExpiration(expiration)
                .setPath(fileInfo.getPath())
                .setFilename(fileInfo.getFilename())
                .setMethod(Constant.GeneratePresignedUrl.Method.GET)
                .generatePresignedUrl();
        return result == null ? null : result.getUrl();
    }

    /**
     * 对缩略图文件生成可以签名访问的 URL，无法生成则返回 null
     *
     * @param expiration 到期时间
     */
    public String generateThPresignedUrl(FileInfo fileInfo, Date expiration) {
        if (fileInfo == null) return null;
        GeneratePresignedUrlResult result = generatePresignedUrl()
                .setExpiration(expiration)
                .setPath(fileInfo.getPath())
                .setFilename(fileInfo.getThFilename())
                .setMethod(Constant.GeneratePresignedUrl.Method.GET)
                .generatePresignedUrl();
        return result == null ? null : result.getUrl();
    }

    /**
     * 是否支持对文件的访问控制列表
     */
    public boolean isSupportAcl() {
        return self.isSupportAcl(properties.getDefaultPlatform());
    }

    /**
     * 是否支持对文件的访问控制列表
     */
    public boolean isSupportAcl(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportAcl(storage);
    }

    /**
     * 是否支持对文件的访问控制列表
     */
    public boolean isSupportAcl(FileStorage fileStorage) {
        if (fileStorage == null) return false;
        return new IsSupportAclAspectChain(aspectList, FileStorage::isSupportAcl).next(fileStorage);
    }

    /**
     * 设置文件的访问控制列表，一般情况下只有对象存储支持该功能
     * 详情见{@link FileInfo#setFileAcl}
     */
    public boolean setFileAcl(FileInfo fileInfo, Object acl) {
        if (fileInfo == null) return false;
        return new SetFileAclAspectChain(
                        aspectList, (_fileInfo, _acl, _fileStorage) -> _fileStorage.setFileAcl(_fileInfo, _acl))
                .next(fileInfo, acl, self.getFileStorageVerify(fileInfo));
    }

    /**
     * 设置缩略图文件的访问控制列表，一般情况下只有对象存储支持该功能
     * 详情见{@link FileInfo#setFileAcl}
     */
    public boolean setThFileAcl(FileInfo fileInfo, Object acl) {
        if (fileInfo == null) return false;
        return new SetThFileAclAspectChain(
                        aspectList, (_fileInfo, _acl, _fileStorage) -> _fileStorage.setThFileAcl(_fileInfo, _acl))
                .next(fileInfo, acl, self.getFileStorageVerify(fileInfo));
    }

    /**
     * 是否支持 Metadata
     */
    public boolean isSupportMetadata() {
        return self.isSupportMetadata(properties.getDefaultPlatform());
    }

    /**
     * 是否支持 Metadata
     */
    public boolean isSupportMetadata(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportMetadata(storage);
    }

    /**
     * 是否支持 Metadata
     */
    public boolean isSupportMetadata(FileStorage fileStorage) {
        if (fileStorage == null) return false;
        return new IsSupportMetadataAspectChain(aspectList, FileStorage::isSupportMetadata).next(fileStorage);
    }

    /**
     * 创建上传预处理器
     */
    public UploadPretreatment of() {
        UploadPretreatment pre = new UploadPretreatment();
        pre.setFileStorageService(self);
        pre.setPlatform(properties.getDefaultPlatform());
        pre.setThumbnailSuffix(properties.getThumbnailSuffix());
        pre.setNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException());
        pre.setNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException());
        return pre;
    }

    /**
     * 创建上传预处理器
     *
     * @param source 源
     */
    public UploadPretreatment of(Object source) {
        return self.of(source, null, null);
    }

    /**
     * 创建上传预处理器
     *
     * @param source 源
     * @param name   文件名
     */
    public UploadPretreatment of(Object source, String name) {
        return self.of(source, name, null);
    }

    /**
     * 创建上传预处理器
     *
     * @param source      源
     * @param name        文件名
     * @param contentType 文件的 MIME 类型
     */
    public UploadPretreatment of(Object source, String name, String contentType) {
        return self.of(source, name, contentType, null);
    }

    /**
     * 默认使用的存储平台是否支持手动分片上传
     */
    public MultipartUploadSupportInfo isSupportMultipartUpload() {
        return self.isSupportMultipartUpload(properties.getDefaultPlatform());
    }

    /**
     * 是否支持手动分片上传
     */
    public MultipartUploadSupportInfo isSupportMultipartUpload(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportMultipartUpload(storage);
    }

    /**
     * 是否支持手动分片上传
     */
    public MultipartUploadSupportInfo isSupportMultipartUpload(FileStorage fileStorage) {
        if (fileStorage == null) return MultipartUploadSupportInfo.notSupport();
        return new IsSupportMultipartUploadAspectChain(aspectList, FileStorage::isSupportMultipartUpload)
                .next(fileStorage);
    }

    /**
     * 手动分片上传-初始化
     */
    public InitiateMultipartUploadPretreatment initiateMultipartUpload() {
        InitiateMultipartUploadPretreatment pre = new InitiateMultipartUploadPretreatment();
        pre.setFileStorageService(self);
        pre.setPlatform(properties.getDefaultPlatform());
        pre.setNotSupportMetadataThrowException(properties.getUploadNotSupportMetadataThrowException());
        pre.setNotSupportAclThrowException(properties.getUploadNotSupportAclThrowException());
        return pre;
    }

    /**
     * 手动分片上传-上传分片
     * @param fileInfo 文件信息
     * @param partNumber 分片号。每一个上传的分片都有一个分片号，一般情况下取值范围是1~10000
     * @param source      源
     * @return 手动分片上传-上传分片预处理器
     */
    public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Object source) {
        return self.uploadPart(fileInfo, partNumber, source, null);
    }

    /**
     * 手动分片上传-上传分片
     * @param fileInfo 文件信息
     * @param partNumber 分片号。每一个上传的分片都有一个分片号，一般情况下取值范围是1~10000
     * @param source      源
     * @param size        源的文件大小
     * @return 手动分片上传-上传分片预处理器
     */
    public UploadPartPretreatment uploadPart(FileInfo fileInfo, int partNumber, Object source, Long size) {
        UploadPartPretreatment pre = new UploadPartPretreatment();
        pre.setFileStorageService(self);
        pre.setFileInfo(fileInfo);
        pre.setPartNumber(partNumber);
        // 这是是个优化，如果是 FileWrapper 对象就直接使用，否则就创建 FileWrapper 并指定 contentType 避免自动识别造成性能浪费
        if (source instanceof FileWrapper) {
            pre.setPartFileWrapper((FileWrapper) source);
        } else {
            pre.setPartFileWrapper(self.wrapper(source, null, "application/octet-stream", size));
        }
        return pre;
    }

    /**
     * 手动分片上传-完成
     * @param fileInfo 文件信息，如果在初始化时传入了 ACL 访问控制列表、Metadata 元数据等信息，
     *                 一定要保证这里的 fileInfo 也有相同的信息，否则有些存储平台会不生效，
     *                 这是因为每个存储平台的逻辑不一样，有些是初始化时传入的，有些是完成时传入的，
     *                 建议将 FileInfo 保存到数据库中，这样就可以使用 fileStorageService.getFileInfoByUrl("https://abc.def.com/xxx.png")
     *                 来获取 FileInfo 方便操作，详情请阅读 https://x-file-storage.xuyanwu.cn/2.2.0/#/%E5%9F%BA%E7%A1%80%E5%8A%9F%E8%83%BD?id=%E4%BF%9D%E5%AD%98%E4%B8%8A%E4%BC%A0%E8%AE%B0%E5%BD%95
     */
    public CompleteMultipartUploadPretreatment completeMultipartUpload(FileInfo fileInfo) {
        CompleteMultipartUploadPretreatment pre = new CompleteMultipartUploadPretreatment();
        pre.setFileStorageService(self);
        pre.setFileInfo(fileInfo);
        return pre;
    }

    /**
     * 手动分片上传-取消
     */
    public AbortMultipartUploadPretreatment abortMultipartUpload(FileInfo fileInfo) {
        AbortMultipartUploadPretreatment pre = new AbortMultipartUploadPretreatment();
        pre.setFileStorageService(self);
        pre.setFileInfo(fileInfo);
        return pre;
    }

    /**
     * 手动分片上传-列举已上传的分片
     */
    public ListPartsPretreatment listParts(FileInfo fileInfo) {
        ListPartsPretreatment pre = new ListPartsPretreatment();
        pre.setFileStorageService(self);
        pre.setFileInfo(fileInfo);
        return pre;
    }

    /**
     * 默认使用的存储平台是否支持列举文件
     */
    public ListFilesSupportInfo isSupportListFiles() {
        return self.isSupportListFiles(properties.getDefaultPlatform());
    }

    /**
     * 是否支持列举文件
     */
    public ListFilesSupportInfo isSupportListFiles(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportListFiles(storage);
    }

    /**
     * 是否支持列举文件
     */
    public ListFilesSupportInfo isSupportListFiles(FileStorage fileStorage) {
        if (fileStorage == null) return ListFilesSupportInfo.notSupport();
        return new IsSupportListFilesAspectChain(aspectList, FileStorage::isSupportListFiles).next(fileStorage);
    }

    /**
     * 列举文件
     */
    public ListFilesPretreatment listFiles() {
        ListFilesPretreatment pre = new ListFilesPretreatment();
        pre.setPlatform(properties.getDefaultPlatform());
        pre.setFileStorageService(self);
        return pre;
    }

    /**
     * 获取文件
     */
    public GetFilePretreatment getFile() {
        return new GetFilePretreatment()
                .setPlatform(properties.getDefaultPlatform())
                .setFileStorageService(self);
    }

    /**
     * 获取文件
     * @param fileInfo 文件信息
     * @return 远程文件信息
     */
    public RemoteFileInfo getFile(FileInfo fileInfo) {
        return getFile()
                .setPlatform(fileInfo.getPlatform())
                .setPath(fileInfo.getPath() != null, fileInfo.getPath())
                .setFilename(fileInfo.getFilename() != null, fileInfo.getFilename())
                .setUrl(fileInfo.getUrl() != null, fileInfo.getUrl())
                .getFile();
    }

    /**
     * 获取缩略图文件
     * @param fileInfo 文件信息
     * @return 远程文件信息
     */
    public RemoteFileInfo getThFile(FileInfo fileInfo) {
        return getFile()
                .setPlatform(fileInfo.getPlatform())
                .setPath(fileInfo.getPath() != null, fileInfo.getPath())
                .setFilename(fileInfo.getThFilename() != null, fileInfo.getThFilename())
                .setUrl(fileInfo.getThUrl() != null, fileInfo.getThUrl())
                .getFile();
    }

    /**
     * 创建上传预处理器
     *
     * @param source      源
     * @param name        文件名
     * @param contentType 文件的 MIME 类型
     * @param size        文件大小
     */
    public UploadPretreatment of(Object source, String name, String contentType, Long size) {
        FileWrapper wrapper = self.wrapper(source, name, contentType, size);
        UploadPretreatment up = self.of().setFileWrapper(wrapper);
        // 这里针对 HttpServletRequestFileWrapper 特殊处理，加载读取到的缩略图文件
        if (wrapper instanceof HttpServletRequestFileWrapper) {
            MultipartFormDataReader.MultipartFormData data =
                    ((HttpServletRequestFileWrapper) wrapper).getMultipartFormData();
            if (data.getThFileBytes() != null) {
                FileWrapper thWrapper = self.wrapper(
                        data.getThFileBytes(), data.getThFileOriginalFilename(), data.getThFileContentType());
                up.thumbnailOf(thWrapper);
            }
        }
        return up;
    }

    /**
     * 对要上传的文件进行包装
     *
     * @param source 源
     */
    public FileWrapper wrapper(Object source) {
        return self.wrapper(source, null);
    }

    /**
     * 对要上传的文件进行包装
     *
     * @param source 源
     * @param name   文件名
     */
    public FileWrapper wrapper(Object source, String name) {
        return self.wrapper(source, name, null);
    }

    /**
     * 对要上传的文件进行包装
     *
     * @param source 源
     * @param name   文件名
     */
    public FileWrapper wrapper(Object source, String name, String contentType) {
        return self.wrapper(source, name, contentType, null);
    }

    /**
     * 对要上传的文件进行包装
     *
     * @param source      源
     * @param name        文件名
     * @param contentType 文件的 MIME 类型
     * @param size        文件大小
     */
    public FileWrapper wrapper(Object source, String name, String contentType, Long size) {
        if (source == null) {
            throw new FileStorageRuntimeException("要包装的文件不能是 null");
        }
        try {
            for (FileWrapperAdapter adapter : fileWrapperAdapterList) {
                if (adapter.isSupport(source)) {
                    return adapter.getFileWrapper(source, name, contentType, size);
                }
            }
        } catch (IOException e) {
            throw new FileStorageRuntimeException("文件包装失败", e);
        }
        throw new FileStorageRuntimeException("不支持此文件");
    }

    /**
     * 是否支持同存储平台复制文件
     */
    public boolean isSupportSameCopy() {
        return self.isSupportSameCopy(properties.getDefaultPlatform());
    }

    /**
     * 是否支持同存储平台复制文件
     */
    public boolean isSupportSameCopy(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportSameCopy(storage);
    }

    /**
     * 是否支持同存储平台复制文件
     */
    public boolean isSupportSameCopy(FileStorage fileStorage) {
        if (fileStorage == null) return false;
        return new IsSupportSameCopyAspectChain(aspectList, FileStorage::isSupportSameCopy).next(fileStorage);
    }

    /**
     * 复制文件
     */
    public CopyPretreatment copy(FileInfo fileInfo) {
        return new CopyPretreatment(fileInfo, self)
                .setNotSupportMetadataThrowException(properties.getCopyNotSupportMetadataThrowException())
                .setNotSupportAclThrowException(properties.getCopyNotSupportAclThrowException());
    }

    /**
     * 复制文件
     */
    public CopyPretreatment copy(String url) {
        return self.copy(self.getFileInfoByUrl(url));
    }

    /**
     * 是否支持同存储平台移动文件
     */
    public boolean isSupportSameMove() {
        return self.isSupportSameMove(properties.getDefaultPlatform());
    }

    /**
     * 是否支持同存储平台移动文件
     */
    public boolean isSupportSameMove(String platform) {
        FileStorage storage = self.getFileStorageVerify(platform);
        return self.isSupportSameMove(storage);
    }

    /**
     * 是否支持同存储平台移动文件
     */
    public boolean isSupportSameMove(FileStorage fileStorage) {
        if (fileStorage == null) return false;
        return new IsSupportSameMoveAspectChain(aspectList, FileStorage::isSupportSameMove).next(fileStorage);
    }

    /**
     * 移动文件
     */
    public MovePretreatment move(FileInfo fileInfo) {
        return new MovePretreatment(fileInfo, self)
                .setNotSupportMetadataThrowException(properties.getMoveNotSupportMetadataThrowException())
                .setNotSupportAclThrowException(properties.getMoveNotSupportAclThrowException());
    }

    /**
     * 移动文件
     */
    public MovePretreatment move(String url) {
        return self.move(self.getFileInfoByUrl(url));
    }

    /**
     * 通过反射调用指定存储平台的方法
     * 详情见{@link ReflectUtil#invoke(Object,String,Object...)}
     */
    public <T> T invoke(String platform, String method, Object... args) {
        return self.invoke((FileStorage) self.getFileStorageVerify(platform), method, args);
    }

    /**
     * 通过反射调用指定存储平台的方法
     * 详情见{@link ReflectUtil#invoke(Object,String,Object...)}
     */
    public <T> T invoke(FileStorage platform, String method, Object... args) {
        return new InvokeAspectChain(aspectList, ReflectUtil::invoke).next(platform, method, args);
    }

    public void destroy() {
        for (FileStorage fileStorage : fileStorageList) {
            try {
                fileStorage.close();
                log.info("销毁存储平台 {} 成功", fileStorage.getPlatform());
            } catch (Exception e) {
                log.error("销毁存储平台 {} 失败，{}", fileStorage.getPlatform(), e.getMessage(), e);
            }
        }
    }
}
