Merge branch 'main' of http://192.168.65.198:3000/toolchaintechnologycenter/spdm-backend
This commit is contained in:
@@ -14,6 +14,7 @@ import com.sdm.common.log.annotation.SysLog;
|
||||
import com.sdm.data.model.entity.FileMetadataInfo;
|
||||
import com.sdm.data.model.req.*;
|
||||
import com.sdm.data.model.resp.KKFileViewURLFromMinioResp;
|
||||
import com.sdm.data.model.resp.MinioDownloadUrlResp;
|
||||
import com.sdm.data.service.IDataFileService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -411,6 +412,12 @@ public class DataFileController implements IDataFeignClient {
|
||||
return IDataFileService.getKKFileViewURLFromMinio(fileId);
|
||||
}
|
||||
|
||||
@GetMapping("/getMinioPresignedUrl")
|
||||
@Operation(summary = "获取MinIO文件下载的预签名URL", description = "获取MinIO文件的预签名URL")
|
||||
public SdmResponse<MinioDownloadUrlResp> getMinioPresignedUrl(@Parameter(description = "文件id") @RequestParam("fileId") Long fileId) {
|
||||
return IDataFileService.getMinioDownloadUrl(fileId);
|
||||
}
|
||||
|
||||
@GetMapping("/queryFileMetadataInfo")
|
||||
@Operation(summary = "根据节点uuid获取节点文件夹信息", description = "获取节点文件夹信息")
|
||||
public SdmResponse<FileMetadataInfoResp> queryFileMetadataInfo(@RequestParam(value = "uuid") String uuid, @RequestParam(value = "uuidOwnType") String uuidOwnType, @RequestParam(value = "dirId") Long dirId) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.sdm.data.model.resp;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Minio下载URL响应类
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MinioDownloadUrlResp {
|
||||
/**
|
||||
* minioDownloadUrl : minio的带签名下载URL
|
||||
*/
|
||||
private String minioDownloadUrl;
|
||||
|
||||
/**
|
||||
* fileName : 文件名
|
||||
*/
|
||||
private String fileName;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import com.sdm.common.entity.resp.data.ChunkUploadMinioFileResp;
|
||||
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
|
||||
import com.sdm.data.model.req.*;
|
||||
import com.sdm.data.model.resp.KKFileViewURLFromMinioResp;
|
||||
import com.sdm.data.model.resp.MinioDownloadUrlResp;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -240,6 +241,15 @@ public interface IDataFileService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Minio带签名的下载URL(用于直接下载文件)
|
||||
* @param fileId 文件id
|
||||
* @return 带签名的下载URL响应
|
||||
*/
|
||||
default SdmResponse<MinioDownloadUrlResp> getMinioDownloadUrl(Long fileId){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,8 +106,23 @@ public interface IMinioService {
|
||||
*/
|
||||
List<String> queryFilesByTags(Map<String, String> tags, String bucketName) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取MinIO文件预览的预签名URL
|
||||
* @param objectKey 文件名(objectKey)
|
||||
* @param bucketName 桶名称
|
||||
* @return 预签名URL
|
||||
*/
|
||||
String getMinioPresignedUrl(String objectKey, String bucketName);
|
||||
|
||||
/**
|
||||
* 获取MinIO文件下载的预签名URL
|
||||
* @param objectKey 文件名(objectKey)
|
||||
* @param bucketName 桶名称
|
||||
* @param fileName 文件名
|
||||
* @return
|
||||
*/
|
||||
String getMinioDownloadPresignedUrl(String objectKey, String bucketName, String fileName);
|
||||
|
||||
Boolean chunkUpload(String bucketName, MultipartFile file, String fileName,Map<String, String> tags);
|
||||
|
||||
Boolean merge(String tempBucketName,String tempFilePath,String mergeBucketName,String fileName);
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.sdm.common.entity.resp.data.PoolInfo;
|
||||
import com.sdm.data.model.enums.ApproveFileActionENUM;
|
||||
import com.sdm.data.model.req.*;
|
||||
import com.sdm.data.model.resp.KKFileViewURLFromMinioResp;
|
||||
import com.sdm.data.model.resp.MinioDownloadUrlResp;
|
||||
import com.sdm.data.service.*;
|
||||
import com.sdm.data.service.approve.FileApproveExecutor;
|
||||
import com.sdm.data.service.approve.FileApproveRequestBuilder;
|
||||
@@ -2217,6 +2218,23 @@ public class MinioFileIDataFileServiceImpl implements IDataFileService {
|
||||
return SdmResponse.success(kkFileViewURLFromMinioResp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdmResponse<MinioDownloadUrlResp> getMinioDownloadUrl(Long fileId) {
|
||||
FileMetadataInfo fileMetadataInfo = fileMetadataInfoService.getById(fileId);
|
||||
if (fileMetadataInfo == null) {
|
||||
return SdmResponse.failed("文件不存在");
|
||||
}
|
||||
|
||||
String objectKey = fileMetadataInfo.getObjectKey();
|
||||
String fileName = fileMetadataInfo.getOriginalName();
|
||||
String minioDownloadUrl = minioService.getMinioDownloadPresignedUrl(objectKey, fileMetadataInfo.getBucketName(), fileName);
|
||||
if (minioDownloadUrl == null) {
|
||||
return SdmResponse.failed("获取下载链接失败");
|
||||
}
|
||||
MinioDownloadUrlResp resp = new MinioDownloadUrlResp(minioDownloadUrl, fileName);
|
||||
return SdmResponse.success(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带时间戳的 KKFileView URL
|
||||
*
|
||||
|
||||
@@ -630,7 +630,13 @@ public class MinioService implements IMinioService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取MinIO文件预览的预签名URL
|
||||
* @param objectKey 文件名(objectKey)
|
||||
* @param bucketName 桶名称
|
||||
* @return
|
||||
*/
|
||||
public String getMinioPresignedUrl(String objectKey, String bucketName) {
|
||||
String presignedUrl = null;
|
||||
try {
|
||||
@@ -644,6 +650,39 @@ public class MinioService implements IMinioService {
|
||||
return presignedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成带签名的下载URL(强制下载,非预览)
|
||||
*
|
||||
* @param objectKey 对象键
|
||||
* @param bucketName 桶名称
|
||||
* @param fileName 下载时显示的文件名
|
||||
* @return 预签名下载URL
|
||||
*/
|
||||
public String getMinioDownloadPresignedUrl(String objectKey, String bucketName, String fileName) {
|
||||
String presignedUrl = null;
|
||||
try {
|
||||
// 对文件名进行URL编码,防止中文和特殊字符问题
|
||||
String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
|
||||
// 设置响应头,强制下载
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
reqParams.put("response-content-disposition", "attachment; filename=\"" + encodedFileName + "\"; filename*=utf-8''" + encodedFileName);
|
||||
|
||||
// 生成预签名URL
|
||||
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(getBucketName(bucketName))
|
||||
.object(objectKey)
|
||||
.expiry(3600, TimeUnit.SECONDS)
|
||||
.extraQueryParams(reqParams)
|
||||
.build();
|
||||
presignedUrl = minioClient.getPresignedObjectUrl(args);
|
||||
} catch (Exception e) {
|
||||
log.error("获取文件下载URL失败: " + objectKey, e);
|
||||
return null;
|
||||
}
|
||||
return presignedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* description: 分片碎文件上传
|
||||
*
|
||||
|
||||
333
data/src/main/resources/static/downloadTest.html
Normal file
333
data/src/main/resources/static/downloadTest.html
Normal file
@@ -0,0 +1,333 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MinIO文件下载测试</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
width: 450px;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 30px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
}
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.result {
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
.result.show {
|
||||
display: block;
|
||||
}
|
||||
.result.success {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.result.error {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.result-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.result-content {
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.download-link {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
color: #667eea;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.log-section {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
.clear-btn {
|
||||
padding: 4px 12px;
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.clear-btn:hover {
|
||||
background: #ee5a5a;
|
||||
}
|
||||
.log-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.log-item {
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.log-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.log-time {
|
||||
color: #888;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.log-info { color: #4fc3f7; }
|
||||
.log-success { color: #81c784; }
|
||||
.log-error { color: #ef5350; }
|
||||
.log-data { color: #ffb74d; }
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.loading.show {
|
||||
display: block;
|
||||
}
|
||||
.spinner {
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>MinIO 文件下载测试</h1>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fileId">文件 ID</label>
|
||||
<input type="text" id="fileId" placeholder="请输入文件ID,例如: 123456">
|
||||
</div>
|
||||
|
||||
<button class="btn" onclick="downloadFile()">获取下载链接并下载</button>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p style="margin-top: 10px; color: #666;">正在获取下载链接...</p>
|
||||
</div>
|
||||
|
||||
<div class="result" id="result">
|
||||
<div class="result-title" id="resultTitle"></div>
|
||||
<div class="result-content" id="resultContent"></div>
|
||||
<span class="download-link" id="downloadLink" onclick="openDownloadUrl()" style="display:none;">点击此处重新下载</span>
|
||||
</div>
|
||||
|
||||
<div class="log-section">
|
||||
<div class="log-header">
|
||||
<span>请求日志</span>
|
||||
<button class="clear-btn" onclick="clearLog()">清空</button>
|
||||
</div>
|
||||
<div class="log-container" id="logContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 保存下载URL,用于重复下载
|
||||
let currentDownloadUrl = '';
|
||||
|
||||
// 添加日志
|
||||
function addLog(message, type = 'info') {
|
||||
const logContainer = document.getElementById('logContainer');
|
||||
const logItem = document.createElement('div');
|
||||
logItem.className = 'log-item';
|
||||
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false }) + '.' + String(now.getMilliseconds()).padStart(3, '0');
|
||||
|
||||
logItem.innerHTML = `<span class="log-time">[${timeStr}]</span><span class="log-${type}">${message}</span>`;
|
||||
logContainer.appendChild(logItem);
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
function clearLog() {
|
||||
document.getElementById('logContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
async function downloadFile() {
|
||||
const fileId = document.getElementById('fileId').value.trim();
|
||||
|
||||
if (!fileId) {
|
||||
showResult('error', '错误', '请输入文件ID');
|
||||
addLog('错误: 请输入文件ID', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
showLoading(true);
|
||||
hideResult();
|
||||
|
||||
const requestUrl = `http://192.168.65.161:7100/simulation/data/data/getMinioPresignedUrl?fileId=${fileId}`;
|
||||
addLog(`发起请求: ${requestUrl}`, 'info');
|
||||
|
||||
try {
|
||||
// 调用接口获取下载链接
|
||||
const response = await fetch(requestUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
addLog(`响应状态: ${response.status} ${response.statusText}`, response.ok ? 'success' : 'error');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
addLog(`响应数据: ${JSON.stringify(data)}`, 'data');
|
||||
|
||||
if (data.code === 200 && data.data) {
|
||||
const downloadUrl = data.data.minioDownloadUrl;
|
||||
const fileName = data.data.fileName;
|
||||
|
||||
currentDownloadUrl = downloadUrl;
|
||||
|
||||
addLog(`获取成功 - 文件名: ${fileName}`, 'success');
|
||||
addLog(`下载链接: ${downloadUrl}`, 'success');
|
||||
|
||||
// 显示成功信息
|
||||
showResult('success', '获取成功', `文件名: ${fileName}`);
|
||||
document.getElementById('downloadLink').style.display = 'inline-block';
|
||||
|
||||
// 使用 window.open 打开新页面下载
|
||||
addLog('正在打开新窗口下载...', 'info');
|
||||
window.open(downloadUrl, '_blank');
|
||||
|
||||
} else {
|
||||
const errorMsg = data.msg || '未知错误';
|
||||
addLog(`获取失败: ${errorMsg}`, 'error');
|
||||
showResult('error', '获取失败', errorMsg);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error);
|
||||
addLog(`请求异常: ${error.message || '网络错误'}`, 'error');
|
||||
showResult('error', '请求失败', error.message || '网络错误,请检查服务是否启动');
|
||||
} finally {
|
||||
showLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openDownloadUrl() {
|
||||
if (currentDownloadUrl) {
|
||||
window.open(currentDownloadUrl, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
function showLoading(show) {
|
||||
document.getElementById('loading').className = show ? 'loading show' : 'loading';
|
||||
}
|
||||
|
||||
function showResult(type, title, content) {
|
||||
const resultDiv = document.getElementById('result');
|
||||
const resultTitle = document.getElementById('resultTitle');
|
||||
const resultContent = document.getElementById('resultContent');
|
||||
|
||||
resultDiv.className = `result show ${type}`;
|
||||
resultTitle.textContent = title;
|
||||
resultContent.textContent = content;
|
||||
}
|
||||
|
||||
function hideResult() {
|
||||
document.getElementById('result').className = 'result';
|
||||
document.getElementById('downloadLink').style.display = 'none';
|
||||
}
|
||||
|
||||
// 支持回车键触发下载
|
||||
document.getElementById('fileId').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
downloadFile();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user