Spring Boot整合OSS案例

wen java案例 3

本文目录导读:

Spring Boot整合OSS案例

  1. 添加依赖
  2. 配置文件
  3. OSS配置类
  4. OSS工具类
  5. Service层
  6. Controller层
  7. 全局异常处理
  8. 补充Service方法
  9. 在OssUtil中添加方法
  10. 使用示例
  11. 前端调用示例
  12. 注意事项

我来为您提供一个完整的Spring Boot整合阿里云OSS的案例。

添加依赖

<!-- pom.xml -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

配置文件

application.yml

server:
  port: 8080
spring:
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB
# 阿里云OSS配置
aliyun:
  oss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: your-access-key-id
    access-key-secret: your-access-key-secret
    bucket-name: your-bucket-name
    url-prefix: https://your-bucket-name.oss-cn-hangzhou.aliyuncs.com/
    # 可选配置
    max-file-size: 10485760  # 10MB

application-dev.yml(可选)

aliyun:
  oss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: your-dev-access-key
    access-key-secret: your-dev-access-secret
    bucket-name: dev-bucket
    url-prefix: https://dev-bucket.oss-cn-hangzhou.aliyuncs.com/

OSS配置类

package com.example.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;
@Data
@Configuration
@ConfigurationProperties(prefix = "aliyun.oss")
public class OssConfig {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
    private String urlPrefix;
    private Long maxFileSize;
    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
}

OSS工具类

package com.example.oss.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.*;
import com.example.oss.config.OssConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Component
@RequiredArgsConstructor
public class OssUtil {
    private final OSS ossClient;
    private final OssConfig ossConfig;
    /**
     * 上传文件到OSS
     */
    public String upload(MultipartFile file) {
        // 校验文件大小
        if (file.getSize() > ossConfig.getMaxFileSize()) {
            throw new RuntimeException("文件大小超出限制");
        }
        // 生成文件路径
        String originalFilename = file.getOriginalFilename();
        String filePath = generateFilePath(originalFilename);
        try {
            // 创建上传请求
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(file.getSize());
            metadata.setContentType(file.getContentType());
            // 上传文件
            ossClient.putObject(ossConfig.getBucketName(), filePath, file.getInputStream(), metadata);
            // 返回文件访问URL
            return ossConfig.getUrlPrefix() + filePath;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }
    /**
     * 上传文件到指定目录
     */
    public String upload(MultipartFile file, String directory) {
        String originalFilename = file.getOriginalFilename();
        String filePath = buildFilePath(directory, originalFilename);
        try {
            ossClient.putObject(ossConfig.getBucketName(), filePath, file.getInputStream());
            return ossConfig.getUrlPrefix() + filePath;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }
    /**
     * 字节数组上传
     */
    public String upload(byte[] data, String fileName) {
        String filePath = generateFilePath(fileName);
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data)) {
            ossClient.putObject(ossConfig.getBucketName(), filePath, inputStream);
            return ossConfig.getUrlPrefix() + filePath;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new RuntimeException("文件上传失败: " + e.getMessage());
        }
    }
    /**
     * 下载文件到本地
     */
    public void download(String filePath, String localFilePath) {
        try {
            ossClient.getObject(
                new GetObjectRequest(ossConfig.getBucketName(), filePath),
                new File(localFilePath)
            );
        } catch (Exception e) {
            log.error("文件下载失败", e);
            throw new RuntimeException("文件下载失败: " + e.getMessage());
        }
    }
    /**
     * 下载文件为字节数组
     */
    public byte[] download(String filePath) {
        try {
            OSSObject ossObject = ossClient.getObject(ossConfig.getBucketName(), filePath);
            try (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 (IOException e) {
            log.error("文件下载失败", e);
            throw new RuntimeException("文件下载失败: " + e.getMessage());
        }
    }
    /**
     * 删除文件
     */
    public void delete(String filePath) {
        try {
            // 从URL中提取文件路径
            String objectPath = filePath;
            if (filePath.startsWith(ossConfig.getUrlPrefix())) {
                objectPath = filePath.substring(ossConfig.getUrlPrefix().length());
            }
            ossClient.deleteObject(ossConfig.getBucketName(), objectPath);
            log.info("文件删除成功: {}", objectPath);
        } catch (Exception e) {
            log.error("文件删除失败", e);
            throw new RuntimeException("文件删除失败: " + e.getMessage());
        }
    }
    /**
     * 批量删除文件
     */
    public void deleteBatch(String... filePaths) {
        try {
            for (String filePath : filePaths) {
                delete(filePath);
            }
        } catch (Exception e) {
            log.error("批量删除文件失败", e);
            throw new RuntimeException("批量删除文件失败: " + e.getMessage());
        }
    }
    /**
     * 生成签名URL(临时访问权限)
     */
    public String generatePresignedUrl(String filePath, long expiresInSeconds) {
        Date expiration = new Date(System.currentTimeMillis() + expiresInSeconds * 1000);
        URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), filePath, expiration);
        return url.toString();
    }
    /**
     * 判断文件是否存在
     */
    public boolean doesObjectExist(String filePath) {
        return ossClient.doesObjectExist(ossConfig.getBucketName(), filePath);
    }
    /**
     * 获取文件元数据
     */
    public ObjectMetadata getObjectMetadata(String filePath) {
        return ossClient.getObjectMetadata(ossConfig.getBucketName(), filePath);
    }
    /**
     * 生成文件路径
     */
    private String generateFilePath(String originalFilename) {
        // 获取文件扩展名
        String fileExtension = "";
        if (StringUtils.hasText(originalFilename) && originalFilename.contains(".")) {
            fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
        }
        // 生成新的文件名
        String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExtension;
        // 按日期分目录存储:yyyy/MM/dd/UUID.ext
        String datePath = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new Date());
        return datePath + "/" + newFileName;
    }
    /**
     * 构建文件路径
     */
    private String buildFilePath(String directory, String filename) {
        // 清理目录路径
        directory = directory.replaceAll("^/+|/+$", "");
        String datePath = new java.text.SimpleDateFormat("yyyy/MM/dd").format(new Date());
        // 生成新的文件名
        String fileExtension = "";
        if (StringUtils.hasText(filename) && filename.contains(".")) {
            fileExtension = filename.substring(filename.lastIndexOf("."));
        }
        String newFileName = UUID.randomUUID().toString().replace("-", "") + fileExtension;
        return directory + "/" + datePath + "/" + newFileName;
    }
}

Service层

package com.example.oss.service;
import com.example.oss.util.OssUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class FileService {
    private final OssUtil ossUtil;
    /**
     * 上传文件
     */
    public String uploadFile(MultipartFile file) {
        validateFile(file);
        return ossUtil.upload(file);
    }
    /**
     * 上传到指定目录
     */
    public String uploadFileToDirectory(MultipartFile file, String directory) {
        validateFile(file);
        return ossUtil.upload(file, directory);
    }
    /**
     * 批量上传
     */
    public List<String> uploadFiles(List<MultipartFile> files) {
        List<String> urls = new ArrayList<>();
        for (MultipartFile file : files) {
            urls.add(uploadFile(file));
        }
        return urls;
    }
    /**
     * 删除文件
     */
    public void deleteFile(String fileUrl) {
        ossUtil.delete(fileUrl);
    }
    /**
     * 下载文件
     */
    public byte[] downloadFile(String filePath) {
        return ossUtil.download(filePath);
    }
    /**
     * 校验文件
     */
    private void validateFile(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new RuntimeException("文件不能为空");
        }
        // 校验文件大小
        long maxSize = 10 * 1024 * 1024; // 10MB
        if (file.getSize() > maxSize) {
            throw new RuntimeException("文件大小不能超过10MB");
        }
        // 校验文件类型
        String contentType = file.getContentType();
        if (!isAllowedFileType(contentType)) {
            throw new RuntimeException("不支持的文件类型");
        }
    }
    /**
     * 判断文件类型是否支持
     */
    private boolean isAllowedFileType(String contentType) {
        // 允许的文件类型
        String[] allowedTypes = {
            "image/jpeg",      // .jpg
            "image/png",       // .png
            "image/gif",       // .gif
            "application/pdf", // .pdf
            "application/msword",           // .doc
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
            "application/vnd.ms-excel",     // .xls
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",        // .xlsx
            "text/plain",      // .txt
            "video/mp4",       // .mp4
            "audio/mpeg"       // .mp3
        };
        for (String type : allowedTypes) {
            if (type.equals(contentType)) {
                return true;
            }
        }
        return false;
    }
}

Controller层

package com.example.oss.controller;
import com.example.oss.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/file")
@RequiredArgsConstructor
public class FileController {
    private final FileService fileService;
    /**
     * 上传文件
     */
    @PostMapping("/upload")
    public ResponseEntity<Map<String, String>> upload(@RequestParam("file") MultipartFile file) {
        String url = fileService.uploadFile(file);
        return ResponseEntity.ok(Map.of("url", url));
    }
    /**
     * 上传到指定目录
     */
    @PostMapping("/upload/{directory}")
    public ResponseEntity<Map<String, String>> uploadToDirectory(
            @RequestParam("file") MultipartFile file,
            @PathVariable("directory") String directory) {
        String url = fileService.uploadFileToDirectory(file, directory);
        return ResponseEntity.ok(Map.of("url", url));
    }
    /**
     * 批量上传
     */
    @PostMapping("/upload/batch")
    public ResponseEntity<Map<String, Object>> uploadBatch(
            @RequestParam("files") List<MultipartFile> files) {
        List<String> urls = fileService.uploadFiles(files);
        return ResponseEntity.ok(Map.of("urls", urls));
    }
    /**
     * 删除文件
     */
    @DeleteMapping("/delete")
    public ResponseEntity<Void> delete(@RequestParam("fileUrl") String fileUrl) {
        fileService.deleteFile(fileUrl);
        return ResponseEntity.noContent().build();
    }
    /**
     * 下载文件
     */
    @GetMapping("/download")
    public ResponseEntity<byte[]> download(@RequestParam("filePath") String filePath) {
        byte[] data = fileService.downloadFile(filePath);
        String filename = filePath.substring(filePath.lastIndexOf("/") + 1);
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment; filename=\"" + filename + "\"")
                .body(data);
    }
    /**
     * 获取文件访问URL
     */
    @GetMapping("/url")
    public ResponseEntity<Map<String, String>> getFileUrl(
            @RequestParam("filePath") String filePath) {
        String url = fileService.getFileUrl(filePath);
        return ResponseEntity.ok(Map.of("url", url));
    }
}

全局异常处理

package com.example.oss.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Map<String, String>> handleRuntimeException(RuntimeException e) {
        log.error("运行时异常", e);
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Map.of("message", e.getMessage()));
    }
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<Map<String, String>> handleMaxUploadSizeExceededException(
            MaxUploadSizeExceededException e) {
        log.error("文件大小超出限制", e);
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Map.of("message", "文件大小超出限制"));
    }
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, String>> handleException(Exception e) {
        log.error("系统异常", e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Map.of("message", "系统异常,请稍后重试"));
    }
}

补充Service方法

// 在FileService中添加这些方法
public String getFileUrl(String filePath) {
    return ossUtil.getFileUrl(filePath);
}

在OssUtil中添加方法

/**
 * 获取文件公开访问URL
 */
public String getFileUrl(String filePath) {
    // 如果文件路径已经是完整URL
    if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
        return filePath;
    }
    // 去除路径开头的斜杠
    filePath = filePath.replaceAll("^/+", "");
    return ossConfig.getUrlPrefix() + filePath;
}
/**
 * 生成临时签名URL
 */
public String generatePresignedUrl(String filePath, long expiresInMinutes) {
    Date expiration = new Date(System.currentTimeMillis() + expiresInMinutes * 60 * 1000);
    URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), filePath, expiration);
    return url.toString();
}

使用示例

// 测试上传
@RestController
@RequiredArgsConstructor
public class TestController {
    private final FileService fileService;
    @PostMapping("/test/upload")
    public String testUpload(@RequestParam("file") MultipartFile file) {
        return fileService.uploadFile(file);
    }
    @DeleteMapping("/test/delete")
    public String testDelete(@RequestParam("fileUrl") String fileUrl) {
        fileService.deleteFile(fileUrl);
        return "删除成功";
    }
}

前端调用示例

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">OSS文件上传</title>
</head>
<body>
    <form id="uploadForm">
        <input type="file" name="file" id="file">
        <button type="button" onclick="upload()">上传</button>
    </form>
    <div id="result" style="margin-top: 20px;"></div>
    <script>
        function upload() {
            const fileInput = document.getElementById('file');
            const file = fileInput.files[0];
            if (!file) {
                alert('请选择文件');
                return;
            }
            const formData = new FormData();
            formData.append('file', file);
            fetch('/api/file/upload', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                document.getElementById('result').innerHTML = 
                    '上传成功:<a href="' + data.url + '" target="_blank">' + data.url + '</a>';
            })
            .catch(error => {
                console.error('上传失败:', error);
                alert('上传失败');
            });
        }
    </script>
</body>
</html>

注意事项

  1. Security凭证:不要在生产环境代码中直接写明AccessKey,建议使用环境变量或配置中心

  2. 文件校验:必须校验文件类型和大小,防止恶意上传

  3. 错误处理:完善的异常处理机制,包括网络异常、OSS服务异常等

  4. 日志记录:记录关键操作日志,便于问题排查

  5. 性能优化:对于大文件,可以考虑使用分片上传

  6. 权限控制:根据业务需求配置Bucket的访问权限(私有或公共读)

  7. 成本控制:设置生命周期规则,定期清理无用文件

  8. 安全配置:启用HTTPS,对敏感数据加密存储

抱歉,评论功能暂时关闭!