Next.js使用antd upload组件上传图片到阿里云oss并实现后端接口

1,134 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

最近在完成大三的一个课程期末设计,独立完成做了一个博客社区,主要技术栈是:

前端:next.js + mobx + ts + antd;

后台管理系统:vue3.0 + pinia + ts + elementUI

后端:next.js + ts + 阿里云oss存储 + redis

开发的时候遇到了很多坑,后面会通过文章的方式总结自己在开发过程中踩到的坑以及一些小经验。

由于要编辑用户头像、文章的头图等功能,都要实现本地图片的上传功能,所以我选择直接上传到阿里云的oss里面,并且给我返回一个https图片链接,这样我们就可以让其他用户看到这张图片了

一、需求分析

老规矩,我们先看看要开发什么,我们的需求是什么:

image-20220512192327041.png

我们要实现添加 文章的头图,也就是下面这个图片的文章头图

image-20220512192721559.png

二、next.js前端upload组件使用

我封装了一个uploadImg组件

import styles from './index.module.scss';
import { Upload, message } from 'antd';
import { LoadingOutlined, PlusOutlined, CloseCircleFilled } from '@ant-design/icons';
import { useState, ChangeEvent } from 'react'interface IProps {
  uploadHeadImg?: (imgUrl: string) => void 
}
​
const UploadImg = (props: IProps) => {
  const { uploadHeadImg } = props
  const [loading, setLoading] = useState(false)
  const [imageUrl, setImageUrl] = useState('')
​
  const handleChange = (info : any) => {
    if (info.file.status === 'uploading') {
      setLoading(true)
      return
    }
    if (info.file.status === 'done') {
      const ossUrl = info.file?.response?.data?.url
      setImageUrl(ossUrl)
      setLoading(false)
      uploadHeadImg(ossUrl)
    }
  }
  const uploadButton = () => {
    return (
      <div>
        {loading ? <LoadingOutlined /> : <PlusOutlined />}
        <div style={{ marginTop: 8 }}>上传图片</div>
      </div>
    )
  }
  const beforeUpload = (file : any) => {
    const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
    if (!isJpgOrPng) {
      message.error('You can only upload JPG/PNG file!');
    }
    const isLt2M = file.size / 1024 / 1024 < 2;
    if (!isLt2M) {
      message.error('Image must smaller than 2MB!');
    }
    return isJpgOrPng && isLt2M;
  }
​
  const handleImgDel = (e: ChangeEvent<HTMLInputElement>) => {
    setImageUrl('')
    uploadHeadImg('')
    e.stopPropagation()
  }
  return (
    <Upload
      action="/api/common/upload" 
      name="file"
      accept='image/*'
      listType="picture-card"
      className="avatar-uploader"
      showUploadList={false}
      beforeUpload={beforeUpload}
      onChange={handleChange}
    >
      {
        imageUrl ?
          (
            <div className={styles.imgContainer}>
              <img src={imageUrl} alt="avatar" style={{ width: '100%' }} />
              <CloseCircleFilled onClick={handleImgDel} className={styles.del} />
            </div>
          )
          
        : uploadButton()}
    </Upload>
  );
};
​
export default UploadImg;

使用这个组件:

<UpLoadImg uploadHeadImg={handleUploadHeadImg} />

传递一个函数作为回调函数,接收阿里云oss返回的url

三、next.js后端代码实现

下面就是我们next.js的后端代码: pages/api/common/upload

在阿里云注册OSS服务就不写啦,百度一大堆教程,下面的代码里面有几个要大家自己填的,分别是注册oss服务之后 1、所在地域 2、accessKeyId 3、accessKeySecret 4、bucket名

import { NextApiRequest, NextApiResponse } from 'next';
import { withIronSessionApiRoute } from 'iron-session/next';
import { ironOptions } from 'config/index';
import { IncomingForm } from 'formidable'
import OSS from 'ali-oss'
import path from 'path'const client = new OSS({
  // yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
  region: '【阿里云oss服务的所在地域】',
  accessKeyId: '【你的accessKeyId】',
  accessKeySecret: '【你的accessKeySecret】',
  bucket: '【你的bucket】',
});
​
export const config = {
  api: {
    bodyParser: false,
  }
};
​
export default withIronSessionApiRoute(upload, ironOptions);
​
async function upload(req: NextApiRequest, res: NextApiResponse) {
  // parse form with a Promise wrapper
  const data = await new Promise((resolve, reject) => {
    const form = new IncomingForm()
    
    form.parse(req, (err, fields, files) => {
      if (err) return reject(err)
      resolve({ fields, files })
    })
  })
  
  // 为了更好的目录结构,通过/的方式来给阿里云oss存储有一个比较好的目录结构
  const nowDate = new Date()
  const year = nowDate.getFullYear()
  const month = nowDate.getMonth()
  const day = nowDate.getDay()
  const nameFront = year + '/' + month + '/' + day + '/'
  const nameBack =  new Date().getTime() + '_';
    
  const resultUrl = await put(nameFront + nameBack + data.files.file.originalFilename, data?.files?.file?.filepath)
  res?.status(200)?.json({
    code: 0,
    msg: '',
    data: {
      url: resultUrl
    }
  });
}
​
async function put (fileName, filePath) {
  try {
    // 填写OSS文件完整路径和本地文件的完整路径。OSS文件完整路径中不能包含Bucket名称。
    // 如果本地文件的完整路径中未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    const result = await client.put(fileName, path.normalize(filePath));
    if (result?.res?.status == 200) {
      return result.url
    }
  } catch (e) {
    console.log(e);
  }
}

坑:

1、上面代码的config一定要写。不然读取不到antd upload组件上传的图片文件的file文件流

export const config = {
  api: {
    bodyParser: false,
  }
};

2、

 // 为了更好的目录结构
  const nowDate = new Date()
  const year = nowDate.getFullYear()
  const month = nowDate.getMonth()
  const day = nowDate.getDay()
  const nameFront = year + '/' + month + '/' + day + '/'
  const nameBack =  new Date().getTime() + '_';

上面这段代码是给上传的图片名添加了前缀,阿里云会自动解析然后帮我们在bucket里面自动的建立文件夹

就是通过拼接年月日的方式来把我们的图片有一个更好的管理

image-20220512194616974.png