From ae2d31733e774493b2d3e11e607ae52d61617f56 Mon Sep 17 00:00:00 2001 From: jlzhou <12020042@qq.com> Date: Thu, 4 May 2023 17:25:50 +0800 Subject: [PATCH] =?UTF-8?q?+webp=E5=9B=BE=E7=89=87=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E6=94=AF=E6=8C=81,=E4=BF=AE=E6=94=B9OssService,=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E5=9B=BE=E7=89=87=E7=BC=A9=E6=94=BE=E6=B0=B4=E5=8D=B0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 + .../system/SysProfileController.java | 2 +- .../common/utils/file/MimeTypeUtils.java | 5 +- .../java/com/ruoyi/oss/core/OssClient.java | 2 + ruoyi-system/pom.xml | 5 + .../system/service/ISysConfigService.java | 6 + .../ruoyi/system/service/ISysOssService.java | 203 ++++++++++++++++++ .../service/impl/SysConfigServiceImpl.java | 26 +++ .../service/impl/SysOssServiceImpl.java | 65 +++++- 9 files changed, 315 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 6a62ac2..33594a4 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,8 @@ 2.0.23 3.1.687 + + @@ -328,6 +330,12 @@ ${ruoyi-vue-plus.version} + + com.github.gotson + webp-imageio + 0.2.2 + + diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java index 9e646ec..5dfe739 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java @@ -114,7 +114,7 @@ public class SysProfileController extends BaseController { if (!StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)) { return R.fail("文件格式不正确,请上传" + Arrays.toString(MimeTypeUtils.IMAGE_EXTENSION) + "格式"); } - SysOssVo oss = iSysOssService.upload(avatarfile); + SysOssVo oss = iSysOssService.uploadImgs(avatarfile,"avatar",200,200,null); String avatar = oss.getUrl(); if (userService.updateUserAvatar(getUsername(), avatar)) { ajax.put("imgUrl", avatar); diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java index 6ca97fe..5b434a1 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MimeTypeUtils.java @@ -7,6 +7,7 @@ package com.ruoyi.common.utils.file; */ public class MimeTypeUtils { public static final String IMAGE_PNG = "image/png"; + public static final String IMAGE_WEBP = "image/webp"; public static final String IMAGE_JPG = "image/jpg"; @@ -16,7 +17,7 @@ public class MimeTypeUtils { public static final String IMAGE_GIF = "image/gif"; - public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; + public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png", "webp"}; public static final String[] FLASH_EXTENSION = {"swf", "flv"}; @@ -27,7 +28,7 @@ public class MimeTypeUtils { public static final String[] DEFAULT_ALLOWED_EXTENSION = { // 图片 - "bmp", "gif", "jpg", "jpeg", "png", + "bmp", "gif", "jpg", "jpeg", "png","webp", // word excel powerpoint "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", // 压缩文件 diff --git a/ruoyi-oss/src/main/java/com/ruoyi/oss/core/OssClient.java b/ruoyi-oss/src/main/java/com/ruoyi/oss/core/OssClient.java index a82853e..4c8a260 100644 --- a/ruoyi-oss/src/main/java/com/ruoyi/oss/core/OssClient.java +++ b/ruoyi-oss/src/main/java/com/ruoyi/oss/core/OssClient.java @@ -22,6 +22,7 @@ import com.ruoyi.oss.enumd.AccessPolicyType; import com.ruoyi.oss.enumd.PolicyType; import com.ruoyi.oss.exception.OssException; import com.ruoyi.oss.properties.OssProperties; +import lombok.Getter; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -38,6 +39,7 @@ public class OssClient { private final String configKey; + @Getter private final OssProperties properties; private final AmazonS3 client; diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index 9c382a3..6a7f2af 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -35,6 +35,11 @@ ruoyi-sms + + com.github.gotson + webp-imageio + + diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java index d6bd54f..6942d87 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java @@ -4,6 +4,7 @@ import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.system.domain.SysConfig; +import java.awt.image.BufferedImage; import java.util.List; /** @@ -93,4 +94,9 @@ public interface ISysConfigService { */ boolean checkConfigKeyUnique(SysConfig config); + /** + * 获取全局水印图片 + * @return + */ + BufferedImage getWatermark(); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java index 6472ceb..4166c01 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysOssService.java @@ -1,5 +1,7 @@ package com.ruoyi.system.service; +import cn.hutool.core.img.Img; +import cn.hutool.core.lang.UUID; import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.system.domain.SysOss; @@ -8,7 +10,13 @@ import com.ruoyi.system.domain.vo.SysOssVo; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; import java.util.Collection; import java.util.List; @@ -19,6 +27,8 @@ import java.util.List; */ public interface ISysOssService { + String IMAGE_WEBP="webp"; + TableDataInfo queryPageList(SysOssBo sysOss, PageQuery pageQuery); List listByIds(Collection ossIds); @@ -27,8 +37,201 @@ public interface ISysOssService { SysOssVo upload(MultipartFile file); + /** + * + * @param file + * @param pre - 图片保存路径前缀 + * @return + */ + SysOssVo upload(MultipartFile file,String pre); + + /** + * 使用默认的最大高宽和水印保存图片 + * @param file + * @param pre - 图片保存路径前缀 + * @return + */ + SysOssVo uploadImgs(MultipartFile file,String pre); + + + /** + * + * @param file + * @param pre - 图片保存路径前缀 + * @param maxWidth - 缩放到指定的宽 小于1表示不缩放 + * @param maxHeight - 缩放到指定的高 小于1表示不缩放 + * @param watermark - 水印图片 null表示不加水印 + * @return + */ + SysOssVo uploadImgs(MultipartFile file,String pre, int maxWidth, int maxHeight, + BufferedImage watermark); + void download(Long ossId, HttpServletResponse response) throws IOException; Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + + default InputStream formatImage(MultipartFile file, int maxWidth, int maxHeight, + BufferedImage watermark) { + try { + if (file == null || file.isEmpty()) { + throw new RuntimeException("上传图片不能为空"); + } + if (!file.getContentType().startsWith("image/")) { + throw new RuntimeException("上传的文件不是图片"); + } + return formatImage(file.getInputStream(), maxWidth, maxHeight, watermark); + } catch (IOException e) { + throw new RuntimeException("处理图片错误", e); + } + } + + default InputStream formatImage(Image img, int maxWidth, int maxHeight, BufferedImage watermark) { + if (img == null) { + throw new RuntimeException("图片不能为空"); + } + return formatImage(Img.from(img), maxWidth, maxHeight, watermark); + } + + default InputStream formatImage(InputStream in, int maxWidth, int maxHeight, BufferedImage watermark) { + return formatImage(Img.from(in), maxWidth, maxHeight, watermark); + } + + default InputStream formatImage(Img img, int maxWidth, int maxHeight, BufferedImage watermark) { + try { + int w = img.getImg().getWidth(null); + int h = img.getImg().getHeight(null); + if(maxWidth>0 && maxHeight > 0){ + if (w > maxWidth || h > maxHeight) { + int outWidth = 0; + int outHeight = 0; + outHeight = maxWidth * h / w; + if (outHeight > maxHeight) { + outHeight = maxHeight; + outWidth = outHeight * maxWidth / h; + } else { + outWidth = maxWidth; + } + img = img.scale(outWidth, outHeight); + w = outWidth; + h = outHeight; + } + } + + if (watermark != null) { + int ww = watermark.getWidth(null); + int wh = watermark.getHeight(null); + if (w > ww && h > wh) { + img = img.pressImage(watermark, 0, 0, 1f); + } + } + + ByteArrayOutputStream pout = new ByteArrayOutputStream(); + + img.setTargetImageType(IMAGE_WEBP).write(pout); + + ByteArrayInputStream pin = new ByteArrayInputStream(pout.toByteArray()); + pout.close(); + pout = null; + return pin; + } catch (Exception e) { + throw new RuntimeException("处理图片错误", e); + } + } + + + + /** + *
+     * - 根据存放规则和文件名生成存放的URI
+     * - 支持
+     * 	- {yyyy}/{MM}/{dd}/{HH}/{mm}/{ss} 年月日时分秒
+     * 	- {UUID} 32位的唯一标志
+     * 	- {id} int类型的唯一id,防止重复建议+年月日路径
+     * 	- {id16} int类型的唯一id16进制表示,防止重复建议+年月日路径
+     * 	- {id36} int类型的唯一id36进制表示,防止重复建议+年月日路径
+     * 	- {filename} 文件基础名称
+     * 	- {ext} 扩展名
+     * 
+ * + * @param rule + * @param filename + * @return + */ + default String generateURI(String rule, String filename) { + Calendar c = Calendar.getInstance(); + if (rule.contains("{yyyy}")) { + rule = rule.replace("{yyyy}", "" + c.get(Calendar.YEAR)); + } + if (rule.contains("{MM}")) { + rule = rule.replace("{MM}", "" + (c.get(Calendar.MONTH) + 1)); + } + if (rule.contains("{dd}")) { + rule = rule.replace("{dd}", "" + c.get(Calendar.DATE)); + } + if (rule.contains("{HH}")) { + rule = rule.replace("{HH}", "" + c.get(Calendar.HOUR_OF_DAY)); + } + if (rule.contains("{mm}")) { + rule = rule.replace("{mm}", "" + c.get(Calendar.MINUTE)); + } + if (rule.contains("{ss}")) { + rule = rule.replace("{ss}", "" + c.get(Calendar.SECOND)); + } + if (rule.contains("{UUID}")) { + rule = rule.replace("{UUID}", UUID.fastUUID().toString(true)); + } + if (rule.contains("{id}")) { + rule = rule.replace("{id}", Integer.toString(id.nextId())); + } + if (rule.contains("{id16}")) { + rule = rule.replace("{id16}", Integer.toString(id.nextId(), 16)); + } + if (rule.contains("{id36}")) { + rule = rule.replace("{id36}", Integer.toString(id.nextId(), 36)); + } + if (rule.contains("{filename}")) { + String temp = null; + if (filename.contains(".")) { + temp = filename.substring(0, filename.lastIndexOf(".")); + } else { + temp = filename; + } + rule = rule.replace("{filename}", temp); + } + + if (rule.contains("{ext}")) { + String temp = null; + if (filename.contains(".")) { + temp = filename.substring(filename.lastIndexOf(".") + 1); + } else { + temp = ""; + } + rule = rule.replace("{ext}", temp.toLowerCase()); + } + return rule; + } + Id id = new Id(); + + static class Id { + private long lastTimestamp = 0; + private int sequence = 0; + + public synchronized int nextId() { + long timestamp = System.currentTimeMillis(); + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & 0xF; + if (sequence == 0) { + while (timestamp <= lastTimestamp) { + timestamp = System.currentTimeMillis(); + } + } + } else { + sequence = 0; + } + lastTimestamp = timestamp; + return (int) (timestamp & 0xFFFF) << 8 | sequence; + } + } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java index 932753b..1403e2a 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java @@ -2,6 +2,8 @@ package com.ruoyi.system.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -18,10 +20,13 @@ import com.ruoyi.system.domain.SysConfig; import com.ruoyi.system.mapper.SysConfigMapper; import com.ruoyi.system.service.ISysConfigService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -33,6 +38,7 @@ import java.util.Map; */ @RequiredArgsConstructor @Service +@Slf4j public class SysConfigServiceImpl implements ISysConfigService, ConfigService { private final SysConfigMapper baseMapper; @@ -224,4 +230,24 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService { return SpringUtils.getAopProxy(this).selectConfigByKey(configKey); } + + + private BufferedImage img; + + @Override + public synchronized BufferedImage getWatermark() { + if (img == null) { + try{ + HttpResponse response = HttpUtil.createGet(selectConfigByKey("image.watermark")).execute(); + if(response.isOk()){ + img = ImageIO.read(response.bodyStream()); + } + log.info("下载默认水印图片成功"); + }catch (Exception e) { + log.warn("下载水印图片失败",e); + } + } + return img; + } + } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java index c36bfa2..68beae3 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysOssServiceImpl.java @@ -3,6 +3,7 @@ package com.ruoyi.system.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -23,6 +24,7 @@ import com.ruoyi.system.domain.SysOss; import com.ruoyi.system.domain.bo.SysOssBo; import com.ruoyi.system.domain.vo.SysOssVo; import com.ruoyi.system.mapper.SysOssMapper; +import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ISysOssService; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.Cacheable; @@ -31,6 +33,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -45,6 +48,7 @@ import java.util.stream.Collectors; @Service public class SysOssServiceImpl implements ISysOssService, OssService { + private final ISysConfigService configService; private final SysOssMapper baseMapper; @Override @@ -120,12 +124,65 @@ public class SysOssServiceImpl implements ISysOssService, OssService { @Override public SysOssVo upload(MultipartFile file) { - String originalfileName = file.getOriginalFilename(); - String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); + return upload(file,null); + } + + @Override + public SysOssVo upload(MultipartFile file, String pre) { + String originalFileName = file.getOriginalFilename(); + String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); OssClient storage = OssFactory.instance(); UploadResult uploadResult; try { - uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType()); + String path = ""; + if(StrUtil.isNotBlank(storage.getProperties().getPrefix())){ + path += storage.getProperties().getPrefix()+"/"; + } + if(StrUtil.isNotBlank(pre)) { + path += pre+"/"; + } + path += generateURI("{yyyy}/{MM}/{dd}/{id36}.{ext}",originalFileName); + uploadResult = storage.upload(file.getInputStream(),path,file.getContentType()); + } catch (IOException e) { + throw new ServiceException(e.getMessage()); + } + // 保存文件信息 + SysOss oss = new SysOss(); + oss.setUrl(uploadResult.getUrl()); + oss.setFileSuffix(suffix); + oss.setFileName(uploadResult.getFilename()); + oss.setOriginalName(originalFileName); + oss.setService(storage.getConfigKey()); + baseMapper.insert(oss); + SysOssVo sysOssVo = new SysOssVo(); + BeanCopyUtils.copy(oss, sysOssVo); + return this.matchingUrl(sysOssVo); + } + + @Override + public SysOssVo uploadImgs(MultipartFile file, String pre) { + return uploadImgs(file,pre,Integer.valueOf(configService.selectConfigByKey("image.maxWidth")),Integer.valueOf(configService.selectConfigByKey("image.maxHeight")),configService.getWatermark()); + } + + @Override + public SysOssVo uploadImgs(MultipartFile file, String pre, int maxWidth, int maxHeight, BufferedImage watermark) { + String originalFileName = file.getOriginalFilename(); + String suffix = StringUtils.substring(originalFileName, originalFileName.lastIndexOf("."), originalFileName.length()); + OssClient storage = OssFactory.instance(); + UploadResult uploadResult; + try ( + InputStream in = formatImage(file,maxWidth,maxWidth,watermark) + ){ + String path = ""; + if(StrUtil.isNotBlank(storage.getProperties().getPrefix())){ + path += storage.getProperties().getPrefix()+"/"; + } + if(StrUtil.isNotBlank(pre)) { + path += pre+"/"; + } + path += generateURI("{yyyy}/{MM}/{dd}/{id36}.webp","a.webp"); + + uploadResult = storage.upload(in,path,"image/webp"); } catch (IOException e) { throw new ServiceException(e.getMessage()); } @@ -134,7 +191,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { oss.setUrl(uploadResult.getUrl()); oss.setFileSuffix(suffix); oss.setFileName(uploadResult.getFilename()); - oss.setOriginalName(originalfileName); + oss.setOriginalName(originalFileName); oss.setService(storage.getConfigKey()); baseMapper.insert(oss); SysOssVo sysOssVo = new SysOssVo();