springboot处理上传图片推荐方案
❤️弱水三千,只取一瓢饮❤️
🤞你好啊,我是小酥肉,欢迎阅读本博客👌
阅读前提示:
本文的代码不能直接运行,是说明问题的核心代码,只是一个实例❤️
选择方案的根据
关于前后端交互图片等二进制数据,我们在方案选择上无非要注重的是下面几点:
- 前端怎么传?
- 后端怎么接收?
- 接收要干什么?
- 怎么保存?保存到哪?
所以本文来介绍springboot(java服务端)处理上传图片的推荐方案和具体的实现流程,
推荐方案
前端ajax+springboot的MultipartFile+关系数据库保存图片路径+上传图片到图片服务器
具体解释:
前端使用ajax核心技术,传输数据类型为from-data,通过http协议传输二进制数据(即图片)
后端在特定Restful 端点处用使用 io流将二进制数据进行接收,并封装为MultipartFile对象,并做业务处理(如图片压缩,重命名)
后端将二进制数据上传到图片服务器,本文使用vsftpd(sftp协议)作为传输端口,nginx/oss存储对象服务器作为图片服务器(也就是图片真实存放的位置)。
下面根据一个业务实战说明整个图片的上传流程
业务背景
现在有一个《上传商品》接口,该接口需要完成
- 参数校验
- 业务处理
- 图片上传
- 保存数据库(尤其是图片在图片服务器的url字段)
定义接收dto
我们首先定义一个ItemAddRequest,用于封装前端传来的参数
@Data
public class ItemAddRequest implements Serializable {
private static final long serialVersionUID = -1895470701830743471L;
/**
* 商品名字
*/
private String itemName;
/**
* 商品图片
*/
private MultipartFile[] images;
}
这里的images
就是接收的图片数组,允许上传多图片。
Postman测试图片数组
然后用Postman的body -> form-data格式来进行传输,选择images字段为file类型,然后选择本地的几张图片进行上传
注意:这里最好用json传,因为对于二进制图片的json数据,数据量很大,推荐就是content-type为multipart/form-data。
可以看我写的博客 form-data VS json
然后在controller的@ModelAttribute接收这个form-data类型的数据,并将其封装为ItemAddRequest进行处理:
@RestController
@RequestMapping("/user/item")
@Slf4j
public class ItemController {
@PostMapping("/upload")
@PreAuthorize("@Autho.hasAuthority('sys:user')")
public ResponseResult<Long> addItem(@ModelAttribute ItemAddRequest itemAddRequest) {
//请求参数是否为空
//对参数业务逻辑校验
checkItemService.validCheckItem(checkItem, images);
//上传图片,保存数据库
checkItemService.saveCheckItem(checkItem,images);
//return sth.....
}
}
注意:这里要注意@ModelAttribute和@RequestBody
使用@ModelAttribute注解的实体类接收前端发来的数据格式需要为"x-www-form-urlencoded",@RequestBody注解的实体类接收前端的数据格式为JSON(application/json)格式。(若是使用@ModelAttribute接收application/json格式,虽然不会报错,但是值并不会自动填入)
参数校验
对dto参数进行校验
这里主要说明对图片的检验:验证图片大小,格式,是否符合要求的图片(我这里用到了训练模型视觉识别商品)
private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png", "gif","webp");
public static void validImage(MultipartFile file) {
// 验证文件是否为空
if (file == null|| file.isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR,"图片不能为空");
}
if (file.getSize() >= 20 * 1024 * 1024) {
throw new BusinessException(ErrorCode.PARAMS_ERROR,"图片大小超出最大限制");
}
//验证文件格式符合要求(jpg,jpeg,png,gif)
String originalFilename = file.getOriginalFilename();
if (originalFilename != null && !originalFilename.isEmpty()) {
String extension = getFileExtension(originalFilename);
if (!ALLOWED_EXTENSIONS.contains(extension.toLowerCase())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR,"图片格式不正确");
}
}
// 验证文件内容是否为有效的图片
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(file.getInputStream());
} catch (IOException e) {
throw new fileException(ErrorCode.OPERATION_ERROR,"ImageIo读取图片失败");
}
if(bufferedImage==null){
throw new fileException(ErrorCode.OPERATION_ERROR,"ImageIo读取图片失败");
}
// py视觉校验该图片
if(!isPyReviewOk()){
throw new fileException(ErrorCode.OPERATION_ERROR,"图片识别失败");
}
}
然后在检测商品的封装中加入检测图片的封装
public void validCheckItem(CheckItem checkItem, MultipartFile[] itemImages) {
//对checkItem检验 ....
//图片检测 循环
for(MultipartFile itemImage:itemImages){
ImageUtils.validImage(itemImage);
}
}
图片上传
在图片上传中,我们要将MultipartFile对象转为file对象,然后将file对象通过ftp服务,将file写入到图片服务器中
这里我使用的图片服务器是nginx,ftp服务用的是vsftpd
关于nginx充当图片服务器的博客推荐:nginx搭建图片服务器
关于vsftpd的博客推荐:一文通关vsftpd
这里我使用的java处理ftp的依赖是jsch:关于其他的依赖请参考
java用ftp做数据传输方案
下面是核心代码(参杂了部分业务逻辑,如将图片重命名并转换文件,ftp上传图片):
public boolean saveCheckItem(CheckItem checkItem,MultipartFile[] itemImages) {
ArrayList<String> names = new ArrayList<>();
for(MultipartFile itemImage:itemImages){
//重命名文件
String newImageName = ImageUtils.generateFileName(itemImage.getOriginalFilename(), checkItem.getUserId());
names.add(newImageName);
//转换文件
File imageFile = FtpUtils.convertMultipartFileToFile(itemImage, newImageName);
//上传文件
FtpUtils.SftpUploadToServer(imageFile);
}
//保存数据库
checkItem.setItemImage("http://47.106.211.119:777/images/"+String.join("\\", names));
checkItem.setUserId(checkItem.getUserId());
return this.save(checkItem);
}
下面是ftp工具的SftpUploadToServer实现(最基础的):
public static void SftpUploadToServer(File file) {
//创建一个JSch对象,
JSch jsch = new JSch();
Session session = null;
try {
//获取一个session,使用test用户传输
session = jsch.getSession("test", "47.106.211.119", 22);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword("xsr2004217");
session.connect();
//使用Channel connect
ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
//进入工作目录(这将是图片保存的位置)
channelSftp.cd("/ershou/img_nginx/nginx/html/images");
log.info("成功连接sftp服务器,cd到工作目录");
//写入图片
FileInputStream inputStream = new FileInputStream(file);
channelSftp.put(inputStream, file.getName());
log.info("传输成功!");
inputStream.close();
channelSftp.disconnect();
//因为io流的原理 这里会遗留一个副本到本地,同时删除本地图片
if (file.delete()) {
log.info("删除本地照片副本成功!");
} else {
log.info("失败啦!",new RuntimeException("删除文件失败!"));
}
} catch (JSchException | SftpException | IOException e) {
throw new RuntimeException(e);
} finally {
//关闭资源
if (session != null && session.isConnected()) {
session.disconnect();
}
}
}
- 这种实现是最常见,最朴素**的图片交互方案
- 我们的实现逻辑是:前端ajax+springboot的MultipartFile+关系数据库保存图片路径+上传图片到图片服务器
- 具体到后端:我们做了参数接收,参数校验,业务处理,保存数据库,上传图片等。
总结
通过阅读本博客您将收获下面知识点:
- springboot处理图片推荐方案
- ftp在java的使用
- form-data VS json
- @ModelAttribute和@RequestBody注解区别
❤️弱水三千,只取一瓢饮❤️
🤞我是小酥肉 ,喜欢简单 ,期待您的留言👌
上期文章:RabbitMQ消息队列(二):业务实战订单超时处理