从AWS SDK for Java 2.x 调用 AWS 服务

从AWS SDK for Java 2.x 调用 AWS 服务

1、JAR包引入

xml 复制代码
<!-- AWS SDK v2 -->
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.20.162</version>
</dependency>

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>auth</artifactId>
    <version>2.20.162</version>
</dependency>

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>apache-client</artifactId>
    <version>2.20.162</version>
</dependency>

2、配置

yaml 复制代码
#上传配置
upload:
  aws: #aws上传
    secretId: AKIA6666
    secretKey: daEiuKneDT1111
    bucket: live
    region: ap-4544-1
    rootPath:  /crm-drd  # 文件存储根路径
    days: 7   # 预签名URL有效期(天数)
    awsHost: https://45454545-1.amazonaws.com
    proxyHost: https://wewewet.ai
    enabled : true

3、读取配置

arduino 复制代码
package com.yicrm.common.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * AWS S3 配置属性
 *
 * @author yicrm
 * @since 2026-01-29
 */
@Data
@Component
@ConfigurationProperties(prefix = "upload.aws")
public class AwsProperties {

    /**
     * 是否启用 AWS S3
     */
    private Boolean enabled = false;

    /**
     * Access Key ID
     */
    private String secretId;

    /**
     * Secret Access Key
     */
    private String secretKey;

    /**
     * 区域
     */
    private String region = "ap-southeast-1";

    /**
     * 存储桶名称
     */
    private String bucket;

    /**
     * 根路径前缀
     */
    private String rootPath = "";

    /**
     * aws域名
     * */
    private String awsHost;

    /**
     * 代理域名(用于替换 S3 URL 的域名)
     */
    private String proxyHost;

    /**
     * 预签名 URL 有效期(天数)
     */
    private Integer days = 7;

    /**
     * 端点 URL(用于兼容 S3 兼容的存储服务)
     */
    private String endpoint;
}

4、AwsUtils方法实现

scss 复制代码
package com.yicrm.common.utils.aws;

import com.yicrm.common.config.AwsProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.UUID;

/**
 * AWS S3 工具类
 *
 * @author yicrm
 * @since 2026-01-29
 */
@Slf4j
@Component
public class AwsUtils {

    @Autowired(required = false)
    private AwsProperties awsProperties;

    private S3Client s3Client;
    private S3Presigner s3Presigner;

    @PostConstruct
    public void init() {
        if (awsProperties != null && Boolean.TRUE.equals(awsProperties.getEnabled())) {
            try {
                AwsBasicCredentials awsCreds = AwsBasicCredentials.create(
                        awsProperties.getSecretId(),
                        awsProperties.getSecretKey()
                );

                software.amazon.awssdk.services.s3.S3ClientBuilder s3ClientBuilder = S3Client.builder()
                        .region(Region.of(awsProperties.getRegion()))
                        .credentialsProvider(StaticCredentialsProvider.create(awsCreds))
                        .httpClient(ApacheHttpClient.builder().build());

                // 如果配置了自定义端点,使用自定义端点
                if (StringUtils.isNotBlank(awsProperties.getEndpoint())) {
                    s3ClientBuilder.endpointOverride(new URL(awsProperties.getEndpoint()).toURI());
                }

                s3Client = s3ClientBuilder.build();

                S3Presigner.Builder presignerBuilder = S3Presigner.builder()
                        .region(Region.of(awsProperties.getRegion()))
                        .credentialsProvider(StaticCredentialsProvider.create(awsCreds));

                if (StringUtils.isNotBlank(awsProperties.getEndpoint())) {
                    presignerBuilder.endpointOverride(new URL(awsProperties.getEndpoint()).toURI());
                }

                s3Presigner = presignerBuilder.build();

                log.info("AWS S3 客户端初始化成功");
            } catch (Exception e) {
                log.error("AWS S3 客户端初始化失败", e);
            }
        }
    }

    @PreDestroy
    public void destroy() {
        if (s3Client != null) {
            s3Client.close();
        }
        if (s3Presigner != null) {
            s3Presigner.close();
        }
    }

    /**
     * 检查 AWS 是否启用
     */
    public boolean isEnabled() {
        return awsProperties != null && Boolean.TRUE.equals(awsProperties.getEnabled()) && s3Client != null;
    }

    /**
     * 上传文件到 S3
     *
     * @param file 文件
     * @return 上传结果
     */
    public AwsUploadResult uploadFile(MultipartFile file) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            String originalFilename = file.getOriginalFilename();
            String fileExtension = "";
            if (originalFilename != null && originalFilename.contains(".")) {
                fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1).toLowerCase();
            }

            String key = UUID.randomUUID().toString().replace("-", "");
            String newFileName = key + (StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");

            // 构建对象键
            String rootPath = StringUtils.isNotBlank(awsProperties.getRootPath()) 
                    ? awsProperties.getRootPath().endsWith("/") 
                        ? awsProperties.getRootPath() 
                        : awsProperties.getRootPath() + "/"
                    : "";
            String objectKey = rootPath + newFileName;

            // 创建临时文件
            File tempFile = File.createTempFile(key, StringUtils.isNotBlank(fileExtension) ? "." + fileExtension : "");
            try {
                Files.copy(file.getInputStream(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);

                // 上传到 S3
                PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                        .bucket(awsProperties.getBucket())
                        .key(objectKey)
                        .contentType("application/octet-stream")
                        .contentDisposition("attachment")
                        .contentLength(file.getSize())
                        .build();

                s3Client.putObject(putObjectRequest, RequestBody.fromFile(tempFile));

                // 生成文件访问 URL
                String fileUrl = generatePresignedUrl(objectKey, true);

                AwsUploadResult result = new AwsUploadResult();
                result.setKey(objectKey);
                result.setUrl(fileUrl);
                result.setOriginalFilename(originalFilename);
                result.setSize(file.getSize());
                result.setContentType(file.getContentType());
                log.info("AWS S3 文件上传成功: key={}, url={}", objectKey, fileUrl);
                return result;
            } finally {
                // 删除临时文件
                if (tempFile.exists()) {
                    tempFile.delete();
                }
            }
        } catch (Exception e) {
            log.error("AWS S3 文件上传失败", e);
            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 上传文件到 S3(使用文件对象)
     *
     * @param file 文件
     * @param objectKey 对象键
     * @return 上传结果
     */
    public AwsUploadResult uploadFile(File file, String objectKey) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .contentLength(file.length())
                    .build();

            s3Client.putObject(putObjectRequest, RequestBody.fromFile(file));

            String fileUrl = generatePresignedUrl(objectKey, true);

            AwsUploadResult result = new AwsUploadResult();
            result.setKey(objectKey);
            result.setUrl(fileUrl);
            result.setOriginalFilename(file.getName());
            result.setSize(file.length());

            log.info("AWS S3 文件上传成功: key={}, url={}", objectKey, fileUrl);
            return result;
        } catch (Exception e) {
            log.error("AWS S3 文件上传失败: key={}", objectKey, e);
            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 下载文件
     *
     * @param objectKey 对象键
     * @param targetPath 目标文件路径
     * @return 下载的文件
     */
    public File downloadFile(String objectKey, String targetPath) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .build();

            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            File targetFile = new File(targetPath);

            // 确保目标文件的父目录存在
            File parentDir = targetFile.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }

            // 将响应流写入文件
            try (FileOutputStream fos = new FileOutputStream(targetFile);
                 InputStream inputStream = response) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }

            log.info("AWS S3 文件下载成功: key={}, path={}", objectKey, targetPath);
            return targetFile;
        } catch (Exception e) {
            log.error("AWS S3 文件下载失败: key={}, path={}", objectKey, targetPath, e);
            throw new RuntimeException("文件下载失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取文件输入流
     *
     * @param objectKey 对象键
     * @return 文件输入流
     */
    public InputStream getFileInputStream(String objectKey) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .build();

            ResponseInputStream<GetObjectResponse> response = s3Client.getObject(getObjectRequest);
            log.info("AWS S3 文件流获取成功: key={}", objectKey);
            return response;
        } catch (Exception e) {
            log.error("AWS S3 文件流获取失败: key={}", objectKey, e);
            throw new RuntimeException("文件流获取失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除文件
     *
     * @param objectKey 对象键
     */
    public void deleteFile(String objectKey) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .build();

            s3Client.deleteObject(deleteObjectRequest);
            log.info("AWS S3 文件删除成功: key={}", objectKey);
        } catch (Exception e) {
            log.error("AWS S3 文件删除失败: key={}", objectKey, e);
            throw new RuntimeException("文件删除失败: " + e.getMessage(), e);
        }
    }

    /**
     * 检查文件是否存在
     *
     * @param objectKey 对象键
     * @return 是否存在
     */
    public boolean fileExists(String objectKey) {
        if (!isEnabled()) {
            return false;
        }

        try {
            HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .build();

            s3Client.headObject(headObjectRequest);
            return true;
        } catch (NoSuchKeyException e) {
            return false;
        } catch (Exception e) {
            log.error("AWS S3 检查文件存在性失败: key={}", objectKey, e);
            return false;
        }
    }

    /**
     * 生成预签名 URL(用于下载)
     *
     * @param objectKey 对象键
     * @param forceInline 是否强制内联显示(用于预览)
     * @return 预签名 URL
     */
    public String generatePresignedUrl(String objectKey, boolean forceInline) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            GetObjectRequest.Builder requestBuilder = GetObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey);

            if (forceInline) {
                requestBuilder.responseContentDisposition("inline");
            }

            GetObjectRequest objectRequest = requestBuilder.build();

            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofDays(awsProperties.getDays()))
                    .getObjectRequest(objectRequest)
                    .build();

            PresignedGetObjectRequest presignedRequest = s3Presigner.presignGetObject(presignRequest);
            String url = presignedRequest.url().toString();

            // 如果配置了代理域名,替换 URL 中的域名
            if (StringUtils.isNotBlank(awsProperties.getProxyHost())) {
                url = replaceUrlDomain(url, awsProperties.getProxyHost());
            }

            log.info("生成预签名 URL: key={}, url={}", objectKey, url);
            return url;
        } catch (Exception e) {
            log.error("生成预签名 URL 失败: key={}", objectKey, e);
            throw new RuntimeException("生成预签名 URL 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成预签名下载 URL(用于文件下载)
     *
     * @param objectKey 对象键
     * @return 预签名下载 URL
     */
    public String generatePresignedDownloadUrl(String objectKey) {
        return generatePresignedDownloadUrl(objectKey, null, null);
    }

    /**
     * 生成预签名下载 URL(用于文件下载,支持自定义文件名)
     *
     * @param objectKey 对象键
     * @param filename 下载时的文件名(可选)
     * @return 预签名下载 URL
     */
    public String generatePresignedDownloadUrl(String objectKey, String filename) {
        return generatePresignedDownloadUrl(objectKey, filename, null);
    }

    /**
     * 生成预签名下载 URL(用于文件下载,支持自定义文件名和有效期)
     *
     * @param objectKey 对象键
     * @param filename 下载时的文件名(可选,如果为空则使用默认文件名)
     * @param days 有效期(天数,如果为空则使用配置的默认值)
     * @return 预签名下载 URL
     */
    public String generatePresignedDownloadUrl(String objectKey, String filename, Integer days) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            GetObjectRequest.Builder requestBuilder = GetObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey);

            // 如果指定了文件名,设置下载时的文件名
            if (StringUtils.isNotBlank(filename)) {
                requestBuilder.responseContentDisposition("attachment; filename="" + filename + """);
            } else {
                // 默认设置为附件下载
                requestBuilder.responseContentDisposition("attachment");
            }

            GetObjectRequest objectRequest = requestBuilder.build();

            // 使用指定的有效期或配置的默认值
            int signatureDays = days != null ? days : awsProperties.getDays();
            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofDays(signatureDays))
                    .getObjectRequest(objectRequest)
                    .build();

            PresignedGetObjectRequest presignedRequest = s3Presigner.presignGetObject(presignRequest);
            String url = presignedRequest.url().toString();

            // 如果配置了代理域名,替换 URL 中的域名
            if (StringUtils.isNotBlank(awsProperties.getProxyHost())) {
                url = replaceUrlDomain(url, awsProperties.getProxyHost());
            }

            log.info("生成预签名下载 URL: key={}, filename={}, days={}, url={}", objectKey, filename, signatureDays, url);
            return url;
        } catch (Exception e) {
            log.error("生成预签名下载 URL 失败: key={}, filename={}", objectKey, filename, e);
            throw new RuntimeException("生成预签名下载 URL 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 生成预签名上传 URL
     *
     * @param objectKey 对象键
     * @return 预签名上传 URL
     */
    public String generatePresignedPutUrl(String objectKey) {
        if (!isEnabled()) {
            throw new IllegalStateException("AWS S3 未启用或未正确配置");
        }

        try {
            PutObjectRequest putObjectRequest = PutObjectRequest.builder()
                    .bucket(awsProperties.getBucket())
                    .key(objectKey)
                    .build();

            PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofDays(awsProperties.getDays()))
                    .putObjectRequest(putObjectRequest)
                    .build();

            PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(presignRequest);
            String url = presignedRequest.url().toString();

            log.info("生成预签名上传 URL: key={}, url={}", objectKey, url);
            return url;
        } catch (Exception e) {
            log.error("生成预签名上传 URL 失败: key={}", objectKey, e);
            throw new RuntimeException("生成预签名上传 URL 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 替换 URL 中的域名
     *
     * @param originalUrl 原始 URL
     * @param targetDomain 目标域名
     * @return 替换后的 URL
     */
    private String replaceUrlDomain(String originalUrl, String targetDomain) {
        try {
            URL url = new URL(originalUrl);
            String targetDomainClean = targetDomain.replaceAll("^https?://", "").replaceAll("/$", "");
            String protocol = url.getProtocol();
            String newUrl = protocol + "://" + targetDomainClean + url.getPath();
            if (url.getQuery() != null) {
                newUrl += "?" + url.getQuery();
            }
            return newUrl;
        } catch (Exception e) {
            log.warn("替换 URL 域名失败: originalUrl={}, targetDomain={}", originalUrl, targetDomain, e);
            return originalUrl;
        }
    }
}

5、文件上传限制

kotlin 复制代码
file:
  upload:
    formatList: pdf
    size: 52428800


package com.yicrm.common.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 文件上传配置属性
 *
 * @author yicrm
 * @since 2026-01-29
 */
@Data
@Component
@ConfigurationProperties(prefix = "file.upload")
public class FileUploadProperties {

    /**
     * 允许的文件格式列表(逗号分隔,如:pdf,doc,docx)
     */
    private String formatList = "";

    /**
     * 文件大小限制(字节),默认 50MB
     */
    private Long size = 50 * 1024 * 1024L;
}

6、AWS实现类

kotlin 复制代码
package com.yicrm.biz.service.business;

import com.yicrm.common.config.FileUploadProperties;
import com.yicrm.common.core.domain.AjaxResult;
import com.yicrm.common.utils.aws.AwsUploadResult;
import com.yicrm.common.utils.aws.AwsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文件上传下载业务服务
 *
 * @author yicrm
 * @since 2026-01-29
 */
@Slf4j
@Service
public class FileUploadBusinessService {

    @Resource
    private AwsUtils awsUtils;

    @Resource
    private FileUploadProperties fileUploadProperties;

    /**
     * AWS S3 文件上传(单个)
     *
     * @param file 文件
     * @return 上传结果
     */
    public AjaxResult uploadFile(MultipartFile file) {
        try {
            if (file == null || file.isEmpty()) {
                return AjaxResult.error("文件不能为空");
            }

            // 验证文件格式和大小
            AjaxResult validationResult = validateFile(file);
            if (validationResult != null) {
                return validationResult;
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            AwsUploadResult result = awsUtils.uploadFile(file);

            Map<String, Object> data = new HashMap<>();
            data.put("key", result.getKey());
            data.put("url", result.getUrl());
            data.put("fileName", result.getKey());
            data.put("originalFilename", result.getOriginalFilename());
            data.put("size", result.getSize());
            data.put("contentType", result.getContentType());
//            data.put("contentType", "application/octet-stream");
//            data.put("Content-Disposition", "attachment; filename="+result.getOriginalFilename());

            return AjaxResult.success("文件上传成功", data);
        } catch (Exception e) {
            log.error("AWS S3 文件上传失败", e);
            return AjaxResult.error("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * AWS S3 文件上传(多个)
     *
     * @param files 文件列表
     * @return 上传结果
     */
    public AjaxResult uploadFiles(List<MultipartFile> files) {
        try {
            if (files == null || files.isEmpty()) {
                return AjaxResult.error("文件列表不能为空");
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            List<Map<String, Object>> results = new ArrayList<>();
            List<String> keys = new ArrayList<>();
            List<String> urls = new ArrayList<>();
            List<String> fileNames = new ArrayList<>();
            List<String> originalFilenames = new ArrayList<>();

            for (MultipartFile file : files) {
                if (file.isEmpty()) {
                    continue;
                }

                // 验证文件格式和大小
                AjaxResult validationResult = validateFile(file);
                if (validationResult != null) {
                    // 如果验证失败,返回错误信息
                    return validationResult;
                }

                AwsUploadResult result = awsUtils.uploadFile(file);

                Map<String, Object> data = new HashMap<>();
                data.put("key", result.getKey());
                data.put("url", result.getUrl());
                data.put("fileName", result.getKey());
                data.put("originalFilename", result.getOriginalFilename());
                data.put("size", result.getSize());
                data.put("contentType", result.getContentType());

                results.add(data);
                keys.add(result.getKey());
                urls.add(result.getUrl());
                fileNames.add(result.getKey());
                originalFilenames.add(result.getOriginalFilename());
            }

            Map<String, Object> responseData = new HashMap<>();
            responseData.put("results", results);
            responseData.put("keys", StringUtils.join(keys, ","));
            responseData.put("urls", StringUtils.join(urls, ","));
            responseData.put("fileNames", StringUtils.join(fileNames, ","));
            responseData.put("originalFilenames", StringUtils.join(originalFilenames, ","));

            return AjaxResult.success("文件上传成功", responseData);
        } catch (Exception e) {
            log.error("AWS S3 批量文件上传失败", e);
            return AjaxResult.error("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 生成 AWS S3 预签名下载 URL
     *
     * @param key 对象键(S3 中的文件路径)
     * @param filename 下载时的文件名(可选)
     * @param days 有效期(天数,可选,默认使用配置值)
     * @return 预签名下载 URL
     */
    public AjaxResult generateDownloadUrl(String key, String filename, Integer days) {
        try {
            if (StringUtils.isBlank(key)) {
                return AjaxResult.error("对象键不能为空");
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            String downloadUrl;
            if (filename != null && days != null) {
                downloadUrl = awsUtils.generatePresignedDownloadUrl(key, filename, days);
            } else if (filename != null) {
                downloadUrl = awsUtils.generatePresignedDownloadUrl(key, filename);
            } else {
                downloadUrl = awsUtils.generatePresignedDownloadUrl(key);
            }

            Map<String, Object> data = new HashMap<>();
            data.put("url", downloadUrl);
            data.put("key", key);
            data.put("filename", filename);

            return AjaxResult.success("生成下载 URL 成功", data);
        } catch (Exception e) {
            log.error("生成 AWS S3 下载 URL 失败: key={}", key, e);
            return AjaxResult.error("生成下载 URL 失败: " + e.getMessage());
        }
    }

    /**
     * 生成 AWS S3 预签名预览 URL
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 预签名预览 URL
     */
    public AjaxResult generatePreviewUrl(String key) {
        try {
            if (StringUtils.isBlank(key)) {
                return AjaxResult.error("对象键不能为空");
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            String previewUrl = awsUtils.generatePresignedUrl(key, true);

            Map<String, Object> data = new HashMap<>();
            data.put("url", previewUrl);
            data.put("key", key);

            return AjaxResult.success("生成预览 URL 成功", data);
        } catch (Exception e) {
            log.error("生成 AWS S3 预览 URL 失败: key={}", key, e);
            return AjaxResult.error("生成预览 URL 失败: " + e.getMessage());
        }
    }

    /**
     * 删除 AWS S3 文件
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 删除结果
     */
    public AjaxResult deleteFile(String key) {
        try {
            if (StringUtils.isBlank(key)) {
                return AjaxResult.error("对象键不能为空");
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            awsUtils.deleteFile(key);

            return AjaxResult.success("文件删除成功");
        } catch (Exception e) {
            log.error("删除 AWS S3 文件失败: key={}", key, e);
            return AjaxResult.error("文件删除失败: " + e.getMessage());
        }
    }

    /**
     * 检查 AWS S3 文件是否存在
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 是否存在
     */
    public AjaxResult checkFileExists(String key) {
        try {
            if (StringUtils.isBlank(key)) {
                return AjaxResult.error("对象键不能为空");
            }

            if (!awsUtils.isEnabled()) {
                return AjaxResult.error("AWS S3 未启用或未正确配置");
            }

            boolean exists = awsUtils.fileExists(key);

            Map<String, Object> data = new HashMap<>();
            data.put("exists", exists);
            data.put("key", key);

            return AjaxResult.success("检查完成", data);
        } catch (Exception e) {
            log.error("检查 AWS S3 文件存在性失败: key={}", key, e);
            return AjaxResult.error("检查失败: " + e.getMessage());
        }
    }

    /**
     * 验证文件格式和大小
     *
     * @param file 文件
     * @return 验证失败返回错误结果,验证成功返回 null
     */
    private AjaxResult validateFile(MultipartFile file) {
        // 验证文件大小
        if (fileUploadProperties.getSize() != null && file.getSize() > fileUploadProperties.getSize()) {
            long maxSizeMB = fileUploadProperties.getSize() / 1024 / 1024;
            return AjaxResult.error(String.format("文件大小超出限制,允许的最大文件大小为:%d MB", maxSizeMB));
        }

        // 验证文件格式
        if (StringUtils.isNotBlank(fileUploadProperties.getFormatList())) {
            String originalFilename = file.getOriginalFilename();
            if (StringUtils.isBlank(originalFilename)) {
                return AjaxResult.error("文件名不能为空");
            }

            // 获取文件扩展名
            String extension = getFileExtension(originalFilename);
            if (StringUtils.isBlank(extension)) {
                return AjaxResult.error("文件格式不支持,无法识别文件类型");
            }

            // 解析允许的格式列表
            String[] allowedFormats = fileUploadProperties.getFormatList().split(",");
            List<String> allowedFormatList = new ArrayList<>();
            for (String format : allowedFormats) {
                String trimmedFormat = format.trim().toLowerCase();
                if (StringUtils.isNotBlank(trimmedFormat)) {
                    allowedFormatList.add(trimmedFormat);
                }
            }

            // 检查文件格式是否在允许列表中
            if (!allowedFormatList.isEmpty() && !allowedFormatList.contains(extension.toLowerCase())) {
                return AjaxResult.error(String.format("文件格式不支持,允许的格式为:%s,当前文件格式:%s",
                        fileUploadProperties.getFormatList(), extension));
            }
        }

        return null; // 验证通过
    }

    /**
     * 获取文件扩展名
     *
     * @param filename 文件名
     * @return 扩展名(不包含点)
     */
    private String getFileExtension(String filename) {
        if (StringUtils.isBlank(filename)) {
            return "";
        }
        int lastDotIndex = filename.lastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) {
            return filename.substring(lastDotIndex + 1);
        }
        return "";
    }
}

7、controller实现

less 复制代码
package com.yicrm.biz.controller;

import com.yicrm.biz.service.business.FileUploadBusinessService;
import com.yicrm.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.List;

/**
 * 文件上传下载控制器
 *
 * @author yicrm
 * @since 2026-01-29
 */
@RestController
@RequestMapping("/file")
@Tag(name = "文件上传下载")
public class FileUploadController {

    @Resource
    private FileUploadBusinessService fileUploadBusinessService;

    /**
     * AWS S3 文件上传(单个)
     *
     * @param file 文件
     * @return 上传结果
     */
    @Operation(summary = "AWS S3 文件上传(单个)", description = "上传单个文件到 AWS S3")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "上传成功",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "上传失败")
    })
    @PostMapping("/aws/upload")
//    @PreAuthorize("@ss.hasPermi('file:upload')")
    public AjaxResult uploadFileToAws(@RequestParam("file") MultipartFile file) {
        return fileUploadBusinessService.uploadFile(file);
    }

    /**
     * AWS S3 文件上传(多个)
     *
     * @param files 文件列表
     * @return 上传结果
     */
    @Operation(summary = "AWS S3 文件上传(多个)", description = "批量上传多个文件到 AWS S3")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "上传成功",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "上传失败")
    })
    @PostMapping("/aws/uploads")
//    @PreAuthorize("@ss.hasPermi('file:upload')")
    public AjaxResult uploadFilesToAws(@RequestParam("files") List<MultipartFile> files) {
        return fileUploadBusinessService.uploadFiles(files);
    }

    /**
     * 生成 AWS S3 预签名下载 URL
     *
     * @param key 对象键(S3 中的文件路径)
     * @param filename 下载时的文件名(可选)
     * @param days 有效期(天数,可选,默认使用配置值)
     * @return 预签名下载 URL
     */
    @Operation(summary = "生成 AWS S3 预签名下载 URL", description = "生成用于文件下载的预签名 URL")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "生成成功",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "生成失败")
    })
    @GetMapping("/aws/download/url")
//    @PreAuthorize("@ss.hasPermi('file:download')")
    public AjaxResult generateDownloadUrl(
            @RequestParam("key") String key,
            @RequestParam(value = "filename", required = false) String filename,
            @RequestParam(value = "days", required = false) Integer days) {
        return fileUploadBusinessService.generateDownloadUrl(key, filename, days);
    }

    /**
     * 生成 AWS S3 预签名预览 URL
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 预签名预览 URL
     */
    @Operation(summary = "生成 AWS S3 预签名预览 URL", description = "生成用于文件预览的预签名 URL")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "生成成功",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "生成失败")
    })
    @GetMapping("/aws/preview/url")
//    @PreAuthorize("@ss.hasPermi('file:preview')")
    public AjaxResult generatePreviewUrl(@RequestParam("key") String key) {
        return fileUploadBusinessService.generatePreviewUrl(key);
    }

    /**
     * 删除 AWS S3 文件
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 删除结果
     */
    @Operation(summary = "删除 AWS S3 文件", description = "从 AWS S3 删除指定文件")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "删除成功",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "删除失败")
    })
    @DeleteMapping("/aws/delete")
//    @PreAuthorize("@ss.hasPermi('file:delete')")
    public AjaxResult deleteFileFromAws(@RequestParam("key") String key) {
        return fileUploadBusinessService.deleteFile(key);
    }

    /**
     * 检查 AWS S3 文件是否存在
     *
     * @param key 对象键(S3 中的文件路径)
     * @return 是否存在
     */
    @Operation(summary = "检查 AWS S3 文件是否存在", description = "检查指定文件是否存在于 AWS S3")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "检查完成",
                    content = @Content(schema = @Schema(implementation = AjaxResult.class))),
            @ApiResponse(responseCode = "500", description = "检查失败")
    })
    @GetMapping("/aws/exists")
//    @PreAuthorize("@ss.hasPermi('file:query')")
    public AjaxResult checkFileExists(@RequestParam("key") String key) {
        return fileUploadBusinessService.checkFileExists(key);
    }
}
相关推荐
青云计划8 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3569 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3569 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye11110 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai11 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟11 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事11 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊11 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
想用offer打牌12 小时前
MCP (Model Context Protocol) 技术理解 - 第五篇
人工智能·后端·mcp