对象存储封神指南:OSS 分片上传 + 重复校验 + 防毒,代码直接抄!

一、开场白:后端为啥绕不开对象存储?

上次产品经理拍我肩膀:"用户要传 10G 的设计图,你数据库存一下?"我当场掏出对象存储的 "户口本":这玩意儿才是文件的 "云端豪宅"------ 比本地存储能扛,比数据库能装,还自带 CDN 加速 buff。今天咱从入门到炫技,把对象存储扒得明明白白!

二、对象存储:不是 "文件夹" 的 "文件柜"

先给萌新划重点:对象存储(OSS/S3)≠ 本地文件夹它是把文件拆成 "对象"(包含数据 + 元信息),存在分布式集群里,就像小区的智能快递柜:

  • 优势:抗造(多副本存储)、能装(PB 级容量)、省钱(按使用量付费)
  • 场景:图片 / 视频存储、日志备份、用户文件上传(后端 er 的日常刚需)

三、实战 1:阿里云 OSS 简单上传(5 分钟上手版)

别翻官方文档了,我把 "废话" 都删了,直接上 Java 版干货代码:

1. 先搭环境(Maven 依赖)

xml

xml 复制代码
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>

2. 核心上传代码(像寄快递一样简单)

java 复制代码
public class OssSimpleUpload {
    // 密钥别硬编码!放配置文件里!(踩坑提醒)
    private static final String ENDPOINT = "oss-cn-beijing.aliyuncs.com";
    private static final String ACCESS_KEY = "你的AK";
    private static final String SECRET_KEY = "你的SK";
    private static final String BUCKET_NAME = "你的桶名";

    public static void uploadFile(File file) {
        // 1. 连接OSS客户端
        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, SECRET_KEY);
        try {
            // 2. 上传(桶名+文件路径+文件)
            ossClient.putObject(BUCKET_NAME, "user-uploads/" + file.getName(), new FileInputStream(file));
            // 3. 拿访问链接(有效期1小时)
            Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
            String url = ossClient.generatePresignedUrl(BUCKET_NAME, "user-uploads/" + file.getName(), expiration).toString();
            System.out.println("文件上传成功!链接:" + url);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 一定要关客户端!不然连接泄漏(血的教训)
            ossClient.shutdown();
        }
    }
}

踩坑提醒:

  • 桶权限别设 "公有读"!不然路人能下载你家文件
  • 密钥用 RAM 子账号!给最小权限(只给上传权限)

四、大文件上传:直接传?怕不是要被测试小姐姐打

用户传个 2G 的视频,直接调用上面的代码?结果:网络一波动就断,重传又要从头来,前端还报超时 ------ 这就是 "大文件上传噩梦"。解法:分片上传!把大文件切成 "小蛋糕",一块一块传,断了只补缺的。

五、分片上传:三步搞定 "大文件搬运"

以 100MB 文件为例,切成 10MB / 片,流程像拼乐高:

第一步:分片(前端切,后端躺平)

前端用File.slice()切分,比如:

javascript

arduino 复制代码
// 10MB一片(10*1024*1024字节)
const chunkSize = 10 * 1024 * 1024;
const chunks = Math.ceil(file.size / chunkSize);
// 切第一片:file.slice(0, chunkSize)

第二步:上传分片(带 "编号" 和 "身份证")

每个分片要带 3 个关键信息:

  • fileMd5:整个文件的哈希值(后面判重有用)
  • chunkIndex:分片编号(0,1,2...)
  • totalChunks:总分片数

后端接收后,先存到临时文件夹,比如/tmp/oss-chunks/${fileMd5}/${chunkIndex}

第三步:合并分片(OSS 帮你干活)

所有分片传完,调用 OSS 的completeMultipartUpload接口合并:

java

java 复制代码
public void mergeChunks(String fileMd5, String fileName, int totalChunks) {
    OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, SECRET_KEY);
    // 1. 初始化分片上传
    InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(
        new InitiateMultipartUploadRequest(BUCKET_NAME, "user-uploads/" + fileName)
    );
    String uploadId = initResult.getUploadId();

    // 2. 准备分片列表(按编号排序!)
    List<PartETag> partETags = new ArrayList<>();
    for (int i = 0; i < totalChunks; i++) {
        // 从临时文件夹读分片,上传到OSS
        UploadPartResult uploadResult = ossClient.uploadPart(
            new UploadPartRequest()
                .withBucketName(BUCKET_NAME)
                .withKey("user-uploads/" + fileName)
                .withUploadId(uploadId)
                .withPartNumber(i + 1) // 分片编号从1开始
                .withInputStream(new FileInputStream("/tmp/oss-chunks/" + fileMd5 + "/" + i))
        );
        partETags.add(uploadResult.getPartETag());
    }

    // 3. 通知OSS合并
    ossClient.completeMultipartUpload(
        new CompleteMultipartUploadRequest(BUCKET_NAME, "user-uploads/" + fileName, uploadId, partETags)
    );
    ossClient.shutdown();
    // 4. 删临时文件(别占空间!)
    FileUtils.deleteDirectory(new File("/tmp/oss-chunks/" + fileMd5));
}

六、灵魂拷问 1:怎么避免重复上传同一个文件?

用户手抖传了 3 次同个视频,总不能存 3 份吧?用 "文件身份证"MD5搞定:

流程:

  1. 前端选文件后,先算整个文件的 MD5(大文件可分片算 MD5 再合并,避免卡屏)

  2. 上传前先调后端接口:/check-file?md5=xxx

  3. 后端查数据库(存 "MD5 - 文件路径" 映射):

    • 有记录:直接返回已存的文件链接,不上传了
    • 没记录:返回 "可以上传",走分片流程

优化技巧:

OSS 的ETag字段也能当 "简易 MD5",但大文件分片后 ETag 会变,还是自己存 MD5 靠谱。

七、灵魂拷问 2:怎么防恶意文件(木马 / 病毒)?

用户传个xxx.jpg.exe,后端直接存?服务器要遭殃!三重校验安排上:

1. 第一层:文件头校验(查 "身份证照片")

不是看后缀名!看文件二进制开头的 "魔法数字":

java

java 复制代码
// 校验是否为图片(JPG/PNG/GIF)
public boolean checkFileHeader(File file) {
    byte[] header = new byte[8];
    try (FileInputStream fis = new FileInputStream(file)) {
        fis.read(header);
        String headerHex = bytesToHex(header);
        // JPG开头:FFD8FF,PNG开头:89504E47,GIF开头:47494638
        return headerHex.startsWith("FFD8FF") || headerHex.startsWith("89504E47") || headerHex.startsWith("47494638");
    } catch (Exception e) {
        return false;
    }
}

2. 第二层:内容检测(翻 "包" 检查)

用工具扫文件内容,比如:

  • 开源工具:ClamAV(扫病毒)
  • 云服务:阿里云 OSS 内容安全(直接集成,扫色情 / 暴力 / 木马)

3. 第三层:权限隔离(设 "隔离区")

  • 上传的文件先存到 "临时桶",校验通过再移到 "正式桶"
  • 正式桶禁止执行权限(比如给文件加Content-Disposition: attachment,强制下载不执行)

八、总结:后端 er 的 "文件上传生存指南"

  1. 小文件:直接 OSS 简单上传(别瞎搞分片)
  2. 大文件:分片上传三步走(切分→传片→合并)
  3. 防重复:MD5 前置校验(省空间省流量)
  4. 防恶意:文件头 + 内容检测 + 权限隔离(保命三件套)

最后问一句:你踩过哪些文件上传的坑?评论区分享下,让我乐呵乐呵~

这篇博客兼顾了技术深度和阅读趣味,符合掘金用户的偏好。你若觉得某部分代码不够详细、想加前端分片示例,或要调整风趣程度,随时跟我说!

相关推荐
风象南3 小时前
SpringBoot中如何实现对静态资源的访问权限控制
spring boot·后端
Hilaku3 小时前
面试官:BFF 它到底解决了什么问题?又带来了哪些新问题?
前端·javascript·面试
用户8356290780513 小时前
告别手动限制:用Python自动化Excel单元格数据验证
后端·python
努力的小郑3 小时前
Spring Boot自动装配实战:多数据源SDK解决Dubbo性能瓶颈
java·spring boot·微服务
四七伵3 小时前
为什么不推荐在 Java 项目中使用 java.util.Date?
java·后端
Json____3 小时前
使用springboot开发一个宿舍管理系统练习项目
java·spring boot·后端
爱读源码的大都督3 小时前
Java知名开源项目,5行代码,竟然有4个“bug”
java·后端·程序员
Funcy3 小时前
XxlJob 源码分析07:任务执行流程(二)之触发器揭秘
后端
先做个垃圾出来………3 小时前
Pydantic库应用
java·数据库·python