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

2023-11-15

使用环境

  • SpringBoot+FastDfs+thumbnailator

  • fdfs环境自己搞吧

thumbnailator

maven依赖:

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

工具类:

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;

@Component
public 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文件的依赖等等,查了半天也没弄明白,很麻烦,这个方便。

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

线程池

使用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
@EnableAsync
public 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方法会从线程池调用线程来执行。

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

bc8fbf37ae5179484cef4296fc0a6c96.png

我画了一张简单的示意图,在这个项目中,客户端一次上传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实现类代码

@Async
public 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里了,这样不算规范,可以不要学我:

@Override
public Future<Integer[]> 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);
}

@Override
public Future<Integer[]> 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<Integer[]>这个东西作为返回值,完全可以不这么做,正常返回就行。我懒得改了,这也是不断探索多线程处理图片的过程中,遗留下来的东西。

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

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

附总体示意图:

aaeaddd9f53aec864a2bc2285df5cddd.png

来源:blog.csdn.net/weixin_43074462/article/

details/101109369

推荐

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 多线程异步上传图片、处理水印、缩略图 的相关文章

  • Selector.close() 是否关闭所有客户端套接字?

    我是 nio 套接字的新手 我已经使用 nio 套接字编写了一个服务器 现在我正在尝试编写关闭钩子以确保通过清理资源正常退出 我的问题是Selector close 方法关闭所有客户端套接字 如果没有 请告诉我如何访问所有客户端套接字 而无
  • Jenkins 未显示 Maven 编译器错误

    在 Jenkins 中构建多模块 maven 3 项目时 如果出现构建错误 我们会收到一条神秘消息 表明 Maven 编译器插件失败 这在上周才刚刚开始发生 INFO BUILD FAILURE INFO INFO Total time 1
  • 当前平台不支持桌面 API

    我遇到过这个错误 java lang UnsupportedOperationException 当前平台不支持桌面 API 我将从我的 java 应用程序中打开一个文件 我用这个方法 Desktop getDesktop open new
  • 用于制作代码编辑器的 JavaFX 相当于 JSyntaxPane 的什么?

    以前在 Swing 中 我使用过JSyntaxPane用于制作一个小型 Java 源代码编辑器 为了练习 我决定用 JavaFX 重做整个项目并添加对更多语言的支持 最好是尽可能多 不过好像没有什么类似的JSyntaxPane 一些研究让我
  • 使用正则表达式验证电子邮件的最大长度

    我找到了用于电子邮件验证的正则表达式 a z0 9 a z0 9 a z0 9 a z0 9 a z 2 4 我希望电子邮件的最大长度为 20 个字符 因此我将其更改为 a z0 9 a z0 9 a z0 9 a z0 9 a z 2 4
  • 是否可以创建 Java RAM 磁盘以与 java.io.* API 一起使用?

    我正在使用一个第三方库 它基本上创建一个输出目录 其中包含不同类型的文件和子目录 我希望能够编写单元测试来确认输出正确 我希望能够将库与 RAM 磁盘一起使用 这样库所做的任何事情都不会以任何方式接触实际的磁盘板 这个想法是让测试运行和清理
  • 生成一定长度的所有排列

    假设我们有一个字母表 abcdefghiklimnop 如何以有效的方式以五个一组的形式重复该字母表来递归生成排列 几天来我一直在为此苦苦挣扎 任何反馈都会有帮助 本质上这与 生成给定字符串的所有排列 https stackoverflow
  • 我的 Kafka 流应用程序刚刚退出,代码为 0,什么也不做

    为了尝试 Kafka 流 我这样做了 public static void main String args final StreamsBuilder builder new StreamsBuilder final Properties
  • @PrePersist 在 Hibernate + Spring 环境中不能与 @MappedSuperclass 一起使用

    我正在使用专用存储库创建 注册新用户 Service public class RegistrationService Autowired private AppUserRepository appUserRepository Transa
  • 扩展多个类

    我知道 Java 不支持多重继承 因为不允许扩展多个类 我只是想知道我的问题是否有解决方法 我有一个名为CustomAction需要扩展两个抽象类 BaseAction and QuoteBaseAction 我无法更改这些抽象类中的任何一
  • 为休息服务实施 JUnit 测试

    我必须为我的休息服务实现一些 JUnit 测试 例如 这是我的休息服务之一 Path dni fe public class HelloWorld POST Path home Consumes MediaType APPLICATION
  • BigDecimal汇总统计

    我有一个 BigDecimal 列表 List
  • 在约束验证器中使用 Guice 进行依赖注入

    我有一个在 ConstraintValidator 的实现中注入类的用例 我正在使用 Google guice 进行依赖项注入 目前无法在验证器内注入 我的场景的简化形式 内部模块 Provides Singleton public Ser
  • 在 Eclipse RCP 应用程序中禁用插件贡献

    我经常遇到这个问题 但尚未找到解决方案 每当我编写一个新的基于 Eclipse RCP 的应用程序并包含来自 Eclipse 平台的插件时 我都会 继承 其中一些插件的 UI 贡献 大多数贡献 菜单项 键盘快捷键 属性页 都很有用 但有时我
  • Hybris:如何在impex中导入zip文件中的媒体?

    我知道我们可以导入未像这样压缩的图像 siteResource jar com project initialdata constants ProjectInitialDataConstants projectinitialdata imp
  • 如何在不使用 -cp 开关的情况下在 Groovy 中自动加载数据库 jar?

    我想简化调用 Oracle 数据库的 Groovy 脚本的执行 如何将 ojdbc jar 添加到默认类路径以便我可以运行 groovy RunScript groovy 代替 groovy cp ojdbc5 jar RunScript
  • ebean 映射到 BYTEA 的数据类型是什么?

    我有一个游戏 2 0 2 需要在数据库中存储一些文件的应用程序 我们使用 Ebean 作为 ORM 我相信我的数据库中需要一个 BYTEA 列来存储该文件 但我不确定在我的模型中使用什么数据类型 我应该使用某种Blob 或者只是一个byte
  • 使用 Hibernate Envers 的复合表

    我有一个带有复合表的应用程序 其中包含一个额外的列 一切正常 直到我们添加 Hibernate Envers Audited org hibernate MappingException 无法读取 no pack response Resp
  • FragmentMap + ActionBar 选项卡

    我一直在尝试插入一个MapView进入一个ActionBar Tab 但我什至无法解决问题 即使谷歌搜索 这是主要活动 Override public void onCreate Bundle savedInstanceState supe
  • MyBatis 枚举的使用

    我知道以前有人问过这个问题 但我无法根据迄今为止找到的信息实施解决方案 所以也许有人可以向我解释一下 我有一个表 状态 它有两列 id 和 name id是PK 我不想使用 POJO Status 而是使用枚举 我创建了这样一个枚举 如下所

随机推荐

  • 数据结构--图的应用--最短路径

    最短路径 两种常见的最短路径问题 一 单源最短路径 用Dijistra迪杰斯特拉 算法 二 所有顶点间的最短路径 用Floyd 弗洛伊德 算法 Dijistra算法 1 初始化 先找出从源点v0到各终点Vk的的直达路径 V0 Vk 即通过一
  • 【element-plus】icon在菜单的使用(二)

    前言 之前觉得升级的icon不好使用是以为不能动态的设置图标 现在发现我错了 升级后的icon同样能满足之前的需求 本篇主要说明一下配置菜单时如何使用icon 一 下载依赖包 因为动态引入的icon随时都会调整 所以之前引入所有的icon最
  • 【满分】【华为OD机试真题2023B卷 JAVA&JS】VLAN资源池

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 VLAN资源池 知识点数组字符串 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 VLAN是一种对局域网设备进行逻辑划分的技术 为了标识不同的VLAN 引入VLAN I
  • C++字符串 处理函数合集

    本博客的内容源自 数据结构 用面向对象方法与C 描述 第二版 的内容 章节4 4 在阅读原文的情况下整理得出的读书笔记 均写在代码的注释中 供大家参考 include
  • HDF5 windows编译 release版本、Debug版本

    由于最近急需的一个项目 需要hdf5库 误打误撞 编译成功 特此记录 1 下载源代码 官网下载地址 https portal hdfgroup org display support HDF5 1 12 2 files 找到如下地址 本人电
  • 设计模式 -- Adapter 适配器模式

    转自 http blog csdn net joyney article details 4000818 以下内容是我和小组的兄弟学习设计模式我做的课件 我整理出来和大家分享 有不妥之处敬请指出 上次做的桥接模式 Bridge 这次是适配器
  • 小程序wxs使用教程

    1 什么是wxs wxs是小程序的一种脚本语言 它类似JavaScript 但是与JavaScript有所不同 wxs是一种数据绑定和逻辑处理的语言 它可以在wxml中使用 并且可以在wxml中直接调用 使用wxs可以实现更高效 更灵活的数
  • Games101,Lecture 13(光线与物体求交,引入包围盒)

    光线追踪 光线追踪更多的用于离线应用 因为一帧一般需要一万个CPU小时 原理 由摄像机发出感应光线 判断与物体相交的点是否可以与 光源连线 无遮挡物 如何判断光线与物体有交点 是管线追踪中较难的部分 光线与物体求交 1 与隐式表示的曲面求交
  • RabbitMQ 端口详解

    4369 epmd 25672 Erlang distribution Epmd 是 Erlang Port Mapper Daemon 的缩写 在 Erlang 集群中相当于 dns 的作用 绑定在4369端口上 5672 5671 AM
  • c++ 定时器_「STM32」定时器中断实验

    在上一篇文章中 STM32 定时器概述 中 定时器可以被人为分为4个大部分 时钟发生器 实际模块 输入捕获 输出比较 而定时器中断实验 我们更多的在操作时钟发射器这个模块 这个应用也是定时器最基本的应用了 时钟选择 内部时钟选择 时钟计算方
  • SpringBoot使用@JsonDeserialize和@JsonSerialize注解的功能简介说明

    转自 SpringBoot使用 JsonDeserialize和 JsonSerialize注解的功能简介说明 下文笔者讲述 JsonDeserialize和 JsonSerialize注解的功能简介说明 SpringMVC 在Spring
  • 【SQL基础】【关键字大写】条件查询:比较、不等于、IN、为空、BETWEEN

    概述 1 内容介绍 条件查询 比较 不等于 IN 为空 BETWEEN 2 建表语句 drop table if exists user profile CREATE TABLE user profile id int NOT NULL d
  • 使用 vite 代替 webpack 搭建 react 前端开发环境

    说明 在大型前端项目中 我们一般会使用 webpack Rollup 等工具进行模块整合 但是庞大的代码量会使得我们在开发阶段花费更多的时间在 代码改动 gt 页面渲染 这个阶段 即使使用 HMR 这个问题也没有完全的解决 项目代码量达到一
  • plsql链接服务器无响应,sqlplus 连接数据库无响应

    一批三台安装服务器 先后出现了sqlplus 连接数据库无响应问题 1 因为几乎同一时间出现问题 起初怀疑是网络组对网络有整体调整 后经过确认网络组有调整 但不影响我们的服务器 2 telnet server 1521端口正常响应 3 从终
  • 深度解析,抖音对口型唱歌类短视频内容制作流程,步骤技巧分享

    就像之前分享的信息差案例一样 任何时候都有信息差 但是还有一种叫认知差 就是认知高的人赚认知低的人的钱 不是有句话很火吗 你永远也赚不到认知以外的钱 更多精彩干货请关注共众号 萤火宠 你的认知很高 可以高客单价赚高认知人群的钱 但是也有些人
  • 图形学-改进的Bresenham算法

    图形学 改进的Bresenham算法 原理 代码 原理 虽然中点Bresenham算法是一种效率很高的算法 但也还有改进的余地 当然 其基本原理仍是每次在最大位移方向上走一步 而另一个方向上走还是不走取决于误差项的判断 根据中点Bresen
  • JSP和JavaBean

    8 JSP 8 1 什么是JSP Java Servlet Pages java服务器端页面 也和Servlet一样 用于实现动态Web技术 最大特点 写JSP就像是写HTML 区别 HTML只给用户提供静态的数据 JSP页面中可以嵌入Ja
  • 智慧政务行业发展报告

    转自微信公众号 智慧城市圈子邱文斌 一 智慧政务行业发展状况 从上个世纪90年代开始 政府信息化的建设就开始围绕 通 进行 而现今正逐渐过渡到 云 的建设 从网络的连通 数据的整合 到云的出现与整合 政府信息化的建设是一个漫长而又快速发展的
  • element-ui 实现多日期选择

    一 前端代码
  • Spring 多线程异步上传图片、处理水印、缩略图

    使用环境 SpringBoot FastDfs thumbnailator fdfs环境自己搞吧 thumbnailator maven依赖