diff --git a/java/pom.xml b/java/pom.xml
index 49eab6e..aecd25c 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -99,12 +99,6 @@
30.0-jre
-
-
- org.springframework.boot
- spring-boot-starter-tomcat
- provided
-
@@ -159,6 +153,14 @@
1.2.3
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+
+
+
+
org.springframework.boot
diff --git a/java/src/main/java/com/yfd/platform/modules/storage/controller/base/StorageSourceController.java b/java/src/main/java/com/yfd/platform/modules/storage/controller/base/StorageSourceController.java
index 6305fb9..4637e78 100644
--- a/java/src/main/java/com/yfd/platform/modules/storage/controller/base/StorageSourceController.java
+++ b/java/src/main/java/com/yfd/platform/modules/storage/controller/base/StorageSourceController.java
@@ -1,13 +1,11 @@
package com.yfd.platform.modules.storage.controller.base;
-import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.config.model.request.SaveStorageSourceRequest;
import com.yfd.platform.modules.config.model.request.UpdateStorageSortRequest;
-import com.yfd.platform.modules.experimentalData.domain.TsFiles;
import com.yfd.platform.modules.storage.convert.StorageSourceConvert;
import com.yfd.platform.modules.storage.model.dto.StorageSourceDTO;
import com.yfd.platform.modules.storage.model.entity.StorageSource;
@@ -22,7 +20,6 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
-import java.util.stream.Collectors;
/**
* 存储源基础设置模块接口
diff --git a/java/src/main/java/com/yfd/platform/modules/storage/convert/impl/StorageSourceConvertImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/convert/impl/StorageSourceConvertImpl.java
index 76a2971..42df9f2 100644
--- a/java/src/main/java/com/yfd/platform/modules/storage/convert/impl/StorageSourceConvertImpl.java
+++ b/java/src/main/java/com/yfd/platform/modules/storage/convert/impl/StorageSourceConvertImpl.java
@@ -18,21 +18,36 @@ import com.yfd.platform.modules.storage.model.result.StorageSourceAdminResult;
import com.yfd.platform.modules.storage.model.result.StorageSourceConfigResult;
import com.yfd.platform.modules.storage.model.result.StorageSourceResult;
import com.yfd.platform.utils.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONObject;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
-import java.io.File;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
import java.util.stream.Collectors;
-
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
/**
* @Date: 2025/1/10 12:44
* @Description:
@@ -161,35 +176,54 @@ public class StorageSourceConvertImpl implements StorageSourceConvert {
}
storageSource.setStoreContent(String.valueOf(result));
//存储路径
- LambdaQueryWrapper queryWrapperData = new LambdaQueryWrapper<>();
- queryWrapperData.eq(StorageSourceConfig::getStorageId, storageSource.getId());
- queryWrapperData.eq(StorageSourceConfig::getName, "filePath");
- String value = storageSourceConfigMapper.selectOne(queryWrapperData).getValue();
+ // 使用方式
+ String value = getConfigValue(storageSource.getId(), "filePath");
storageSource.setValueData(value);
+ String accessKeyValue = getConfigValue(storageSource.getId(), "accessKey");
+ String secretKeyValue = getConfigValue(storageSource.getId(), "secretKey");
+ String endPointValue = getConfigValue(storageSource.getId(), "endPoint");
+ Map minioConfig = new HashMap<>();
+ minioConfig.put("endPoint", endPointValue);
+ minioConfig.put("accessKey", accessKeyValue);
+ minioConfig.put("secretKey", secretKeyValue);
+ minioConfig.put("region", "us-east-1");
+ try {
+ Map diskSpace = getMinioDiskSpace(minioConfig);
- if(StringUtils.isNotBlank(value)){
- String[] values = value.split(";");
- // 将结果存入集合
- List valueList = Arrays.asList(values);
+ // 在main方法中添加计算逻辑
+ double freePercentage = (double)diskSpace.get("freeSpace") / diskSpace.get("totalSpace") * 100;
+ double freeSpaceGB = (double)diskSpace.get("freeSpace") / (1024 * 1024 * 1024);
+ storageSource.setSpaceOccupancyRatio(String.format("%.2f", freePercentage) + "%");
+ storageSource.setRemainingSpaceSize(String.format("%.1f", freeSpaceGB) + " GB");
- StringBuilder spaceOccupancyRatio = new StringBuilder();
- StringBuilder remainingSpaceSize = new StringBuilder();
-
- for (String valueData : valueList){
- String percentage = calculateLocalStorageUsage(valueData);
- String spaceSize = calculateFreeSpaceInGB(valueData);
- if (spaceOccupancyRatio.length() > 0) {
- spaceOccupancyRatio.append(";"); // 添加分号分隔
- }
- spaceOccupancyRatio.append(percentage);
- remainingSpaceSize.append(spaceSize);
- }
- //空间使用率
- storageSource.setSpaceOccupancyRatio(spaceOccupancyRatio.toString());
- storageSource.setRemainingSpaceSize(remainingSpaceSize.toString());
+ } catch (Exception e) {
+ e.printStackTrace();
}
+// if(StringUtils.isNotBlank(value)){
+// String[] values = value.split(";");
+// // 将结果存入集合
+// List valueList = Arrays.asList(values);
+//
+// StringBuilder spaceOccupancyRatio = new StringBuilder();
+// StringBuilder remainingSpaceSize = new StringBuilder();
+//
+// for (String valueData : valueList){
+// String percentage = calculateLocalStorageUsage(valueData);
+// String spaceSize = calculateFreeSpaceInGB(valueData);
+// if (spaceOccupancyRatio.length() > 0) {
+// spaceOccupancyRatio.append(";"); // 添加分号分隔
+// }
+// spaceOccupancyRatio.append(percentage);
+// remainingSpaceSize.append(spaceSize);
+// }
+// //空间使用率
+// storageSource.setSpaceOccupancyRatio(spaceOccupancyRatio.toString());
+// storageSource.setRemainingSpaceSize(remainingSpaceSize.toString());
+// }
+
+
}
}
@@ -216,6 +250,263 @@ public class StorageSourceConvertImpl implements StorageSourceConvert {
return resultPage;
}
+ private String getConfigValue(int storageId, String configName) {
+ return Optional.ofNullable(storageSourceConfigMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(StorageSourceConfig::getStorageId, storageId)
+ .eq(StorageSourceConfig::getName, configName)
+ )).map(StorageSourceConfig::getValue).orElse(null);
+ }
+
+
+ /**
+ * 获取Minio存储服务的磁盘空间信息
+ *
+ * @param minioConfig Minio配置信息,包含以下键值:
+ * - endPoint: Minio服务地址 (e.g., "http://192.168.1.208:9000")
+ * - accessKey: 访问密钥
+ * - secretKey: 秘密密钥
+ * - region: 区域(可选,默认为"us-east-1")
+ *
+ * @return 包含磁盘空间信息的Map:
+ * - totalSpace: 总空间(字节)
+ * - freeSpace: 可用空间(字节)
+ * - usedSpace: 已用空间(字节)
+ *
+ * @throws Exception 如果请求失败或解析出错
+ */
+ public static Map getMinioDiskSpace(Map minioConfig) throws Exception {
+ // 从配置中获取Minio连接信息
+ String endPoint = minioConfig.get("endPoint");
+ String accessKey = minioConfig.get("accessKey");
+ String secretKey = minioConfig.get("secretKey");
+
+ // 1. 构建Admin API URL
+ // 解析URI获取主机名、端口和协议
+ URI uri = new URI(endPoint);
+ String host = uri.getHost();
+ int port = uri.getPort();
+ String protocol = uri.getScheme();
+
+ // 组合完整的Admin API URL
+ String adminUrl = protocol + "://" + host + ":" + port + "/minio/admin/v3/info";
+
+ // 2. 生成签名所需的时间参数(使用UTC时区)
+ // 日期格式要求:
+ // amzDate: yyyyMMdd'T'HHmmss'Z' (e.g., "20230718T093000Z")
+ // dateStamp: yyyyMMdd (e.g., "20230718")
+ ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
+ String amzDate = now.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));
+ String dateStamp = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+
+ // 3. 准备请求头
+ // 使用TreeMap确保头字段按小写字母排序(AWS签名要求)
+ Map headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ headers.put("Host", host + ":" + port); // 主机头(必需)
+ headers.put("X-Amz-Date", amzDate); // AWS格式的时间戳
+ headers.put("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD"); // 表示没有请求体
+
+ // 4. 生成AWS V4签名
+ // 关键点:服务名使用"s3"(Minio Admin API使用S3签名方案)
+ String region = minioConfig.getOrDefault("region", "us-east-1"); // 获取区域或使用默认值
+ String authorization = getV4Signature(
+ accessKey,
+ secretKey,
+ "s3", // 服务名称(固定为"s3")
+ region, // 区域(如"us-east-1")
+ "GET", // HTTP方法
+ "/minio/admin/v3/info", // API路径
+ headers, // 请求头集合
+ dateStamp, // 日期戳
+ amzDate // 完整时间戳
+ );
+ headers.put("Authorization", authorization); // 添加签名到头信息
+
+ // 5. 配置HTTP客户端超时参数
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(5000) // 连接超时5秒
+ .setSocketTimeout(15000) // 响应超时15秒
+ .build();
+
+ // 使用try-with-resources确保HttpClient正确关闭
+ try (CloseableHttpClient client = HttpClients.custom()
+ .setDefaultRequestConfig(requestConfig)
+ .build()) {
+
+ // 创建GET请求
+ HttpGet request = new HttpGet(adminUrl);
+
+ // 添加所有请求头
+ headers.forEach(request::addHeader);
+
+ // 执行请求并获取响应
+ HttpResponse response = client.execute(request);
+ int statusCode = response.getStatusLine().getStatusCode();
+
+ // 读取响应内容
+ HttpEntity entity = response.getEntity();
+ String result = EntityUtils.toString(entity);
+
+ // 6. 检查HTTP状态码
+ if (statusCode != 200) {
+ // 错误处理:打印详细信息
+ System.err.println("Minio API请求失败. 状态码: " + statusCode);
+ System.err.println("响应内容: " + result);
+ System.err.println("请求URL: " + adminUrl);
+ System.err.println("签名参数: dateStamp=" + dateStamp + ", amzDate=" + amzDate);
+ System.err.println("签名服务: s3");
+
+ // 抛出异常
+ throw new RuntimeException("Minio API请求失败. 状态码: " + statusCode + ", 响应: " + result);
+ }
+
+ // 7. 解析磁盘空间数据
+ return parseDiskSpace(result);
+ }
+ }
+
+ /**
+ * 生成AWS V4签名
+ *
+ * 参考AWS官方文档:https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4_signing.html
+ */
+ private static String getV4Signature(String accessKey, String secretKey, String service,
+ String region, String method, String canonicalUri,
+ Map headers, String dateStamp, String amzDate)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+
+ // 1. 创建规范请求 (Canonical Request)
+ String canonicalQueryString = ""; // 没有查询参数
+
+ // 获取签名头列表(按小写字母排序)
+ String signedHeaders = getSignedHeaders(headers);
+
+ // 构建规范头(按小写字段名排序)
+ StringBuilder canonicalHeaders = new StringBuilder();
+ for (Map.Entry entry : headers.entrySet()) {
+ String key = entry.getKey().toLowerCase(); // 转换为小写
+ String value = entry.getValue().trim().replaceAll("\\s+", " "); // 标准化空格
+ canonicalHeaders.append(key).append(":").append(value).append("\n");
+ }
+
+ // 组装规范请求
+ String canonicalRequest = method + "\n" + // HTTP方法
+ canonicalUri + "\n" + // 规范路径
+ canonicalQueryString + "\n" + // 查询字符串
+ canonicalHeaders + "\n" + // 规范头
+ signedHeaders + "\n" + // 签名头列表
+ "UNSIGNED-PAYLOAD"; // 请求体哈希(无内容)
+
+ // 2. 创建待签名字符串 (String to Sign)
+ String credentialScope = dateStamp + "/" + region + "/" + service + "/aws4_request";
+ String stringToSign = "AWS4-HMAC-SHA256\n" + // 签名算法
+ amzDate + "\n" + // 请求时间戳
+ credentialScope + "\n" + // 凭证范围
+ sha256Hex(canonicalRequest); // 规范请求的SHA256哈希
+
+ // 3. 计算签名
+ // 生成签名密钥
+ byte[] kSecret = ("AWS4" + secretKey).getBytes(StandardCharsets.UTF_8);
+ byte[] kDate = hmacSha256(dateStamp, kSecret); // 日期密钥
+ byte[] kRegion = hmacSha256(region, kDate); // 区域密钥
+ byte[] kService = hmacSha256(service, kRegion); // 服务密钥
+ byte[] kSigning = hmacSha256("aws4_request", kService); // 最终签名密钥
+
+ // 计算签名
+ byte[] signature = hmacSha256(stringToSign, kSigning);
+
+ // 4. 构建Authorization头
+ return "AWS4-HMAC-SHA256 " +
+ "Credential=" + accessKey + "/" + credentialScope + ", " +
+ "SignedHeaders=" + signedHeaders + ", " +
+ "Signature=" + bytesToHex(signature);
+ }
+
+ /**
+ * 获取签名头列表(按小写字母排序)
+ */
+ private static String getSignedHeaders(Map headers) {
+ StringBuilder signedHeaders = new StringBuilder();
+ for (String key : headers.keySet()) {
+ if (signedHeaders.length() > 0) {
+ signedHeaders.append(";");
+ }
+ signedHeaders.append(key.toLowerCase()); // 全部转换为小写
+ }
+ return signedHeaders.toString();
+ }
+
+ /**
+ * 解析磁盘空间信息
+ */
+ private static Map parseDiskSpace(String jsonResponse) {
+ Map diskSpace = new HashMap<>();
+
+ // 解析JSON响应
+ JSONObject json = new JSONObject(jsonResponse);
+
+ // 提取磁盘信息(假设单节点部署)
+ JSONObject drive = json.getJSONArray("servers") // 获取servers数组
+ .getJSONObject(0) // 获取第一个服务器
+ .getJSONArray("drives") // 获取drives数组
+ .getJSONObject(0); // 获取第一个驱动器
+
+ // 获取空间信息
+ long total = drive.getLong("totalspace"); // 总空间
+ long used = drive.getLong("usedspace"); // 已用空间
+ long free = drive.getLong("availspace"); // 可用空间
+
+ // 存入结果Map
+ diskSpace.put("totalSpace", total);
+ diskSpace.put("freeSpace", free);
+ diskSpace.put("usedSpace", used);
+
+ return diskSpace;
+ }
+
+// ===== 辅助方法 =====
+
+ /**
+ * 计算HMAC-SHA256签名
+ */
+ private static byte[] hmacSha256(String data, byte[] key)
+ throws NoSuchAlgorithmException, InvalidKeyException {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(new SecretKeySpec(key, "HmacSHA256"));
+ return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * 计算字符串的SHA256哈希
+ */
+ private static String sha256Hex(String data) throws NoSuchAlgorithmException {
+ java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
+ byte[] hash = md.digest(data.getBytes(StandardCharsets.UTF_8));
+ return bytesToHex(hash);
+ }
+
+ /**
+ * 字节数组转十六进制字符串
+ */
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : bytes) {
+ String hex = Integer.toHexString(0xff & b); // 转换为无符号十六进制
+ if (hex.length() == 1) {
+ hexString.append('0'); // 补零
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+
+
+
+
+
+
+
// 计算本地路径的空间剩余率
public static String calculateLocalStorageUsage(String path) {
try {
diff --git a/java/src/main/java/com/yfd/platform/modules/storage/model/param/MinIOParam.java b/java/src/main/java/com/yfd/platform/modules/storage/model/param/MinIOParam.java
index cd2ab70..259ce58 100644
--- a/java/src/main/java/com/yfd/platform/modules/storage/model/param/MinIOParam.java
+++ b/java/src/main/java/com/yfd/platform/modules/storage/model/param/MinIOParam.java
@@ -20,7 +20,7 @@ public class MinIOParam extends S3BaseParam {
@StorageParamItem(name = "服务地址", order = 5, description = "为 minio 的服务地址,非 web 访问地址,一般为 http://ip:9000")
private String endPoint;
- @StorageParamItem(name = "minio宿主机挂载路径", order = 0,description = "只支持绝对路径
Docker 方式部署的话需提前映射宿主机路径! " )
- private String filePath;
+// @StorageParamItem(name = "minio宿主机挂载路径", order = 0,description = "只支持绝对路径
Docker 方式部署的话需提前映射宿主机路径! " )
+// private String filePath;
}