前言
最近在开发自己的个人博客,其中涉及有个功能是用户可以发布碎片的,能够上传图片,但是上传后过大,想着能不能进行压缩,然后搜集了一些资料后,一开始选择了缩略图,但是图片本身也得存储,也是占用了很大的空间,客户端在查看时需要加载很久,继续搜集资料,最终得出了自己的解决方案,将图片转为webp格式并结合缩略图一同存储。
成果
极大的优化了存储空间,提升了网页的加载速度,并且图片的质量被没有损失太多。
一张4m的图片上传后压缩的情况
webp 格式只有280Kb 缩略图格式更是只有5kb
至于缩略图的作用,小图展示时采用缩略图的方式,大图时再加载webp格式,可进一步优化用户体验
优化步骤
要转为webp格式以及生成缩略图需要导入一下两个依赖
<!-- 图片压缩-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.14</version>
</dependency>
<!-- webp-imageio 依赖 -->
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>
关键代码
getUniqueFileName的详细代码可看完整接口代码
File.separator等价于 "/"
public Result<?> uploadImg(MultipartFile file,Long userId) {
//用户文件夹
String userPath = "/path/to/upload/dir" + File.separator + userId +File.separator;
String serverUrl = "http://localhost:8080"
// 获取上传文件的名字,生成唯一的名称
String fileName = file.getOriginalFilename();
String uniqueFileName = getUniqueFileName(fileName);
//webp文件名
String webpUrl = uniqueFileName.substring(0, uniqueFileName.lastIndexOf(".")) + ".webp";
try {
Path path = Paths.get(userPath + webpUrl);
// 获取原始文件的编码
BufferedImage image = ImageIO.read(file.getInputStream());
// 创建WebP ImageWriter实例
ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/webp").next();
// 配置编码参数
WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale());
// 设置压缩模式
writeParam.setCompressionMode(WebPWriteParam.MODE_DEFAULT);
// 配置ImageWriter输出
writer.setOutput(new FileImageOutputStream(path.toFile()));
// 进行编码,重新生成新图片
writer.write(null, new IIOImage(image, null, null), writeParam);
// 压缩成缩略图并保存
File thumbnailFile = new File(userPathCurrentMonth+ File.separator + "thumbnail_" + uniqueFileName);
Thumbnails.of(path.toFile())
.size(320, 180)
.toFile(thumbnailFile);
// 可访问的路径
String userPathCurrentMonthUrl = serverUrl + "/files" + File.separator + userId +File.separator
// 生成文件的URL
String fileUrl = userPathCurrentMonthUrl+ webpUrl;
// 生成缩略图的URL
String thumbnailUrl = userPathCurrentMonthUrl + "thumbnail_" + uniqueFileName;
return Result.success();
} catch (Exception e) {
return Result.error("上传文件失败");
}
}
webConfig配置
用于配置Web应用程序的资源处理,使程序可以访问特定目录下的文件
package com.llpy.textservice.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* web配置
*
* @author llpy
* @date 2024/07/04
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String uploadDir = "/path/to/upload/dir";
registry.addResourceHandler("/files/**")
.addResourceLocations("file:" + uploadDir + "/");
}
}
完整接口代码
我这里是去限制了每个用户的空间大小,根据不同的用户权限限制用户的存储空间,并且我是部署在服务器上的,我的路径以及访问链接都通过配置文件进行读取
@Service
public class ImagesServiceImpl implements ImagesService {
@Value("${file.upload-dir}")
private String uploadDir;
@Value("${server.url}")
private String serverUrl;
@Autowired
private UserService userService;
@Autowired
private PhotoWallMapper photoWallMapper;
@Override
public Result<?> uploadImg(MultipartFile file,Long userId) {
if (file.isEmpty()) {
return Result.error("文件为空");
}
//如果不是图片,不给上传
if (!Objects.requireNonNull(file.getContentType()).startsWith("image")) {
return Result.error("文件格式不正确");
}
//获取用户大小限制
UserDto2 user = userService.getUser(userId);
long maxSize = 0L;
for (CommonEnum value : CommonEnum.values()) {
if(value.getKey().equals(user.getRoleName())){
maxSize = value.getMaxSize();
}
}
//用户文件夹
String userPath = uploadDir + File.separator + userId +File.separator;
// 获取当前日期并格式化为 "yyyy-MM" 格式
String folderByMonth = new SimpleDateFormat("yyyy-MM").format(new Date());
// 用户当前月份的文件夹
String userPathCurrentMonth =userPath+folderByMonth;
// 获取上传文件的名字,生成唯一的名称
String fileName = file.getOriginalFilename();
String uniqueFileName = getUniqueFileName(fileName);
//webp文件名
String webpUrl = uniqueFileName.substring(0, uniqueFileName.lastIndexOf(".")) + ".webp";
try {
Path path = Paths.get(userPathCurrentMonth + File.separator + webpUrl);
// 检查用户文件夹大小
Path userFolder = Paths.get(userPath);
long folderSize = getFolderSize(userFolder);
if (folderSize + file.getSize() > maxSize) {
return Result.error("您的剩余空间" + (maxSize - folderSize) / 1024 / 1024 +"MB,不足以上传该文件,请联系管理员扩容空间");
}
// 确保上传目录存在
Path uploadPath = Paths.get(userPathCurrentMonth);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 获取原始文件的编码
BufferedImage image = ImageIO.read(file.getInputStream());
// 创建WebP ImageWriter实例
ImageWriter writer = ImageIO.getImageWritersByMIMEType("image/webp").next();
// 配置编码参数
WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale());
// 设置压缩模式
writeParam.setCompressionMode(WebPWriteParam.MODE_DEFAULT);
// 配置ImageWriter输出
writer.setOutput(new FileImageOutputStream(path.toFile()));
// 进行编码,重新生成新图片
writer.write(null, new IIOImage(image, null, null), writeParam);
// 压缩成缩略图并保存
// 生成缩略图
File thumbnailFile = new File(userPathCurrentMonth+ File.separator + "thumbnail_" + uniqueFileName);
Thumbnails.of(path.toFile())
.size(320, 180)
.toFile(thumbnailFile);
// 可访问的路径
String userPathCurrentMonthUrl = serverUrl + "/files" + File.separator + userId +File.separator +folderByMonth + File.separator;
// 生成文件的URL
String fileUrl = userPathCurrentMonthUrl+ webpUrl;
// 生成缩略图的URL
String thumbnailUrl = userPathCurrentMonthUrl + "thumbnail_" + uniqueFileName;
// 保存到数据库
PhotoWall photoWall = new PhotoWall();
photoWall.setUserId(userId).setImgUrl(fileUrl).setThumbnailImgUrl(thumbnailUrl);
photoWallMapper.insert(photoWall);
// 返回文件URL
return Result.success(fileUrl);
} catch (Exception e) {
//发生异常删除上传的图片
File file1 = new File(userPathCurrentMonth + File.separator + uniqueFileName);
if (file1.exists()) {
file1.delete();
}
File file2 = new File(userPathCurrentMonth + File.separator + "thumbnail_" + uniqueFileName);
if (file2.exists()) {
file2.delete();
}
return Result.error("上传文件失败");
}
}
/**
* 获取唯一文件名
*
* @param fileName 文件名
* @return {@code String}
*/
private static String getUniqueFileName(String fileName) {
String fileExtension = "";
// 提取文件扩展名
if (fileName != null && fileName.contains(".")) {
fileExtension = fileName.substring(fileName.lastIndexOf("."));
}
// 生成唯一的文件名
return UUID.randomUUID().toString().replace("-", "") + fileExtension;
}
/**
* 获取文件夹大小
*
* @param folder 文件夹
* @return long
*/
public long getFolderSize(Path folder) {
if (!Files.exists(folder) || !Files.isDirectory(folder)) {
return 0;
}
try {
return Files.walk(folder)
.filter(Files::isRegularFile)
.mapToLong(p -> {
try {
return Files.size(p);
} catch (IOException e) {
return 0L;
}
})
.sum();
} catch (IOException e) {
throw new RuntimeException("获取文件夹大小失败");
}
}
}
总结
在上传图片时,采用将图片转为webp和生成缩略图的格式,可以极大的优化用户体验以及存储空间。