目录
1、添加相关配置
2、添加依赖
3、实现方法
1️⃣基础版:
2️⃣优化版(推荐使用):
3️⃣上传远程主机上的目录内容:
4️⃣直接上传远程主机中的目录内容
业务背景:需要需要minio进行上传指定目录下所有的内容,具体如下:
1、添加相关配置
# minio 服务
minio:#Minio服务所在地址endpoint: http://192.168.1.20:9000# 默认存储桶名称 bucketName: minio-upload#访问的keyaccessKey: zItdfkAbUVWNkIo#访问的秘钥secretKey: JlZp3WTG6PFEckFW7UHXqXSzGu1ICwMXQ6Ep#存储桶列表名称bucketNames: data-upload,testdata
配置文件 MinioConfig
import io.minio.MinioClient;
import io.minio.errors.ServerException;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.List;/*** minio 配置** @author ATB* @date 2025*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {/*** 端点*/private String endpoint;/*** 访问密钥*/private String accessKey;/*** 密钥*/private String secretKey;/*** 存储桶名称*/private String bucketName;/*** 存储桶名称列表*/private List<String> bucketNames;/*** 注入 Minio 客户端** @return {@link MinioClient }*/@Beanpublic MinioClient minioClient() throws ServerException {try {return MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();} catch (Exception e) {throw new ServerException("-----创建Minio客户端失败-----" + e.getMessage() , 202, "未处理");}//return MinioClient.builder()// .endpoint(endpoint)// .credentials(accessKey, secretKey)// .build();}
}
当然也可以不用这个配置类的,需要使用@Value获取配置参数就行。
2、添加依赖
<!-- https://mvnrepository.com/artifact/io.minio/minio --><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.17</version></dependency><dependency><groupId>com.amazonaws</groupId><artifactId>aws-java-sdk-s3</artifactId><version>1.12.540</version></dependency>
3、实现方法
1️⃣和2️⃣是对本地的目录进行操作,3️⃣是对远程机器的目录进行操作。
1️⃣基础版:
其中minioConfig.getEndpoint()就是什么配置类中的参数,也可以使用如下方法获取配置文件的内容,还可以直接写成固定值都是可以的。
@Value("minio.endpoint") private String endpoint;
@Autowiredprivate MinioConfig minioConfig;@Resourceprivate MinioClient minioClient;/*** 上传目录** @param bucketName 存储桶名称* @param directoryPath directory path (目录路径) /home/data/app/models* @return {@link ResponseEntity }<{@link Object }>*/@ApiOperation(value = "上传目录")@GetMapping("/uploadCatalog")public ResponseEntity<Object> uploadCatalog(@RequestParam("bucketName") String bucketName, @RequestParam("directoryPath") String directoryPath) {System.out.println("bucketName = " + bucketName);System.out.println("directoryPath = " + directoryPath);try {// 初始化 MinIO 客户端MinioClient minioClient = MinioClient.builder().endpoint(minioConfig.getEndpoint()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();// 递归遍历目录及其子目录Files.walkFileTree(Paths.get(directoryPath), new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {// 将子目录视为普通文件上传String relativePath;if (directoryPath.length() + 1 > dir.toString().length()) {// 如果超出范围,可以选择返回空字符串或其他默认值relativePath = " /home/data/app/models";} else {relativePath = StringUtils.substringAfter(dir.toString(), directoryPath);}log.info("Uploading directory: " + relativePath);try {// 上传目录作为文件minioClient.putObject(PutObjectArgs.builder().bucket(bucketName)// 结尾加斜杠表示目录.object("/model-file-path/" + relativePath).stream(new FileInputStream(dir.toString()), 0, -1)// 设置为目录类型.contentType("application/x-directory").build());log.info("Uploaded directory: " + relativePath);} catch (Exception e) {log.error("Failed to upload directory: " + dir.getFileName());e.printStackTrace();}// 继续遍历子目录return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {// 上传文件String relativePath = file.toString().substring(directoryPath.length() + 1);log.info("Uploading file: " + relativePath);try {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("/model-file-path/" + relativePath).stream(new FileInputStream(file.toFile()), Files.size(file), -1)// 自动检测文件类型.contentType(Files.probeContentType(file)) .build());log.info("Uploaded file: " + relativePath);} catch (Exception e) {log.warn("Failed to upload file: " + file.getFileName());e.printStackTrace();}// 继续遍历下一个文件return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {log.warn("Failed to process file: " + file.getFileName() + ", reason: " + exc.getMessage());// 忽略失败的文件return FileVisitResult.CONTINUE;}});log.info("All files and directories uploaded successfully.");} catch (IOException e) {log.warn("Error occurred: " + e.getMessage());e.printStackTrace();}HashMap<String, Object> response = new HashMap<>();response.put("data", "directoryPath");response.put("code", HttpStatus.OK.value());response.put("msg", "上传成功");return ResponseEntity.status(HttpStatus.OK).body(response);}
2️⃣优化版(推荐使用):
不同的文件类型也都可以直接上传
/*** 上传内容目录** @param bucketName 存储桶名称* @param directoryPath directory path (目录路径)* @return {@link ResponseEntity }<{@link Object }>*/@GetMapping("/uploadContentsCatalog")public ResponseEntity<Object> uploadContentsCatalog(@RequestParam("bucketName") String bucketName,@RequestParam("directoryPath") String directoryPath) {log.info("Upload catalog: bucket={}, path={}", bucketName, directoryPath);Map<String, Object> response = new HashMap<>();List<String> successList = new ArrayList<>();List<String> failedList = new ArrayList<>();try {MinioClient minioClient = MinioClient.builder().endpoint(minioConfig.getEndpoint()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();Path basePath = Paths.get(directoryPath);Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {if (!dir.equals(basePath)) { // 根目录不需要作为“目录对象”上传String relativePath = basePath.relativize(dir).toString().replace("\\", "/") + "/";try {// “模拟”上传一个空对象表示目录(非必要,可跳过)minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("/model-file-path/" + relativePath).stream(new ByteArrayInputStream(new byte[0]), 0, -1).contentType("application/x-directory").build());log.info("Uploaded directory: {}", relativePath);successList.add(relativePath);} catch (Exception e) {log.error("Failed to upload directory: {}", relativePath, e);failedList.add(relativePath);}}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {String relativePath = basePath.relativize(file).toString().replace("\\", "/");try (InputStream is = new FileInputStream(file.toFile())) {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("/model-file-path/" + relativePath).stream(is, Files.size(file), -1)//.contentType(Files.probeContentType(file)).contentType(Optional.ofNullable(Files.probeContentType(file)).orElse("application/octet-stream")).build());log.info("Uploaded file: {}", relativePath);successList.add(relativePath);} catch (Exception e) {log.warn("Failed to upload file: {}", relativePath, e);failedList.add(relativePath);}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) {String relativePath = basePath.relativize(file).toString().replace("\\", "/");log.warn("Could not access file: {}, reason: {}", relativePath, exc.getMessage());failedList.add(relativePath);return FileVisitResult.CONTINUE;}});} catch (Exception e) {log.error("Unexpected error during directory upload", e);return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", 500, "msg", "上传过程中出现错误", "error", e.getMessage()));}response.put("code", HttpStatus.OK.value());response.put("msg", "上传完成");response.put("successCount", successList.size());response.put("failCount", failedList.size());response.put("successItems", successList);response.put("failedItems", failedList);return ResponseEntity.ok(response);}
3️⃣上传远程主机上的目录内容:
实现从远程主机上传目录到 MinIO,你可以通过 SSH(SFTP 或 SCP)下载远程目录到本地临时目录,然后按之前逻辑上传至 MinIO。
添加依赖(JSch 连接远程机器)
<dependency><groupId>com.jcraft</groupId><artifactId>jsch</artifactId><version>0.1.55</version>
</dependency>
具体实现
/*** 上传远程目录** @param bucketName 存储桶名称* @param remoteDirectory remote 目录* @return {@link ResponseEntity }<{@link Object }>*/@PostMapping("/uploadRemoteCatalog")public ResponseEntity<Object> uploadRemoteCatalog(@RequestParam("bucketName") String bucketName,@RequestParam("remoteDirectory") String remoteDirectory) {String remoteHost = "192.168.1.88";String username = "root";String password = "12345678";String tempDir = System.getProperty("java.io.tmpdir") + "/upload_" + UUID.randomUUID();File localTempDir = new File(tempDir);localTempDir.mkdirs();log.info("Start downloading remote dir [{}] from {} to local temp: {}", remoteDirectory, remoteHost, tempDir);Map<String, Object> response = new HashMap<>();List<String> successList = new ArrayList<>();List<String> failedList = new ArrayList<>();try {// 1. 下载远程目录到本地临时目录JSch jsch = new JSch();Session session = jsch.getSession(username, remoteHost, 22);session.setPassword(password);session.setConfig("StrictHostKeyChecking", "no");session.connect();Channel channel = session.openChannel("sftp");channel.connect();ChannelSftp sftpChannel = (ChannelSftp) channel;// 下载远程目录到本地临时目录downloadRemoteDirectory(sftpChannel, remoteDirectory, tempDir);sftpChannel.exit();session.disconnect();log.info("Remote directory downloaded to local temp: {}", tempDir);// 2. 上传本地目录到 MinIO(与之前逻辑一致)Path basePath = Paths.get(tempDir);MinioClient minioClient = MinioClient.builder().endpoint(minioConfig.getEndpoint()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {if (!dir.equals(basePath)) {String relativePath = basePath.relativize(dir).toString().replace("\\", "/") + "/";try {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("/model-file-path/" + relativePath).stream(new ByteArrayInputStream(new byte[0]), 0, -1).contentType("application/x-directory").build());successList.add(relativePath);} catch (Exception e) {log.error("Failed to upload directory: {}", relativePath, e);failedList.add(relativePath);}}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {String relativePath = basePath.relativize(file).toString().replace("\\", "/");try (InputStream is = new FileInputStream(file.toFile())) {String contentType = Optional.ofNullable(Files.probeContentType(file)).orElse("application/octet-stream");minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("/model-file-path/" + relativePath).stream(is, Files.size(file), -1).contentType(contentType).build());successList.add(relativePath);} catch (Exception e) {log.warn("Failed to upload file: {}", relativePath, e);failedList.add(relativePath);}return FileVisitResult.CONTINUE;}});} catch (Exception e) {log.error("Upload from remote server failed", e);return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("code", 500, "msg", "远程上传失败", "error", e.getMessage()));} finally {// 3. 清理删除临时目录try {log.warn("Delete temp dir: {}", localTempDir);FileUtils.deleteDirectory(localTempDir);} catch (IOException e) {log.warn("Failed to delete temp dir: {}", tempDir);}}response.put("code", HttpStatus.OK.value());response.put("msg", "上传完成");response.put("successCount", successList.size());response.put("failCount", failedList.size());response.put("successItems", successList);response.put("failedItems", failedList);return ResponseEntity.ok(response);}/*** 下载 Remote Directory** @param sftp SFTP* @param remoteDir 远程目录* @param localDir 本地目录* @throws SftpException SFTP 异常*/private void downloadRemoteDirectory(ChannelSftp sftp, String remoteDir, String localDir) throws SftpException {File localFolder = new File(localDir);if (!localFolder.exists()) {localFolder.mkdirs();}@SuppressWarnings("unchecked")Vector<ChannelSftp.LsEntry> entries = sftp.ls(remoteDir);for (ChannelSftp.LsEntry entry : entries) {String fileName = entry.getFilename();if (".".equals(fileName) || "..".equals(fileName)) continue;String remotePath = remoteDir + "/" + fileName;String localPath = localDir + "/" + fileName;if (entry.getAttrs().isDir()) {downloadRemoteDirectory(sftp, remotePath, localPath);} else {try (OutputStream output = new FileOutputStream(localPath)) {sftp.get(remotePath, output);} catch (IOException e) {log.error("Failed to download file: {}", remotePath, e);}}}}
至此可以实现远程上传到minio中。远程上传文件目录还可以继续优化,如考虑:支持私钥登录、上传前压缩、并发、上传进度等,所以可以形成一个远程上传的工具类,具体如下:
import com.jcraft.jsch.*;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;/*** 远程上传器** @author ATB* @date 2025*/
@Slf4j
public class RemoteUploader {public static class SSHConfig {public String host;public int port = 22;public String username;public String password;public SSHConfig(String host, String username, String password) {this.host = host;this.username = username;this.password = password;}}public static class UploadResult {public List<String> success = new ArrayList<>();public List<String> failed = new ArrayList<>();}/*** 1️⃣ 直接上传** @param sshConfig SSH 配置* @param remoteDir 远程目录* @param bucketName 存储桶名称* @param minioClient Minio 客户端* @return {@link UploadResult }* @throws Exception 例外*/public static UploadResult uploadRemoteDirectoryToMinio(SSHConfig sshConfig,String remoteDir,String bucketName,MinioClient minioClient) throws Exception {// 1. 下载远程目录String tempDirPath = System.getProperty("java.io.tmpdir") + "/upload_" + UUID.randomUUID();File tempDir = new File(tempDirPath);tempDir.mkdirs();JSch jsch = new JSch();Session session = jsch.getSession(sshConfig.username, sshConfig.host, sshConfig.port);session.setPassword(sshConfig.password);session.setConfig("StrictHostKeyChecking", "no");session.connect();ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");sftp.connect();downloadRemoteDirectory(sftp, remoteDir, tempDirPath);sftp.disconnect();session.disconnect();// 2. 上传目录到 MinIOUploadResult result = uploadLocalDirectoryToMinio(tempDirPath, bucketName, minioClient);// 3. 删除临时目录try {FileUtils.deleteDirectory(tempDir);} catch (IOException e) {log.warn("Failed to delete temp dir: {}", tempDirPath);}return result;}private static void downloadRemoteDirectory(ChannelSftp sftp, String remoteDir, String localDir) throws SftpException {new File(localDir).mkdirs();@SuppressWarnings("unchecked")Vector<ChannelSftp.LsEntry> files = sftp.ls(remoteDir);for (ChannelSftp.LsEntry entry : files) {String name = entry.getFilename();if (".".equals(name) || "..".equals(name)) continue;String remotePath = remoteDir + "/" + name;String localPath = localDir + "/" + name;if (entry.getAttrs().isDir()) {downloadRemoteDirectory(sftp, remotePath, localPath);} else {try (OutputStream os = new FileOutputStream(localPath)) {sftp.get(remotePath, os);} catch (IOException e) {log.error("Failed to download file: {}", remotePath, e);}}}}/*** 2️⃣ 将远程目录上传到压缩后 Minio** @param sshConfig SSH 配置* @param remoteDir 远程目录* @param bucketName 存储桶名称* @param minioClient Minio 客户端* @return {@link UploadResult }* @throws Exception 例外*/public static UploadResult uploadRemoteDirectoryToMinioWithCompression(SSHConfig sshConfig,String remoteDir,String bucketName,MinioClient minioClient) throws Exception {// 建立 SSH 会话JSch jsch = new JSch();Session session = jsch.getSession(sshConfig.username, sshConfig.host, sshConfig.port);session.setPassword(sshConfig.password);session.setConfig("StrictHostKeyChecking", "no");session.connect();// 打包远程目录String remoteTarPath = remoteTarDirectory(session, remoteDir);// SFTP 下载 tar.gzChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");sftp.connect();String localTarPath = downloadRemoteTar(sftp, remoteTarPath);// 可选:清理远程文件sftp.rm(remoteTarPath);sftp.disconnect();session.disconnect();// 本地解压String extractPath = extractTarGz(localTarPath);// 上传解压后的目录UploadResult result = uploadLocalDirectoryToMinio(extractPath, bucketName, minioClient);// 清理临时文件new File(localTarPath).delete();FileUtils.deleteDirectory(new File(extractPath));return result;}/*** 在远程打包 .tar.gz** @param session 会期* @param remoteDir 远程目录* @return {@link String }* @throws JSchException JSCH 例外* @throws IOException io异常*/private static String remoteTarDirectory(Session session, String remoteDir) throws JSchException, IOException {String parent = remoteDir.endsWith("/") ? remoteDir.substring(0, remoteDir.length() - 1) : remoteDir;String tarName = "/tmp/upload_" + UUID.randomUUID() + ".tar.gz";String command = String.format("tar -czf %s -C %s .", tarName, parent);log.info("Executing remote tar: {}", command);ChannelExec exec = (ChannelExec) session.openChannel("exec");exec.setCommand(command);exec.setInputStream(null);exec.setErrStream(System.err);exec.connect();InputStream input = exec.getInputStream();byte[] tmp = new byte[1024];while (input.read(tmp, 0, 1024) >= 0) {}exec.disconnect();return tarName;}/*** 下载 .tar.gz 文件** @param sftp SFTP* @param remoteTarPath 远程 tar 路径* @return {@link String }* @throws Exception 例外*/private static String downloadRemoteTar(ChannelSftp sftp, String remoteTarPath) throws Exception {String localTarPath = System.getProperty("java.io.tmpdir") + "/" + UUID.randomUUID() + ".tar.gz";try (FileOutputStream fos = new FileOutputStream(localTarPath)) {sftp.get(remoteTarPath, fos);}return localTarPath;}/*** 解压 .tar.gz** @param tarGzPath tar gz 路径* @return {@link String }* @throws IOException io异常*/private static String extractTarGz(String tarGzPath) throws IOException {String outputDir = tarGzPath.replace(".tar.gz", "_extracted");Files.createDirectories(Paths.get(outputDir));ProcessBuilder pb = new ProcessBuilder("tar", "-xzf", tarGzPath, "-C", outputDir);Process process = pb.start();try {if (process.waitFor() != 0) throw new RuntimeException("Failed to extract tar.gz");} catch (InterruptedException e) {Thread.currentThread().interrupt();}return outputDir;}/*** 将本地目录上传到 Minio** @param localDirPath 本地目录路径* @param bucketName 存储桶名称* @param minioClient Minio 客户端* @return {@link UploadResult }* @throws IOException io异常*/private static UploadResult uploadLocalDirectoryToMinio(String localDirPath,String bucketName,MinioClient minioClient) throws IOException {UploadResult result = new UploadResult();Path basePath = Paths.get(localDirPath);Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {if (!dir.equals(basePath)) {String relativePath = basePath.relativize(dir).toString().replace("\\", "/") + "/";try {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(relativePath).stream(new ByteArrayInputStream(new byte[0]), 0, -1).contentType("application/x-directory").build());result.success.add(relativePath);} catch (Exception e) {log.warn("Failed to upload directory: {}", relativePath, e);result.failed.add(relativePath);}}return FileVisitResult.CONTINUE;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {String relativePath = basePath.relativize(file).toString().replace("\\", "/");try (InputStream is = new FileInputStream(file.toFile())) {String contentType = Optional.ofNullable(Files.probeContentType(file)).orElse("application/octet-stream");minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(relativePath).stream(is, Files.size(file), -1).contentType(contentType).build());result.success.add(relativePath);} catch (Exception e) {log.warn("Failed to upload file: {}", relativePath, e);result.failed.add(relativePath);}return FileVisitResult.CONTINUE;}});return result;}//==[考虑直接远程上传等方式]================================================================================================================// TODO: 考虑直接远程上传等方式public void uploadRemoteFileToMinio(SSHConfig sshConfig, String remoteFilePath, String bucketName, String objectName, MinioClient minioClient) throws Exception {// 建立 SSH 会话JSch jsch = new JSch();Session session = jsch.getSession(sshConfig.username, sshConfig.host, sshConfig.port);session.setPassword(sshConfig.password);session.setConfig("StrictHostKeyChecking", "no");session.connect();ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");sftp.connect();try (InputStream remoteStream = sftp.get(remoteFilePath)) {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(remoteStream, -1, PutObjectArgs.MIN_MULTIPART_SIZE)// 可通过文件后缀判断更具体类型.contentType("application/octet-stream").build());log.info("Uploaded from remote: {} to MinIO: {}", remoteFilePath, objectName);} finally {sftp.disconnect();}}/*** 从远程主机读取文件流,上传到 MinIO** @param sshHost 远程主机 IP 或域名* @param sshPort SSH 端口,默认 22* @param username 用户名* @param password 密码(或设置为 null 以使用私钥登录)* @param privateKey 私钥路径(或 null)* @param remotePath 远程文件绝对路径* @param minioClient MinIO 客户端* @param bucketName MinIO bucket 名称* @param objectName 上传后对象名(key)*/public static void uploadRemoteFileToMinio(String sshHost,int sshPort,String username,String password,String privateKey,String remotePath,MinioClient minioClient,String bucketName,String objectName) throws Exception {JSch jsch = new JSch();if (privateKey != null && !privateKey.isBlank()) {jsch.addIdentity(privateKey);}Session session = jsch.getSession(username, sshHost, sshPort);if (password != null && !password.isBlank()) {session.setPassword(password);}session.setConfig("StrictHostKeyChecking", "no");session.connect();ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");sftp.connect();try (InputStream inputStream = sftp.get(remotePath)) {long fileSize = sftp.lstat(remotePath).getSize();minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, fileSize, -1).contentType("application/octet-stream").build());log.info("上传成功: remote={}, object={}", remotePath, objectName);} catch (Exception e) {log.error("上传失败: {}", e.getMessage(), e);throw e;} finally {sftp.disconnect();session.disconnect();}}}
实现调用:
/*** 上传压缩** @param bucketName 存储桶名称* @param remotePath 远程路径* @return {@link ResponseEntity }<{@link ? }>*/@PostMapping("/uploadCompressed")public ResponseEntity<?> uploadCompressed(@RequestParam String bucketName,@RequestParam String remotePath) {try {RemoteUploader.SSHConfig ssh = new RemoteUploader.SSHConfig("192.168.1.88", "root", "12345678");MinioClient client = MinioClient.builder().endpoint(minioConfig.getEndpoint()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();// 直接远程上传// RemoteUploader.UploadResult result = RemoteUploader.uploadRemoteDirectoryToMinio(ssh, remotePath, bucketName, client);// 压缩传输上传RemoteUploader.UploadResult result = RemoteUploader.uploadRemoteDirectoryToMinioWithCompression(ssh, remotePath, bucketName, client);return ResponseEntity.ok(Map.of("code", 200,"msg", "上传完成(压缩)","successCount", result.success.size(),"failCount", result.failed.size(),"successItems", result.success,"failedItems", result.failed));} catch (Exception e) {return ResponseEntity.status(500).body(Map.of("code", 500,"msg", "上传失败","error", e.getMessage()));}}
4️⃣直接上传远程主机中的目录内容
添加工具类:
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;/*** remote dir uploader** @author ATB* &date 2025/05/28*/
@Slf4j
public class RemoteDirUploader {public static class UploadResult {public List<String> success = new ArrayList<>();public List<String> failed = new ArrayList<>();}/*** 将远程目录上传到 Minio** @param sshHost SSH 主机* @param sshPort SSH 端口* @param username 用户名* @param password 密码* @param privateKey 私钥* @param remoteDir 远程目录* @param bucketName 存储桶名称* @param minioPrefix minio 前缀* @param minioClient Minio 客户端* @throws Exception 例外*/public static UploadResult uploadRemoteDirectoryToMinio(String sshHost, int sshPort,String username, String password, String privateKey,String remoteDir, String bucketName, String minioPrefix,MinioClient minioClient) throws Exception {JSch jsch = new JSch();if (privateKey != null && !privateKey.isBlank()) {jsch.addIdentity(privateKey);}Session session = jsch.getSession(username, sshHost, sshPort);if (password != null && !password.isBlank()) {session.setPassword(password);}session.setConfig("StrictHostKeyChecking", "no");session.connect();ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");sftp.connect();UploadResult result;try {result = uploadRecursive(sftp, remoteDir, remoteDir, bucketName, minioPrefix, minioClient);} finally {sftp.disconnect();session.disconnect();}return result;}private static UploadResult uploadRecursive(ChannelSftp sftp, String basePath, String currentPath,String bucketName, String minioPrefix,MinioClient minioClient) throws Exception {UploadResult result = new UploadResult();@SuppressWarnings("unchecked")Vector<ChannelSftp.LsEntry> entries = sftp.ls(currentPath);for (ChannelSftp.LsEntry entry : entries) {String filename = entry.getFilename();if (filename.equals(".") || filename.equals("..")) continue;String fullPath = currentPath + "/" + filename;String relativePath = fullPath.substring(basePath.length()).replaceFirst("^/", "");String objectName = minioPrefix + relativePath;if (entry.getAttrs().isDir()) {// 递归子目录uploadRecursive(sftp, basePath, fullPath, bucketName, minioPrefix, minioClient);} else {long fileSize = entry.getAttrs().getSize();try (InputStream inputStream = sftp.get(fullPath)) {minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, fileSize, -1).contentType("application/octet-stream").build());log.info("上传成功: {}", objectName);result.success.add(objectName);} catch (Exception e) {log.warn("上传失败: {} - {}", objectName, e.getMessage());result.failed.add(objectName);}}}return result;}
}
然后接口调用:
/*** 流式直接上传** @param bucketName 存储桶名称* @param remotePath 远程路径* @return {@link ResponseEntity }<{@link ? }>*/@PostMapping("/streamingUploads")public ResponseEntity<?> streamingUploads(@RequestParam String bucketName,@RequestParam String remotePath) {try {MinioClient client = MinioClient.builder().endpoint(minioConfig.getEndpoint()).credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey()).build();RemoteDirUploader.UploadResult result = RemoteDirUploader.uploadRemoteDirectoryToMinio("192.168.5.88", 22, "root", "123456", null,remotePath, bucketName, "model-file/data/",minioClient);return ResponseEntity.ok(Map.of("code", 200,"msg", "上传完成","successCount", result.success.size(),"failCount", result.failed.size(),"successItems", result.success,"failedItems", result.failed));} catch (Exception e) {return ResponseEntity.status(500).body(Map.of("code", 500,"msg", "上传失败","error", e.getMessage()));}}
至此!就可以实现本地或者是远程机器上的目录上传到minio中了!