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

使用环境 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依赖:
    net.coobird    thumbnailator    0.4.8 工具类:
import net.coobird.thumbnailator.thumbnails;import net.coobird.thumbnailator.geometry.positions;import org.springframework.stereotype.component;import javax.imageio.imageio;import java.io.file;import java.io.ioexception;@componentpublic class pictureutil {    /**     * 水印图片     */    private static file markico = null;    //开机静态加载水印图片    static {        try {            markico = new file(new file().getcanonicalpath() + /icon.png);            logutil.info(pictureutil.class, 水印图片加载 + (markico.exists() ? 成功 : 失败));        } catch (exception e) {        }    }    /**     * 加水印     */    public void photomark(file sourcefile, file tofile) throws ioexception {        thumbnails.of(sourcefile)                .size(600, 450)//尺寸                .watermark(positions.bottom_center/*水印位置:中央靠下*/,                 imageio.read(markico), 0.7f/*质量,越大质量越高(1)*/)                //.outputquality(0.8f)                .tofile(tofile);//保存为哪个文件    }    /**     * 生成图片缩略图     */    public void photosmaller(file sourcefile, file tofile) throws ioexception {        thumbnails.of(sourcefile)                .size(200, 150)//尺寸                //.watermark(positions.center, imageio.read(markico), 0.1f)                .outputquality(0.4f)//缩略图质量                .tofile(tofile);    }    /**     * 生成视频缩略图(这块还没用到呢)     */    public void photosmallerforvedio(file sourcefile, file tofile) throws ioexception {        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线程池,方便易用,只需配置和加注解即可。
import org.springframework.context.annotation.bean;import org.springframework.context.annotation.configuration;import org.springframework.core.task.taskexecutor;import org.springframework.scheduling.annotation.enableasync;import org.springframework.scheduling.concurrent.threadpooltaskexecutor;import java.util.concurrent.threadpoolexecutor;@configuration@enableasyncpublic class poolconfig {    @bean//return new asyncresult(res);    public taskexecutor taskexecutor() {        threadpooltaskexecutor executor = new threadpooltaskexecutor();        executor.initialize();  // 设置核心线程数        executor.setcorepoolsize(4);  // 设置最大线程数        executor.setmaxpoolsize(32); // 设置队列容量        executor.setqueuecapacity(512); // 设置线程活跃时间(秒)        executor.setkeepaliveseconds(60); // 设置默认线程名称        executor.setthreadnameprefix(threadpool-); // 设置拒绝策略        executor.setrejectedexecutionhandler(new threadpoolexecutor.callerrunspolicy()); // 等待所有任务结束后再关闭线程池        executor.setwaitfortaskstocompleteonshutdown(true);        return executor;    }} 避坑知识点:配置springboot线程池,类上需要@configuration、@enableasync这两个注解,实际调用时,需要遵守一个规则,即在调用的方法的类上必须使用注解@enableasync,调用一个带有@async的方法。
比如a类使用了注解@enableasync 在a类中调用b类的有@async的方法,只有这样多线程才生效,a类内调用a类的@async方法不生效。可以理解为controller层使用@enableasync注解,service层方法上标注@async。这样在controller层调用的service方法会从线程池调用线程来执行。
异步逻辑:为什么要用多线程? 我画了一张简单的示意图,在这个项目中,客户端一次上传10多张图片,每个图片单独上传,等待所有图片上传返回200后,继续执行操作,如果一步一步处理,客户端需等待服务器处理完所有逻辑,这样浪费没必要的时间。顾使用异步操作,客户端只需上传图片,无需等待服务器处理(我们服务器很辣鸡,一个10m的图可能要搞10多秒,见笑)
业务代码 @apioperation(上传业务图片)@postmapping(/push/photo/{id}/{name})public r pushhousingphotomethod(        @apiparam(sourceid) @pathvariable integer id,        @apiparam(图片名称不约束,可不填则使用原名,可使用随机码或原名称,但必须带扩展名) @pathvariable(required = false) string name,        @requestparam multipartfile file) throws interruptedexception, executionexception, ioexception {    string filename = file.getoriginalfilename();    string ext = stringutils.substring(filename, filename.lastindexof('.'),filename.length());    file tempphoto = file.createtempfile(uuidutil.make32bituuid(), ext);    file.transferto(tempphoto);//转储临时文件    service.pushphoto(id, name, tempphoto);    return new r();} 业务代码里隐藏了一些项目相关的信息,就是某些名改了,嗯。
可以看到,使用stringutils.substring(filename, filename.lastindexof(’.’),filename.length());这句代码,调用apache.common.lang3工具类获取出了扩展名,因为扩展名对图片处理工具类有用,他通过扩展名识别图片格式,所以这个必须有,如代码,生成了一个使用随机码命名,但带有.png扩展名的临时文件,保存在默认临时路径以供处理。file.createtempfile(uuidutil.make32bituuid(), ext);是生成临时文件的方法,uuidutil也很简单,我贴出来吧,省着还要找
注意:controller类上需要标注注解@enableasync
/** * 生成一个32位无横杠的uuid */public synchronized static string make32bituuid(){    return uuid.randomuuid().tostring().replace(-,);} 避坑知识点:spring使用multipartfile接收文件,但不能直接把multipartfile传下去处理,而是保存为临时文件,并不是多此一举。因为multipartfile也是临时文件,他的销毁时间是你这个controller层方法return的时候。
如果不使用异步,是可以在调用的方法里去处理multipartfile文件的,但如果使用异步处理,肯定是这边线程还没处理完,那边controller层已经return了,这个multipartfile就被删除了,于是你的异步线程就找不到这张图了。那还处理个啥,对吧。所以需要手动保存为自己创建的临时文件,再在线程中处理完把他删掉。
贴service层impl实现类代码
@asyncpublic void pushhousingphoto(integer id,string name,file file) throws interruptedexception, executionexception, ioexception {    //存储fdfs表id    long starttime = system.currenttimemillis();    integer[] numb = fastdfsservice.uploadphoto(stringutils.isblank(name) ? file.getname() : name, file).get();    sourcephotoscontext context = new sourcephotoscontext();    context.setsourceid(id);    context.setnumber(numb[0]);    context.setnumber2(numb[1]);    //保存图片关系    sourcephotoscontextservice.insertnew(context);    long endtime = system.currenttimemillis();    logutil.info(this.getclass(),source [ +id+ ] 绑定图片 [ +name+ ] 成功,内部处理耗时 [+ (endtime-starttime) +ms ]);    //return new r();} 这里的number和number2分别是带水印的原图和缩略图,context是个表,用来存图片和缩略图对应fdfs路径的,就不贴了。可见这个方法上带有注解@async 所以整个方法会异步执行。
加水印处理写到fdfs的service里了,这样不算规范,可以不要学我:
@overridepublic future uploadphoto(string filename, multipartfile file) throws ioexception {    string ext = stringutils.substring(filename, filename.lastindexof('.'));    //创建临时文件    file sourcephoto = file.createtempfile(uuidutil.make32bituuid(), ext);    file.transferto(sourcephoto);    return uploadphoto(filename, sourcephoto);}@overridepublic future uploadphoto(string filename, file sourcephoto) throws ioexception {    string ext = stringutils.substring(filename, filename.lastindexof('.'));    //创建临时文件    file markedphoto = file.createtempfile(uuidutil.make32bituuid(), ext);    file smallerphoto = file.createtempfile(uuidutil.make32bituuid(), ext);    //加水印 缩图    pictureutil.photomark(sourcephoto, markedphoto);    pictureutil.photosmaller(markedphoto, smallerphoto);    //上传    integer markedphotonumber = uploadphotoctrl(filename, markedphoto);    integer smallerphotonumber = uploadphotoctrl(mini_ + filename, smallerphoto);    //删除临时文件    sourcephoto.delete();    markedphoto.delete();    smallerphoto.delete();    integer[] res = new integer[]{markedphotonumber, smallerphotonumber};    return new asyncresult(res);} 使用了方法重载,一个调用了另一个,方便以后处理multipartfile和file格式的图片都能使用,可以见到使用了future这个东西作为返回值,完全可以不这么做,正常返回就行。我懒得改了,这也是不断探索多线程处理图片的过程中,遗留下来的东西。
在service中fastdfsservice.uploadphoto(stringutils.isblank(name) ? file.getname() : name, file).get()这句就是得到了这个future的内容,可以去掉.get() 和future。可见这一个小小的异步功能,其实走过了很多弯路。future其实是异步调用方法时,从.get()等待异步处理的结果,等待得到结果后获取内容并执行。现在使用spring线程池处理,已经不需要这样做了。
以上,希望你在实现这个功能时可以少走弯路。
附总体示意图:


Aflac与Sproutel合作推出社交鸭子机器人 旨在帮助孩子们面对癌症
lm317充电器电路图
全球晶圆制造行业格局或将大洗牌
骁龙670改名骁龙710:8核10nm工艺
物联网实施成功的重点是什么
Spring多线程异步上传图片、处理水印、缩略图
便携式土壤水分检测仪的性能及适用范围介绍
华为p10会和小米5C一样没有了吗?那小米平板3还有吗?
多芯4芯航空插头
农田灌溉预付费电能表的应用分析
Flash存储器Am29F040结构分析
栅极驱动器是什么?罗姆有哪些产品
关于人脸识别技术保障人脸信息安全的建议
申通回应用户信息泄露事件:已组织小组调查
故障电弧断路器的工作方式和工作原理的介绍
基于ARM920T和Linux的SOHO路由器设计
硅单向开关(SUS)的特点及检测
Mentor Graphics Veloce功耗分析入选 EDN“2015 年百款热销产品”
消息称Nokia6.3或于2020年底前上市
群创关闭上海和南京模组厂并转战印度,面临大裁员压力