编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

前后端开发文件上传(前端给后端传文件流)

wxchong 2024-10-07 17:08:04 开源技术 11 ℃ 0 评论

文件上传

1.环境

前端

Vue3


PrimeVUE

后端

Python


FastAPI

2.文件存储

在软件开发中,上传文件是经常会使用到的一个功能,那么文件存储也就变得非常重要起来了。在以往的小项目中,文件存储都是在直接在磁盘上存储的,但这样的方式,随着微服务的发展,部署在不同机器上的微服务之间需要共享数据,直接存储在磁盘上的文件就不方便文件共享了。鉴于此,我们在开发相对较大的软件时,就需要将文件存储在数据库或者分布式文件系统中,在此,我们选择了MongoDB的GridFS来存储文件,也就是说在文件上传时将文件存储到MongoDB数据库中,然后接口返回文件的OID。

3.文件上传方式

文件上传一般的方式是需要支持单文件上传和多文件上传的,但本质上来讲是一回事,所以在设计文件上传接口时,我们将文件上传统一为一个接口,支持多个文件上传即可。

4.后端文件上传源代码

# coding: utf-8
import settings
from logger import logger
from api.upload import router
from fastapi import File
from fastapi import Depends
from fastapi import UploadFile
from typing import List
from pymongo import MongoClient
from gridfs import GridFS
from common.common import find_current_usr


@router.post(path='/upload_files')
async def upload_files(files: List[UploadFile] = File(...), current_usr: dict = Depends(find_current_usr)):
    res = {'res': False, 'oids': []}

    with MongoClient(settings.MONGO_HOST, settings.MONGO_PORT) as connect:
        filedb = connect.filedb
        mgfs = GridFS(filedb)

        for file in files:
            # 写入gridfs
            logger.log('Upload file {0} from {1}'.format(file.filename, current_usr['name']))
            dic = dict()
            dic['filename'] = file.filename
            oid = mgfs.put(await file.read(), **dic)
            res['oids'].append(str(oid))

        res['res'] = True
    return res

通过以上源代码,我们可以看到:接口接收在表单中传递的 files 参数,files 类型为列表,然后对接收到的文件循环写入 MongoDB,最后返回 MongoDB 的文件 OID 列表。返回值:

{'res': False, 'oids': []}

其中:res 表示是否上传成功,oids 表示 MongoDB 文件 OID 列表。

5.前端文件上传封装

由于后端接收的是文件,而不是普通的数据,所以前端在传输数据时必须以表单的形式传递,也就是说,请求头中必须指定:

'Content-Type': 'multipart/form-data'

另外由于在上传文件时需要携带令牌,所以,需要对上传文件的 js 代码进行封装,代码如下:

function errorToString (error) {
  let text = ''
  if (error.response.data) {
    if (error.response.data instanceof Object)
      text = JSON.stringify(error.response.data)
    else
      text = error.response.data
  }
  return text
}

// 文件上传
const fileConfig = {
  baseURL: '',
  timeout: 60000,
  headers: {
    'Content-Type': 'multipart/form-data'
  },
  responseType: 'json'
}

// 携带令牌的文件上传
const uploadRequest = (url, data) => {
  let _id = loading()
  // eslint-disable-next-line
  const promise = new Promise((resolve, reject) => {
    fileConfig.headers['Authorization'] =
        localStorage.getItem('token_type') + ' ' + localStorage.getItem('access_token')

    axios.post(
        url, data, fileConfig
    ).then((response) => {
      loaded(_id)
      resolve(response)
    }).catch((error) => {
      loaded(_id)
      console.log(error)
      // 对error中的数据进行处理
      let status = error.response.status
      let txt = errorToString(error)
      // 弹出错误信息
      if (status === 401) {
        swal({
          title: status.toString(),
          text: txt,
          icon: 'error',
          button: "确定",
        }).then((value) => {
          console.log(value)
          window.top.location.href = '/index.html'
        })
      } else {
        swal(status.toString(), txt, 'error')
      }
      // reject(error)
    })
  })

  return promise
}

这样经过封装后,前端在上传文件时,只需要调用 uploadRequest 函数就可以完成文件的上传,调用格式:

      uploadRequest(
          '/qycommon_api/upload/upload_files',
          formData
      ).then((response) => {
        if (response.data.res) {
          ......
        } else {
          ......
        }
      })

在上面的代码中,我们看到 formData 这个变量,那么下面我们来说明一下前端上传文件的方法。

6.前端上传文件代码

前端上传文件,我们采用 PrimeVUE 的 upload 组件,该组件实际上是一个三态组件,包括:选择文件、上传文件、取消文件。但我们在一般的使用中,希望做到直接选择文件后就上传,同时获取到上传文件的 OID ,然后将 OID 记录到数据表中。实现代码如下:

组件

            <FileUpload
                  name="demo[]"
                  mode="basic"
                  accept="image/*"
                  chooseLabel="更换"
                  :auto="true"
                  @select="upload_avatar_file"/>

事件

  upload_avatar_file (event) {
      var formData = new FormData()
      event.files.forEach((file) => {
        formData.append('files', file)
      })

      let _this = this
      uploadRequest(
          '/qycommon_api/upload/upload_files',
          formData
      ).then((response) => {
        if (response.data.res) {
          swal({title: "提示!", text: "操作成功!", icon: "success", button: false, timer: 1000})
          _this.download_avatar_file(response.data.oids[0])  // 下载文件
          _this.write_my_avatar(response.data.oids[0])  // 将文件的 OID 写入数据表
        } else {
          swal({title: "提示!", text: "操作失败!", icon: "error", button: false, timer: 1000})
        }
      })
    },

说明:

1.select 事件

在组件中,我们使用了 select 事件来上传文件,而不是 uploader 事件,同时将 auto 设置为 true,表示使用自动上传。这样会在上传文件时出现错误,因为我们没有指定组件的 url ,但不会有问题,因为我们在选择文件时已经将文件上传了。只是在浏览器的调试环境中可以看到请求了一个 null 的错误。

2.FormData

在自定义的文件上传中,我们使用了 FormData ,循环将文件写入 FormData 的 files 中。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表