首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

短视频上传怎么做|写个支持分片上传/断点续传/秒传功能的文件服务吧

编程知识
2024年08月17日 10:08

前言

各位平时使用的短视频应用,微信 & 微博等图文社区,它们的图文动态 & 视频上传的能力,都是极其核心的业务。

本质来说,这都是文件的上传,这篇文章带大家写一个文件上传服务,探究其核心原理,相信能为你带来一些帮助。

感谢我的好友 Trembling 对本文的支持

主要包含以下能力:

  • 文件上传
  • 文件下载
  • 分片上传
  • 断点续传
  • 文件秒传

往期视频讲解 📺:B站:白泽talk,公众号:白泽talk

image-20240726234405804

FileService 主要能力

pd定义:

service FileService {
  // pre sign a file url for user get it
  rpc PreSignGet(PreSignGetRequest) returns (PreSignGetResponse);
  // pre sign a file url for user put it
  rpc PreSignPut(PreSignPutRequest) returns (PreSignPutResponse);
  // report a file has been uploaded
  rpc ReportUploaded(ReportUploadedRequest) returns (ReportUploadedResponse);
  // pre sign a file url for user put it with slicing
  rpc PreSignSlicingPut(PreSignSlicingPutRequest) returns (PreSignSlicingPutResponse);
  // get upload progress rate for slicing put
  rpc GetProgressRate4SlicingPut(GetProgressRate4SlicingPutRequest) returns (GetProgressRate4SlicingPutResponse);
  // merge a slicing uploading file
  rpc MergeFileParts(MergeFilePartsRequest) returns (MergeFilePartsResponse);
  // remove a file
  rpc RemoveFile(RemoveFileRequest) returns (RemoveFileResponse);
}

FileService用于向各个业务领域提供文件上传、下载的能力。在FileService的所有接口中,都存在一个名为file context的结构体,该参数通常用于指定文件相关的信息。其结构如下:

message FileContext {
  // 所属业务领域,用于创建bucket
  string domain = 1;
  // 所属业务名称
  string biz_name = 2;
  // 文件id
  int64 file_id = 3;
  // 文件sha256 hash
  string hash = 4;
  // 文件类型
  string file_type = 5;
  // 文件大小,单位byte
  int64 size = 6;
  // 文件访问链接的过期时间
  int64 expire_seconds = 7;
  // 文件名
  string filename = 8;
}

在各个请求中,domainbiz_name两个参数为必传项,这个2个参数组合后,将使得FileService所依赖的表以业务领域为维度进行第一次分表。在此基础上,可以为每个业务领域配置一个对应的二次分表,可以指定其分为若干张子表。

Quick Start

FileService对于不同业务领域和不同业务项,其文件数量的扩增速度是不同的,带来的数据量很有可能天差地别。所以,要使用FileService的能力,需要在FileService中进行配置分表数量。

config.yaml中,可以配置如下内容:

data:
  db_sharding_config:
    file_shortvideo_short_video:
      sharding: file_shortvideo_short_video
      sharding_number: 5

db_sharding_config项用于配置分表数量,其下的file_shortvideo_short_video为分表key,其中"file"为固定值,shortvideo为业务领域,short_video为业务名称。sharding_number为分表数量。业务领域和业务名称需要在使用FileService时传入。如不进行配置,则默认分表数量为1。

主要链路

普通上传/下载

sequenceDiagram participant up as 上游服务 participant fs as FileService participant minio as Minio Server up->>fs: PreSignPut fs-->>up: PreSignPutResponse(Minio上传链接) up->>minio: 上传文件 minio-->>up: 上传结果 up ->> fs: ReportUploaded fs-->>up: ReportUploadedResponse up->>fs: PreSignGet fs-->>up: PreSignGetResponse(下载链接) up->>minio: 下载文件 minio-->>up: 返回文件

分片上传/断点续传

sequenceDiagram participant up as 上游服务 participant fs as FileService participant minio as Minio Server up->>fs: PreSignSlicingPut fs-->>up: PreSignSlicingPutResponse(分片上传链接数组) up->>minio: 上传一部分文件 minio-->>up: 上传结果 up->>up: 喝杯咖啡 up->>fs: GetProgressRate4SlicingPut fs-->>up: GetProgressRate4SlicingPutResponse(分片上传进度) up->>minio: 上传剩余文件 minio-->>up: 上传结果 up->>fs: MergeFileParts fs-->>up: MergeFilePartsResponse up->>fs: PreSignGet fs-->>up: PreSignGetResponse(下载链接) up->>minio: 下载文件 minio-->>up: 返回文件

主要能力

普通上传

FileService.PreSignPut提供了最基础的上传能力。该接口需要额外上传的参数包括:

  • hash: 文件的sha256值
  • file_type: 文件类型
  • size:文件大小(字节数)
  • expire_seconds:文件上传链接的过期时间

例如,可以向此接口传入这样的参数:

{
    "file_context": {
        "domain": "shortvideo",
        "biz_name": "short_video",
        "hash": "8D6BB0819A2C1E66F846031DC54AAF47",
        "file_type": "pdf",
        "size": 1181178,
        "expire_seconds": 86400
    }
}

访问后,该接口将返回一个上传链接(http),由上游服务/前端直接将文件上传到该链接。一个上传示例代码(Python):

with open(file_path, 'rb') as file_data:
    response = requests.put(
        minio_url, # 接口返回的上传链接
        data=file_data,
        headers={"Content-Type": "application/octet-stream"}
    )
    print(response)
    return response.status_code

通过上述Python代码的方式上传完成后,并不能直接访问到该文件,需要通过FileService.ReportUploaded接口来进行上传确认,只有上传确认后的文件,才会在数据库中被标记为uploaded并能被访问到。关于FileService.ReportUploaded的具体使用,将在下文中详细介绍。

分片上传

在一些情况下,需要上传的文件较大,如果直接上传,可能出现如下问题:

  1. 上传较慢
  2. 上传过程中如果出现问题,则需要重新上传整个文件

所以,FileService提供了分片上传的能力。

首先,可以通过FileService.PreSignSlicingPut预注册一个分片上传任务。该接口需要传入的参数与FileService.PreSignPut相同。接口返回的主要内容包括:

  • urls:数组,各个分片的上传链接,且已经按照分片号排序
  • upload_id:上传任务的id
  • parts:分片总数
  • file_id:文件id

参数示例:

{
    "file_context": {
        "domain": "shortvideo",
        "biz_name": "short_video",
        "hash": "8D6BB0819A2C1E66F846031DC54AAF47",
        "file_type": "pdf",
        "size": 72246060,
        "expire_seconds": 86400
    }
}

此时,由上有服务/前端对文件进行分片(每一片大小为5MB),然后将各个分片进行上传,一个文件分片的示例如下(Python):

def slicing(filename):
    file_size = 5 * 1024 * 1024  # 10MB
    
    files = list()

    # 打开文件
    with open(filename, 'rb') as f:
        index = 0
        while True:
            # 定位到要读取的位置
            f.seek(index * file_size)
            # 读取数据
            data = f.read(file_size)
            # 如果已经读到文件末尾,退出循环
            if not data:
                break
            # 写入分割后的文件
            with open(f'{filename}_{index}', 'wb') as f1:
                f1.write(data)
            files.append(data)
            # 更新位置
            index += 1
    return files

全部分片上传完成后,可以通过FileService.MergeFileParts来合并分片。主要参数包括:

  • file_id
  • upload_id

参数示例:

{
    "upload_id": "ZDNlOWI2MjktMjAzOC00NzJkLWE0ODYtOGMzZTBlZmJlODUwLmRmN2M5ZWQyLTYxMzMtNDM4NS1hNTljLWEwMzRlNTI5NWNkNHgxNzIzNzM5ODA2MTM2NzU3MzE5",
    "file_context": {
        "file_id": 1824123073628999680,
        "domain": "shortvideo",
        "biz_name": "short_video"
    }
    
}

与不同上传不同的是,分片上传在上传完成并调用FileService.MergeFileParts后,会自动进行上传确认,无需再次调用FileService.ReportUploaded

断点续传

在上述分片上传的过程中,可以通过FileService.GetProgressRate4SlicingPut来获取分片上传的具体情况,主要传入的参数包括:

  • file_id
  • upload_id

该接口的返回值中包含一个名为parts的map,key为分片号,value为该分片是否上传完成,上游服务或服务端可以根据该信息来决定哪些分片需要重新上传

示例参数:

{
    "upload_id": "ZDNlOWI2MjktMjAzOC00NzJkLWE0ODYtOGMzZTBlZmJlODUwLjA3ZTgyNmY0LWE4YjQtNDQxMC04M2QzLWY4ODQ4MTRiZGM4Y3gxNzIzNzM4ODUzMTMzMjY4ODQ3",
    "file_context": {
        "file_id": 1824119076553756672,
        "domain": "shortvideo",
        "biz_name": "short_video"
    }
    
}

返回值示例:

{
    "parts": {
        "1": true,
        "2": true,
        "3": true,
        "4": true,
        "5": true,
        "6": true,
        "7": true,
        "8": true,
        "9": true,
        "10": true,
        "11": true,
        "12": true,
        "13": true,
        "14": true
    },
    "meta": {
        "reason": [],
        "biz_code": 0,
        "message": "success",
        "domain": ""
    },
    "progress_rate": 100
}

上传确认

在上传完成时(分片上传除外,分片上传结束时调用FileService.MergeFileParts会自动进行上传确认),都需要调用FileService.ReportUploaded来进行上报,该接口必传参数为:

  • file_id

这一接口主要完成这样一件事:检查文件的hash,检查通过后将文件标记为“上传成功状态”,否则文件将不可被查询到。

参数示例:

{
    "file_context": {
        "domain": "shortvideo",
        "biz_name": "short_video",
        "file_id": "1824118603822141440"
    }
}

下载文件

通过FileService.PreSignGet接口则可以获取下载文件链接,该接口主要传入的参数包括:

  • file_id
  • expire_seconds

参数示例:

{
    "file_context": {
        "domain": "shortvideo",
        "biz_name": "short_video",
        "file_id": "1824123073628999680",
        "expire_seconds": 86400,
        "filename": "data.mp4"
    }
}

秒传

FileService.PreSignPutFileService.PreSignSlicingPut接口中,如果传入的hash已经存在,则会返回一个file_id,这个file_id可以用于获取下载链接,从而不需要再次上传文件。

From:https://www.cnblogs.com/YLTFY1998/p/18364146
本文地址: http://shuzixingkong.net/article/1181
0评论
提交 加载更多评论
其他文章 使用 prefetchComponents 进行组件预取
title: 使用 prefetchComponents 进行组件预取 date: 2024/8/17 updated: 2024/8/17 author: cmdragon excerpt: 摘要:本文介绍Nuxt.js中的prefetchComponents功能,用于预取组件以提高用户体验。通过
使用 prefetchComponents 进行组件预取 使用 prefetchComponents 进行组件预取
1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到
今天刷面试题看到一个有意思的面试题, 1000T的文件怎么能以最快速度从南京传到北京? 网络传输 首先我们考虑通过网络传输,需要多长时间。 我特地咨询了在运营商工作的同学,目前带宽: 家庭宽带下行最大1Gbps,上行300M 企业级专线分数据专线和互联网专线: 数据专线 最大100Gbps,价格最低
1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到 1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到 1000T的文件怎么能快速从南京传到北京?最佳方案你肯定想不到
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
​IT寒冬之下,程序员这个职业不再像以往那么吃香,尤其是APP开发的门槛越来越高,使得安卓程序员不得不求变,如果不在技术上及时转型提高,逆水行舟未来不可期呀。 有鉴于此,博主整理了几个可供安卓程序员的技术转型发展方向,供大家参考。 1、继续深耕Android的应用开发 谷歌爸爸是安卓的爹,要想继续吃
throw和throws的区别
throw 和 throws 在 Java 中是两个与异常处理相关的关键字,但它们的作用和使用场景有所不同。 throw 用于在方法内部主动抛出一个异常对象。例如: if (condition) { throw new RuntimeException("自定义的异常信息");
并查集扩展
并查集扩展 目录并查集扩展普通并查集例题:1.洛谷P1197 星球大战2.洛谷P1955 程序自动分析带权并查集例题:1.洛谷P2024 食物链2.洛谷P1196 银河英雄传说3.洛谷P5937 Parity Game扩展域并查集例题:1.洛谷P1525 关押罪犯 普通并查集 例题: 1.洛谷P11
并查集扩展
T113s工业套件简述
T113s工业套件简述 提示 T113开发交流QQ群:120575746 此开发板的任何问题都可以在我们的论坛交流讨论 https://forums.100ask.net/c/aw/ 硬件简述​ 100ASK_T113s3-Industrial-DevKit 是百问网设计的一款专门针对于工
T113s工业套件简述 T113s工业套件简述
debian10环境安装rtpengine
操作系统 :debian 10.13_x64 rtpengine版本:10.5 最新的debian12环境可通过apt直接安装rtpengine,但工作中有时候还会涉及到debian10这样的老系统,今天记录下debian10环境安装rtpengine的笔记,并提供相关演示效果及资源下载。 我将从以
debian10环境安装rtpengine debian10环境安装rtpengine debian10环境安装rtpengine
Jetpack架构组件学习(5)——Hilt 注入框架使用
原文: Jetpack架构组件学习(5)——Hilt 注入框架使用-Stars-One的杂货小窝 本篇需要有Kotlin基础知识,否则可能阅读本篇会有所困难! 介绍说明 实际上,郭霖那篇文章已经讲得比较明白了(具体参考链接都贴在下文了),这里简单总结下: 如果按照之前我们的MVC写法,我们可以直接在
Jetpack架构组件学习(5)——Hilt 注入框架使用 Jetpack架构组件学习(5)——Hilt 注入框架使用 Jetpack架构组件学习(5)——Hilt 注入框架使用