Java 图片上传与 WebP 转换实现
本代码主要处理图片上传到本地 image 文件夹,限制文件类型和大小,并自动将非 WebP 格式的图片转换为 WebP 格式保存。
1. 项目依赖配置
首先需要在 pom.xml 中添加必要的依赖:
<dependencies>
<!-- WebP图像处理 -->
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>
<!-- Spring Web (如果使用Spring Boot) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Thumbnailator (可选,用于缩略图生成) -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.18</version>
</dependency>
</dependencies>2. Spring Boot配置文件
在 application.properties 中设置文件上传大小限制:
# 设置最大文件上传大小为20MB
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB3. 图片上传与转换服务类
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Locale;
@Service
public class ImageUploadService {
// 允许上传的图片类型
private static final String[] ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "webp"};
// 最大文件大小20MB
private static final long MAX_FILE_SIZE = 20 * 1024 * 1024;
// 上传目录
private static final String UPLOAD_DIR = "image/";
/**
* 上传图片并转换为WebP格式
* @param file 上传的图片文件
* @return 保存后的文件路径
* @throws IOException 文件操作异常
* @throws IllegalArgumentException 文件格式或大小不符合要求
*/
public String uploadAndConvertToWebp(MultipartFile file) throws IOException, IllegalArgumentException {
// 检查文件是否为空
if (file.isEmpty()) {
throw new IllegalArgumentException("上传的文件为空");
}
// 检查文件大小
if (file.getSize() > MAX_FILE_SIZE) {
throw new IllegalArgumentException("文件大小超过20MB限制");
}
// 获取文件扩展名并验证
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename).toLowerCase();
if (!isAllowedExtension(extension)) {
throw new IllegalArgumentException("只允许上传jpg, jpeg, png, webp格式的图片");
}
// 验证文件内容是否确实是图片
if (!isValidImage(file)) {
throw new IllegalArgumentException("文件内容不是有效的图片");
}
// 创建上传目录(如果不存在)
Path uploadPath = Paths.get(UPLOAD_DIR);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 读取原始图片
BufferedImage image = ImageIO.read(file.getInputStream());
if (image == null) {
throw new IllegalArgumentException("无法读取图片内容");
}
// 生成新的文件名(使用原始文件名+webp扩展名)
String fileNameWithoutExt = originalFilename.substring(0, originalFilename.lastIndexOf('.'));
String webpFileName = fileNameWithoutExt + ".webp";
Path webpFilePath = uploadPath.resolve(webpFileName);
// 如果原始格式已经是webp,直接保存
if ("webp".equals(extension)) {
Files.copy(file.getInputStream(), webpFilePath);
} else {
// 转换为webp格式
convertToWebp(image, webpFilePath.toFile());
}
return webpFilePath.toString();
}
/**
* 将图片转换为WebP格式
* @param image 原始图片
* @param outputFile 输出文件
* @throws IOException 文件操作异常
*/
private void convertToWebp(BufferedImage image, File outputFile) throws IOException {
// 获取WebP ImageWriter
Iterator<ImageWriter> writers = ImageIO.getImageWritersByMIMEType("image/webp");
if (!writers.hasNext()) {
throw new IllegalStateException("没有找到WebP格式的ImageWriter");
}
ImageWriter writer = writers.next();
// 配置编码参数
WebPWriteParam writeParam = new WebPWriteParam(Locale.getDefault());
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType(writeParam.getCompressionTypes()[WebPWriteParam.LOSSY_COMPRESSION]);
writeParam.setCompressionQuality(0.8f); // 设置质量为80%
try (FileImageOutputStream output = new FileImageOutputStream(outputFile)) {
writer.setOutput(output);
writer.write(null, new IIOImage(image, null, null), writeParam);
} finally {
writer.dispose();
}
}
/**
* 获取文件扩展名
* @param filename 文件名
* @return 扩展名
*/
private String getFileExtension(String filename) {
int dotIndex = filename.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < filename.length() - 1) {
return filename.substring(dotIndex + 1);
}
return "";
}
/**
* 检查扩展名是否允许
* @param extension 文件扩展名
* @return 是否允许
*/
private boolean isAllowedExtension(String extension) {
for (String allowed : ALLOWED_EXTENSIONS) {
if (allowed.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 验证文件内容是否是有效的图片
* @param file 上传的文件
* @return 是否是有效图片
* @throws IOException 文件操作异常
*/
private boolean isValidImage(MultipartFile file) throws IOException {
try (InputStream input = file.getInputStream()) {
// 读取文件头
byte[] header = new byte[8];
int read = input.read(header);
if (read < 4) {
return false;
}
// 检查文件头是否符合图片格式
String realType = getRealFileType(header);
String extension = getFileExtension(file.getOriginalFilename()).toLowerCase();
return realType.equalsIgnoreCase(extension);
}
}
/**
* 通过魔数验证文件真实类型
* @param fileBytes 文件头字节
* @return 真实文件类型
*/
private String getRealFileType(byte[] fileBytes) {
if (fileBytes == null || fileBytes.length < 4) {
return "unknown";
}
// 读取前4个字节并转换为十六进制
StringBuilder hexBuilder = new StringBuilder();
for (int i = 0; i < Math.min(4, fileBytes.length); i++) {
hexBuilder.append(String.format("%02X", fileBytes[i]));
}
String fileHeader = hexBuilder.toString();
// 检查匹配的魔数
if (fileHeader.startsWith("FFD8FF")) {
return "jpg";
} else if (fileHeader.startsWith("89504E47")) {
return "png";
} else if (fileHeader.startsWith("47494638")) {
return "gif";
} else if (fileHeader.startsWith("424D")) {
return "bmp";
} else if (fileHeader.startsWith("52494646")) { // WEBP: RIFF
return "webp";
}
return "unknown";
}
}4. Spring Boot控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/images")
public class ImageUploadController {
@Autowired
private ImageUploadService imageUploadService;
@PostMapping("/upload")
public ResponseEntity<?> uploadImage(@RequestParam("file") MultipartFile file) {
try {
String filePath = imageUploadService.uploadAndConvertToWebp(file);
return ResponseEntity.ok().body("图片上传成功: " + filePath);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
} catch (IOException e) {
return ResponseEntity.internalServerError().body("文件处理错误: " + e.getMessage());
}
}
}5. 功能说明
这个实现提供了以下功能:
文件类型验证:通过文件扩展名和文件头魔数双重验证,确保上传的是 jpg、jpeg、png 或 webp 格式的图片
文件大小限制:限制上传文件最大为 20MB
自动转换 WebP:如果上传的不是 webp 格式图片,会自动转换为 webp 格式保存
安全性检查:验证文件内容确实是图片,防止伪造文件扩展名的攻击
目录自动创建:如果目标目录不存在会自动创建
6. 使用说明
将代码集成到 Spring Boot 项目中
确保
image/目录有写入权限通过 POST 请求
/api/images/upload上传图片上传成功后,图片会以 webp 格式保存在
image/目录下
7. 扩展建议
缩略图生成:可以结合 Thumbnailator 库生成缩略图
异步处理:对于大文件可以考虑使用异步处理
分布式存储:如果需要,可以将图片保存到云存储服务
更详细的错误处理:可以根据业务需求添加更详细的错误分类和处理
Java 图片上传与 WebP 转换实现
https://uniomo.com/archives/java-tu-pian-shang-chuan-yu-webp-zhuan-huan-shi-xian