React+node 图片剪裁上传,集成本地存储和阿里云OSS

前言

最近一直在调研图片上传阿里云oss功能,上篇文章主要讲述了阿里云oss大文件分片、断点续传。这篇文章是在原有基础上,前端加了图片剪裁、后端加了本地存储功能。

先看效果 技术栈

  1. 前端:react+Ts+antd
  2. 后端:node+koa+koa-body+ali-oss

功能

  1. 图片剪裁(antd剪裁组件
  2. 本地文件上传(antd上传组件+axios
  3. 文件存储本地(node+koa-body
  4. 文件存储阿里oss(node+ali-oss

核心代码

前端上传功能使用antd中的上传组件,可参考官网进行配置,这里不展开讨论了

环境配置

引入依赖环境

使用koa搭建node服务,搭配koa周边依赖包开发起来更方便,详细依赖环境如下:

php 复制代码
const app = new (require('koa'))(); // koa 
const router = require('koa-Router')();  // koa 路由
const cors = require('@koa/cors') // 运行koa中跨域 
const OSS = require('ali-oss') // 阿里云oss-sdk
const {koaBody} = require('koa-body'); // 处理请求体中间件、用于解析json、表单 | 包含file内容请求,会生成临时文件,将文件信息添加到ctx.request对象属性,通过ctx.request.files获取
const path = require('path'); // 对文件路径的操作
const staticServe = require('koa-static') // 使上传的文件能在浏览器中访问
const fs = require('fs') // 文件的读写操作 
const PORT = '9000' // 端口

配置阿里云oss

javascript 复制代码
const client = new OSS({
    // yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
    region: 'oss-cn-beijing',
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    accessKeyId: 'xxx',
    accessKeySecret: 'xx',
    // 填写Bucket名称。
    bucket: 'xxx'
});

使用中间件

实现本地上传、解决跨域等功能,需使用koa中间件完成功能

php 复制代码
// __dirname node中全局变量,当前文件所在目录
// 上传本地目录
const UPLOAD_PATH = path.join(__dirname,'public/')
// 上传文件后的地址,用于拼接图片名称,回显前端
const UPLOAD_URL = `http://localhost:${PORT}`
//  使用跨域中间件
app.use(cors())

//应用koabody中间件,处理文件上传操作,生成临时文件路径
app.use(koaBody({
    multipart:true ,// 解析文件格式内容
    formidable:{
        // 上传文件的存储的位置
        uploadDir:UPLOAD_PATH,
        keepExtensions:true // 保留文件的扩展名
    }
}))
// app.use(bodyParser());
app.use(staticServe(UPLOAD_PATH))
app.use(router);
// 启动服务
app.listen(PORT,()=>{
    console.log('启动成功 9000')
});

阿里云分片上传

接口代码

javascript 复制代码
router.post('/upload_oss',async(ctx)=>{
    console.log('请求了',ctx.request.files.file)
    console.log('上传oss upload_oss',ctx.request)
    const file = ctx.request.files.file
    let result  = await multipartUpload(file) 
    ctx.body={
        msg:"请求成功了",
        result
    }
})

分片操作

javascript 复制代码
// 上传进度
const progress = (p, _checkpoint) => {
    // Object的上传进度。
    console.log('分片进度',p); 
    // 分片上传的断点信息。
    // console.log(_checkpoint); 
  };

async function multipartUpload(file) {
    try {
      // 依次填写Object完整路径(例如exampledir/exampleobject.txt)和本地文件的完整路径(例如D:\\localpath\\examplefile.txt)。Object完整路径中不能包含Bucket名称。
      // 如果本地文件的完整路径中未指定本地路径(例如examplefile.txt),则默认从示例程序所属项目对应本地路径中上传文件。
      const result = await client.multipartUpload(file.originalFilename, file.filepath, {
        progress, // 如无需进度实时回显,可不配置
        // headers,
        // 指定meta参数,自定义Object的元信息。通过head接口可以获取到Object的meta数据。
      });
      console.log(result); 
      return result
    } catch (e) {
      // 捕获超时异常。
       
      console.log('捕获超时异常。',e);
    }
  }

本地文件上传

存储本地文件两种方式

一、renameSync(移动临时文件)

javascript 复制代码
fs.renameSync(ctx.request.files.file.filepath, filePath);

优点:直接使用fs.renameSync,一次操作即可重命名或移动文件,非常简洁和高效。 缺点:fs.renameSync 是一个同步方法,会直接阻塞代码执行,直到文件操作完成

二、创建文件流存储

javascript 复制代码
const reader = fs.createReadStream(ctx.request.files.file.filepath);
const writer = fs.createWriteStream(filePath);
reader.pipe(writer);

优点:可以同时处理多个文件的上传,不会阻塞代码执行。 缺点:在处理单个文件时可能略微复杂。

renameSync 存储单文件

javascript 复制代码
// 请求中的第二个参数是请求中间件函数,可用于请求前的业务操作 
router.post('/upload_local',async(ctx,next)=>{
    
    const filePath = UPLOAD_PATH+`/${ctx.request.files.file.originalFilename}`
    fs.renameSync(ctx.request.files.file.filepath, filePath);
    await next()
},async(ctx)=>{
    ctx.body={
        msg:"请求成功了", 
        imgUrl:UPLOAD_URL+`/${ctx.request.files.file.originalFilename}` 
    }
})

常见问题

multer存储文件名乱码怎么办?

在第一版代码中,采用的是multer存储文件,上传文件是中文命名,multer中无法解析格式,导致文件名乱码。 解决方式:将使用 Latin-1 编码的文件名转换为 UTF-8 编码 示例代码:

javascript 复制代码
// 文件名称命名
    filename:function(req,file,cb){
        console.log('文件名称命名')  
        const decodedName = Buffer.from(file.originalname, "latin1").toString(
            "utf8"
          );
        console.log('decodedName',decodedName)
        cb(null,decodedName)
    }

Buffer.from(file.originalname, "latin1")使用 Buffer.from 方法将以 Latin-1 编码的 file.originalname 字符串转换为一个 Buffer 对象。Latin-1 是一种字符编码,也称为 ISO-8859-1。 toString("utf8")使用 toString 方法将 Buffer 对象转换回字符串,指定目标编码为 UTF-8。这将将 Latin-1 编码的字符串转换为 UTF-8 编码的字符串。

Koa接收不到file对象内容

正常情况下,引入koa-body中间件后,可以获取到前端表单数据,如下: 如果获取不到,大概率是 routes 代码书写顺序错乱导致 ⚠ 中间件的应用需要写在routes前面才可以 正常执行顺序如下:

php 复制代码
const koaBody  = require('koa-body')({multipart: true});
app.use(koaBody); //中间件的应用需要写在routes前面才可以
app.use(router.routes())

koa-body 和 koa-bodypaser不兼容

在第一版代码中,使用 koa-bodypaser + multer来存储本地文件,koa-body+ali-oss 上传阿里云存储。

这两个功能单独使用是没有问题,但是服务端同时定义两个接口,则会出现阿里云oss或本地存储失败。百度搜了一圈没找到解决方案,最终果断弃坑 multer, 使用koa-body 来解析请求体内容.

koa-body不仅可以解析请求体内容、还可以生产文件临时路径、方便存储操作

  • koa-body 用于解析 请求中的 formData 文件内容格式、 json、buffer
  • koa-bodypaser 用于解析 非文件内容数据,比如json、表单
  • 如果node业务中涉及 以上两种情况下,建议使用koa-body。
相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#