文件上传
项目地址: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();
}
}