refactor(storage): 新增存储模块 - 本地和 S3 两种存储模式

This commit is contained in:
吴泽威
2024-12-30 21:01:26 +08:00
parent eb2cac54f7
commit bf2e30e560
29 changed files with 2437 additions and 196 deletions

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>top.continew</groupId>
<artifactId>continew-starter-storage</artifactId>
<version>${revision}</version>
</parent>
<artifactId>continew-starter-storage-core</artifactId>
<description>ContiNew Starter 存储模块 - 核心模块</description>
<dependencies>
<!--redisson 缓存模块-->
<dependency>
<groupId>top.continew</groupId>
<artifactId>continew-starter-cache-redisson</artifactId>
</dependency>
<!--图片处理工具-主要用做图片缩略处理-->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.constant;
/**
* 存储 常量
*
* @author echo
* @date 2024/12/16 19:09
*/
public class StorageConstant {
/**
* 默认存储 Key
*/
public static final String DEFAULT_KEY = "storage:default_config";
/**
* 云服务商 域名前缀
* <p>目前只支持 阿里云-oss 华为云-obs 腾讯云-cos</p>
*/
public static final String[] CLOUD_SERVICE_PREFIX = new String[] {"oss", "cos", "obs"};
/**
* 缩略图后缀
*/
public static final String SMALL_SUFFIX = "small";
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.dao;
import top.continew.starter.storage.model.resp.UploadResp;
/**
* 存储记录持久层接口
*
* @author echo
* @date 2024/12/17 16:49
*/
public interface StorageDao {
/**
* 记录上传信息
*
* @param uploadResp 上传信息
*/
void add(UploadResp uploadResp);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.dao.impl;
import top.continew.starter.storage.dao.StorageDao;
import top.continew.starter.storage.model.resp.UploadResp;
/**
* 默认记录实现,此类并不能真正保存记录,只是用来脱离数据库运行,保证文件上传功能可以正常使用
*
* @author echo
* @date 2024/12/18 08:48
**/
public class StorageDaoDefaultImpl implements StorageDao {
@Override
public void add(UploadResp uploadResp) {
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.decorator;
import top.continew.starter.storage.model.resp.ThumbnailResp;
import top.continew.starter.storage.model.resp.UploadResp;
import top.continew.starter.storage.strategy.StorageStrategy;
import java.io.InputStream;
/**
* 装饰器基类 - 用于重写
*
* @author echo
* @date 2024/12/30 19:33
*/
public abstract class AbstractStorageDecorator<C> implements StorageStrategy<C> {
protected StorageStrategy<C> delegate;
protected AbstractStorageDecorator(StorageStrategy<C> delegate) {
this.delegate = delegate;
}
@Override
public C getClient() {
return delegate.getClient();
}
@Override
public boolean bucketExists(String bucketName) {
return delegate.bucketExists(bucketName);
}
@Override
public void createBucket(String bucketName) {
delegate.createBucket(bucketName);
}
@Override
public UploadResp upload(String fileName, InputStream inputStream, String fileType) {
return delegate.upload(fileName, inputStream, fileType);
}
@Override
public UploadResp upload(String fileName,
String path,
InputStream inputStream,
String fileType,
boolean isThumbnail) {
return delegate.upload(fileName, path, inputStream, fileType, isThumbnail);
}
@Override
public UploadResp upload(String bucketName,
String fileName,
String path,
InputStream inputStream,
String fileType,
boolean isThumbnail) {
return delegate.upload(bucketName, fileName, path, inputStream, fileType, isThumbnail);
}
@Override
public void upload(String bucketName, String fileName, String path, InputStream inputStream, String fileType) {
delegate.upload(bucketName, fileName, path, inputStream, fileType);
}
@Override
public ThumbnailResp uploadThumbnail(String bucketName,
String fileName,
String path,
InputStream inputStream,
String fileType) {
return delegate.uploadThumbnail(bucketName, fileName, path, inputStream, fileType);
}
@Override
public InputStream download(String bucketName, String fileName) {
return delegate.download(bucketName, fileName);
}
@Override
public void delete(String bucketName, String fileName) {
delegate.delete(bucketName, fileName);
}
@Override
public String getImageBase64(String bucketName, String fileName) {
return delegate.getImageBase64(bucketName, fileName);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.enums;
import cn.hutool.core.util.StrUtil;
import top.continew.starter.core.enums.BaseEnum;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 文件类型枚举
*
* @author Charles7c
* @since 2023/12/23 13:38
*/
public enum FileTypeEnum implements BaseEnum<Integer> {
/**
* 其他
*/
UNKNOWN(1, "其他", Collections.emptyList()),
/**
* 图片
*/
IMAGE(2, "图片", List
.of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")),
/**
* 文档
*/
DOC(3, "文档", List.of("txt", "pdf", "doc", "xls", "ppt", "docx", "xlsx", "pptx")),
/**
* 视频
*/
VIDEO(4, "视频", List.of("mp4", "avi", "mkv", "flv", "webm", "wmv", "m4v", "mov", "mpg", "rmvb", "3gp")),
/**
* 音频
*/
AUDIO(5, "音频", List.of("mp3", "flac", "wav", "ogg", "midi", "m4a", "aac", "amr", "ac3", "aiff")),;
private final Integer value;
private final String description;
private final List<String> extensions;
/**
* 根据扩展名查询
*
* @param extension 扩展名
* @return 文件类型
*/
public static FileTypeEnum getByExtension(String extension) {
return Arrays.stream(FileTypeEnum.values())
.filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase()))
.findFirst()
.orElse(FileTypeEnum.UNKNOWN);
}
FileTypeEnum(Integer value, String description, List<String> extensions) {
this.value = value;
this.description = description;
this.extensions = extensions;
}
public List<String> getExtensions() {
return this.extensions;
}
@Override
public Integer getValue() {
return this.value;
}
@Override
public String getDescription() {
return this.description;
}
@Override
public String getColor() {
return BaseEnum.super.getColor();
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.manger;
import top.continew.starter.cache.redisson.util.RedisUtils;
import top.continew.starter.core.validation.ValidationUtils;
import top.continew.starter.storage.constant.StorageConstant;
import top.continew.starter.storage.strategy.StorageStrategy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 存储策略管理器
*
* @author echo
* @date 2024/12/16
*/
public class StorageManager {
/**
* 存储策略连接信息
*/
private static final Map<String, StorageStrategy<?>> STORAGE_STRATEGY = new ConcurrentHashMap<>();
/**
* 加载存储策略
*
* @param code 存储码
* @param strategy 对应存储策略
*/
public static void load(String code, StorageStrategy<?> strategy) {
STORAGE_STRATEGY.put(code, strategy);
}
/**
* 卸载存储策略
*
* @param code 存储码
*/
public static void unload(String code) {
STORAGE_STRATEGY.remove(code);
}
/**
* 根据 存储 code 获取对应存储策略
*
* @param code 代码
* @return {@link StorageStrategy }
*/
public static StorageStrategy<?> instance(String code) {
StorageStrategy<?> strategy = STORAGE_STRATEGY.get(code);
ValidationUtils.throwIfEmpty(strategy, "未找到存储配置:" + code);
return strategy;
}
/**
* 获取默认存储策略
*
* @return {@link StorageStrategy }
*/
public static StorageStrategy<?> instance() {
return instance(RedisUtils.get(StorageConstant.DEFAULT_KEY));
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.model.req;
/**
* 存储配置信息
*
* @author echo
* @date 2024/11/04 15:13
**/
public class StorageProperties {
/**
* 编码
*/
private String code;
/**
* 访问密钥
*/
private String accessKey;
/**
* 私有密钥
*/
private String secretKey;
/**
* 终端节点
*/
private String endpoint;
/**
* 桶名称
*/
private String bucketName;
/**
* 域名
*/
private String domain;
/**
* 作用域
*/
private String region;
/**
* 是否是默认存储
*/
private Boolean isDefault;
public StorageProperties() {
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public Boolean getIsDefault() {
return isDefault;
}
public void setIsDefault(Boolean isDefault) {
this.isDefault = isDefault;
}
public StorageProperties(String code,
String accessKey,
String secretKey,
String endpoint,
String bucketName,
String domain,
String region,
Boolean isDefault) {
this.code = code;
this.accessKey = accessKey;
this.secretKey = secretKey;
this.endpoint = endpoint;
this.bucketName = bucketName;
this.domain = domain;
this.region = region;
this.isDefault = isDefault;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.model.resp;
/**
* 缩略图
*
* @author echo
* @date 2024/12/20 17:00
*/
public class ThumbnailResp {
/**
* 缩略图大小(字节)
*/
private Long thumbnailSize;
/**
* 缩略图地址 格式 xxx/xxx/xxx.small.jpg
*/
private String thumbnailPath;
public ThumbnailResp() {
}
public ThumbnailResp(Long thumbnailSize, String thumbnailPath) {
this.thumbnailSize = thumbnailSize;
this.thumbnailPath = thumbnailPath;
}
public Long getThumbnailSize() {
return thumbnailSize;
}
public void setThumbnailSize(Long thumbnailSize) {
this.thumbnailSize = thumbnailSize;
}
public String getThumbnailPath() {
return thumbnailPath;
}
public void setThumbnailPath(String thumbnailPath) {
this.thumbnailPath = thumbnailPath;
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.model.resp;
import java.time.LocalDateTime;
/**
* 上传结果
*
* @author echo
* @date 2024/12/10
*/
public class UploadResp {
/**
* 存储 code
*/
private String code;
/**
* 访问地址
* <p>如果桶为私有,则提供临时链接,时间默认为 12 小时</p>
*/
private String url;
/**
* 文件基础路径
*/
private String basePath;
/**
* 原始 文件名
*/
private String originalFilename;
/**
* 扩展名
*/
private String ext;
/**
* 文件大小(字节)
*/
private long size;
/**
* 已上传对象的实体标记(用来校验文件)-S3
*/
private String eTag;
/**
* 存储路径
* <p></p> 格式 桶/文件名 continew/2024/12/24/1234.jpg
*/
private String path;
/**
* 存储桶
*/
private String bucketName;
/**
* 缩略图大小(字节)
*/
private Long thumbnailSize;
/**
* 缩略图URL
*/
private String thumbnailUrl;
/**
* 上传时间
*/
private LocalDateTime createTime;
public UploadResp() {
}
public UploadResp(String code,
String url,
String basePath,
String originalFilename,
String ext,
long size,
String eTag,
String path,
String bucketName,
Long thumbnailSize,
String thumbnailUrl,
LocalDateTime createTime) {
this.code = code;
this.url = url;
this.basePath = basePath;
this.originalFilename = originalFilename;
this.ext = ext;
this.size = size;
this.eTag = eTag;
this.path = path;
this.bucketName = bucketName;
this.thumbnailSize = thumbnailSize;
this.thumbnailUrl = thumbnailUrl;
this.createTime = createTime;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public String getOriginalFilename() {
return originalFilename;
}
public void setOriginalFilename(String originalFilename) {
this.originalFilename = originalFilename;
}
public String getExt() {
return ext;
}
public void setExt(String ext) {
this.ext = ext;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public String geteTag() {
return eTag;
}
public void seteTag(String eTag) {
this.eTag = eTag;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public Long getThumbnailSize() {
return thumbnailSize;
}
public void setThumbnailSize(Long thumbnailSize) {
this.thumbnailSize = thumbnailSize;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.strategy;
import top.continew.starter.storage.model.resp.ThumbnailResp;
import top.continew.starter.storage.model.resp.UploadResp;
import java.io.InputStream;
/**
* 存储策略接口
*
* @author echo
* @date 2024/12/16 11:19
*/
public interface StorageStrategy<C> {
/**
* 获得客户端 - 用于重写时 获取对应存储 code 客户端
*
* @return {@link Object }
*/
C getClient();
/**
* 检查桶是否存在
* <p> S3: 检查桶是否存在 </p>
* <p>local: 检查 默认路径 是否存在 </p>
*
* @param bucketName 桶名称
* @return true 存在 false 不存在
*/
boolean bucketExists(String bucketName);
/**
* 创建桶
* <p> S3: 创建桶 </p>
* <p> local: 创建 默认路径下 指定文件夹 </p>
*
* @param bucketName 桶名称
*/
void createBucket(String bucketName);
/**
* 上传文件 - 默认桶
*
* @param fileName 文件名
* @param inputStream 输入流
* @param fileType 文件类型
* @return 上传响应
*/
UploadResp upload(String fileName, InputStream inputStream, String fileType);
/**
* 上传文件 - 默认桶
*
* @param fileName 文件名
* @param path 路径
* @param inputStream 输入流
* @param fileType 文件类型
* @param isThumbnail 是缩略图
* @return {@link UploadResp }
*/
UploadResp upload(String fileName, String path, InputStream inputStream, String fileType, boolean isThumbnail);
/**
* 上传文件
*
* @param bucketName 桶名称
* @param fileName 文件名
* @param path 路径
* @param inputStream 输入流
* @param fileType 文件类型
* @param isThumbnail 是缩略图
* @return 上传响应
*/
UploadResp upload(String bucketName,
String fileName,
String path,
InputStream inputStream,
String fileType,
boolean isThumbnail);
/**
* 文件上传-基础上传
*
* @param bucketName 桶名称 - 基础上传不做处理
* @param fileName 文件名 - 基础上传不做处理
* @param path 路径 - 基础上传不做处理
* @param inputStream 输入流
* @param fileType 文件类型
* @return {@link UploadResp }
*/
void upload(String bucketName, String fileName, String path, InputStream inputStream, String fileType);
/**
* 上传缩略图
*
* @param bucketName 桶名称
* @param fileName 文件名
* @param inputStream 输入流
* @param fileType 文件类型
* @return {@link UploadResp }
*/
ThumbnailResp uploadThumbnail(String bucketName,
String fileName,
String path,
InputStream inputStream,
String fileType);
/**
* 下载文件
*
* @param bucketName 桶名称
* @param fileName 文件名
* @return 文件输入流
*/
InputStream download(String bucketName, String fileName);
/**
* 删除文件
*
* @param bucketName 桶名称
* @param fileName 文件名
*/
void delete(String bucketName, String fileName);
/**
* 获取图像Base64
*
* @param bucketName 桶名称
* @param fileName 文件名
* @return Base64编码的图像
*/
String getImageBase64(String bucketName, String fileName);
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.util;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 图像缩略图工具
*
* @author echo
* @date 2024/12/20 16:49
*/
public class ImageThumbnailUtils {
// 默认缩略图尺寸100x100
private static final int DEFAULT_WIDTH = 100;
private static final int DEFAULT_HEIGHT = 100;
/**
* 根据输入流生成默认大小100x100的缩略图并写入输出流
*
* @param inputStream 原始图片的输入流
* @param outputStream 缩略图输出流
* @param suffix 后缀
* @throws IOException IOException
*/
public static void generateThumbnail(InputStream inputStream,
OutputStream outputStream,
String suffix) throws IOException {
generateThumbnail(inputStream, outputStream, DEFAULT_WIDTH, DEFAULT_HEIGHT, suffix);
}
/**
* 根据输入流和自定义尺寸生成缩略图并写入输出流
*
* @param inputStream 原始图片的输入流
* @param outputStream 缩略图输出流
* @param width 缩略图宽度
* @param height 缩略图高度
* @param suffix 后缀
* @throws IOException IOException
*/
public static void generateThumbnail(InputStream inputStream,
OutputStream outputStream,
int width,
int height,
String suffix) throws IOException {
// 读取原始图片
BufferedImage originalImage = ImageIO.read(inputStream);
// 调整图片大小
Image tmp = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage thumbnail = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 画出缩略图
Graphics2D g2d = thumbnail.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
// 写入输出流
ImageIO.write(thumbnail, suffix, outputStream);
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.storage.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import top.continew.starter.core.constant.StringConstants;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 储存工具
*
* @author echo
* @date 2024/12/16 19:55
*/
public class StorageUtils {
public StorageUtils() {
}
/**
* 格式文件名
*
* @param fileName 文件名
* @return {@link String }
*/
public static String formatFileName(String fileName) {
// 获取文件后缀名
String suffix = FileUtil.extName(fileName);
// 获取当前时间的年月日时分秒格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String datetime = LocalDateTime.now().format(formatter);
// 获取当前时间戳
String timestamp = String.valueOf(System.currentTimeMillis());
// 生成新的文件名
return datetime + timestamp + "." + suffix;
}
/**
* 默认文件目录
*
* @param fileName 文件名
* @return {@link String }
*/
public static String defaultFileDir(String fileName) {
LocalDate today = LocalDate.now();
return Paths.get(String.valueOf(today.getYear()), String.valueOf(today.getMonthValue()), String.valueOf(today
.getDayOfMonth()), fileName).toString();
}
/**
* 默认路径地址 格式 2024/03/10/
*
* @return {@link String }
*/
public static String defaultPath() {
LocalDate today = LocalDate.now();
return Paths.get(String.valueOf(today.getYear()), String.valueOf(today.getMonthValue()), String.valueOf(today
.getDayOfMonth())) + StringConstants.SLASH;
}
/**
* 根据 endpoint 判断是否带有 http 或 https如果没有则加上 http 前缀。
*
* @param endpoint 输入的 endpoint 字符串
* @return URI 对象
*/
public static URI createUriWithProtocol(String endpoint) {
// 判断 endpoint 是否包含 http:// 或 https:// 前缀
if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
// 如果没有协议前缀,则加上 http://
endpoint = "http://" + endpoint;
}
// 返回 URI 对象
return URI.create(endpoint);
}
/**
* 生成缩略图文件名
*
* @param fileName 文件名
* @param suffix 后缀
* @return {@link String }
*/
public static String buildThumbnailFileName(String fileName, String suffix) {
// 获取文件的扩展名
String extName = FileNameUtil.extName(fileName);
// 去掉扩展名
String baseName = StrUtil.subBefore(fileName, StringConstants.DOT, true);
// 拼接新的路径:原始路径 + .缩略图后缀 + .扩展名
return baseName + "." + suffix + "." + extName;
}
/**
* 可重复读流
*
* @param inputStream 输入流
* @return {@link InputStream }
*/
public static InputStream ensureByteArrayStream(InputStream inputStream) {
return (inputStream instanceof ByteArrayInputStream)
? inputStream
: new ByteArrayInputStream(IoUtil.readBytes(inputStream));
}
}