vue3 + fastapi 实现选择目录所有文件自定义上传到服务器

简介: vue3 + fastapi 实现选择目录所有文件自定义上传到服务器

⭐前言

大家好,我是yma16,本文分享关于vue3 + fastapi 实现选择目录文件上传到服务器指定位置。

vue3系列相关文章:

前端vue2、vue3去掉url路由“ # ”号——nginx配置

csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板

认识vite_vue3 初始化项目到打包

python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示

python系列文章:

python爬虫_基本数据类型

python爬虫_函数的使用

python爬虫_requests的使用

python爬虫_selenuim可视化质量分

python爬虫_django+vue3可视化csdn用户质量分

python爬虫_正则表达式获取天气预报并用echarts折线图显示

python爬虫_requests获取bilibili锻刀村系列的字幕并用分词划分可视化词云图展示

python爬虫_selenuim登录个人markdown博客站点

python爬虫_requests获取小黄人表情保存到文件夹

python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示

💖 技术栈选择

前端:vue3 + ts + antd

后端:python + fastapi

vue3优势

Vue3相比较于Vue2有以下几个优势:

  1. 更快的渲染速度:Vue3通过重新设计响应式系统和虚拟DOM,可以实现更快的渲染速度。在内存使用和性能方面,Vue3比Vue2更加高效。
  2. 更好的TypeScript支持:Vue3更好地支持TypeScript,TypeScript在Vue3中的使用更加直接、正式、稳定,并且类型推导更加准确。
  3. 更好的组件化开发:Vue3可以更方便地编写组件,将模板、脚本和样式分离开来,使得代码更加易读易维护。
  4. 更好的开发体验:Vue3增加了很多新的特性,如Composition API、Teleport、Suspense等,这些特性使得开发过程更加简单、便捷、灵活。
  5. 更多的生态支持:随着Vue3的面世,越来越多的插件和库开始支持Vue3,例如Vue Router、Vuex等,这些生态工具的发展将有助于Vue3的快速发展。

fastapi优势

FastAPI的优势主要体现在以下几个方面:

  1. 高性能:FastAPI使用异步编程模型,使用基于事件循环的异步处理请求,可以轻松处理大量的并发请求,提高服务器性能。
  2. 简单易用的API开发:FastAPI能够自动生成API文档,因此开发者可以通过它来快速地编写API,而不必花费大量时间去编写文档。
  3. 高可靠性:FastAPI 自动进行类型检查,能够避免类型错误引起的运行时错误,提高了API的稳定性。
  4. 支持原生Python语法:FastAPI可以使用Python原生语法来编写代码,不需要学习新的语言,可以更方便地使用Python的生态系统。
  5. 兼容多种前端框架:FastAPI 可以与多种前端框架配合使用,包括React、Angular、Vue.js等,提供了更大的开发自由度。
  6. 广泛的社区支持:FastAPI社区非常活跃,拥有大量的开发者和用户,提供了丰富的资源和支持。

⭐前端页面搭建

布局:

上下结构

上方为选择目录

下方为选择文件夹

实现效果图如下

vue3 语法糖代码实现

<script  lang="ts" setup>
import { ref,reactive,computed } from 'vue';
import { InboxOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { uploadFile,uploadUrl } from "../../service/gpt/index";
import { UploadOutlined } from '@ant-design/icons-vue';
const state:any=reactive({
    fileList:[],
    loading:false,
    text:'',
    dirList:[],
    dirPath:'',
    customFile:null,
    activeKey:'1',
    movieUrl:''
});
const upUrl=async ()=>{
    state.loading=true
    try{
        const res=await uploadUrl({
            url:state.movieUrl
        })
        console.log('res',res)
    }
    catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}
const remove=(e:any)=> {
  console.log('drop file',e);
    state.fileList=[]
}
const removeDir=(e:any)=>{
    state.dirList=state.dirList.filter((file:any)=>file.uid!==e.uid)
}
const customRequesHandle=(e:any)=>{
    console.log(e,'custom')
}
const beforeUpload = (file:any) => {
    console.log('file before',file)
    state.fileList=[file]
    return false;
};
const beforeUploadDir = (file:any) => {
    state.dirList.push(file)
    return false;
};
const uploadSingleFile= async ()=>{
    state.loading=true
    console.log(typeof state.fileList[0],'file 类型')
    try{
        const formData=new FormData();
        formData.append('file',state.fileList[0])
        const res=await uploadFile(formData)
        console.log('res',res)
    }catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}
const upBtnDisabled=computed(()=>{
    return state.fileList.length===0
})
    const change=(e:any)=>{
    console.log('change e',e)
    }
const upDir=async ()=>{
    if(state.dirList.length===0){
        return message.warning('请选择文件夹!')
    }
    state.loading=true
    const paramsData:any={
        dirList:state.dirList,
        dirPath:state.dirPath,
    }
    try{
        state.dirList.forEach(async (file:any)=>{
            try{
                const formData=new FormData();
                formData.append('file',file)
                const res=await uploadFile(formData)
                console.log('res',res)
            }catch(r){
                message.error(JSON.stringify(r))
            }
        })
    }catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}
const previewDirFile=async (file:any)=>{
    return new Promise(resolve=>resolve(false))
}
</script>
<template>
    <div>
        <a-spin :spinning="state.loading" tip="upload...">
        <div class="header-tools">
        </div>
            <a-tabs v-model:activeKey="state.activeKey">
                <a-tab-pane key="1" tab="上传文件">
                    <div>
                        上传文件夹
                       <div style="margin: 5px;border: 1px dotted #1890ff;padding: 20px">
                           <div style="margin: 10px 0;max-height: 200px;overflow: auto">
                               <a-upload :before-upload="beforeUploadDir" v-model:file-list="state.dirList"
                                         list-type="picture"
                                         @remove="removeDir" directory>
                                   <a-button>
                                       <upload-outlined></upload-outlined>
                                       上传文件夹
                                   </a-button>
                               </a-upload>
                               <div >
                               </div>
                           </div>
                           <div style="margin:10px 0">
                               <a-button type="primary" block @click="upDir" :disabled="state.dirList.length===0" >点击开始解析文件夹</a-button>
                           </div>
                       </div>
                        上传单文件
                        <div style="margin: 5px;border: 1px dotted #1890ff;padding: 20px">
                        <div>
                            <a-upload-dragger
                                    :file-list="state.fileList"
                                    list-type="picture"
                                    :multiple="false"
                                    :before-upload="beforeUpload"
                                    @remove="remove"
                                    @change="change"
                            >
                                <p class="ant-upload-drag-icon">
                                    <inbox-outlined></inbox-outlined>
                                </p>
                                <p class="ant-upload-text">点击上传或者拖拽到这</p>
                                <p class="ant-upload-hint">
                                    选择文件
                                </p>
                            </a-upload-dragger>
                        </div>
                        <div style="margin:10px 0">
                            <a-button type="primary" block @click="uploadSingleFile" :disabled="upBtnDisabled">点击开始上传文件</a-button>
                        </div>
                        </div>
                    </div>
                </a-tab-pane>
            </a-tabs>
        </a-spin>
    </div>
</template>
<style>
    .header-tools{
        text-align: center;
        font-size: 24px;
        font-weight: bold;
    }
    .content-box{
    }
    .des{
        margin:20px 0;
    }
</style>

💖 调整请求content-type传递formData

axios封装

import axios from "axios";
// 实例
const createInstance = (baseURL:string)=>{
    return axios.create({
        baseURL:baseURL,
        timeout: 10000,
        headers: {'X-Custom-Header': 'yma16'}
    })
};
// @ts-ignore
const http:any=createInstance('');
// 添加请求拦截器
http.interceptors.request.use(function (config:any) {
    // 在发送请求之前做些什么
    return config;
}, function (error:any) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response:any) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error:any) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});
// 文件上传
const createUploadInstance = (baseURL:string)=>{
    return axios.create({
        baseURL:baseURL,
        timeout: 10000,
        headers: {"Content-Type": "multipart/form-data"}
    })
};
// @ts-ignore
const uploadHttp:any=createUploadInstance('');
// 添加请求拦截器
uploadHttp.interceptors.request.use(function (config:any) {
    // 在发送请求之前做些什么
    return config;
}, function (error:any) {
    // 对请求错误做些什么
    return Promise.reject(error);
});
// 添加响应拦截器
uploadHttp.interceptors.response.use(function (response:any) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error:any) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});
export {http,uploadHttp};

service对接后端

import {uploadHttp} from "../../http/index";
export const uploadFile: any = (formData: any) => {
    return uploadHttp.post("/api/uploadFile/action", formData);
};

⭐后端接口实现

安装环境

pip install uvicorn
pip install fastapi
pip install python-multipart

上传单个文件接口实现:

from fastapi import FastAPI, status, File, Form, UploadFile
from fastapi import FastAPI, status, File, Form, UploadFile
from fastapi.middleware.cors import CORSMiddleware
import os
app = FastAPI()
# 跨域配置
origins = [
    "http://localhost:3000",
]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
@app.get("/api")
async def root():
    return {"data": "fast api!"}
# 上传文件
@app.post("/api/uploadFile/action")
async def create_file(
    file:UploadFile
):
    writeBytes('./media',file)
    return {
        'code':200,
        "msg":'success'
    }
# 将file写入dirs目录文件
def writeBytes(dirs,file):
    bytesFile=file.file.read()
    filename=file.filename
    if not os.path.exists(dirs):
        os.makedirs(dirs)
    with open(dirs+'/'+ filename, "wb") as f:
        f.write(bytesFile)

uvicorn运行fastapi

uvicorn server.main:app --reload --port 7777

💖 swagger文档测试接口

swagger文档地址:

http://ip:port/docs

上传成功!

⭐前后端实现效果

💖 上传单个文件

💖 上传目录文件

上传目录文件的接口实现:

  • file为二进制文件
  • dir为目录名称
  • name为完整的文件名称
# 上传目录文件
@app.post("/api/uploadDirFile/action")
async def uploadDirFile(
    file:UploadFile,
    dir:str=Form(),
    name:str=Form()
):
    print(dir,'dir_____________')
    writeBytes('./media/'+dir,name,file)
    return {
        'code':200,
        "msg":'success'
    }
# 将二进制数据写入目录文件
def writeBytes(dirs,name,file):
    bytesFile=file.file.read()
    filename=name
    if not os.path.exists(dirs):
        os.makedirs(dirs)
    with open(dirs+'/'+ filename, "wb") as f:
        f.write(bytesFile)

⭐总结

文件上传注意事项

前端:

  1. 请求头配置 headers: {"Content-Type": "multipart/form-data"}
  2. 参数传递使用new FormData()

后端:

  1. 接受参数使用 Uploadfile格式
  2. 解析文件内容名称包括类型按格式写入文件

multipart/form-data

multipart/form-data 是一种常用的 HTTP 请求方法,通常用于上传文件或大量数据。它将请求的数据分成多个部分(part),每一部分使用一个 boundary 分隔符来分开,每个部分包含一个头部和一个内容体,头部描述了该部分的属性,如数据类型、数据编码等。在 HTTP 消息体中,每个部分之间必须以 “–boundary\r\n” 开始,以 “–boundary–\r\n” 结束,即在结尾处添加额外的 “–” 标记。在客户端使用该方法请求时,需要明确指定请求头中的 Content-Type 为 multipart/form-data。服务端接收到该请求后,需要解析出每个部分中的请求数据。

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!


目录
相关文章
|
2月前
|
机器学习/深度学习 存储 监控
内部文件审计:企业文件服务器审计对网络安全提升有哪些帮助?
企业文件服务器审计是保障信息安全、确保合规的关键措施。DataSecurity Plus 是由卓豪ManageEngine推出的审计工具,提供全面的文件访问监控、实时异常告警、用户行为分析及合规报告生成功能,助力企业防范数据泄露风险,满足GDPR、等保等多项合规要求,为企业的稳健发展保驾护航。
|
2月前
|
安全 Linux Shell
使用SCP命令在CentOS 7上向目标服务器传输文件
以上步骤是在CentOS 7系统上使用SCP命令进行文件传输的基础,操作简洁,易于理解。务必在执行命令前确认好各项参数,尤其是目录路径和文件名,以避免不必要的传输错误。
231 17
|
2月前
|
自然语言处理 Unix Linux
解决服务器中Jupyter笔记本的文件名字符编码问题
通过上述步骤,可以有效解决Jupyter笔记本的文件名字符编码问题,确保所有文件能在服务器上正常访问并交互,避免因编码问题引起的混淆和数据丢失。在处理任何编码问题时,务必谨慎并确保备份,因为文件名变更是
103 17
|
4月前
|
弹性计算 Ubuntu Linux
阿里云服务器镜像怎么选?公共/自定义/共享/云市场/社区镜像区别与适用场景梳理
在购买阿里云服务器的过程中,选择合适的镜像(即云服务器的操作系统)是至关重要的一步。阿里云服务器镜像涵盖了公共镜像、自定义镜像、共享镜像、云市场镜像(镜像市场)和社区镜像等多种类型,对于新手用户来说,面对这些不同类型的镜像,往往会感到困惑,不知道它们之间的区别,更不知道如何根据自身需求进行选择。本文为大家解析这些镜像的特点、区别,并为大家提供选择参考。
829 60
|
4月前
|
存储 弹性计算 安全
阿里云服务器自定义、快速、活动、云市场镜像四种主流方式解析与选择参考
阿里云服务器如何购买?目前主要的购买方式有自定义购买、快速购买、通过活动购买、通过云市场镜像页面购买这四种购买方式。然而,面对阿里云服务器多样化的购买方式和配置选项,许多用户可能会感到迷茫,不知道该如何选择最适合自己的购买途径。本文将详细解析阿里云服务器的四种主流购买方式的适用场景及购买流程,以供大家了解他们之间的区别及选择参考。
194 58
|
2月前
|
安全 Linux 网络安全
Python极速搭建局域网文件共享服务器:一行命令实现HTTPS安全传输
本文介绍如何利用Python的http.server模块,通过一行命令快速搭建支持HTTPS的安全文件下载服务器,无需第三方工具,3分钟部署,保障局域网文件共享的隐私与安全。
443 0
|
3月前
|
存储 弹性计算 网络协议
如何自定义购买阿里云服务器ECS?详细参考步骤,答疑解惑
阿里云ECS(弹性计算服务)支持用户根据需求自定义配置服务器,包括实例规格、存储、带宽、镜像类型及安全组等。购买前需完成实名认证并确保账户余额充足。操作流程涵盖选择付费模式(包年包月/按量付费)、地域、镜像、网络设置、登录凭证及高级选项等。创建实例约需3-5分钟,建议慎重选择不可更改的配置(如地域),并注意安全性与带宽计费策略。详细步骤可参考官方文档。
|
5月前
|
存储 弹性计算 安全
阿里云服务器四种购买方式解析:自定义、快速、活动、云市场镜像选购流程参考
阿里云服务器主要的购买方式有自定义购买、快速购买、通过活动购买、通过云市场镜像页面购买这四种购买方式。然而,面对阿里云服务器多样化的购买方式和配置选项,有些新手用户并不清楚他们的区别及具体流程,因此可能不知道哪种方式更适合自己。本文将详细解析阿里云服务器的四种主流购买方式的适用场景及购买流程,帮助用户轻松选择最适合自己的购买途径。
|
21天前
|
弹性计算 编解码 大数据
性价比最高提升50%!阿里云企业级云服务器上新
阿里云ECS云服务器推出全新升级的u2系列实例,包括基于Intel的u2i实例与首个基于AMD的u2a实例,提供企业级独享算力,综合性价比最高提升50%。u2i实例已开放公测,适用于中小型数据库、企业网站建设等场景。同时发布基于AMD的第九代旗舰实例g9ae,性能提升65%,适用于大数据、视频转码等密集型业务。
135 0
|
1月前
|
弹性计算 运维 安全
阿里云轻量应用服务器是什么?看完你就知道了
阿里云轻量应用服务器是面向网站建设、开发测试等轻量场景的云服务器,按套餐售卖,内置多种应用镜像,支持一键部署,操作简单,适合个人开发者和中小企业使用。
220 0

热门文章

最新文章