build: 提供默认MinIO存储

This commit is contained in:
fit2-zhao 2023-06-14 16:44:36 +08:00 committed by fit2-zhao
parent 3ba24d93a4
commit c31a9d7ee6
22 changed files with 376 additions and 6 deletions

View File

@ -1,10 +1,12 @@
package io.metersphere; package io.metersphere;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySource;
@ -18,6 +20,9 @@ import org.springframework.context.annotation.PropertySource;
"file:/opt/metersphere/conf/metersphere.properties", "file:/opt/metersphere/conf/metersphere.properties",
}, encoding = "UTF-8", ignoreResourceNotFound = true) }, encoding = "UTF-8", ignoreResourceNotFound = true)
@ServletComponentScan @ServletComponentScan
@EnableConfigurationProperties({
MinioProperties.class
})
public class Application { public class Application {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);

View File

@ -0,0 +1,29 @@
package io.metersphere.sdk.config;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig {
public static final String BUCKET = "metersphere";
@Bean
public MinioClient minioClient(MinioProperties minioProperties) throws Exception {
// 创建 MinioClient 客户端
MinioClient minioClient = MinioClient.builder()
.endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.build();
boolean exist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(BUCKET).build());
if (!exist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(BUCKET).build());
}
return minioClient;
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.sdk.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = MinioProperties.MINIO_PREFIX)
@Getter
@Setter
public class MinioProperties {
public static final String MINIO_PREFIX = "minio";
private String endpoint;
private String accessKey;
private String secretKey;
}

View File

@ -0,0 +1,5 @@
package io.metersphere.sdk.constants;
public enum StorageConstants {
MINIO, GIT
}

View File

@ -0,0 +1,20 @@
package io.metersphere.sdk.file;
import io.metersphere.sdk.util.CommonBeanFactory;
public class FileCenter {
// 多种实现时打开
/*public static FileRepository getRepository(String storage) {
if (StringUtils.equals(StorageConstants.GIT.name(), storage)) {
LogUtils.info("扩展GIT存储方式");
return null;
} else {
return getDefaultRepository();
}
}*/
public static FileRepository getDefaultRepository() {
return CommonBeanFactory.getBean(MinioRepository.class);
}
}

View File

@ -0,0 +1,43 @@
package io.metersphere.sdk.file;
import org.springframework.web.multipart.MultipartFile;
public interface FileRepository {
/**
* 保存文件
*
* @param file
* @param request
* @return
* @throws Exception
*/
public String saveFile(MultipartFile file, FileRequest request) throws Exception;
/**
* 保存文件
*
* @param bytes
* @param request
* @return
* @throws Exception
*/
public String saveFile(byte[] bytes, FileRequest request) throws Exception;
/**
* 删除文件
*
* @param request
* @throws Exception
*/
public void delete(FileRequest request) throws Exception;
/**
* 获取文件字节内容
*
* @param request
* @return
* @throws Exception
*/
public byte[] getFile(FileRequest request) throws Exception;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.sdk.file;
import lombok.Data;
@Data
public class FileRequest {
// 项目id
private String projectId;
// 存储类型
private String storage;
// 资源id为空时存储在项目目录下
private String resourceId;
// 文件名称
private String fileName;
}

View File

@ -0,0 +1,114 @@
package io.metersphere.sdk.file;
import io.metersphere.sdk.config.MinioConfig;
import io.minio.*;
import io.minio.messages.Item;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@Component
public class MinioRepository implements FileRepository {
@Resource
private MinioClient client;
private String getPath(FileRequest request) {
// 文件存储路径
return StringUtils.join(
request.getProjectId(),
File.separator,
StringUtils.isNotBlank(request.getResourceId()) ? request.getResourceId() + File.separator : StringUtils.EMPTY,
request.getFileName());
}
@Override
public String saveFile(MultipartFile file, FileRequest request) throws Exception {
// 文件存储路径
String filePath = getPath(request);
client.putObject(PutObjectArgs.builder()
.bucket(MinioConfig.BUCKET)
.object(filePath)
.stream(file.getInputStream(), file.getSize(), -1) // 文件内容
.build());
return request.getFileName();
}
@Override
public String saveFile(byte[] bytes, FileRequest request) throws Exception {
String filePath = getPath(request);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
client.putObject(PutObjectArgs.builder()
.bucket(MinioConfig.BUCKET)
.object(filePath)
.stream(inputStream, bytes.length, -1)
.build());
}
return request.getFileName();
}
@Override
public void delete(FileRequest request) throws Exception {
String filePath = getPath(request);
// 删除单个文件
removeObject(MinioConfig.BUCKET, filePath);
}
private boolean removeObject(String bucketName, String objectName) throws Exception {
client.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName) // 存储桶
.object(objectName) // 文件名
.build());
return true;
}
public void removeObjects(String bucketName, String objectName) throws Exception {
List<String> objects = listObjects(bucketName, objectName);
for (String object : objects) {
removeObject(bucketName, object);
}
}
/**
* 递归获取某路径下的所有文件
*/
public List<String> listObjects(String bucketName, String objectName) throws Exception {
List<String> list = new ArrayList<>(12);
Iterable<Result<Item>> results = client.listObjects(
ListObjectsArgs.builder()
.bucket(bucketName)
.prefix(objectName)
.build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir()) {
List<String> files = listObjects(bucketName, item.objectName());
list.addAll(files);
} else {
list.add(item.objectName());
}
}
return list;
}
@Override
public byte[] getFile(FileRequest request) throws Exception {
return getFileAsStream(request).readAllBytes();
}
public InputStream getFileAsStream(FileRequest request) throws Exception {
String fileName = getPath(request);
return client.getObject(GetObjectArgs.builder()
.bucket(MinioConfig.BUCKET) // 存储桶
.object(fileName) // 文件名
.build());
}
}

View File

@ -1,10 +1,12 @@
package io.metersphere.api; package io.metersphere.api;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.api"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.api"})
public class Application { public class Application {

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -1,10 +1,12 @@
package io.metersphere.bug; package io.metersphere.bug;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.bug"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.bug"})
public class Application { public class Application {

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -1,11 +1,13 @@
package io.metersphere.functional; package io.metersphere.functional;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -14,6 +16,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.functional"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.functional"})
public class Application { public class Application {

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -1,10 +1,12 @@
package io.metersphere.project; package io.metersphere.project;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.project"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.project"})
public class Application { public class Application {

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -1,10 +1,12 @@
package io.metersphere.system; package io.metersphere.system;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.system"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.system"})
public class Application { public class Application {

View File

@ -0,0 +1,89 @@
package io.metersphere.system.controller;
import io.metersphere.sdk.file.FileCenter;
import io.metersphere.sdk.file.FileRepository;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.file.MinioRepository;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
@SpringBootTest
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class FileCenterTest {
@Resource
private MinioRepository repository;
@Test
@Order(1)
public void testRepository() throws Exception {
FileRepository repository = FileCenter.getDefaultRepository();
Assertions.assertTrue(repository instanceof MinioRepository);
}
@Test
@Order(2)
public void testSaveFile() throws Exception {
MockMultipartFile mockFile = new MockMultipartFile(
"file",
"test.txt",
"text/plain",
"Hello, World!".getBytes()
);
// 创建一个FileRequest对象作为测试用的请求参数
FileRequest request = new FileRequest();
request.setFileName("test.txt");
request.setProjectId("test-project");
request.setResourceId("test-resource-id");
repository.saveFile(mockFile, request);
Assertions.assertTrue(repository.saveFile(mockFile, request) != null);
Assertions.assertTrue(repository.saveFile("Hello, World!".getBytes(), request) != null);
}
@Test
@Order(3)
public void testGetFile() throws Exception {
// 创建一个FileRequest对象作为测试用的请求参数
FileRequest request = new FileRequest();
request.setFileName("test.txt");
request.setProjectId("test-project");
request.setResourceId("test-resource-id");
repository.getFile(request);
Assertions.assertTrue(repository.getFile(request) != null);
}
@Test
@Order(4)
public void testDelFile() throws Exception {
// 创建一个FileRequest对象作为测试用的请求参数
FileRequest request = new FileRequest();
request.setFileName("test.txt");
request.setProjectId("test-project");
request.setResourceId("test-resource-id");
repository.delete(request);
}
@Test
@Order(5)
public void testFile() throws Exception {
MockMultipartFile mockFile = new MockMultipartFile(
"file",
"test.txt",
"text/plain",
"Hello, World!".getBytes()
);
// 创建一个FileRequest对象作为测试用的请求参数
FileRequest request = new FileRequest();
request.setFileName("test.txt");
request.setProjectId("test-project");
repository.saveFile(mockFile, request);
Assertions.assertTrue(repository.saveFile(mockFile, request) != null);
Assertions.assertTrue(repository.saveFile("Hello, World!".getBytes(), request) != null);
}
}

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -1,10 +1,12 @@
package io.metersphere.plan; package io.metersphere.plan;
import io.metersphere.sdk.config.MinioProperties;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration;
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,9 @@ import org.springframework.context.annotation.ComponentScan;
LdapAutoConfiguration.class, LdapAutoConfiguration.class,
Neo4jAutoConfiguration.class Neo4jAutoConfiguration.class
}) })
@EnableConfigurationProperties({
MinioProperties.class
})
@ServletComponentScan @ServletComponentScan
@ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.plan"}) @ComponentScan(basePackages = {"io.metersphere.sdk", "io.metersphere.plan"})
public class Application { public class Application {

View File

@ -10,4 +10,4 @@ embedded.redis.enabled=true
# kafka # kafka
embedded.kafka.enabled=false embedded.kafka.enabled=false
# minio # minio
embedded.minio.enabled=false embedded.minio.enabled=true

View File

@ -223,6 +223,7 @@
<exclude>io/metersphere/**/dto/**</exclude> <exclude>io/metersphere/**/dto/**</exclude>
<exclude>io/metersphere/**/config/**</exclude> <exclude>io/metersphere/**/config/**</exclude>
<exclude>io/metersphere/**/constants/**</exclude> <exclude>io/metersphere/**/constants/**</exclude>
<exclude>io/metersphere/sdk/**</exclude>
</excludes> </excludes>
</configuration> </configuration>
<executions> <executions>