最近公司要写一个文件上传和下载的需求,之前写过很多次了,今天做个记录,希望能帮助到大家
*Controller 代码*
@RestController
@RequestMapping("/upload")
@Api(tags="图片接口")
public class UploadController {
/**
* 批量上传
* @param files
* @return
*/
@ApiOperation("批量上传")
@PostMapping
public Result uploadImage(@RequestParam(value = "文件夹路径",required = false) String folderName,@RequestParam("files") MultipartFile[] files){
return UploadUtil.uploadImage(files,folderName==null?"imgs/":folderName);
}
/**
* 单个上传
* @param file
* @return
*/
@ApiOperation("单个图片上传")
@PostMapping("only")
public Result uploadImageOnly(@RequestParam(value = "文件夹路径",required = false) String folderName,@RequestParam("file")MultipartFile file){
return UploadUtil.uploadImageOnly(file,folderName==null?"imgs/":folderName);
}
}
UploadUtil代码
import io.renren.common.exception.RenException;
import io.renren.common.utils.Result;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
/**
* 文件上传
* @Author ztz
* @Date 2022年12月13日 0013 09:13
* @Version 1.0
*/
@Component
public class UploadUtil {
/**
* 本地目录,因为@Value 无法直接被注入。故采用@PostConstruct方式获取
*/
@Value("${accessFile.location}")
private String location;
private static String staticLocation;
@PostConstruct
public void init() {
staticLocation = location;
}
public static List<String> types = Arrays.asList("PNG", "JPG", "JPEG", "BMP", "GIF", "SVG");
public static List<String> VIDEOTYPES = Arrays.asList("MP4", "MP3");
/**
* 批量上传
*
* @param files
* @return
*/
public static Result uploadImage(@RequestParam("files") MultipartFile[] files, String folderName) {
List<String> urls = new ArrayList<>();
try {
for (MultipartFile multipartFile : files) {
urls.add(getImageUrl(multipartFile, folderName));
}
} catch (Exception e) {
return new Result().error(e.getMessage());
}
return new Result().ok(urls);
}
/**
* 单文件上传
*
* @param file
* @return
*/
public static Result uploadImageOnly(MultipartFile file, String folderName) {
String url = null;
try {
url = getImageUrl(file, folderName);
} catch (Exception e) {
return new Result().error(e.getMessage());
}
return new Result().ok(url);
}
/**
* 音频、视频上传
*
* @param file
* @return
*/
public static Result uploadVideo(MultipartFile file, String folderName) {
String url = null;
try {
url = getVideoUrl(file, folderName);
} catch (Exception e) {
return new Result().error(e.getMessage());
}
return new Result().ok(url);
}
private static String getVideoUrl(MultipartFile file, String folderName) throws Exception {
String reuslt = null;
if (file != null) {
if (!checkFileSize(file, 50, "m")) {
return reuslt;
}
String suffix = FilenameUtils.getExtension(file.getOriginalFilename());
if (file.getInputStream() == null) {
return reuslt;
}
if (!VIDEOTYPES.contains(suffix.toUpperCase())) {
return reuslt;
}
try {
//1. 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID() + "." + FilenameUtils.getExtension(file.getOriginalFilename());
//2. 创建一个目录对象
String folderPath = staticLocation + (fileName != null ? folderName : "");
File dir = new File(folderPath);
//3. 判断当前目录是否存在
if (!dir.exists()) {
//若目录不存在,需要创建
dir.mkdirs();
}
//4. 将临时文件转存到指定位置
file.transferTo(new File(folderPath + fileName));
return fileName;
} catch (Exception e) { // 上传出现问题直接抛出系统异常,不属于业务范围
e.printStackTrace();
throw new RenException("文件上传出错");
}
}
return reuslt;
}
private static String getImageUrl(MultipartFile file, String folderName) throws Exception {
String reuslt = null;
if (file != null) {
if (!checkFileSize(file, 10, "m")) {
return reuslt;
}
String suffix = FilenameUtils.getExtension(file.getOriginalFilename());
String contentType = file.getContentType();
if (!contentType.contains(contentType)) {
return reuslt;
}
if (!types.contains(suffix.toUpperCase())) {
return reuslt;
}
//先把byte转成kb
BigDecimal imgKb = new BigDecimal(file.getSize()).divide(new BigDecimal(1024), 2, BigDecimal.ROUND_UP);
byte[] bytes = PicUtils.compressPicForScale(file.getBytes(), imgKb.multiply(new BigDecimal(0.8)).longValue());
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
try {
//1. 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID() + "." +FilenameUtils.getExtension(file.getOriginalFilename());
//2. 创建一个目录对象
String folderPath = staticLocation + (fileName != null ? folderName : "");
File dir = new File(folderPath);
//3. 判断当前目录是否存在
if (!dir.exists()) {
//若目录不存在,需要创建
dir.mkdirs();
}
//4. 将临时文件转存到指定位置
File codeImgFile = new File(folderPath + fileName);
FileUtils.copyToFile(inputStream, codeImgFile);
return fileName;
} catch (Exception e) { // 上传出现问题直接抛出系统异常,不属于业务范围
e.printStackTrace();
throw new RenException("文件上传出错");
}
}
return reuslt;
}
/**
* 判断文件大小
*
* @param :multipartFile:上传的文件
* @param size: 限制大小
* @param unit:限制单位(B,K,M,G)
* @return boolean:是否大于
*/
public static boolean checkFileSize(MultipartFile multipartFile, int size, String unit) {
long len = multipartFile.getSize();//上传文件的大小, 单位为字节.
//准备接收换算后文件大小的容器
double fileSize = 0;
if ("B".equals(unit.toUpperCase())) {
fileSize = (double) len;
} else if ("K".equals(unit.toUpperCase())) {
fileSize = (double) len / 1024;
} else if ("M".equals(unit.toUpperCase())) {
fileSize = (double) len / 1048576;
} else if ("G".equals(unit.toUpperCase())) {
fileSize = (double) len / 1073741824;
}
//如果上传文件大于限定的容量
if (fileSize > size) {
return false;
}
return true;
}
}
PicUtils代码,网上找的直接拿来用了。找不到原作者见谅。net.coobird.thumbnailator.Thumbnails需要自己maven导入。这里就不帖pom文件了
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/**
* @PROJECT_NAME: water_chivalry
* @AUTHOR: Hanson-Hsc
* @DATE: 2020-07-27 09:08
* @DESCRIPTION: 图片压缩工具
* @VERSION:
*/
public class PicUtils {
//以下是常量,按照阿里代码开发规范,不允许代码中出现魔法值
private static final Logger logger = LoggerFactory.getLogger(PicUtils.class);
private static final Integer ZERO = 0;
private static final Integer ONE_ZERO_TWO_FOUR = 1024;
private static final Integer NINE_ZERO_ZERO = 900;
private static final Integer THREE_TWO_SEVEN_FIVE = 3275;
private static final Integer TWO_ZERO_FOUR_SEVEN = 2047;
private static final Double ZERO_EIGHT_FIVE = 0.85;
private static final Double ZERO_SIX = 0.6;
private static final Double ZERO_FOUR_FOUR = 0.44;
private static final Double ZERO_FOUR = 0.4;
/**
* 根据指定大小压缩图片
*
* @param imageBytes 源图片字节数组
* @param desFileSize 指定图片大小,单位kb
* @return 压缩质量后的图片字节数组
*/
public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize) {
if (imageBytes == null || imageBytes.length <= ZERO || imageBytes.length < desFileSize * ONE_ZERO_TWO_FOUR) {
return imageBytes;
}
long srcSize = imageBytes.length;
double accuracy = getAccuracy(srcSize / ONE_ZERO_TWO_FOUR);
try {
while (imageBytes.length > desFileSize * ONE_ZERO_TWO_FOUR) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(imageBytes.length);
Thumbnails.of(inputStream)
.scale(accuracy) //0-1 float 压缩大小
.outputQuality(accuracy) //0-1 压缩质量
.toOutputStream(outputStream);
imageBytes = outputStream.toByteArray();
}
logger.info("图片原大小={}kb | 压缩后大小={}kb",
srcSize / ONE_ZERO_TWO_FOUR, imageBytes.length / ONE_ZERO_TWO_FOUR);
} catch (Exception e) {
logger.error("【图片压缩】msg=图片压缩失败!", e);
}
return imageBytes;
}
/**
* 自动调节精度(经验数值)
*
* @param size 源图片大小
* @return 图片压缩质量比
*/
private static double getAccuracy(long size) {
double accuracy;
if (size < NINE_ZERO_ZERO) {
accuracy = ZERO_EIGHT_FIVE;
} else if (size < TWO_ZERO_FOUR_SEVEN) {
accuracy = ZERO_SIX;
} else if (size < THREE_TWO_SEVEN_FIVE) {
accuracy = ZERO_FOUR_FOUR;
} else {
accuracy = ZERO_FOUR;
}
return accuracy;
}
如果图片是存在本地的话,想在项目里访问还配置静态资源路径。
WebMvcConfig 代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//匹配url 中的资源映射
@Value("${accessFile.resourceHandler}")
private String resourceHandler;
//上传文件保存的本地目录
@Value("${accessFile.location}")
private String location;
/**
* 配置静态资源映射
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//匹配到resourceHandler,将URL映射至location,也就是本地文件夹
registry.addResourceHandler(resourceHandler).addResourceLocations("file:" + location);
}
/**
* 跨域
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
如果项目里有shiro 的话记得一定要放行。不然会一直报无权限的
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new Oauth2Filter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/imgs/**", "anon");//放行静态资源路径
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}