0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Spring多线程异步上传图片、处理水印、缩略图

jf_ro2CN3Fa 来源:芋道源码 作者:芋道源码 2022-12-12 16:02 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群


使用环境

  • SpringBoot+FastDfs+thumbnailator
  • fdfs环境自己搞吧

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

thumbnailator

maven依赖:

<dependency>
<groupId>net.coobirdgroupId>
<artifactId>thumbnailatorartifactId>
<version>0.4.8version>
dependency>

工具类:

importnet.coobird.thumbnailator.Thumbnails;
importnet.coobird.thumbnailator.geometry.Positions;
importorg.springframework.stereotype.Component;
importjavax.imageio.ImageIO;
importjava.io.File;
importjava.io.IOException;

@Component
publicclassPictureUtil{

/**
*水印图片
*/
privatestaticFilemarkIco=null;

//开机静态加载水印图片
static{
try{
markIco=newFile(newFile("").getCanonicalPath()+"/icon.png");
LogUtil.info(PictureUtil.class,"水印图片加载"+(markIco.exists()?"成功":"失败"));
}catch(Exceptione){
}
}

/**
*加水印
*/
publicvoidphotoMark(FilesourceFile,FiletoFile)throwsIOException{
Thumbnails.of(sourceFile)
.size(600,450)//尺寸
.watermark(Positions.BOTTOM_CENTER/*水印位置:中央靠下*/,
ImageIO.read(markIco),0.7f/*质量,越大质量越高(1)*/)
//.outputQuality(0.8f)
.toFile(toFile);//保存为哪个文件
}

/**
*生成图片缩略图
*/
publicvoidphotoSmaller(FilesourceFile,FiletoFile)throwsIOException{
Thumbnails.of(sourceFile)
.size(200,150)//尺寸
//.watermark(Positions.CENTER,ImageIO.read(markIco),0.1f)
.outputQuality(0.4f)//缩略图质量
.toFile(toFile);
}

/**
*生成视频缩略图(这块还没用到呢)
*/
publicvoidphotoSmallerForVedio(FilesourceFile,FiletoFile)throwsIOException{
Thumbnails.of(sourceFile)
.size(440,340)
.watermark(Positions.BOTTOM_CENTER,ImageIO.read(markIco),0.1f)
.outputQuality(0.8f)
.toFile(toFile);
}
}

这个插件很好用,只需集成调用即可,我记得我还试过另外几个,需要另外在linux下配置.so文件的依赖等等,查了半天也没弄明白,很麻烦,这个方便。

这个插件又很不好用,必须要先调整尺寸,才能加水印,而且调整尺寸简直是负压缩。压了分辨率图片还能变大那种。但是简单嘛,这块不是重点。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

线程池

使用springboot线程池,方便易用,只需配置和加注解即可。

importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.core.task.TaskExecutor;
importorg.springframework.scheduling.annotation.EnableAsync;
importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
importjava.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
publicclassPoolConfig{
@Bean//returnnewAsyncResult<>(res);
publicTaskExecutortaskExecutor(){
ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();
executor.initialize();//设置核心线程数
executor.setCorePoolSize(4);//设置最大线程数
executor.setMaxPoolSize(32);//设置队列容量
executor.setQueueCapacity(512);//设置线程活跃时间(秒)
executor.setKeepAliveSeconds(60);//设置默认线程名称
executor.setThreadNamePrefix("ThreadPool-");//设置拒绝策略
executor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());//等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
returnexecutor;
}
}

避坑知识点:配置springboot线程池,类上需要@Configuration@EnableAsync这两个注解,实际调用时,需要遵守一个规则,即在调用的方法的类上必须使用注解@EnableAsync,调用一个带有@Async的方法。

比如A类使用了注解@EnableAsync 在A类中调用B类的有@Async的方法,只有这样多线程才生效,A类内调用A类的@Async方法不生效。可以理解为Controller层使用@EnableAsync注解,Service层方法上标注@Async。这样在Controller层调用的Service方法会从线程池调用线程来执行。

异步逻辑:为什么要用多线程?

abafc9dc-794f-11ed-8abf-dac502259ad0.png

我画了一张简单的示意图,在这个项目中,客户端一次上传10多张图片,每个图片单独上传,等待所有图片上传返回200后,继续执行操作,如果一步一步处理,客户端需等待服务器处理完所有逻辑,这样浪费没必要的时间。顾使用异步操作,客户端只需上传图片,无需等待服务器处理(我们服务器很辣鸡,一个10M的图可能要搞10多秒,见笑)

业务代码

@ApiOperation("上传业务图片")
@PostMapping("/push/photo/{id}/{name}")
publicRpushHousingPhotoMethod(
@ApiParam("SourceId")@PathVariableIntegerid,
@ApiParam("图片名称不约束,可不填则使用原名,可使用随机码或原名称,但必须带扩展名")@PathVariable(required=false)Stringname,
@RequestParamMultipartFilefile)throwsInterruptedException,ExecutionException,IOException{
StringfileName=file.getOriginalFilename();
Stringext=StringUtils.substring(fileName,fileName.lastIndexOf('.'),fileName.length());
FiletempPhoto=File.createTempFile(UUIDUtil.make32BitUUID(),ext);
file.transferTo(tempPhoto);//转储临时文件
service.pushPhoto(id,name,tempPhoto);
returnnewR();
}

业务代码里隐藏了一些项目相关的信息,就是某些名改了,嗯。

可以看到,使用StringUtils.substring(fileName, fileName.lastIndexOf(’.’),fileName.length());这句代码,调用apache.common.lang3工具类获取出了扩展名,因为扩展名对图片处理工具类有用,他通过扩展名识别图片格式,所以这个必须有,如代码,生成了一个使用随机码命名,但带有.png扩展名的临时文件,保存在默认临时路径以供处理。File.createTempFile(UUIDUtil.make32BitUUID(), ext);是生成临时文件的方法,UUIDUtil也很简单,我贴出来吧,省着还要找

注意:controller类上需要标注注解@EnableAsync

/**
*生成一个32位无横杠的UUID
*/
publicsynchronizedstaticStringmake32BitUUID(){
returnUUID.randomUUID().toString().replace("-","");
}

避坑知识点:Spring使用MultipartFile接收文件,但不能直接把MultipartFile传下去处理,而是保存为临时文件,并不是多此一举。因为MultipartFile也是临时文件,他的销毁时间是你这个Controller层方法return的时候。

如果不使用异步,是可以在调用的方法里去处理MultipartFile文件的,但如果使用异步处理,肯定是这边线程还没处理完,那边Controller层已经return了,这个MultipartFile就被删除了,于是你的异步线程就找不到这张图了。那还处理个啥,对吧。所以需要手动保存为自己创建的临时文件,再在线程中处理完把他删掉。

贴Service层Impl实现类代码

@Async
publicvoidpushHousingPhoto(Integerid,Stringname,Filefile)throwsInterruptedException,ExecutionException,IOException{
//存储FDFS表id
LongstartTime=System.currentTimeMillis();
Integer[]numb=fastDfsService.upLoadPhoto(StringUtils.isBlank(name)?file.getName():name,file).get();
SourcePhotosContextcontext=newSourcePhotosContext();
context.setSourceId(id);
context.setNumber(numb[0]);
context.setNumber2(numb[1]);
//保存图片关系
sourcePhotosContextService.insertNew(context);
LongendTime=System.currentTimeMillis();
LogUtil.info(this.getClass(),"source["+id+"]绑定图片["+name+"]成功,内部处理耗时["+(endTime-startTime)+"ms]");
//returnnewR();
}

这里的number和number2分别是带水印的原图和缩略图,context是个表,用来存图片和缩略图对应fdfs路径的,就不贴了。可见这个方法上带有注解@Async 所以整个方法会异步执行。

加水印处理写到fdfs的service里了,这样不算规范,可以不要学我:

@Override
publicFutureupLoadPhoto(StringfileName,MultipartFilefile)throwsIOException{
Stringext=StringUtils.substring(fileName,fileName.lastIndexOf('.'));
//创建临时文件
FilesourcePhoto=File.createTempFile(UUIDUtil.make32BitUUID(),ext);
file.transferTo(sourcePhoto);
returnupLoadPhoto(fileName,sourcePhoto);
}

@Override
publicFutureupLoadPhoto(StringfileName,FilesourcePhoto)throwsIOException{
Stringext=StringUtils.substring(fileName,fileName.lastIndexOf('.'));
//创建临时文件
FilemarkedPhoto=File.createTempFile(UUIDUtil.make32BitUUID(),ext);
FilesmallerPhoto=File.createTempFile(UUIDUtil.make32BitUUID(),ext);
//加水印缩图
pictureUtil.photoMark(sourcePhoto,markedPhoto);
pictureUtil.photoSmaller(markedPhoto,smallerPhoto);
//上传
IntegermarkedPhotoNumber=upLoadPhotoCtrl(fileName,markedPhoto);
IntegersmallerPhotoNumber=upLoadPhotoCtrl("mini_"+fileName,smallerPhoto);
//删除临时文件
sourcePhoto.delete();
markedPhoto.delete();
smallerPhoto.delete();
Integer[]res=newInteger[]{markedPhotoNumber,smallerPhotoNumber};
returnnewAsyncResult(res);
}

使用了方法重载,一个调用了另一个,方便以后处理MultipartFile和File格式的图片都能使用,可以见到使用了Future这个东西作为返回值,完全可以不这么做,正常返回就行。我懒得改了,这也是不断探索多线程处理图片的过程中,遗留下来的东西。

在service中fastDfsService.upLoadPhoto(StringUtils.isBlank(name) ? file.getName() : name, file).get()这句就是得到了这个future的内容,可以去掉.get()Future<>。可见这一个小小的异步功能,其实走过了很多弯路。future其实是异步调用方法时,从.get()等待异步处理的结果,等待得到结果后获取内容并执行。现在使用spring线程池处理,已经不需要这样做了。

以上,希望你在实现这个功能时可以少走弯路。

附总体示意图:

abd5ea90-794f-11ed-8abf-dac502259ad0.png

审核编辑 :李倩

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 多线程
    +关注

    关注

    0

    文章

    279

    浏览量

    20911
  • spring
    +关注

    关注

    0

    文章

    341

    浏览量

    15770

原文标题:Spring 多线程异步上传图片、处理水印、缩略图

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Linux多线程对比单线程的优势

    :「资源利用率」:通过多线程,可以更有效地利用CPU资源,特别是多核CPU。「并行处理」:线程允许同时执行多个任务,提高程序的执行效率。「简化设计」:使用线程可以简化程序设计,因为
    发表于 12-01 06:11

    商品图片批量上传接口设计与实现

    设计、实现步骤、代码示例、错误处理和性能优化。文章基于Python Flask框架,确保内容真实可靠,适合开发者快速上手。 1. 接口设计概述 一个标准的商品图片批量上传接口应遵循RESTful原则,使用HTTP POST方法。
    的头像 发表于 10-13 15:25 195次阅读

    rt-thread studio 如何进行多线程编译?

    ,使用的是5800h+32g内存+sn550 ssd,开启16线程编译时cpu的占用率也只能到30%,编译完整个工程需要3分钟 感觉多线程编译设置没有生效,有办法提高编译速度吗 rtthread studio版本是 2.2.9
    发表于 10-11 09:16

    多线程与多处理有何区别

    处理也称为进程,进程是一个在自己的内存空间中运行的独立程序。
    的头像 发表于 09-16 14:21 376次阅读

    【HZ-T536开发板免费体验】—— linux创建线程

    的执行任务成为单线程多线程是程序中包含多个执行流,在一个程序中可以同时运行多个不同的线程来执行不同的任务。 多线程提高了CPU的使用卤率。多线程
    发表于 09-01 21:31

    从底层解读labview的TDMS高级异步写入的工作原理

    的内部机制会安全地处理这些并发的写入请求,将它们缓冲并按顺序写入磁盘,确保文件的正确性。所以,从程序设计的角度看,你可以放心地在多线程环境中使用异步写入函数操作同一个文件。 使用异步
    发表于 08-14 17:05

    产品图片上传API接口

    ​ 在电商平台、内容管理系统或移动应用中,产品图片上传API接口是核心功能之一。它允许用户或第三方应用通过HTTP请求将图片文件上传到服务器,实现产品图像的快速添加和管理。本文将逐步介
    的头像 发表于 07-25 14:30 455次阅读
    产品<b class='flag-5'>图片</b><b class='flag-5'>上传</b>API接口

    ocr识别时数据集上传压缩包,上传成功,但不显示图片图片数量仍显示0,为什么?

    ocr识别时数据集上传压缩包,上传成功,但不显示图片图片数量仍显示0
    发表于 07-23 08:11

    多线程的安全注意事项

    多线程安全是指多个线程同时访问或修改共享资源时,能够保证程序的正确性和可靠性。 开发者选择TaskPool或Worker进行多线程开发时,在TaskPool和Worker的工作线程中导
    发表于 06-20 07:49

    TaskPool和Worker的对比分析

    askPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗
    发表于 06-18 06:43

    鸿蒙NEXT上传图片功能PhotoViewPicker核心功能解析

    */ isOriginalPhoto: boolean; } 获取到资源后,通常需要进行以下处理: 使用文件 URI 读取文件内容 进行必要的格式转换(如压缩图片上传到服务器或保存到本地 // 这里以oss
    发表于 06-06 15:00

    鸿蒙开发实现图片上传上传用户头像)

    }) }) 2. 拷贝图片到缓存目录 当前上传应用文件功能,仅支持上传应用缓存文件路径(cacheDir)下的文件。 使用上传下载模块,需声明权限:ohos.permission
    发表于 05-24 23:09

    请问如何在Python中实现多线程与多进程的协作?

    大家好!我最近在开发一个Python项目时,需要同时处理多个任务,且每个任务需要不同的计算资源。我想通过多线程和多进程的组合来实现并发,但遇到了一些问题。 具体来说,我有两个任务,一个是I/O密集型
    发表于 03-11 06:57

    请问rt-thread studio如何进行多线程编译?

    ,使用的是5800h+32g内存+sn550 ssd,开启16线程编译时cpu的占用率也只能到30%,编译完整个工程需要3分钟 感觉多线程编译设置没有生效,有办法提高编译速度吗
    发表于 02-19 08:30

    TFP401作为HDMI的解码芯片,DE信号始终解析不正常是怎么回事?

    ,而且不管选用什么分辨率的输入信号,通过FPGA抓取信号发现,这段高电平的长度均为36个像素时钟周期,且真正的DE信号,也比理应真实的多两个像素时钟周期。 FPGA抓取信号如图(缩略图,与示波器抓取信号一致) 两个放大图,理应抓取到的正确列分辨率为1280,实际为1282 感谢各位了!
    发表于 12-20 07:02