minio分权限存储

This commit is contained in:
2025-11-12 18:08:37 +08:00
parent 4b9e9ad89b
commit 09992e1887
24 changed files with 723 additions and 81 deletions

View File

@@ -83,6 +83,13 @@
<version>8.5.7</version> <!-- 使用最新版本 -->
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio-admin</artifactId>
<version>8.5.7</version> <!-- 可根据最新版本替换 -->
</dependency>
<!-- Apache Commons IO 用于流操作 -->
<dependency>
<groupId>commons-io</groupId>

View File

@@ -1,31 +1,40 @@
package com.sdm.data.config;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import io.minio.admin.MinioAdminClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.port}") // API端口默认9000
// API端口默认9000
private int port;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.bucket-name}")
private String bucketName;
@Value("${minio.secure}")
private boolean secure;
private String secretBusinessBucket; // 保密业务代码桶
private String spdmBucket; // 普通业务数据桶
// 初始化超级管理员客户端(用于 admin 操作:桶、用户、策略)
@Bean
public MinioAdminClient minioAdminClient() {
return MinioAdminClient.builder()
.endpoint(endpoint, port, secure)
.credentials(accessKey, secretKey)
.build();
}
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
@@ -33,8 +42,4 @@ public class MinioConfig {
.credentials(accessKey, secretKey)
.build();
}
public String getBucketName() {
return bucketName;
}
}
}

View File

@@ -351,6 +351,14 @@ public class DataFileController implements IDataFeignClient {
return IDataFileService.uploadAvatar(avatar);
}
@PostMapping(value = "/uploadScriptFile")
@Operation(summary = "上传脚本文件", description = "上传脚本文件")
public SdmResponse uploadScriptFile(@RequestParam("script") MultipartFile script) {
return IDataFileService.uploadScriptFile(script);
}
// 图片预览
@GetMapping("/previewImage")

View File

@@ -0,0 +1,127 @@
package com.sdm.data.controller;
import com.sdm.common.common.SdmResponse;
import com.sdm.data.model.req.MinioCreatePolicyReq;
import com.sdm.data.model.req.MinioCreateUserReq;
import com.sdm.data.model.req.MinioCreateUserWithBucketReq;
import com.sdm.data.service.impl.MinioAdminService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/minioAdmin")
@Tag(name = "MinIO管理接口", description = "用于管理MinIO用户和策略的接口")
public class MinioAdminController {
@Autowired
private MinioAdminService minioAdminService;
@PostMapping("/createUserWithBucketFullAccess")
@Operation(summary = "创建MinIO用户并授予bucket访问权限", description = "创建一个新的MinIO用户并授予bucket访问权限")
public SdmResponse createUserWithBucketFullAccess(@RequestBody MinioCreateUserWithBucketReq req) {
try {
minioAdminService.createUserWithBucketFullAccess(req.getUsername(), req.getPassword(), req.getBucketName());
return SdmResponse.success("创建MinIO用户并授予bucket访问权限成功");
} catch (InvalidCipherTextException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
log.error("创建MinIO用户失败: username={}, bucketName={}", req.getUsername(), req.getBucketName(), e);
return SdmResponse.failed("创建用户失败: " + e.getMessage());
}
}
@PostMapping("/createUser")
@Operation(summary = "创建MinIO用户", description = "创建一个新的MinIO用户并可选地关联策略")
public SdmResponse createUser(@RequestBody MinioCreateUserReq req) {
try {
minioAdminService.createUser(req.getAccessKey(), req.getSecretKey(), req.getPolicyName(), req.getMemberOf());
return SdmResponse.success("用户创建成功");
} catch (Exception e) {
log.error("创建MinIO用户失败: accessKey={}", req.getAccessKey(), e);
return SdmResponse.failed("创建用户失败: " + e.getMessage());
}
}
@GetMapping("/deleteUser")
@Operation(summary = "删除MinIO用户", description = "根据用户名删除指定的MinIO用户")
public SdmResponse deleteUser(@RequestParam String accessKey) {
try {
minioAdminService.deleteUser(accessKey);
return SdmResponse.success("用户删除成功");
} catch (Exception e) {
log.error("删除MinIO用户失败: accessKey={}", accessKey, e);
return SdmResponse.failed("删除用户失败: " + e.getMessage());
}
}
@PostMapping("/createPolicy")
@Operation(summary = "创建访问策略", description = "创建一个新的访问策略")
public SdmResponse createPolicy(@RequestBody MinioCreatePolicyReq req) {
try {
minioAdminService.createPolicy(req.getPolicyName(), req.getPolicyJson());
return SdmResponse.success("策略创建成功");
} catch (Exception e) {
log.error("创建MinIO策略失败: policyName={}", req.getPolicyName(), e);
return SdmResponse.failed("创建策略失败: " + e.getMessage());
}
}
@GetMapping("attachPolicyToUser")
@Operation(summary = "绑定策略到用户", description = "将指定的策略绑定到用户")
public SdmResponse attachPolicyToUser(
@RequestParam String username,
@RequestParam String policyName) {
try {
minioAdminService.attachPolicyToUser(username, policyName);
return SdmResponse.success("策略绑定成功");
} catch (Exception e) {
log.error("绑定策略到用户失败: username={}, policyName={}", username, policyName, e);
return SdmResponse.failed("绑定策略失败: " + e.getMessage());
}
}
@GetMapping("/listAllUsers")
@Operation(summary = "列出所有用户", description = "获取所有MinIO用户的列表")
public SdmResponse listAllUsers() {
try {
List<String> users = minioAdminService.listAllUsers();
return SdmResponse.success(users);
} catch (Exception e) {
log.error("列出MinIO用户失败", e);
return SdmResponse.failed("获取用户列表失败: " + e.getMessage());
}
}
@GetMapping("/listAllPolicies")
@Operation(summary = "列出所有策略", description = "获取所有MinIO策略的列表")
public SdmResponse listAllPolicies() {
try {
Map<String, String> policies = minioAdminService.listAllPolicies();
return SdmResponse.success(policies);
} catch (Exception e) {
log.error("列出MinIO策略失败", e);
return SdmResponse.failed("获取策略列表失败: " + e.getMessage());
}
}
@GetMapping("/getUserPolicy")
@Operation(summary = "获取用户策略", description = "获取指定用户关联的策略")
public SdmResponse getUserPolicy(@RequestParam String username) {
try {
String policy = minioAdminService.getUserPolicy(username);
return SdmResponse.success(policy);
} catch (Exception e) {
log.error("获取用户策略失败: username={}", username, e);
return SdmResponse.failed("获取用户策略失败: " + e.getMessage());
}
}
}

View File

@@ -63,10 +63,13 @@ public class FileSearchReq extends BaseReq {
private String fileSizeUnit;
@Schema(description = "文件创建时间")
private LocalDateTime createTime;
@Schema(description = "文件创建搜索开始时间")
private LocalDateTime startTime;
@Schema(description = "文件业务类型1模型文件 2仿真报告、3计算文件、4曲线文件、5云图文件6网格文件7计算过程文件",implementation = FileBizTypeEnum.class)
@Schema(description = "文件创建搜索结束时间")
private LocalDateTime endTime;
@Schema(description = "文件业务类型1模型文件 2仿真报告、3计算文件、4曲线文件、5云图文件6网格文件7计算过程文件",enumAsRef = true)
private Integer fileBizType;
/**

View File

@@ -0,0 +1,15 @@
package com.sdm.data.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "创建MinIO策略请求参数")
public class MinioCreatePolicyReq {
@Schema(description = "策略名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String policyName;
@Schema(description = "策略内容JSON格式", requiredMode = Schema.RequiredMode.REQUIRED)
private String policyJson;
}

View File

@@ -0,0 +1,23 @@
package com.sdm.data.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "创建MinIO用户请求参数")
public class MinioCreateUserReq {
@Schema(description = "访问密钥(用户名)", requiredMode = Schema.RequiredMode.REQUIRED)
private String accessKey;
@Schema(description = "私有密钥(密码)", requiredMode = Schema.RequiredMode.REQUIRED)
private String secretKey;
@Schema(description = "策略名称")
private String policyName;
@Schema(description = "所属用户组列表")
private List<String> memberOf;
}

View File

@@ -0,0 +1,18 @@
package com.sdm.data.model.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "创建MinIO用户并授予bucket访问权限请求参数")
public class MinioCreateUserWithBucketReq {
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
@Schema(description = "桶名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String bucketName;
}

View File

@@ -19,10 +19,16 @@ public class QueryBigFileReq extends BaseReq{
private String fileName;
/**
* 文件创建时间 createTime
* 文件创建时间 startTime
*/
@Schema(description = "文件创建时间")
private LocalDateTime createTime;
@Schema(description = "文件创建搜索开始时间")
private LocalDateTime startTime;
/**
* 文件创建时间 endTime
*/
@Schema(description = "文件创建搜索结束时间")
private LocalDateTime endTime;
/**
* 文件大小

View File

@@ -200,6 +200,15 @@ public interface IDataFileService {
return null;
}
/**
* 上传脚本文件
* @param scriptFile 脚本
* @return 上传文件id
*/
default SdmResponse<Long> uploadScriptFile(MultipartFile scriptFile){
return null;
}
/**
* 图片预览
*

View File

@@ -13,6 +13,13 @@ public interface IMinioService {
* @return 存储桶名称
*/
public String getBucketName();
/**
* 获取MinIO的保密业务存储桶名称
*
* @return 保密业务存储桶名称
*/
public String getSecretBusinessBucketName();
/**
* 创建目录
@@ -20,6 +27,14 @@ public interface IMinioService {
* @param objectKey 绝对路径目录名
*/
public void createDirectoryByObjectKey(String objectKey);
/**
* 创建目录
*
* @param objectKey 绝对路径目录名
* @param bucketName 桶名称
*/
public void createDirectoryByObjectKey(String objectKey, String bucketName);
/**
* 递归删除指定目录下的所有对象。
@@ -27,12 +42,27 @@ public interface IMinioService {
* @param directoryName 目录名称objectKey
*/
public void deleteDirectoryRecursively(String directoryName);
/**
* 递归删除指定目录下的所有对象。
*
* @param directoryName 目录名称objectKey
* @param bucketName 桶名称
*/
public void deleteDirectoryRecursively(String directoryName, String bucketName);
/**
* 从MinIO删除文件
* @param minioObjectKey 文件名objectKey
*/
public void deleteFile(String minioObjectKey);
/**
* 从MinIO删除文件
* @param minioObjectKey 文件名objectKey
* @param bucketName 桶名称
*/
public void deleteFile(String minioObjectKey, String bucketName);
/**
* 上传文件到MinIO
@@ -41,6 +71,15 @@ public interface IMinioService {
* @param tags 文件标签
*/
public void uploadFile(MultipartFile file, String objectName, Map<String, String> tags);
/**
* 上传文件到MinIO
* @param file 文件对象
* @param objectName 文件名objectKey
* @param tags 文件标签
* @param bucketName 桶名称
*/
public void uploadFile(MultipartFile file, String objectName, Map<String, String> tags, String bucketName);
/**
* 重命名MinIO中的文件
@@ -50,6 +89,16 @@ public interface IMinioService {
* @throws Exception 异常信息
*/
public void renameFile(String oldObjectName, String newObjectName);
/**
* 重命名MinIO中的文件
*
* @param oldObjectName 原文件名objectKey
* @param newObjectName 新文件名objectKey
* @param bucketName 桶名称
* @throws Exception 异常信息
*/
public void renameFile(String oldObjectName, String newObjectName, String bucketName);
/**
@@ -57,14 +106,28 @@ public interface IMinioService {
* @param objectName 文件名objectKey
*/
byte[] downloadFile(String objectName) throws Exception;
/**
* 从MinIO下载文件
* @param objectName 文件名objectKey
* @param bucketName 桶名称
*/
byte[] downloadFile(String objectName, String bucketName) throws Exception;
InputStream getMinioInputStream(String objectName);
InputStream getMinioInputStream(String objectName, String bucketName);
/**
* 根据标签查询文件
*/
List<String> queryFilesByTags(Map<String, String> tags) throws Exception;
/**
* 根据标签查询文件
*/
List<String> queryFilesByTags(Map<String, String> tags, String bucketName) throws Exception;
String getMinioPresignedUrl(String objectKey);
String getMinioPresignedUrl(String objectKey, String bucketName);
}
}

View File

@@ -179,7 +179,8 @@ public class DataStorageAnalysisImpl implements DataStorageAnalysis {
.eq(ObjectUtils.isNotEmpty(queryBigFileReq.getFileSuffix()), FileStorage::getFileSuffix, queryBigFileReq.getFileSuffix())
.eq(ObjectUtils.isNotEmpty(queryBigFileReq.getFileBizType()), FileStorage::getFileBizType, queryBigFileReq.getFileBizType())
.like(ObjectUtils.isNotEmpty(queryBigFileReq.getFileName()), FileStorage::getFileName, queryBigFileReq.getFileName())
.gt(ObjectUtils.isNotEmpty(queryBigFileReq.getCreateTime()), FileStorage::getCreateTime, queryBigFileReq.getCreateTime())
.gt(ObjectUtils.isNotEmpty(queryBigFileReq.getStartTime()), FileStorage::getCreateTime, queryBigFileReq.getStartTime())
.lt(ObjectUtils.isNotEmpty(queryBigFileReq.getEndTime()), FileStorage::getCreateTime, queryBigFileReq.getEndTime())
.gt(ObjectUtils.isNotEmpty(fileSizeInBytes), FileStorage::getFileSize, fileSizeInBytes).list();
PageInfo<FileStorage> page = new PageInfo<>(list);
return PageUtils.getJsonObjectSdmResponse(list, page);

View File

@@ -0,0 +1,201 @@
package com.sdm.data.service.impl;
import com.sdm.data.config.MinioConfig;
import io.minio.admin.MinioAdminClient;
import io.minio.admin.UserInfo;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class MinioAdminService {
@Autowired
private MinioAdminClient minioAdminClient;
@Autowired
private MinioConfig minioConfig;
/**
* 创建MinIO用户
*
* @param accessKey 用户名(AccessKey)
* @param secretKey 密码(SecretKey)
* @param policyName 关联的策略名称(可为null)
* @param memberOf 所属用户组列表(可为null)
*/
public void createUser(String accessKey, String secretKey, String policyName, List<String> memberOf) throws InvalidCipherTextException, NoSuchAlgorithmException, IOException, InvalidKeyException {
// 用户状态设置为启用
minioAdminClient.addUser(accessKey, UserInfo.Status.ENABLED, secretKey, policyName, memberOf);
}
/**
* 创建用户并为其分配指定桶的完全访问权限
*
* @param username 用户名
* @param password 密码
* @param bucketName 桶名称
* @throws InvalidCipherTextException 无效加密文本异常
* @throws NoSuchAlgorithmException 无此算法异常
* @throws IOException IO异常
* @throws InvalidKeyException 无效密钥异常
*/
public void createUserWithBucketFullAccess(String username, String password, String bucketName)
throws InvalidCipherTextException, NoSuchAlgorithmException, IOException, InvalidKeyException {
// 生成策略名称
String policyName = bucketName + "-full-access-policy";
// 创建针对指定桶的完全访问策略
createBucketFullAccessPolicy(policyName, bucketName);
// 创建用户
createUser(username, password, null,null);
// 绑定策略到用户
attachPolicyToUser(username, policyName);
}
/**
* 删除MinIO用户
*
* @param accessKey 用户名(AccessKey)
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public void deleteUser(String accessKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
minioAdminClient.deleteUser(accessKey);
}
/**
* 创建MinIO访问策略
*
* @param policyName 策略名称
* @param policyJson 策略JSON字符串
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public void createPolicy(String policyName, String policyJson)
throws NoSuchAlgorithmException, InvalidKeyException, IOException {
minioAdminClient.addCannedPolicy(policyName, policyJson);
}
/**
* 创建针对指定桶的完全访问策略
*
* @param policyName 策略名称
* @param bucketName 桶名称
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public void createBucketFullAccessPolicy(String policyName, String bucketName)
throws NoSuchAlgorithmException, InvalidKeyException, IOException {
String policyJson = "{\n" +
" \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Action\": \"s3:*\",\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::" + bucketName + "\",\n" +
" \"arn:aws:s3:::" + bucketName + "/*\"\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
createPolicy(policyName, policyJson);
}
/**
* 将策略绑定到用户
*
* @param username 用户名
* @param policyName 策略名称
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public void attachPolicyToUser(String username, String policyName)
throws NoSuchAlgorithmException, InvalidKeyException, IOException {
// 第二个参数false表示操作对象是用户而非用户组
minioAdminClient.setPolicy(username, false, policyName);
}
/**
* 列出所有MinIO用户
*
* @return 用户列表(AccessKey集合)
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
* @throws InvalidCipherTextException 无效加密文本异常
*/
public List<String> listAllUsers()
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidCipherTextException {
Map<String, UserInfo> userMap = minioAdminClient.listUsers();
return new ArrayList<>(userMap.keySet());
}
/**
* 列出所有MinIO访问策略
*
* @return 策略名称列表
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public Map<String,String> listAllPolicies() throws NoSuchAlgorithmException, InvalidKeyException, IOException {
Map<String,String> policyNames = minioAdminClient.listCannedPolicies();
return policyNames;
}
/**
* 查询用户关联的策略
*
* @param username 用户名(AccessKey)
* @return 策略名称(无关联策略时返回null)
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
* @throws InvalidCipherTextException 无效加密文本异常
*/
public String getUserPolicy(String username)
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidCipherTextException {
Map<String, UserInfo> userMap = minioAdminClient.listUsers();
UserInfo userInfo = userMap.get(username);
if (userInfo != null) {
return userInfo.policyName();
}
return null;
}
/**
* 根据策略名称查询具体策略内容
*
* @param policyName 策略名称
* @return 策略的JSON字符串内容若策略不存在则返回null
* @throws NoSuchAlgorithmException 无此算法异常
* @throws InvalidKeyException 无效密钥异常
* @throws IOException IO异常
*/
public String getPolicyByPolicyName(String policyName)
throws NoSuchAlgorithmException, InvalidKeyException, IOException {
// 先检查策略是否存在
if (minioAdminClient.listCannedPolicies().containsKey(policyName)) {
// 获取并返回策略的JSON内容
return minioAdminClient.listCannedPolicies().get(policyName);
}
return null;
}
}

View File

@@ -65,9 +65,11 @@ import java.util.stream.Collectors;
@Slf4j
@Service
public class MinioFileIDataFileServiceImpl implements IDataFileService {
private static final List<DirTypeEnum> INIT_DIR = List.of(
// 初始化用户业务库目录
private static final List<DirTypeEnum> INIT_SPMD_DIR = List.of(
DirTypeEnum.KNOWLEDGE_BASE_DIR, DirTypeEnum.PROJECT_NODE_DIR,
DirTypeEnum.AVATAR_DIR, DirTypeEnum.SIMULATION_PARAMETER_DIR, DirTypeEnum.TRAIN_MODEL_DIR);
DirTypeEnum.AVATAR_DIR, DirTypeEnum.SIMULATION_PARAMETER_DIR, DirTypeEnum.TRAIN_MODEL_DIR,DirTypeEnum.SCRIPT_DIR);
// fileData 知识库文件列表可见的数据
private final List<Integer> fileDatdList = Arrays.asList(
NumberConstants.ZERO,
@@ -599,8 +601,9 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
public SdmResponse fileSearch(FileSearchReq minioFileSearchReq) {
FileMetadataInfo fileMetadataInfo = null;
QueryBigFileReq queryBigFileReq = new QueryBigFileReq();
FileMetadataInfo fileMetadataInfo = null;
if (ObjectUtils.isNotEmpty(minioFileSearchReq.getParentUuid())) {
// 项目节点下搜索文件
fileMetadataInfo = fileMetadataInfoService.lambdaQuery().eq(FileMetadataInfo::getRelatedResourceUuid, minioFileSearchReq.getParentUuid()).one();
@@ -609,13 +612,12 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
fileMetadataInfo = fileMetadataInfoService.getById(minioFileSearchReq.getParentDirId());
}
if (ObjectUtils.isEmpty(fileMetadataInfo)) {
return SdmResponse.success();
if (ObjectUtils.isNotEmpty(fileMetadataInfo)) {
queryBigFileReq.setDirId(fileMetadataInfo.getId());
}
QueryBigFileReq queryBigFileReq = new QueryBigFileReq();
BeanUtils.copyProperties(minioFileSearchReq, queryBigFileReq);
queryBigFileReq.setDirId(fileMetadataInfo.getId());
SdmResponse<PageDataResp<List<FileStorage>>> searchResult = dataStorageAnalysis.listBigFile(queryBigFileReq);
PageDataResp<List<FileStorage>> pageDataResp = searchResult.getData();
List<FileStorage> data = pageDataResp.getData();
@@ -812,7 +814,7 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
@Override
@Transactional(rollbackFor = Exception.class)
public boolean initSystemDirectory(String company) {
for (DirTypeEnum dirType : INIT_DIR) {
for (DirTypeEnum dirType : INIT_SPMD_DIR) {
String dirMinioObjectKey = getDirMinioObjectKey(dirType.getDirName());
try {
// 检查目录是否已存在
@@ -1451,20 +1453,65 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
String newFilename = filenameWithoutSuffix + "_" + timestamp + "." + suffix;
// 再上传训练模型文件
String simulationParamMinioObjectKey = getFileMinioObjectKey(trainModelDirMetadataInfo.getObjectKey() + newFilename);
String trainingMinioObjectKey = getFileMinioObjectKey(trainModelDirMetadataInfo.getObjectKey() + newFilename);
try {
minioService.uploadFile(trainModelFile, simulationParamMinioObjectKey, null);
minioService.uploadFile(trainModelFile, trainingMinioObjectKey, null);
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(simulationParamMinioObjectKey, originalFilename, null,
FileMetadataInfo fileInfo = createFileMetadata(trainingMinioObjectKey, originalFilename, null,
null, null, null, parTrainModelDirId, trainModelFile.getSize());
fileMetadataInfoService.save(fileInfo);
return SdmResponse.success(fileInfo.getId());
} catch (Exception e) {
minioService.deleteFile(simulationParamMinioObjectKey);
log.error("上传仿真参数文件", e);
throw new RuntimeException("上传仿真参数文件: " + e.getMessage(), e);
minioService.deleteFile(trainingMinioObjectKey);
log.error("上传训练模型文件失败", e);
throw new RuntimeException("上传训练模型文件失败: " + e.getMessage(), e);
}
}
@Override
public SdmResponse<Long> uploadScriptFile(MultipartFile scriptFile) {
// 先创建 script 目录
String dirMinioObjectKey = getDirMinioObjectKey(DirTypeEnum.SCRIPT_DIR.getDirName());
Optional<FileMetadataInfo> fileMetadataInfoByObjectKey = getFileMetadataInfoByObjectKey(dirMinioObjectKey);
// 检查目录是否已存在
if (!fileMetadataInfoByObjectKey.isPresent()) {
return SdmResponse.failed("script 目录不存在,等待initSystemDirectory 初始化完成");
}
// 检查文件是否为空
if (scriptFile == null || scriptFile.isEmpty()) {
return SdmResponse.failed("请选择要上传的仿真参数库文件");
}
// 获取 script 目录的 id
FileMetadataInfo scriptDirMetadataInfo = fileMetadataInfoByObjectKey.get();
Long parScriptDirId = scriptDirMetadataInfo.getId();
String originalFilename = scriptFile.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
// 生成带时间戳的文件名以避免冲突
String filenameWithoutSuffix = originalFilename.substring(0, originalFilename.lastIndexOf("."));
String timestamp = String.valueOf(System.currentTimeMillis());
String newFilename = filenameWithoutSuffix + "_" + timestamp + "." + suffix;
// 再上传脚本文件
String scriptFileMinioObjectKey = getFileMinioObjectKey(scriptDirMetadataInfo.getObjectKey() + newFilename);
try {
minioService.uploadFile(scriptFile, scriptFileMinioObjectKey, null);
// 创建目录元数据并保存到数据库
FileMetadataInfo fileInfo = createFileMetadata(scriptFileMinioObjectKey, originalFilename, null,
null, null, null, parScriptDirId, scriptFile.getSize());
fileMetadataInfoService.save(fileInfo);
return SdmResponse.success(fileInfo.getId());
} catch (Exception e) {
minioService.deleteFile(scriptFileMinioObjectKey);
log.error("上传传脚本文件", e);
throw new RuntimeException("上传传脚本文件: " + e.getMessage(), e);
}
}

View File

@@ -1,6 +1,5 @@
package com.sdm.data.service.minio;
import com.sdm.data.config.MinioConfig;
import com.sdm.data.service.IMinioService;
import io.minio.*;
@@ -16,6 +15,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -39,16 +39,41 @@ public class MinioService implements IMinioService {
this.minioConfig = minioConfig;
}
/**
* 在应用启动时初始化桶
*/
@PostConstruct
public void initializeBucket() {
try {
createBucketIfNotExists(minioConfig.getSpdmBucket());
log.info("桶{}初始化完成", minioConfig.getSpdmBucket());
} catch (Exception e) {
log.error("桶{}初始化失败", minioConfig.getSpdmBucket(), e);
}
try {
createBucketIfNotExists(minioConfig.getSecretBusinessBucket());
log.info("桶{}初始化完成", minioConfig.getSecretBusinessBucket());
} catch (Exception e) {
log.error("桶{}初始化失败", minioConfig.getSecretBusinessBucket(), e);
}
}
public String getBucketName() {
return minioConfig.getBucketName();
return minioConfig.getSpdmBucket();
}
public String getSecretBusinessBucketName() {
return minioConfig.getSecretBusinessBucket();
}
/**
* 创建目录
*
* @param objectKey 绝对路径目录名
* @param bucketName 桶名称
*/
public void createDirectoryByObjectKey(String objectKey) {
public void createDirectoryByObjectKey(String objectKey, String bucketName) {
try {
// 确保目录名以 "/" 结尾
@@ -57,7 +82,7 @@ public class MinioService implements IMinioService {
// 使用空内容创建目录对象
ObjectWriteResponse objectWriteResponse = minioClient.putObject(
PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(objectKey)
.stream(new ByteArrayInputStream(new byte[0]), 0, -1)
.build());
@@ -65,6 +90,15 @@ public class MinioService implements IMinioService {
throw new RuntimeException(e);
}
}
/**
* 创建目录默认使用spdmBucket
*
* @param objectKey 绝对路径目录名
*/
public void createDirectoryByObjectKey(String objectKey) {
createDirectoryByObjectKey(objectKey, minioConfig.getSpdmBucket());
}
private static String dealDirPath(String dirPath) {
if (dirPath.startsWith("/")) {
@@ -81,8 +115,9 @@ public class MinioService implements IMinioService {
* 递归删除指定目录下的所有对象。
*
* @param directoryName 目录名称
* @param bucketName 桶名称
*/
public void deleteDirectoryRecursively(String directoryName) {
public void deleteDirectoryRecursively(String directoryName, String bucketName) {
try {
// 确保目录名以 "/" 结尾
@@ -91,7 +126,7 @@ public class MinioService implements IMinioService {
// 列出目录下的所有对象
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.prefix(directoryName)
.recursive(true)
.build());
@@ -111,7 +146,7 @@ public class MinioService implements IMinioService {
Iterable<Result<DeleteError>> deleteErrors = minioClient.removeObjects(
RemoveObjectsArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.objects(deleteObjects)
.build());
@@ -126,17 +161,28 @@ public class MinioService implements IMinioService {
throw new RuntimeException(e);
}
}
/**
* 递归删除指定目录下的所有对象默认使用spdmBucket
*
* @param directoryName 目录名称
*/
public void deleteDirectoryRecursively(String directoryName) {
deleteDirectoryRecursively(directoryName, minioConfig.getSpdmBucket());
}
/**
* 从MinIO删除文件
* @param minioObjectKey 文件名
* @param bucketName 桶名称
*/
public void deleteFile(String minioObjectKey) {
public void deleteFile(String minioObjectKey, String bucketName) {
try {
// 确保目录名以 "/" 结尾
minioObjectKey = dealDirPath(minioObjectKey);
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(minioObjectKey)
.build());
} catch (Exception e) {
@@ -145,12 +191,23 @@ public class MinioService implements IMinioService {
}
/**
* 上传文件到MinIO
* 从MinIO删除文件默认使用spdmBucket
*/
public void uploadFile(MultipartFile file, String objectName, Map<String, String> tags) {
public void deleteFile(String minioObjectKey) {
deleteFile(minioObjectKey, minioConfig.getSpdmBucket());
}
/**
* 上传文件到MinIO
* @param file 文件对象
* @param objectName 文件名
* @param tags 文件标签
* @param bucketName 桶名称
*/
public void uploadFile(MultipartFile file, String objectName, Map<String, String> tags, String bucketName) {
try {
// 检查存储桶是否存在,不存在则创建
createBucketIfNotExists();
// 检查存储桶是否存在,不存在则创建(为了向后兼容,仍然保留此处检查)
createBucketIfNotExists(bucketName);
// 转换标签为MinIO格式
Map<String, String> tagMap = tags != null ? tags : Map.of();
@@ -159,7 +216,7 @@ public class MinioService implements IMinioService {
try (InputStream inputStream = file.getInputStream()) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(objectName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
@@ -172,14 +229,22 @@ public class MinioService implements IMinioService {
}
}
/**
* 上传文件到MinIO默认使用spdmBucket
*/
public void uploadFile(MultipartFile file, String objectName, Map<String, String> tags) {
uploadFile(file, objectName, tags, minioConfig.getSpdmBucket());
}
/**
* 重命名MinIO中的文件
*
* @param oldObjectName 原文件名
* @param newObjectName 新文件名
* @param bucketName 桶名称
* @throws Exception 异常信息
*/
public void renameFile(String oldObjectName, String newObjectName) {
public void renameFile(String oldObjectName, String newObjectName, String bucketName) {
try {
// 参数校验
if (!StringUtils.hasText(oldObjectName)) {
@@ -193,7 +258,7 @@ public class MinioService implements IMinioService {
// 检查原文件是否存在
StatObjectResponse stat = minioClient.statObject(
StatObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(oldObjectName)
.build());
@@ -201,10 +266,10 @@ public class MinioService implements IMinioService {
// 复制文件到新名称
minioClient.copyObject(
CopyObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(newObjectName)
.source(CopySource.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(oldObjectName)
.build())
.build());
@@ -212,7 +277,7 @@ public class MinioService implements IMinioService {
// 删除原文件
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(oldObjectName)
.build());
} catch (Exception e) {
@@ -220,28 +285,52 @@ public class MinioService implements IMinioService {
throw new RuntimeException(e);
}
}
/**
* 重命名MinIO中的文件默认使用spdmBucket
*
* @param oldObjectName 原文件名
* @param newObjectName 新文件名
* @throws Exception 异常信息
*/
public void renameFile(String oldObjectName, String newObjectName) {
renameFile(oldObjectName, newObjectName, minioConfig.getSpdmBucket());
}
/**
* 从MinIO下载文件
* @param objectName 对象名称
* @param bucketName 桶名称
*/
public byte[] downloadFile(String objectName) throws Exception {
public byte[] downloadFile(String objectName, String bucketName) throws Exception {
try (InputStream stream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(objectName)
.build())) {
return IOUtils.toByteArray(stream);
}
}
/**
* 从MinIO下载文件默认使用spdmBucket
*/
public byte[] downloadFile(String objectName) throws Exception {
return downloadFile(objectName, minioConfig.getSpdmBucket());
}
@Override
public InputStream getMinioInputStream(String objectName) {
return getMinioInputStream(objectName, minioConfig.getSpdmBucket());
}
public InputStream getMinioInputStream(String objectName, String bucketName) {
try {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.object(objectName)
.build());
} catch (Exception e) {
@@ -251,13 +340,15 @@ public class MinioService implements IMinioService {
/**
* 根据标签查询文件
* @param tags 标签
* @param bucketName 桶名称
*/
public List<String> queryFilesByTags(Map<String, String> tags) throws Exception {
public List<String> queryFilesByTags(Map<String, String> tags, String bucketName) throws Exception {
List<String> result = new ArrayList<>();
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.build());
for (Result<Item> itemResult : results) {
@@ -268,6 +359,13 @@ public class MinioService implements IMinioService {
}
return result;
}
/**
* 根据标签查询文件默认使用spdmBucket
*/
public List<String> queryFilesByTags(Map<String, String> tags) throws Exception {
return queryFilesByTags(tags, minioConfig.getSpdmBucket());
}
/**
* 检查文件标签是否匹配查询条件
@@ -293,27 +391,31 @@ public class MinioService implements IMinioService {
/**
* 检查存储桶是否存在,不存在则创建
*/
private void createBucketIfNotExists() throws ServerException, InsufficientDataException,
private void createBucketIfNotExists(String bucketName) throws ServerException, InsufficientDataException,
ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException,
InvalidResponseException, XmlParserException, InternalException {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.build());
if (!found) {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(minioConfig.getBucketName())
.bucket(bucketName)
.build());
}
}
@Override
public String getMinioPresignedUrl(String objectKey) {
return getMinioPresignedUrl(objectKey, minioConfig.getSpdmBucket());
}
public String getMinioPresignedUrl(String objectKey, String bucketName) {
String presignedUrl = null;
try {
// 生成预签名URL
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(minioConfig.getBucketName()).object(objectKey).expiry(3600, TimeUnit.SECONDS).build();
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectKey).expiry(3600, TimeUnit.SECONDS).build();
presignedUrl = minioClient.getPresignedObjectUrl(args);
} catch (Exception e) {
log.error("获取文件预览URL失败: " + objectKey, e);

View File

@@ -100,17 +100,12 @@ fileSystem:
system: system
chose: minio # 这里选择minio或者system
#地址: https://play.min.io
#凭据:
#账号: minioadmin / 密码: minioadmin
#特点:
#由 MinIO 官方提供,用于演示和测试。
#公开可访问,但数据可能定期清理。
#支持 S3 API适合快速验证代码
# spdmadmin/spdmadmin 作为普通用户只能看到spdm桶的数据
minio:
endpoint: 192.168.65.161
port: 9000
access-key: minioadmin
secret-key: minioadmin
secure: false
bucket-name: spdm
secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问)
spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限)

View File

@@ -95,7 +95,8 @@ minio:
access-key: minioadmin
secret-key: minioadmin
secure: false
bucket-name: spdm
secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问)
spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限)
management:

View File

@@ -148,4 +148,5 @@ minio:
access-key: minioadmin
secret-key: minioadmin
secure: false
bucket-name: spdm
secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问)
spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限)

View File

@@ -146,4 +146,5 @@ minio:
access-key: minioadmin
secret-key: minioadmin
secure: false
bucket-name: spdm
secret-business-bucket: secretbusiness # 存放保密业务代码、脚本的桶(仅超级管理员访问)
spdm-bucket: spdm # 普通业务数据桶(分配给用户读写权限)