阿里云oss开发实践:大文件分片、断点续传、实时进度 React+Node+Socket.IO

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 阿里云oss开发实践:大文件分片、断点续传、实时进度 React+Node+Socket.IO

1. 前言

这两天在学习阿里云oss上传。踩了不少坑, 终于实现了大文件分片、断点续传的功能。这篇文章主要分享学习笔记,希望能给大家一些帮助。


先看效果 ↓

技术栈

  1. 前端: react+Ts + axios 上传文件
  2. Node部分:定义接口、阿里云 oss
  3. socket.io :实时同步上传进度

特别说明 axios 中 onUploadProgress 只是上传本地文件的进度,不是上传服务器存入的进度,需要socket.io 从服务端实时返回上传进度

2. 环境安装

需进行阿里云oss配置,获取appid、密钥等参数 ↓https://ram.console.aliyun.com/manage/ak?spm=a2c8b.12215393.top-nav.dak.6c12336aYGPYmv

以下是创建node服务所需依赖包

// 下载 Koa 模块
npm install koa
// 下载 Koa Router 模块 https://www.npmjs.com/package/koa-Router[包含示例代码]
npm install koa-router
// 下载 @koa/cors 模块
npm install @koa/cors
// 下载 ali-oss 模块
npm install ali-oss
// 下载 koa-body 模块
npm install koa-body
// 下载 socket.io 
npm install socket.io


3. 前端部分

前端使用react+Ts,但无论哪种框架,其实业务逻辑是一样的

ⅰ. 初始化socket

let userId = localStorage.getItem('userId');
            if (!userId) {
                userId = new Date().getTime() + '';
                localStorage.setItem('userId', userId);
            }   
               let host = 'http://127.0.0.1:3000'
                const soket = io(host); 
                soket.on('connect', function(){
                    console.log('链接了 Connected to server');  
                 }); 
        // 模拟用户登录
                 soket.emit('login',{
                    userId
                })
                soket.on('success', data => {
                    console.log('success',data)
                })

ⅱ. 文件上传

const upload = async () => {
  // FileList 内置接口
  const file = (inputRef.current?.files as FileList)[0];
  console.log('inputRef', file);
  if (!file) {
    message.error('没有选择文件');
    return;
  }
  let formData = new FormData();
  formData.append('file', file);
  let userId = localStorage.getItem('userId') as string
  formData.append('userId',userId)
  await axios.post(host+'/upload', formData, {
    // onUploadProgress 监听的是客户端发送数据的进度,而不是存储服务器的进度。
    onUploadProgress: (progressEvent: any) => {
      const percentage = Math.round((progressEvent.loaded * 100) / progressEvent.total);
      console.log(`Upload progress: ${percentage}%`, progressEvent);
    }
  });
};

ⅲ. 进度回显

const [progress,setProgress] = useState<number>(0)
soket.on('uploadding', data => {
         console.log('uploadding',data)
        setProgress(data)
  })

4. node部分

后台使用koa创建node服务,主要分为api接口、阿里云业务函数、socket.io 实时发送上传进度

ⅰ. socket.io

const { createServer } = require("http"); // 导出创建服务的模块函数
const { Server } = require("socket.io"); // 创建socket.io 服务的函数
const httpServer = createServer(app.callback());  // 创建一个http服务实例,app.callback() 作为服务器的请求处理函数
const io = new Server(httpServer, {
    cors: {
        origin: "*" // 配置socket允许跨越
    }
});

ⅱ. 上传接口

// 上传接口
router.post('/upload', async (ctx, next) => {
    let file = ctx.request.files.file;
    // 用户id 
    let userId = ctx.request.body.userId
    try {
        let result = null;
        await next();
        // 判断文件大小,超过partSize进行分片上传
        if (file.size < partSize) {
            console.log('直连操作');
            result = await commonUpload(file, userId);
        } else {
            console.log('分片上传');
            result = await multipartUpload(file, userId);
        }
        ctx.body = {
            code: 200,
            message: 'success',
            data: result
        };
    } catch (error) {
        console.log('error', error);
        ctx.body = {
            message: '上传失败',
            code: 401
        };
    }
});

ⅲ. 暂停接口

router.post('/break', async (ctx) => {
    let userId = ctx.request.body.userId
    // 取出 当前用户的阿里云实例,用于仅关闭当前上传
    let itemClient = userList[userId]['client']
    if (itemClient) {
        itemClient.cancel();
        ctx.body = {
            code: 200,
            message: "暂停成功"
        }
    } else {
        ctx.body = {
            code: 401,
            message: "暂停失败"
        }
    }
});

ⅳ. 继续上传接口

// 继续上传 
router.post('/continue', async (ctx) => {
    let userId = ctx.request.body.userId
    ctx.body = {
        code: 200,
        message: '已继续上传',
    };
    try {
        resumeMultiparUpload(userId)
    } catch (error) {
        console.log('继续上传报错')
    }
});

ⅴ. 分片上传

// 分片上传
const multipartUpload = async (file, userId) => {
    try {
        let result = await userList[userId].client.multipartUpload(file.originalFilename, file.filepath, {
            parallel,
            partSize,
            progress: (p, checkpoint) => {
                onProgress(p, checkpoint, userId)
            }
        });
        return result;
    } catch (error) {
        console.log('multipartUpload', error)
    }
};

ⅵ. 断点续传

// 断点续传
const resumeMultiparUpload = async (userId) => {
    // 获取当前用户分片缓存 
    try {
        let checkpoint = checkpoints[userId]
        const { uploadId, file } = checkpoint;
        let result = await userList[userId].client.multipartUpload(uploadId, file, {
            parallel,// 分片数量
            partSize,//分片大小
            progress: (p, checkpoint) => {
                onProgress(p, checkpoint, userId)
            },// 上传进度回调函数
            checkpoint // 断点续传缓存目录
        });
        //上传后,删除切片数据
        delete checkpoints[userId]
        console.log('result', result)
        return result;
    } catch (error) {
        console.log('error 获取当前用户分片缓存')
    }
}

ⅶ. 进度回显

// 上传进度
const onProgress = async (p, checkpoint, userId) => { // p 进度,checkpoint 当前分片上传数据
    let step = Math.floor(p * 100); // 转换为百分比
    io.to(userList[userId].socketId).emit('uploadding', step) // 发给当前客户端
    // io.emit('uploadding',step) 群发
    // 存储分片数据,用户续传
    console.log('上传进度', step)
    checkpoints[userId] = checkpoint
};

ⅷ. socket.io私聊

const userList = {}  // 用户数据
const partSize = 1024 * 100; // 每个分片大小(byte) 100kb
const parallel = 3; // 同时上传的分片数
let checkpoints = {}; // 记录上传分片数据,用于断点续传
// oss客户端实例   
// socket.io系统事件,监听链接状态
io.on("connection", (socket) => {
    // 监听客户端信息数据,存储用户信息
    socket.on('login', (data) => { 
        // 用户未链接oss,进行创建oss实例
        if (!userList[data.userId]) {
            let client = new OSS({
                // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
                region: 'oss-cn-beijing',
                // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控W制台创建RAM用户。
                accessKeyId: 'xxx',
                accessKeySecret: 'xx',
                bucket: 'xxx
            });
            // 将socket.id 与用户信息关联存储,方便私聊发送
            userList[data.userId] = {
                ...data,
                socketId: socket.id,
                client: client
            }
            console.log('socket.id', socket.id)
            console.log('获取到用户数据')
        } else { // 已链接oss,进行只更新socket.id
            userList[data.userId].socketId = socket.id
        }
    })
    socket.emit('success', '服务端链接成功了')
    // socket.io 系统事件-客户端断开监听
    socket.on('disconnect', () => {
        console.log('客户端断开了')
        // io.emit('quit', socket.id)
    })
});

5. 纯前端操作部分

如无服务端业务要求,建议客户端调用阿里云sdk,实现上传oss功能,操作如node一致,阿里云sdk同样支持分片上传等,最方便的是无需再创建socket获取进度。

兄弟们,点赞收藏过20,下篇文章更新呀

6. Socket 相关api


在 Socket.IO 中,客户端和服务端都有一些系统事件。

1. 服务端系统事件:


  1. connection:当客户端与服务器建立连接时触发。可以在此事件中执行与连接相关的操作。


io.on("connection", (socket) => {
  // 处理连接事件
});


  1. disconnect:当客户端与服务器断开连接时触发。可以在此事件中执行与断开连接相关的操作。


socket.on("disconnect", () => {
  // 处理断开连接事件
});


  1. error:当在连接过程中发生错误时触发。可以在此事件中处理连接错误。


socket.on("error", (err) => {
  // 处理连接错误事件
});
  1. to 在 Socket.IO 中,to 方法用于向特定房间或客户端发送消息。它允许你将消息发送给指定的接收者。


to 方法的使用方法如下:

io.on('connection', (socket) => {
  // 建议 将socket.id 与用户信息关联存储,方便私聊发送
  // 向指定客户端发送消息
  io.to(socket.id).emit('message', 'Hello from server!');
});


使用 io.to(socket.id).emit('message', 'Hello from server!') 向特定客户端发送消息,其中 socket.id 表示当前客户端的唯一标识符。

客户端系统事件:


  1. connect:当客户端成功连接到服务器时触发。该事件仅发生在客户端连接成功时。


socket.on("connect", () => {
  // 处理连接成功事件
});


  1. disconnect:当客户端与服务器断开连接时触发。可以在此事件中执行与断开连接相关的操作。


socket.on("disconnect", () => {
  // 处理断开连接事件
});


  1. error:当在连接过程中发生错误时触发。可以在此事件中处理连接错误。


socket.on("error", (err) => {
  // 处理连接错误事件
});


7. 注意

socket 开启跨越

socket.io 需配置跨越,否则无法链接,参考 https://socket.io/zh-CN/docs/v4/handling-cors/

const io = new Server(httpServer, {
  cors: {
    origin: "http://localhost:8080" // 前端访问地址 、"*" 允许所有跨越
  }
});
httpServer.listen(3000);

koa+socket使用事项

接口后台和socket端口一致情况下,需使用包含socket的服务实例来创建监听,否则socket无法链接

在koa中使用socket.io 需要创建一个包含socket.io的服务实例,代码示例如下:

const app = new (require("koa"))();
const router = require("koa-Router")();
const { createServer } = require("http");
const { koaBody } = require("koa-body");
const {Server} = require('socket.io')
app.use(cors()); // 允许接口跨域
app.use(router.routes());
// 创建socket服务  
const httpServer = createServer(app.callback());
const io = new Server(httpServer,{
    cors:{
        origin:"*" // 允许socket跨域
    }
}) 
io.on('connection', () => { 
  console.log('服务链接了')
  /* … */ });  
// 使用包含socket的服务示例,如果使用koa中的app实例,则无法监听socket服务
httpServer.listen(9000, () => {
  console.log("koa server listening on port 9000");
});


8. 待做功能

sts临时授权

oss上传应设置会话时长,超时进行重新获取,业务步骤类似token鉴权,阿里云oss操作也应该进行鉴权

相关实践学习
借助OSS搭建在线教育视频课程分享网站
本教程介绍如何基于云服务器ECS和对象存储OSS,搭建一个在线教育视频课程分享网站。
相关文章
|
23天前
|
运维 Cloud Native 持续交付
云原生技术解析:从IO出发,以阿里云原生为例
【10月更文挑战第24天】随着互联网技术的不断发展,传统的单体应用架构逐渐暴露出扩展性差、迭代速度慢等问题。为了应对这些挑战,云原生技术应运而生。云原生是一种利用云计算的优势,以更灵活、可扩展和可靠的方式构建和部署应用程序的方法。它强调以容器、微服务、自动化和持续交付为核心,旨在提高开发效率、增强系统的灵活性和可维护性。阿里云作为国内领先的云服务商,在云原生领域有着深厚的积累和实践。
51 0
|
2月前
|
JSON 前端开发 JavaScript
socket.io即时通信前端配合Node案例
本文介绍了如何使用socket.io库在Node.js环境下实现一个简单的即时通信前端配合案例,包括了服务端和客户端的代码实现,以及如何通过socket.io进行事件的发送和监听来实现实时通信。
38 2
|
4月前
|
前端开发 对象存储
oss的断点续传在react中的应用
使用阿里云OSS的`multipartUpload`可自动管理文件切片与断点续传吗?测试时,上传一半中断(如刷新页面或重启浏览器),再续应从断点处继续。
87 10
|
3月前
|
缓存 JavaScript Serverless
阿里云云效产品使用合集之如何在Serverless Devs阶段指定Node.js版本
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
3月前
|
JavaScript 前端开发 Ubuntu
如何在 VPS 上安装 Express(Node.js 框架)并设置 Socket.io
如何在 VPS 上安装 Express(Node.js 框架)并设置 Socket.io
58 0
|
3月前
|
存储 JavaScript 数据库
如何使用 Socket.IO、Angular 和 Node.js 创建实时应用程序
如何使用 Socket.IO、Angular 和 Node.js 创建实时应用程序
25 0
|
5月前
|
敏捷开发 缓存 测试技术
阿里云云效产品使用问题之构建Vue3项目,怎么让node_modules缓存下来
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
4月前
|
移动开发 运维 JavaScript
阿里云云效操作报错合集之遇到Node.js的内存溢出问题,该怎么办
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
4月前
|
JavaScript Shell 应用服务中间件
阿里云云效操作报错合集之Node.js构建报错,该如何排查问题
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
4月前
|
资源调度 前端开发 开发工具
阿里云云效操作报错合集之Node-Sass模块在构建过程中,出现报错"ENOENT: no such file or directory, scandir ",该如何处理
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。