From 9b85c51b51de74e3a644fb25ca418fecabfb99d0 Mon Sep 17 00:00:00 2001
From: wr <1754607820@qq.com>
Date: Mon, 12 Jan 2026 14:47:28 +0800
Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=98=BF=E9=87=8C?=
=?UTF-8?q?=E4=BA=91oss?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 65 ++++
.../njcn/ali/oss/config/AliYunOssConfig.java | 42 +++
.../com/njcn/ali/oss/util/AliYunOssUtils.java | 299 ++++++++++++++++++
src/main/resources/META-INF/spring.factories | 5 +
4 files changed, 411 insertions(+)
create mode 100644 pom.xml
create mode 100644 src/main/java/com/njcn/ali/oss/config/AliYunOssConfig.java
create mode 100644 src/main/java/com/njcn/ali/oss/util/AliYunOssUtils.java
create mode 100644 src/main/resources/META-INF/spring.factories
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..53be5a1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,65 @@
+
+
+ 4.0.0
+
+ com.njcn
+ aliyun-oss-springboot-starter
+ 1.0.0
+
+
+
+ nexus-releases
+ Nexus Release Repository
+ http://192.168.1.22:8001/nexus/content/repositories/releases/
+
+
+ nexus-snapshots
+ Nexus Snapshot Repository
+ http://192.168.1.22:8001/nexus/content/repositories/snapshots/
+
+
+
+ 8
+ 8
+ UTF-8
+ 0.5.3
+ 4.8.1
+ 8.2.1
+
+ 灿能阿里云oss组件提取的starter模块
+ jar
+
+
+
+
+ com.njcn
+ common-core
+ 1.0.0
+ compile
+
+
+
+
+ com.aliyun.oss
+ aliyun-sdk-oss
+ 3.18.0
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+ 2.3.12.RELEASE
+ compile
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/njcn/ali/oss/config/AliYunOssConfig.java b/src/main/java/com/njcn/ali/oss/config/AliYunOssConfig.java
new file mode 100644
index 0000000..891c2d3
--- /dev/null
+++ b/src/main/java/com/njcn/ali/oss/config/AliYunOssConfig.java
@@ -0,0 +1,42 @@
+package com.njcn.ali.oss.config;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+/**
+ * OSS配置类,存储连接所需的核心参数
+ * @author web2023
+ */
+@Configuration
+@ConfigurationProperties(
+ prefix = "aliyun.io"
+)
+@Data
+public class AliYunOssConfig {
+
+ private String endpoint;
+ private String accessKey;
+ private String secretKey;
+ private String bucket;
+
+ @Bean(destroyMethod = "shutdown")
+ public OSS getAliYunClient() {
+ // 1. 校验配置(避免因配置为空导致Bean创建失败)
+ if (!StringUtils.hasText(endpoint) || !StringUtils.hasText(accessKey) || !StringUtils.hasText(secretKey)) {
+ throw new IllegalArgumentException("OSS配置缺失:endpoint/accessKey/secretKey不能为空");
+ }
+ return new OSSClientBuilder().build(
+ this.getEndpoint(),
+ this.getAccessKey(),
+ this.getSecretKey()
+ );
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/njcn/ali/oss/util/AliYunOssUtils.java b/src/main/java/com/njcn/ali/oss/util/AliYunOssUtils.java
new file mode 100644
index 0000000..dd8a6a2
--- /dev/null
+++ b/src/main/java/com/njcn/ali/oss/util/AliYunOssUtils.java
@@ -0,0 +1,299 @@
+package com.njcn.ali.oss.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjUtil;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.common.comm.ResponseMessage;
+import com.aliyun.oss.model.*;
+import com.njcn.ali.oss.config.AliYunOssConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.net.URL;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 阿里云OSS核心工具类
+ * 封装常用操作:上传、下载、删除、查询、生成临时访问URL等
+ *
+ * @author web2023
+ */
+@Component
+public class AliYunOssUtils {
+ private static final Logger logger = LoggerFactory.getLogger(AliYunOssUtils.class);
+ @Resource
+ private OSS ossClient;
+ @Resource
+ private AliYunOssConfig ossConfig;
+
+ /**
+ * 上传文件(字节数组)
+ *
+ * @param objectName 文件在OSS中的路径/名称
+ * @param content 文件字节数组
+ * @return 上传结果ETag
+ */
+ public String uploadFile(String objectName, byte[] content) {
+ return uploadFile(objectName, new ByteArrayInputStream(content));
+ }
+
+ /**
+ * 上传文件(InputStream)
+ *
+ * @param objectName 文件在OSS中的路径/名称
+ * @param inputStream 文件输入流
+ * @return 上传结果ETag
+ */
+ public String uploadFile(String objectName, InputStream inputStream) {
+ try {
+ PutObjectResult result = ossClient.putObject(
+ ossConfig.getBucket(),
+ objectName,
+ inputStream
+ );
+ return result.getETag();
+ } catch (Exception e) {
+ logger.error("文件{}上传失败", objectName, e);
+ throw new RuntimeException("文件上传失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 上传文件(MultipartFile)
+ *
+ * @param objectName 文件在OSS中的路径/名称
+ * @param multipartFile 文件输入流
+ * @return 上传结果ETag
+ */
+ public String uploadFile(String objectName, MultipartFile multipartFile) {
+ try {
+ PutObjectResult result = ossClient.putObject(
+ ossConfig.getBucket(),
+ objectName,
+ multipartFile.getInputStream()
+ );
+ return result.getETag();
+ } catch (Exception e) {
+ logger.error("文件{}上传失败", objectName, e);
+ throw new RuntimeException("文件上传失败:" + objectName, e);
+ }
+ }
+
+
+ /**
+ * 上传本地文件
+ *
+ * @param objectName 文件在OSS中的路径/名称
+ * @param localFilePath 本地文件路径
+ * @return 上传结果ETag
+ */
+ public String uploadLocalFile(String objectName, String localFilePath) {
+ File file = new File(localFilePath);
+ if (!file.exists()) {
+ throw new RuntimeException("本地文件不存在:" + localFilePath);
+ }
+ try {
+ PutObjectResult result = ossClient.putObject(
+ ossConfig.getBucket(),
+ objectName,
+ file
+ );
+ return result.getETag();
+ } catch (Exception e) {
+ logger.error("本地文件{}上传失败", localFilePath, e);
+ throw new RuntimeException("本地文件上传失败:" + localFilePath, e);
+ }
+ }
+
+ /**
+ * 下载文件到本地
+ *
+ * @param objectName OSS中的文件名称/路径
+ * @param localFilePath 本地保存路径
+ */
+ public void downloadFile(String objectName, String localFilePath) {
+ try {
+ ossClient.getObject(
+ new GetObjectRequest(ossConfig.getBucket(), objectName),
+ new File(localFilePath)
+ );
+ } catch (Exception e) {
+ logger.error("文件{}下载失败", objectName, e);
+ throw new RuntimeException("文件下载失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 下载文件为字节流
+ *
+ * @param objectName OSS中的文件名称/路径
+ * @return 文件字节流
+ */
+ public InputStream downloadStream(String objectName) {
+ try (OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), objectName);
+ InputStream inputStream = ossObject.getObjectContent()) {
+ return inputStream;
+ } catch (Exception e) {
+ logger.error("文件{}下载为字节数组失败", objectName, e);
+ throw new RuntimeException("文件下载为字节数组失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 下载文件为字节数组
+ *
+ * @param objectName OSS中的文件名称/路径
+ * @return 文件字节数组
+ */
+ public byte[] downloadFileToBytes(String objectName) {
+ try (OSSObject ossObject = ossClient.getObject(ossConfig.getBucket(), objectName);
+ InputStream inputStream = ossObject.getObjectContent();
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+
+ byte[] buffer = new byte[1024];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+ return outputStream.toByteArray();
+ } catch (Exception e) {
+ logger.error("文件{}下载为字节数组失败", objectName, e);
+ throw new RuntimeException("文件下载为字节数组失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 下载文件为文件
+ *
+ * @param folderPath OSS中的文件名称/路径
+ * @return 文件
+ */
+ public File getLastFile(String folderPath) {
+ try {
+ // 列出文件夹下所有文件(分页处理,避免文件过多)
+ ObjectListing objectListing = ossClient.listObjects(ossConfig.getBucket(), folderPath);
+ List objectSummaries = objectListing.getObjectSummaries();
+
+ // 处理空文件夹
+ if (objectSummaries.isEmpty()) {
+ logger.warn("OSS文件夹 {} 下无文件", folderPath);
+ return null;
+ }
+
+ // 按最后修改时间降序排序,取第一个(最新)
+ String latestFileName = objectSummaries.stream()
+ .filter(summary -> !summary.getKey().equals(folderPath))
+ .max(Comparator.comparing(OSSObjectSummary::getLastModified))
+ .map(OSSObjectSummary::getKey)
+ .orElse(null);
+ if (ObjUtil.isNull(latestFileName)) {
+ throw new RuntimeException("OSS文件夹 " + folderPath + " 下无可用文件");
+ }
+ File file = File.createTempFile(latestFileName.split(".xlsx")[0], ".xlsx");
+ InputStream inputStream = ossClient.getObject(ossConfig.getBucket(), latestFileName).getObjectContent();
+ FileUtil.writeFromStream(inputStream, file);
+ inputStream.close();
+ return file;
+ } catch (Exception e) {
+ logger.error("查找OSS文件夹 {} 最新文件失败", folderPath, e);
+ throw new RuntimeException("查找最新文件失败:" + folderPath, e);
+ }finally {
+
+ }
+ }
+
+ public static void main(String[] args) {
+ String latestFileName="latestFileNam.xlsx";
+
+ String s = latestFileName.split(".xlsx")[0];
+ System.out.println();
+ }
+ /**
+ * 删除单个文件
+ *
+ * @param objectName OSS中的文件名称/路径
+ */
+ public void deleteFile(String objectName) {
+ try {
+ VoidResult voidResult = ossClient.deleteObject(ossConfig.getBucket(), objectName);
+ ResponseMessage response = voidResult.getResponse();
+ System.out.println(response);
+ } catch (Exception e) {
+ logger.error("文件{}删除失败", objectName, e);
+ throw new RuntimeException("文件删除失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 批量删除文件
+ *
+ * @param objectNames 文件名称列表
+ */
+ public void deleteFiles(List objectNames) {
+ try {
+ // 静默删除,不返回删除结果
+ DeleteObjectsRequest request = new DeleteObjectsRequest(ossConfig.getBucket())
+ .withKeys(objectNames);
+ ossClient.deleteObjects(request);
+ } catch (Exception e) {
+ logger.error("批量删除文件失败", e);
+ throw new RuntimeException("批量删除文件失败", e);
+ }
+ }
+
+ /**
+ * 判断文件是否存在
+ *
+ * @param objectName OSS中的文件名称/路径
+ * @return true-存在,false-不存在
+ */
+ public boolean isFileExist(String objectName) {
+ try {
+ boolean exists = ossClient.doesObjectExist(ossConfig.getBucket(), objectName);
+ return exists;
+ } catch (Exception e) {
+ logger.error("判断文件{}是否存在失败", objectName, e);
+ throw new RuntimeException("判断文件是否存在失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 生成文件临时访问URL
+ *
+ * @param objectName OSS中的文件名称/路径
+ * @param expireSeconds 过期时间(秒)
+ * @return 临时访问URL
+ */
+ public String generatePresignedUrl(String objectName, int expireSeconds) {
+ try {
+ Date expiration = new Date(System.currentTimeMillis() + expireSeconds * 1000L);
+ URL url = ossClient.generatePresignedUrl(
+ ossConfig.getBucket(),
+ objectName,
+ expiration
+ );
+ String urlStr = url.toString();
+ logger.info("文件{}生成临时访问URL成功,过期时间:{}秒", objectName, expireSeconds);
+ return urlStr;
+ } catch (Exception e) {
+ logger.error("生成文件{}临时访问URL失败", objectName, e);
+ throw new RuntimeException("生成临时访问URL失败:" + objectName, e);
+ }
+ }
+
+ /**
+ * 关闭OSS客户端
+ */
+ public void close() {
+ if (ossClient != null) {
+ ossClient.shutdown();
+ logger.info("OSS客户端已关闭");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..a7a06ba
--- /dev/null
+++ b/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,5 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ com.njcn.ali.oss.config.AliYunOssConfig,\
+ com.njcn.ali.oss.util.AliYunOssUtils
+
+