initial commit

This commit is contained in:
2025-12-23 19:47:02 +08:00
commit 7e439d0bed
79 changed files with 5120 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.yelink.example</groupId>
<artifactId>public-common</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>yelink-minio-starter</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package com.yelink.minio.configure;
import com.yelink.minio.properties.MinioConfig;
import com.yelink.minio.service.MinioService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MinioAutoconfigure.
*
* @author cwp
* @date 2024-05-16 17:45
*/
@Configuration
@EnableConfigurationProperties({MinioConfig.class})
@Slf4j
@RequiredArgsConstructor
public class MinioAutoconfigure {
@Bean
public MinioService minioService(MinioConfig minioConfig) {
return new MinioService(minioConfig);
}
}

View File

@@ -0,0 +1,24 @@
package com.yelink.minio.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* MinioConfig.
* @author cwp
* @date 2024-05-16 17:19
*/
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
private String applicationName;
}

View File

@@ -0,0 +1,277 @@
package com.yelink.minio.service;
import com.yelink.minio.properties.MinioConfig;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* MinioService.
*
* @author cwp
* @date 2024-05-16 17:33
*/
@Slf4j
public class MinioService {
private static final Map<String, String> MIME_TYPES = new HashMap<>();
static {
MIME_TYPES.put("txt", "text/plain");
MIME_TYPES.put("html", "text/html");
MIME_TYPES.put("css", "text/css");
MIME_TYPES.put("js", "application/javascript");
MIME_TYPES.put("json", "application/json");
MIME_TYPES.put("xml", "application/xml");
MIME_TYPES.put("jpg", "image/jpeg");
MIME_TYPES.put("jpeg", "image/jpeg");
MIME_TYPES.put("png", "image/png");
MIME_TYPES.put("gif", "image/gif");
MIME_TYPES.put("bmp", "image/bmp");
MIME_TYPES.put("pdf", "application/pdf");
// 添加更多扩展名和 Content-Type 对应关系
}
private final MinioConfig config;
private final SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS");
private MinioClient client;
public MinioService(MinioConfig config) {
this.config = config;
try {
client = MinioClient.builder().endpoint(config.getEndpoint())
.credentials(config.getAccessKey(), config.getSecretKey()).build();
} catch (Exception ex) {
log.error("Minio 初始化失败Trace", ex);
}
}
/**
* 根据文件扩展名获取 Content-Type 不使用MultipartFile.getContentType()是因为它基于客户端传输的content_type不一定准确.
*
* @param fileName 文件名,包含后缀
* @return MIME 类型
*/
private String getContentType(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
return MIME_TYPES.getOrDefault(extension, "application/octet-stream");
}
/**
* 文件上传.
*
* @param file 文件
* @param path minio 远程路径
* @return 公网访问 url
*/
public String upload(MultipartFile file, String path) throws Exception {
String contentType;
if (ObjectUtils.isEmpty(file.getOriginalFilename())) {
contentType = file.getContentType();
} else {
contentType = getContentType(file.getOriginalFilename());
}
return upload(file, path, contentType);
}
/**
* 文件上传.
*
* @param file 文件
* @param path minio 远程路径
* @param contentType 文件类型
* @return 公网访问 url
*/
public String upload(MultipartFile file, String path, String contentType) throws Exception {
String filename = genPath(path, getExtension(file));
return upload(file.getInputStream(), filename, contentType);
}
/**
* 文件上传.
*
* @param content 文件内容
* @param fileExtension 后缀名
* @return 内网访问 url
*/
public String upload(String content, String fileExtension) throws Exception {
// 转换字符串为输入流
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
// 构建对象名称
String objectName = config.getApplicationName() + "/" + format.format(new Date()) + "." + fileExtension;
return upload(inputStream, objectName, "text/plain");
}
/**
* 文件上传.
*
* @param inputStream inputStream
* @param objectName 文件名
* @param contentType 文件类型
* @return 内网访问 url
* @throws Exception e
*/
public String upload(InputStream inputStream, String objectName, String contentType) throws Exception {
try {
PutObjectArgs args = PutObjectArgs.builder().contentType(contentType).object(objectName)
.bucket(config.getBucket()).stream(inputStream, inputStream.available(), -1).build();
client.putObject(args);
} finally {
inputStream.close();
}
return config.getBucket() + "/" + objectName;
}
/**
* 根据完整 URL 来移除 minio 服务器中的对象.
*
* @param url 例:<a href="http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png">...</a>
*/
public void removeObjectByUrl(String url) {
removeObject(extractObjectName(url));
}
/**
* 根据对象名移除 minio 服务器中的对象.
*
* @param name 例materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
*/
public void removeObject(String name) {
try {
RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(config.getBucket()).object(name).build();
client.removeObject(args);
} catch (Exception e) {
log.error("MinIO 文件删除错误", e);
}
}
/**
* 获取对象文件公网访问 url.
*
* @param filename 对象名称
* @return 公网访问 url
*/
public String minioPublicUrl(String filename) {
return config.getBucket() + "/" + filename;
}
/**
* 生成对象路径.
*
* @param basicPath 根路径
* @param extension 文件扩展名
* @return basicPath/yyyyMMdd/uuid.extension
*/
public String genPath(String basicPath, String extension) {
return "iot/" + basicPath + extension;
}
/**
* 获取文件的扩展名.
*
* @param file 文件
* @return 例:.jpg
*/
public String getExtension(MultipartFile file) {
return getExtension(Objects.requireNonNull(file.getOriginalFilename()));
}
/**
* 获取文件的扩展名.
*
* @param name 文件名称
* @return 例:.jpg
*/
public String getExtension(String name) {
return name.substring(name.lastIndexOf('.'));
}
/**
* 下载文件,用于受保护的 minio 服务器 此方法未测试.
*/
public String download(String object) {
GetObjectArgs args = GetObjectArgs.builder().bucket(config.getBucket()).object(object).build();
try {
// 读取对象内容
try (InputStream inputStream = client.getObject(args)) {
// Read the input stream and print to the console till EOF.
byte[] buf = new byte[16384];
int bytesRead;
StringBuilder content = new StringBuilder();
while ((bytesRead = inputStream.read(buf, 0, buf.length)) >= 0) {
String readContent = new String(buf, 0, bytesRead, StandardCharsets.UTF_8);
content.append(readContent);
}
return content.toString();
}
} catch (Exception ex) {
log.error("MinIO 文件下载错误:", ex);
throw new RuntimeException(ex.getMessage());
}
}
/**
* 根据url下载文件 此方法未测试.
*/
public String downloadByUrl(String url) {
return download(extractObjectName(url));
}
/**
* 提取 url 中的 minio 对象路径.
*
* @param url 例:<a href="http://127.0.0.1:9000/furniture/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png">...</a>
* @return 例:/materials/20230315/7375e567-d771-46a7-bccf-bb48886ab3d2.png
*/
public String extractObjectName(String url) {
return url.substring(minioPublicUrl("").length());
}
/**
* 将一个对象拷贝到指定路径.
*
* @param objectName minio 中需要拷贝的对象
* @param path minio 要拷贝到的路径
* @return 拷贝后路径的公网访问 url
*/
public String copy(String objectName, String path) {
String extension = getExtension(objectName);
String targetObject = genPath(path, extension);
CopyObjectArgs args = CopyObjectArgs.builder().bucket(config.getBucket()).object(targetObject)
.source(CopySource.builder().bucket(config.getBucket()).object(objectName).build()).build();
try {
client.copyObject(args);
} catch (Exception ex) {
log.error("minIO 文件拷贝失败:", ex);
throw new RuntimeException(ex.getMessage());
}
return minioPublicUrl(targetObject);
}
/**
* 通过 url 拷贝.
*
* @see MinioService#copy(String, String)
*/
public String copyByUrl(String url, String path) {
return copy(extractObjectName(url), path);
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yelink.minio.configure.MinioAutoconfigure