vue3 + fastapi implements customized uploading of all files in the selected directory to the server

Article directory

    • ?Foreword
      • Technology stack selection
    • ?Front-end page construction
      • Adjust request content-type to pass formData
    • ?Backend interface implementation
      • swagger document test interface
    • ? Front-end and back-end implementation effects
      • Upload a single file
      • Upload directory files
    • ?Summarize
    • ?Finish


yma16-logo

?Foreword

Hello everyone, I am yma16. This article shares about vue3 + fastapi to implement selected directory files and upload them to the specified location on the server.
Vue3 series related articles:
Front-end vue2 and vue3 remove the “#” number in url routing – nginx configuration
csdn new star plan vue3 + ts + antd track – using inscode to build vue3(ts) + antd front-end template
Understand vite_vue3 initialization project to packaging
python_selenuim obtains the city where the csdn new star track player is located and displays it on the echarts map
Python series of articles:
python crawler_basic data types
python crawler_use of functions
Use of python crawler_requests
python crawler_selenuim visualization quality score
python crawler_django + vue3 visual csdn user quality score
python crawler_regular expression to obtain weather forecast and display it with echarts line chart
Python crawler_requests obtains the subtitles of the bilibili Blacksmithing Village series and uses word segmentation to divide the visual word cloud diagram for display
python crawler_selenuim log in to personal markdown blog site
Python crawler_requests gets the Minion expression and saves it to a folder
python_selenuim obtains the city where the csdn new star track player is located and displays it on the echarts map

Technology stack selection

Front-end: vue3 + ts + antd
Backend: python + fastapi

vue3 advantages
Vue3 has the following advantages compared to Vue2:

  1. Faster rendering speed: Vue3 can achieve faster rendering speed by redesigning the responsive system and virtual DOM. In terms of memory usage and performance, Vue3 is more efficient than Vue2.

  2. Better TypeScript support: Vue3 better supports TypeScript. The use of TypeScript in Vue3 is more direct, formal and stable, and type derivation is more accurate.

  3. Better component development: Vue3 makes it easier to write components and separate templates, scripts and styles, making the code more readable and maintainable.

  4. Better development experience: Vue3 adds many new features, such as Composition API, Teleport, Suspense, etc. These features make the development process simpler, more convenient and more flexible.

  5. More ecological support: With the advent of Vue3, more and more plug-ins and libraries have begun to support Vue3, such as Vue Router, Vuex, etc. The development of these ecological tools will contribute to the rapid development of Vue3.

fastapi advantages
The advantages of FastAPI are mainly reflected in the following aspects:

  1. High performance: FastAPI uses an asynchronous programming model and asynchronous processing of requests based on event loops, which can easily handle a large number of concurrent requests and improve server performance.

  2. Easy-to-use API development: FastAPI can automatically generate API documentation, so developers can use it to quickly write APIs without spending a lot of time writing documentation.

  3. High reliability: FastAPI automatically performs type checking, which can avoid runtime errors caused by type errors and improve the stability of the API.

  4. Supports native Python syntax: FastAPI can use Python native syntax to write code without having to learn a new language, making it easier to use the Python ecosystem.

  5. Compatible with multiple front-end frameworks: FastAPI can be used with multiple front-end frameworks, including React, Angular, Vue.js, etc., providing greater development freedom.

  6. Extensive community support: The FastAPI community is very active, has a large number of developers and users, and provides a wealth of resources and support.

?Front-end page construction

layout:
upper and lower structure
Above is the directory selection
Below is the folder selection
The implementation effect diagram is as follows
upload

vue3 syntax sugar code implementation

<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 type')
    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('Please select a folder!')
    }
    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="Upload files">
                    <div>
                        Upload folder
                       <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>
                                       Upload folder
                                   </a-button>
                               </a-upload>
                               <div>

                               </div>
                           </div>
                           <div style="margin:10px 0">
                               <a-button type="primary" block @click="upDir" :disabled="state.dirList.length===0" >Click to start parsing the folder</a-button>
                           </div>

                       </div>

                        Upload leaflet file
                        <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">Click to upload or drag and drop here</p>
                                <p class="ant-upload-hint">
                                    Select a document
                                </p>
                            </a-upload-dragger>
                        </div>
                        <div style="margin:10px 0">
                            <a-button type="primary" block @click="uploadSingleFile" :disabled="upBtnDisabled">Click to start uploading files</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>

Adjust request content-type to pass formData

axios package

import axios from "axios";

//Instance
const createInstance = (baseURL:string)=>{<!-- -->
    return axios.create({<!-- -->
        baseURL:baseURL,
        timeout: 10000,
        headers: {<!-- -->'X-Custom-Header': 'yma16'}
    })
};

// @ts-ignore
const http:any=createInstance('');


//Add request interceptor
http.interceptors.request.use(function (config:any) {<!-- -->
    //What to do before sending the request
    return config;
}, function (error:any) {<!-- -->
    //What to do with request errors
    return Promise.reject(error);
});

//Add response interceptor
http.interceptors.response.use(function (response:any) {<!-- -->
    // Status codes within the 2xx range will trigger this function.
    //Do something with the response data
    return response;
}, function (error:any) {<!-- -->
    // Status codes outside the 2xx range will trigger this function.
    // Do something with the response error
    return Promise.reject(error);
});

// File Upload
const createUploadInstance = (baseURL:string)=>{<!-- -->
    return axios.create({<!-- -->
        baseURL:baseURL,
        timeout: 10000,
        headers: {<!-- -->"Content-Type": "multipart/form-data"}
    })
};

// @ts-ignore
const uploadHttp:any=createUploadInstance('');


//Add request interceptor
uploadHttp.interceptors.request.use(function (config:any) {<!-- -->
    //What to do before sending the request
    return config;
}, function (error:any) {<!-- -->
    //What to do with request errors
    return Promise.reject(error);
});

//Add response interceptor
uploadHttp.interceptors.response.use(function (response:any) {<!-- -->
    // Status codes within the 2xx range will trigger this function.
    //Do something with the response data
    return response;
}, function (error:any) {<!-- -->
    // Status codes outside the 2xx range will trigger this function.
    // Do something with the response error
    return Promise.reject(error);
});

export {<!-- -->http,uploadHttp};

Service docking backend

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

?Backend interface implementation

Installation Environment

pip install uvicorn
pip install fastapi
pip install python-multipart

pipi install

Upload a single file interface implementation:

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()
# Cross-domain configuration
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!"}


# upload files
@app.post("/api/uploadFile/action")
async def create_file(
    file:UploadFile
):
    writeBytes('./media',file)
    return {<!-- -->
        'code':200,
        "msg":'success'
    }

#Write file to dirs directory file
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 runs fastapi

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

swagger document test interface

swagger document address:
http://ip:port/docs

Upload successful!
upload

? Front-end and back-end implementation effects

Upload a single file

uploadFile

Upload directory files

uploadDir
Interface implementation for uploading directory files:

  • file is a binary file
  • dir is the directory name
  • name is the complete file name
# Upload directory files
@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'
    }

#Write binary data to directory file
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)

?Summary

Notes on file upload
front end:

  1. Request header configuration headers: {"Content-Type": "multipart/form-data"}
  2. Parameter passing uses new FormData()

rear end:

  1. Accept parameters using Uploadfile format
  2. Parse file content name including type and write file in format

multipart/form-data

multipart/form-data is a common HTTP request method, usually used to upload files or large amounts of data. It divides the requested data into multiple parts. Each part is separated by a boundary delimiter. Each part contains a header and a content body. The header describes the attributes of the part, such as data type and data encoding. wait. In the HTTP message body, each part must start with “–boundary\r\
” and end with “–boundary–\r\
”, that is, add an extra “–” mark at the end. When the client uses this method to request, it needs to explicitly specify that the Content-Type in the request header is multipart/form-data. After the server receives the request, it needs to parse out the request data in each part.

?End

This article ends here. If there are any errors or shortcomings, please point them out!
scene

Likes are the motivation for my creation!
Collection is the direction of my efforts!
Comments are the wealth of my progress!
Thank you for reading!