在现代 Web 应用中,文件上传是核心功能之一,而当面对大文件(如视频、压缩包等)时,传统的单文件上传方案往往会遇到超时、网络波动失败、效率低下等问题。阿里云 OSS(Object Storage Service)作为稳定可靠的对象存储服务,提供了完善的文件上传解决方案。本文将从基础的简单上传讲起,逐步深入到分片上传,并结合 Redis 实现断点续传功能,深入剖析OSS文件上传功能,帮助你构建高效、稳定的大文件上传系统。

OSS 文件上传基础:简单上传

        简单上传适用于小文件场景(通常建议文件大小≤100MB),通过 OSS 的PutObject接口直接将文件上传到 OSS。这种方式实现简单,适合快速集成。

        

1. 环境准备

(1)依赖配置(Maven)

首先在pom.xml中添加 OSS SDK 相关依赖:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

如果使用的是Java 9及以上的版本,则需要添加以下JAXB相关依赖:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

(2)OSS 配置

application.yml中配置 OSS 连接信息:

aliyun:
  oss:
    endpoint: oss-cn-beijing.aliyuncs.com # 地域Endpoint
    access-key-id: your-access-key-id
    access-key-secret: your-access-key-secret
    bucket-name: your-bucket-name # 存储桶名称

创建配置类初始化 OSS 客户端:

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "aliyun")
public class AliYunConfig {
    private String accessKey; // 阿里云AccessKeyId

    private String accessKeySecret; // 阿里云AccessKeySecret

    private String ossBucket; // 存储桶名称

    private String ossEndpoint; // OSS地域节点

    @Bean
    public OSS oSSClient() {
        return new OSSClient(ossEndpoint, accessKey, accessKeySecret);
    }
}

创建统一结果返回类:

package com.netflow.utils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * 统一结果返回类
 * @param <T> 返回数据的类型
 */

public class Msg<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    // 状态码
    private int code;
    // 消息
    private String message;
    // 返回数据
    private T data;
    // 时间戳
    private long timestamp;
    // 附加数据(可选)
    private Map<String, Object> extra;

    // 构造方法
    private Msg() {
        this.timestamp = System.currentTimeMillis();
    }

    private Msg(int code, String message) {
        this();
        this.code = code;
        this.message = message;
    }

    private Msg(int code, String message, T data) {
        this(code, message);
        this.data = data;
    }

    // 成功静态工厂方法
    public static <T> Msg<T> success() {
        return new Msg<>(200, "操作成功");
    }

    public static <T> Msg<T> success(String message) {
        return new Msg<>(200, message);
    }

    public static <T> Msg<T> success(T data) {
        return new Msg<>(200, "操作成功", data);
    }

    public static <T> Msg<T> success(String message, T data) {
        return new Msg<>(200, message, data);
    }

    // 失败静态工厂方法
    public static <T> Msg<T> fail() {
        return new Msg<>(500, "操作失败");
    }

    public static <T> Msg<T> fail(String message) {
        return new Msg<>(500, message);
    }

    public static <T> Msg<T> fail(int code, String message) {
        return new Msg<>(code, message);
    }

    // 链式调用方法
    public Msg<T> code(int code) {
        this.code = code;
        return this;
    }

    public Msg<T> message(String message) {
        this.message = message;
        return this;
    }

    public Msg<T> data(T data) {
        this.data = data;
        return this;
    }

    public Msg<T> extra(String key, Object value) {
        if (this.extra == null) {
            this.extra = new HashMap<>();
        }
        this.extra.put(key, value);
        return this;
    }

    // Getter方法
    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public T getData() {
        return data;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public Map<String, Object> getExtra() {
        return extra;
    }

    @Override
    public String toString() {
        return "Msg{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                ", timestamp=" + timestamp +
                ", extra=" + extra +
                '}';
    }
}

2. 简单上传实现

简单上传的核心是通过 OSS 客户端的putObject方法直接上传文件流,代码如下:

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.netflow.config.AliYunConfig;
import com.netflow.utils.Msg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
@Slf4j
public class OSScontroller {

    @Autowired
    OSS ossClient;
    @Autowired
    AliYunConfig aliYunConfig;

    @PostMapping("/upload")
    public Msg<String> uploadFile(@RequestParam("file") MultipartFile file) {
        try {
            //获取初始文件名
            String fileName = file.getOriginalFilename();
            //获取文件后缀
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            //UUID作为文件名
            String newFileName = UUID.randomUUID().toString()+ "-"+ fileName + suffixName;

            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(aliYunConfig.getOssBucket(), newFileName, file.getInputStream());

            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            return Msg.success("上传成功");
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException | IOException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return Msg.fail("上传失败");
    }

}

3. 简单上传的局限性

简单上传虽然实现简单,但存在明显短板:

  • 文件大小限制:单次上传最大支持 5GB(OSS 限制),但实际网络中 1GB 以上文件易失败;
  • 无断点续传:网络中断后需重新上传整个文件;
  • 效率低:单线程上传,无法利用带宽资源。

因此,对于大文件(建议>100MB),需采用分片上传方案。想要了解分片上传内容的小伙伴可以参考我的另一篇文章

OSS文件上传(二):分片上传-CSDN博客

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐