基于FastAPI框架的文件上传服务。主要功能包括:
- 初始化日志:通过logging模块配置日志输出文件、日志级别和格式。
- 定义全局变量:获取脚本所在目录和项目目录,定义上传文件的保存目录。
- 创建FastAPI应用:实例化FastAPI对象,用于构建API接口。
- 文件上传接口:通过app.post装饰器定义一个上传文件的接口。接口接收一个UploadFile类型的参数file,表示上传的文件。接口的主要逻辑包括:
- 验证文件格式是否符合要求(只接受.xlsx文件)。
- 读取文件的二进制内容。
- 调用save_upload_file函数保存文件,并根据保存结果返回相应的响应。
- 文件保存函数:save_upload_file函数用于保存上传的文件。主要逻辑包括:
- 确保上传目录存在,如果不存在则创建。
- 生成唯一的文件名,格式为时间戳.xlsx。
- 将文件内容写入到指定路径。
- 返回文件的名称、路径和大小,如果保存失败则返回None。、
- 启动服务:根据运行模式(线上模式或调试模式),使用uvicorn模块启动FastAPI应用
在FastAPI中,File
和UploadFile
是处理文件上传的两个关键组件,它们允许你在API端点中接收和处理用户上传的文件。下面是这两个类的简要解释:
File
File
是FastAPI的一个依赖项,它本身不是一个具体的类,而是一个类型注解,用来声明一个参数应该接收一个上传的文件。它通常与UploadFile
一起使用,后者才是实际处理文件上传的类。使用File
时,你可以指定一些额外的参数来约束上传的文件,比如文件的大小限制。但是,直接使用File
的情况较少,更多时候是通过UploadFile
间接使用。
UploadFile
UploadFile
是FastAPI提供的一个类,它实现了异步操作,用于处理上传的文件。当用户通过HTTP请求上传文件时,FastAPI会自动将上传的文件包装成UploadFile
对象。这个对象提供了文件操作的异步方法,如读取文件内容、获取文件名、获取文件类型等。例如,你可以使用await upload_file.read()
来读取文件的二进制内容,这对于处理大文件特别有用,因为它支持流式读取,不会一次性加载整个文件到内存中。
import logging
import os
from datetime import datetime
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse, FileResponse
import uvicorn
# 初始化日志
logging.basicConfig(filename='file_server.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
script_dir = os.path.dirname(os.path.abspath(__file__))
pro_dir = os.path.abspath(os.path.join(script_dir, '..'))
dir_upload = os.path.abspath(os.path.join(pro_dir, 'files'))
app = FastAPI()
@app.post("/upload_file/")
async def upload_file(file: UploadFile = File(...)):
"""
接收上传的文件,保存到服务器,并返回文件信息。
"""
# 验证文件格式
if file.content_type != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
raise HTTPException(status_code=400, detail="上传文件格式不符合要求")
return {
"message": "文件格式错误"
}
try:
# 读取文件的二进制内容
file_content = await file.read()
# 调用save_upload_file函数保存文件
file_info = await save_upload_file(dir_upload, file_content)
if file_info is not None:
# 如果文件保存成功,则返回文件信息
return JSONResponse(content=file_info)
else:
# 文件保存失败,返回错误信息
return JSONResponse(content={"message": "File save fail"},
status_code=500)
except Exception as e:
logger.error("Error file upload: %s", str(e))
return JSONResponse(content={"message": "File uploaded fail"},
status_code=500)
# 定义并实现文件保存函数
async def save_upload_file(dir_upload, file_content) -> dict:
try:
# 确保上传目录存在
if not os.path.exists(dir_upload):
os.makedirs(dir_upload)
# 使用当前时间戳生成文件名
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
file_name = f"{timestamp}.xlsx"
file_path = os.path.join(dir_upload, file_name)
# 保存文件到指定路径
with open(file_path, "wb") as f:
f.write(file_content)
file_size = os.path.getsize(file_path)
return {
"file_name": file_name,
"file_path": file_path,
"file_size": file_size
}
except Exception as e:
logger.error("File save fail: %s", str(e))
return None
## 文件下载
@app.get("/get_file/", summary="get file", tags=['文件'])
async def get_file(file_name: str):
"""
根据文件名提供文件的 HTTP 服务
"""
try:
file_path = os.path.join(dir_upload, file_name)
print("file_path", file_path)
if not os.path.exists(file_path):
return {
"status": 0,
"message": "File not found"
}
return FileResponse(file_path, media_type="application/octet-stream", filename=file_name)
except Exception as e:
return {
"status": 0,
"message": str(e)
}
if __name__ == "__main__":
## 线上模式
# uvicorn.run("sms_server:app", host="0.0.0.0", port=1315)
## debug 模式
uvicorn.run("file_server:app", host="0.0.0.0", port=1316, reload=True, )