Skip to content

文件上传

项目地址:misscmszb项目下的fileUploadDev分支

该代码是将lin-cms中springBoot版本中的文件上传功能抽取出来,单独放一个项目

核心代码:

Uploader.java接口里封装上传方法

java

/**
 * 文件上传服务接口
 */
public interface Uploader {
    /**
     * 上传文件
     * @param fileMap 文件map
     * @return 文件数据
     */
    List<FileObj> upload(MultiValueMap<String, MultipartFile> fileMap);

    List<FileObj> upload(MultiValueMap<String, MultipartFile> fileMap, UploadHandler preHandler);
}

AbstractUploader.java类实现Uploader里的接口

这是一个抽象类,其中有四个抽象方法,在LocalUploader.java里实现

java
package com.zb.misscmszb.module.file;

import com.zb.misscmszb.exception.*;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 文件上传类的基类
 * 模版模式
 */
public abstract class AbstractUploader implements Uploader{
    // 预处理器
    private UploadHandler uploadHandler;

    /**
     * 获取文件上传配置
     */
    protected abstract FileConfiguration getFileConfiguration();

    /**
     * 获取文件保存路径
     * @param newFilename 文件新名称
     * @return 文件保存路径
     */
    protected abstract String getStorePath(String newFilename);

    /**
     * 返回文件存储位置类型
     */
    protected abstract String getFileType();

    /**
     * 处理单个文件
     * @param bytes 文件的字节数
     * @param newFilename 文件的新名称
     * @return 是否处理完成
     */
    protected abstract boolean handleOneFile(byte[] bytes, String newFilename);

    /**
     * 上传文件
     *
     * @param fileMap 文件map
     * @return 文件数据
     */
    @Override
    public List<FileObj> upload(MultiValueMap<String, MultipartFile> fileMap) {
        // 校验上传文件是否为空和数量
        checkFileMap(fileMap);
        return handleMultipartFiles(fileMap);
    }

    @Override
    public List<FileObj> upload(MultiValueMap<String, MultipartFile> fileMap, UploadHandler uploadHandler) {
        this.uploadHandler = uploadHandler;
        return upload(fileMap);
    }

    private void checkFileMap(MultiValueMap<String, MultipartFile> fileMap) {
        if (fileMap.isEmpty()) {
            throw new NotFoundException(10026,  "file not found");
        }
        int nums = getFileConfiguration().getNums();
        if (fileMap.size() > nums) {
            throw new FileTooManyException(10180, "too many files, amount of files must less than" + nums);
        }
    }

    private List<FileObj> handleMultipartFiles(MultiValueMap<String, MultipartFile> fileMap) {
        long singleFileLimit = getSingleFileLimit();
        List<FileObj> fileList = new ArrayList<>();
        fileMap.keySet().forEach(key -> fileMap.get(key).forEach(file -> {
                    if (!file.isEmpty()) {
                        handleFile(fileList, singleFileLimit, file);
                    }
        }));
        return fileList;
    }

    /**
     * 初始化存储文件夹
     * @param fileDataList 文件列表
     * @param singleFileLimit 单个文件的最大值
     * @param file 上传的文件
     */
    private void handleFile(List<FileObj> fileDataList, long singleFileLimit, MultipartFile file) {
        byte[] bytes = getFileBytes(file);
        String[] include = getFileConfiguration().getInclude();
        String[] exclude = getFileConfiguration().getExclude();
        // 获取文件的后缀名
        String ext = checkOneFile(include, exclude, singleFileLimit, file.getOriginalFilename(), bytes.length);
        // 生成新的文件名
        String newFileName = getNewFilename(ext);
        // 获取文件保存路径并生成文件夹
        String storePath = getStorePath(newFileName);
        // 生成文件的md5值
        String md5 = FileUtil.getFileMD5(bytes);
        FileObj fileData = FileObj.builder().
                name(newFileName).
                md5(md5).
                key(file.getName()).
                path(storePath).
                size(bytes.length).
                type(getFileType()).
                extension(ext).
                build();
        // 如果预处理器不为空,且处理结果为false,直接返回, 否则处理
        if (uploadHandler != null && !uploadHandler.preHandle(fileData)) {
            return;
        }
        boolean ok = handleOneFile(bytes, newFileName);
        if (ok) {
            fileDataList.add(fileData);
            // 上传到本地或云上成功之后,调用afterHandle
            if (uploadHandler != null) {
                uploadHandler.afterHandle(fileData);
            }
        }
    }

    // 获取单个上传文件的大小
    private long getSingleFileLimit() {
        String singleLimit = getFileConfiguration().getSingleLimit();
        return FileUtil.parseSize(singleLimit);
    }

    /**
     * 获取上传文件的字节数
     * @param file 上传文件
     * @return 上传文件的字节数
     */
    private byte[] getFileBytes(MultipartFile file) {
        byte[] bytes;
        try {
            bytes = file.getBytes();
        } catch (Exception e) {
            throw new FailedException(10190, "read file date failed");
        }
        return bytes;
    }

    /**
     * 校验单个文件
     * @param include 支持的类型
     * @param exclude 不支持的类型
     * @param singleFileLimit 单个文件大小限制
     * @param originName 文件原始名称
     * @param length 文件大小
     * @return 文件的扩展名,例如: .jpg
     */
    private String checkOneFile(String[] include, String[] exclude, long singleFileLimit, String originName, int length) {
        String ext = FileUtil.getFileExt(originName);
        // 检测后缀名
        if (!this.checkExt(include, exclude, ext)) {
            throw new FileExtensionException(ext + "文件类型不支持");
        }
        // 检测单个文件的大小
        if (length > singleFileLimit) {
            throw new FileTooLargeException(originName + "文件不能超过" + singleFileLimit);
        }
        return ext;
    }

    /**
     * 检查文件的后缀名是否在include或者exclude
     * @param include 支持的类型
     * @param exclude 不支持的类型
     * @param ext 后缀名
     * @return 是否通过
     */
    private boolean checkExt(String[] include, String[] exclude, String ext) {
        int inLen = include == null ? 0 : include.length;
        int exLen = exclude == null ? 0 : exclude.length;
        // 如果两者都有,取include
        if (inLen > 0 && exLen > 0) {
            return this.findInInclude(include, ext);
        } else if (inLen > 0) {
            // 有include,无exclude
            return this.findInInclude(include, ext);
        } else if (exLen > 0) {
            // 有exclude,无include
            return !this.findInInclude(exclude, ext);
        } else {
            // 二者都没有
            return true;
        }
    }

    private boolean findInInclude(String[] include, String ext) {
        for (String s : include) {
            if (s.equals(ext)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 生成新的文件名
     * @param ext 文件后缀名
     * @return 新的文件名
     */
    private String getNewFilename(String ext) {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        return uuid + ext;
    }
}

LocalUploader.java实现文件上传到本地

java
package com.zb.misscmszb.extension.file;

import com.zb.misscmszb.module.file.AbstractUploader;
import com.zb.misscmszb.module.file.FileConfiguration;
import com.zb.misscmszb.module.file.FileConstant;
import com.zb.misscmszb.module.file.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 文件上传到本地
 */
@Slf4j
public class LocalUploader extends AbstractUploader {

    @Autowired
    private FileConfiguration fileConfiguration;

    /**
     * 获取文件上传配置
     */
    @Override
    protected FileConfiguration getFileConfiguration() {
        return fileConfiguration;
    }

    /**
     * 获取文件保存路径
     *
     * @param newFilename 文件新名称
     * @return 文件保存路径
     */
    @Override
    protected String getStorePath(String newFilename) {
        Date date = new Date();
        String formattedDate = new SimpleDateFormat("yyyy/MM/dd").format(date);
        Path path = Paths.get(fileConfiguration.getStoreDir(), formattedDate).toAbsolutePath();
        java.io.File file = new File(path.toString());
        if (!file.exists()) {
            file.mkdirs();
        }
        return Paths.get(formattedDate, newFilename).toString();
    }

    /**
     * 返回文件存储位置类型
     */
    @Override
    protected String getFileType() {
        return FileConstant.LOCAL;
    }

    /**
     * 处理单个文件
     *
     * @param bytes       文件的字节数
     * @param newFilename 文件的新名称
     * @return 是否处理完成
     */
    @Override
    protected boolean handleOneFile(byte[] bytes, String newFilename) {
        // 获取绝对路径
        String absolutePath = FileUtil.getFileAbsolutePath(fileConfiguration.getStoreDir(), getStorePath(newFilename));
        try {
            BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(new java.io.File(absolutePath)));
            stream.write(bytes);
            stream.close();
        } catch (Exception e) {
            log.error("write file to local err:", e);
            return false;
        }
        return true;
    }
}

还需要在UploaderConfiguration.java里进行配置

java
package com.zb.misscmszb.extension.file;

import com.zb.misscmszb.module.file.Uploader;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

/**
 * 文件上传配置类
 */
@Configuration(proxyBeanMethods = false)
public class UploaderConfiguration {
    @Bean
    @Order
    @ConditionalOnMissingBean
    public Uploader uploader() {
        return new LocalUploader();
    }
}

如有转载或 CV 的请标注本站原文地址