介绍
文件夹中有很多图片,需要一个图片的预览功能,所以需要在文件夹上显示最多前九张的九宫格缩略图图片。为什么需要使用异步,因为图片在保存生成缩略图时,需要读取服务器上传的图片,由于读取特别慢,每张图片平均要2秒,所以需要异步来帮助我们实现。
大体思路
通过建立线程池,把所有要生成的缩略图逻辑放入一个方法内,在Controller中调用service方法。方法连接线程池是通过注解@Async(“名字”)注入
实现结果:
调用保存数据接口,生成缩略图操作在线程中慢慢进行,结果直接返回200,方便我们继续进行别的任务
具体代码
需要准备生成图片缩略图的工具类
工具类介绍
拿到九张服务器图片作为参数,服务器是保存有固定的视频和文档类型的默认图标,方便我们对视频和文件类型处理,最后生成的缩略图保存到服务器并返回一张服务器图片路径。
package com.qizhi.itfin.common.util;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* 图片缩略图
*
*/
@Slf4j
@Component
public class ImageUtil {
public static String combineImage(List<String> imageList, String url, String creator, String partnerId, String path,
String floorImage, String fileImage, String videoImage) throws Exception {
// 放入底板图
String floor = floorImage;
imageList.add(0, floor);
String[] Wfiles = {".doc", ".docx", ".pdf", ".txt", ".swf", ".xlsx", ".ppt", "xls"};
String[] Vfiles = {".avi", ".asf", ".asx", ".wmv", ".wmv9", ".flv", ".f4v", ".mkv", ".mov", ".m4v", ".mts",
".3gp", ".mp4", ".mpg", ".mpeg", ".dat", ".ogm", ".vob", ".rm", ".rmvb", ".ts", ".tp", ".ifo", ".nsv"};
List<String> newUrl = new ArrayList<>();
// 创建一个BufferedImage数据
for (String string : imageList) {
String name = string.substring(string.lastIndexOf(".")).toLowerCase();
if (Arrays.asList(Wfiles).contains(name)) {
// 文档
string = fileImage;
newUrl.add(string);
} else if (Arrays.asList(Vfiles).contains(name)) {
// 视频
string = videoImage;
newUrl.add(string);
} else {
newUrl.add(string);
}
}
BufferedImage[] imageArray = new BufferedImage[newUrl.size()];
List<String> localUrl = new ArrayList<>();
for (String imgUrl : newUrl) {
String newImgUrl = imgUrl.replace(url, path);
localUrl.add(newImgUrl);
}
// 初始化
for (int i = 0; i < localUrl.size(); i++) {
System.out.println("第" + i + "个" + "------" + new Date().getSeconds());
imageArray[i] = ImageIO.read(new File((String)localUrl.get(i)));
}
// 用第一张图片作为底图,在他上面继续合成其他图片
Graphics2D graphics2d = imageArray[0].createGraphics();
// 获取第一张图片的width,和height
int width0 = imageArray[0].getWidth();
int height0 = imageArray[0].getHeight();
// 遍历,把剩下的其他图片都画在底图上(也就是从第二张图片开始)
if (imageArray.length == 1) {
graphics2d.drawImage(imageArray[0], 0, 0, width0, height0, null);
} else if (imageArray.length == 2) {
// 获取第一张图片的width,和height
graphics2d.drawImage(imageArray[1], 0, 0, width0, height0, null);
} else {
for (int i = 1; i < imageArray.length; i++) {
if (i <= 3) {
graphics2d.drawImage(imageArray[i], (i - 1) * 210 + i * 8, 8, 210, 210, null);
} else if (i > 3 && i <= 6) {
graphics2d.drawImage(imageArray[i], (i - 4) * 210 + (i - 3) * 8, 226, 210, 210, null);
} else if (i > 6 && i <= 9) {
graphics2d.drawImage(imageArray[i], (i - 7) * 210 + (i - 6) * 8, 442, 210, 210, null);
} else {
System.out.println("最多显示9张");
}
}
}
// 生成md5
String md5;
md5 = MD5Encryption.getEncryption(System.currentTimeMillis() + "");
// 生成新的存储路径
String filePath = File.separator + partnerId + File.separator + creator + File.separator + md5.substring(0, 2)
+ File.separator + md5.substring(2, 4) + File.separator + md5.substring(4, 6) + File.separator
+ md5.substring(6, 8) + File.separator + md5.substring(8, 10) + File.separator + md5.substring(10, 12)
+ File.separator + md5 + ".png";
// 输出画好的图片
File file = new File(path + filePath);
// 判断文件父目录是否存在
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
boolean result = ImageIO.write(imageArray[0], "png", file);
// 释放图形上下文使用的系统资源
graphics2d.dispose();
// 生成图片的绝对引用地址
String fileUrl = StringUtils.replace(filePath, "\\", "/");
String visitUrl = url + fileUrl;
log.info("保存缩略图返回结果" + result + "----本地url---" + file + "----访问路径---" + visitUrl);
return visitUrl;
}
}`
准备线程池类
package com.qizhi.itfin.thread;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
/**
* 配置线程池
*
* @author xuewei.xia
*/
@Component
public class OrderThreadAsyncConfigurer implements AsyncConfigurer {
@Value("${field.order.thread.corePoolSize}")
private Integer corePoolSize;
@Value("${field.order.thread.maxPoolSize}")
private Integer maxPoolSize;
@Value("${field.order.thread.queueCapacity}")
private Integer queueCapacity;
/**
* 注入异步线程池
*/
@Bean(name = "OrderAsyncExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
// 设置核心线程数
threadPool.setCorePoolSize(corePoolSize);
// 设置最大线程数
threadPool.setMaxPoolSize(maxPoolSize);
// 线程池所使用的缓冲队列
threadPool.setQueueCapacity(queueCapacity);
// 等待任务在关机时完成--表明等待所有线程执行完
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(60);
// 线程名称前缀
threadPool.setThreadNamePrefix("OrderAsyncTask-");
// 初始化线程
threadPool.initialize();
return threadPool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
里面配置参数如图
准备工作完成,看下controller层代码
```
@SuppressWarnings("unchecked")
@RequestMapping(value = "/updateImages")
public R updateImgeMaterial(Long orderId, @RequestParam("imagesMaterial") String data,
@RequestParam("type") String type, @RequestParam("otherType") String otherType, String removeIdList) {
PmsUser user = (PmsUser)ShiroUtils.getUserEntity();
Map<String,
Object> resultMap = odrOrderMediaService.updateImgeMaterial(orderId, data, type, otherType, removeIdList,
url, String.valueOf(user.getUserId()), String.valueOf(user.getPartnerId()), path, floorImage, fileImage,
videoImage);
if ((Integer)resultMap.get("result") > 0) {
// 异步调用生成缩略图
Map<Integer, List<ImagesManVo>> mapImage = (Map<Integer, List<ImagesManVo>>)resultMap.get("mapImage");
Map<Integer, String> frontMap = (Map<Integer, String>)resultMap.get("frontMap");
Map<Integer, String> thumbnailMap = (Map<Integer, String>)resultMap.get("thumbnailMap");
List<Integer> typeIdList = (List<Integer>)resultMap.get("typeIdList");
List<OdrOrderMedia> customList = (List<OdrOrderMedia>)resultMap.get("customList");
JSONArray customType = (JSONArray)resultMap.get("customType");
if (mapImage != null && !mapImage.isEmpty()) {
odrOrderMediaService.combineImage(mapImage, frontMap, thumbnailMap, typeIdList, url,
String.valueOf(user.getUserId()), String.valueOf(user.getPartnerId()), path, floorImage, fileImage,
videoImage, orderId, customList, customType, user.getBasePartnerId().intValue());
}
return R.ok();
}
return R.error();
}
所有跟缩略图相关的数据要单独拉出来处理,如上代码是保存后从map中拿到所有文件夹中的图片数据,来另写生成缩略图方法执行,如上代码生成缩略图方法为combineImage方法。让我们看看这个方法:
/**
* 异步线程生成缩略图
*/
@Async("OrderAsyncExecutor")
@Transactional
@Override
public void combineImage(Map<Integer, List<ImagesManVo>> mapImage, Map<Integer, String> frontMap,
Map<Integer, String> thumbnailMap, List<Integer> typeIdList, String url, String creator, String partnerId,
String path, String floorImage, String fileImage, String videoImage, Long orderId,
List<OdrOrderMedia> customList, JSONArray customType, Integer basePartnerId) {
customList.clear();
log.info("进入生成图片缩略图方法");
// 保存缩略图
for (Integer key : mapImage.keySet()) {
// 比对保存前后 前九张拼接的id值是否一样
log.info("frontMap对象-----" + frontMap.get(key));
log.info("thumbnailMap对象-----" + thumbnailMap.get(key));
if (thumbnailMap != null && !thumbnailMap.get(key).equals(frontMap.get(key))) {
typeIdList.add(key);
// 拿到同个类型的文件
List<ImagesManVo> otherIdList = mapImage.get(key);
log.info("otherIdList大小-----" + otherIdList.size());
if (CollectionUtils.isNotEmpty(otherIdList)) {
for (ImagesManVo imagesManVo : otherIdList) {
int i = 0;
List<ImagesVo> list = imagesManVo.getImageList();
if (CollectionUtils.isNotEmpty(list)) {
List<String> urlList = new ArrayList<>();
for (ImagesVo imagesVo : list) {
if (i < 9) {
urlList.add(imagesVo.getVisitUrl());
i++;
}
}
try {
// 生成缩略图
String visitUrl = ImageUtil.combineImage(urlList, url, creator, partnerId, path,
floorImage, fileImage, videoImage);
OdrOrderMedia odrOrderMedia = new OdrOrderMedia();
odrOrderMedia.setOrderId(orderId);
odrOrderMedia.setThumbnail(1);
odrOrderMedia.setTypeId(imagesManVo.getTypeId());
odrOrderMedia.setVisitUrl(visitUrl);
odrOrderMedia.setCreateTime(new Date());
customList.add(odrOrderMedia);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
log.info("生成的所有的缩略图集合 :" + customList);
// 抓取空集合所在的图片类型 添加到删除缩略图
if (CollectionUtils.isNotEmpty(customType)) {
for (int i = 0; i < customType.size(); i++) {
typeIdList.add(customType.getInteger(i));
}
}
// 删除缩略图
if (CollectionUtils.isNotEmpty(typeIdList)) {
QueryWrapper<OdrOrderMedia> queryWrapper = new QueryWrapper<OdrOrderMedia>();
queryWrapper.eq("orderId", orderId);
queryWrapper.eq("thumbnail", 1);
queryWrapper.in("typeId", typeIdList);
odrOrderMediaMapper.delete(queryWrapper);
}
// 保存所有缩略图
if (CollectionUtils.isNotEmpty(customList)) {
odrOrderMediaMapper.insertBathMedia(customList);
}
}
注解 @Async(“OrderAsyncExecutor”) 对应我们线程池注入的 @Bean(name = “OrderAsyncExecutor”)
这段代码逻辑:比对保存前后图片九张的图片id是否一致,一致不生成缩略图,不一致说明有发生改变,要重新生成新的缩略图。
第一次写博客,有补充的或好的想法欢迎给予建议。