FastAPI后端工程化项目记录

以下是一个使用fastapi上传视频的接口,记录一下工程化后端程序的业务逻辑

重点是代码如何抽离

项目结构优化

project/
├── .env                      # 环境变量配置
├── app/
│   ├── __init__.py
│   ├── main.py               # 主应用入口
│   ├── core/                 # 核心配置
│   │   ├── __init__.py
│   │   ├── config.py         # 应用配置
│   │   ├── database.py       # 数据库配置
│   │   └── middleware.py     # 中间件配置
│   ├── models/               # 数据模型
│   │   ├── __init__.py
│   │   └── video.py          # 视频模型
│   ├── schemas/              # Pydantic模型
│   │   ├── __init__.py
│   │   └── video.py          # 视频响应模型
│   ├── services/             # 业务逻辑
│   │   ├── __init__.py
│   │   └── video_service.py  # 视频服务
│   ├── utils/                # 工具函数
│   │   ├── __init__.py
│   │   └── file_utils.py     # 文件处理工具
│   └── routers/              # 路由模块
│       ├── __init__.py
│       ├── video.py          # 视频路由
│       └── train.py          # 训练路由
├── static/                   # 静态文件
│   └── videos/               # 视频存储
└── alembic/                  # 数据库迁移

1. 数据库配置 (app/core/database.py)

from tortoise import Tortoise
from tortoise.contrib.fastapi import register_tortoise
from app.core.config import settingsasync def init_db():"""初始化数据库连接"""await Tortoise.init(db_url=settings.DATABASE_URL,modules={'models': ['app.models']},generate_schemas=False  # 禁用自动建表(适配已有表))def setup_database(app):"""注册数据库到FastAPI应用"""register_tortoise(app,db_url=settings.DATABASE_URL,modules={'models': ['app.models']},generate_schemas=False,  # 禁用自动建表add_exception_handlers=True)

2. 应用配置 (app/core/config.py)

import os
from pydantic import BaseSettingsclass Settings(BaseSettings):DATABASE_URL: str = "mysql://user:password@localhost:3306/video_service"UPLOAD_DIR: str = "static/videos"ALLOWED_EXTENSIONS: set = {".mp4", ".mov", ".avi", ".mkv", ".webm"}MAX_FILE_SIZE_MB: int = 2000MAX_FILE_SIZE: int = MAX_FILE_SIZE_MB * 1024 * 1024class Config:env_file = ".env"settings = Settings()

3. 数据模型 (app/models/video.py)

from tortoise.models import Model
from tortoise import fieldsclass VideoRecord(Model):id = fields.IntField(pk=True)original_filename = fields.CharField(max_length=255)saved_filename = fields.CharField(max_length=255)server_path = fields.CharField(max_length=512)file_size = fields.BigIntField()file_type = fields.CharField(max_length=10)upload_time = fields.DatetimeField(auto_now_add=True)unique_id = fields.UUIDField()class Meta:table = "video_records"  # 指定已有表名table_description = None  # 禁用自动字段修改

4. Pydantic模型 (app/schemas/video.py)

from datetime import datetime
from pydantic import BaseModelclass VideoUploadResponse(BaseModel):message: strsaved_filename: strpath: strabsolute_path: stroriginal_filename: strsize: intrecord_id: intupload_time: datetimefile_type: str

5. 文件工具 (app/utils/file_utils.py)

import os
import uuid
from datetime import datetime
from app.core.config import settingsdef ensure_upload_dir():"""确保上传目录存在"""os.makedirs(settings.UPLOAD_DIR, exist_ok=True)def is_valid_video(filename: str) -> bool:"""检查文件扩展名是否为允许的视频格式"""return any(filename.lower().endswith(ext) for ext in settings.ALLOWED_EXTENSIONS)def generate_new_filename(original_filename: str) -> str:"""生成唯一的带时间戳的新文件名"""ext = os.path.splitext(original_filename)[1]timestamp = datetime.now().strftime("%Y%m%d%H%M%S")unique_id = uuid.uuid4().hex[:6]return f"video_{timestamp}_{unique_id}{ext}"

6. 视频服务 (app/services/video_service.py)

import os
import logging
from fastapi import HTTPException
from app.models.video import VideoRecord
from app.utils.file_utils import generate_new_filename, is_valid_video
from app.core.config import settingslogger = logging.getLogger(__name__)async def upload_video(file):"""视频上传服务逻辑"""# 验证文件类型if not is_valid_video(file.filename):raise HTTPException(400, f"不支持的文件格式。仅支持: {', '.join(settings.ALLOWED_EXTENSIONS)}")# 计算文件大小file_size = 0while chunk := await file.read(10 * 1024 * 1024):  # 10MB chunksfile_size += len(chunk)await file.seek(0)# 检查文件大小if file_size > settings.MAX_FILE_SIZE:raise HTTPException(413, f"文件太大,最大允许 {settings.MAX_FILE_SIZE_MB}MB")# 生成新文件名new_filename = generate_new_filename(file.filename)save_path = os.path.join(settings.UPLOAD_DIR, new_filename)abs_save_path = os.path.abspath(save_path)file_ext = os.path.splitext(file.filename)[1].lower()# 保存文件try:with open(save_path, "wb") as buffer:while chunk := await file.read(10 * 1024 * 1024):buffer.write(chunk)logger.info(f"视频保存成功: {abs_save_path}")except Exception as e:logger.error(f"文件保存失败: {abs_save_path}, 错误: {str(e)}")if os.path.exists(save_path):os.remove(save_path)raise HTTPException(500, f"文件保存失败: {str(e)}")finally:await file.close()# 创建数据库记录try:video_record = await VideoRecord.create(original_filename=file.filename,saved_filename=new_filename,server_path=abs_save_path,file_size=file_size,file_type=file_ext)logger.info(f"数据库记录创建成功,ID: {video_record.id}")except Exception as e:if os.path.exists(save_path):os.remove(save_path)logger.error(f"数据库操作失败: {str(e)}")raise HTTPException(500, f"数据库操作失败: {str(e)}")return {"saved_filename": new_filename,"path": f"/{settings.UPLOAD_DIR}/{new_filename}","absolute_path": abs_save_path,"original_filename": file.filename,"size": file_size,"record_id": video_record.id,"upload_time": video_record.upload_time,"file_type": file_ext}

7. 视频路由 (app/routers/video.py)

from fastapi import APIRouter, UploadFile, File
from fastapi.responses import JSONResponse
from app.services.video_service import upload_video
from app.schemas.video import VideoUploadResponserouter = APIRouter()@router.post("/upload-video", response_model=VideoUploadResponse)
async def api_upload_video(file: UploadFile = File(...)):"""视频上传接口"""result = await upload_video(file)return JSONResponse(status_code=200,content={"message": "视频上传并记录成功",**result})

8. 主应用 (app/main.py)

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
from app.core import config, database, middleware
from app.routers import video, trainsettings = config.settings@asynccontextmanager
async def lifespan(app: FastAPI):# 初始化数据库await database.init_db()yieldapp = FastAPI(lifespan=lifespan)# 注册中间件
app.add_middleware(middleware.CORSMiddleware)
app.middleware("http")(middleware.log_request_middleware)# 挂载静态目录
app.mount("/static", StaticFiles(directory=settings.UPLOAD_DIR), name="static")# 包含路由
app.include_router(video.router, prefix='/api/video')
app.include_router(train.router, prefix='/api/train')if __name__ == '__main__':import uvicornuvicorn.run("app.main:app", host='0.0.0.0', port=4010, reload=True)

9. 中间件 (app/core/middleware.py)

import time
import logging
from fastapi.middleware.cors import CORSMiddleware
from fastapi import Request# 配置日志
logging.basicConfig(filename='request_logs.log', format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)def setup_cors(app):app.add_middleware(CORSMiddleware,allow_origins=["*"],allow_credentials=True,allow_methods=["GET", "POST"],allow_headers=["*"],)async def log_request_middleware(request: Request, call_next):start_time = time.time()response = await call_next(request)process_time = time.time() - start_timelog_message = (f"Method: {request.method}, Path: {request.url.path}, "f"Status: {response.status_code}, Time: {process_time:.2f}s, "f"URL: {request.url}")logging.info(log_message)return response

10. 环境文件 (.env)

# 数据库配置
DATABASE_URL=mysql://user:password@localhost:3306/video_service# 文件配置
UPLOAD_DIR=static/videos
ALLOWED_EXTENSIONS=.mp4,.mov,.avi,.mkv,.webm
MAX_FILE_SIZE_MB=2000

适配已有数据库表的要点

1. 禁用自动建表:

   # database.pygenerate_schemas=False  # 禁用自动创建表结构

2. 模型严格匹配表结构:

# models/video.pyclass VideoRecord(Model):# 字段与现有表严格对应class Meta:table = "video_records"  # 指定物理表名table_description = None  # 禁用ORM元数据修改

3. 手动验证表结构:

在应用启动后,建议运行以下脚本验证模型与表结构一致性:

from app.models.video import VideoRecordfrom tortoise import run_asyncasync def check_table():conn = Tortoise.get_connection("default")columns = await conn.execute_query("DESCRIBE video_records")print("现有表字段:", [col['Field'] for col in columns])if __name__ == "__main__":run_async(check_table())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92086.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92086.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

令牌桶限流算法

你提供的 Java 代码实现的是令牌桶限流算法(Token Bucket Algorithm),这是目前最常用、最灵活的限流算法之一。它允许一定程度的“突发流量”,同时又能控制平均速率。下面我将:逐行详细解释 TokenBucketLimiter 类的每…

基于springboot的宠物商城设计与实现

管理员:登录,个人中心,用户管埋,宠物分类管理,宠物信息管理,留言反馈,宠物论坛,系统管理,订单管理用户:宠物信息,宠物论坛,公告信息&a…

Python day36

浙大疏锦行 Python day36. 复习日 本周内容: 如何导入模块以及库项目的规范拆分和写法官方文档的阅读MLP神经网络的训练在GPU上训练模型可视化以及推理

【gaussian-splatting】用自己的数据复现高斯泼溅(一)

1.环境准备1.1.下载diff-gaussian-rasterization这里本来没啥说的,直接从github上下载就行了,但是我踩坑了,下的版本不对,后续运行报错参数个数对不上,特在此给大家避坑,注意一定要下带3dgs版本的diff-gaus…

中国移动h10g-01_S905L处理器安卓7.1当贝纯净版线刷机包带root权限_融合终端网关

下载固件之前请先将主板上的屏蔽罩取下,查看处理器型号 是否为S905L型号,然后再下载固件进行刷机; 本页面的固件是采用双公头数据线进行刷机的哈; 安卓4.4.2版本固件下载地址:点此进行下载 安卓7.1版本固件下载地址…

夜天之书 #110 涓滴开源:Cronexpr 的故事

在年初的一篇关于商业开源的博文当中,我介绍了在开发商业软件的过程中,衍生出开源公共软件库的模式。在那篇博文里面,我只是简单罗列了相关开源库的名字及一句话总结。近期,我会结合商业开源实践的最新进展,对其中一些…

完整的登陆学生管理系统(配置数据库)

目录 要求 思路 1. 登录模块(LoginFrame.java) 2. 学生信息管理模块(StudentFrame.java) 3. 数据层(StudentDAO.java) 4. 业务层(StudentService.java / UserService.java) 5…

译 | 在 Python 中从头开始构建 Qwen-3 MoE

文章出自:基于 2个Expert 的 MoE 架构分步指南 本篇适合 MoE 架构初学者。文章亮点在于详细拆解 Qwen 3 MoE 架构,并用简单代码从零实现 MoE 路由器、RMSNorm 等核心组件,便于理解内部原理。 该方法适用于需部署高性能、高效率大模型&#x…

Spring Boot + ShardingSphere 分库分表实战

🚀Spring Boot ShardingSphere 实战:分库分表,性能暴增的终极指南! ✅ 适用场景:千万级大表、高并发、读写分离场景 ✅ 核心框架:Spring Boot 3.x ShardingSphere-JDBC 5.4.1 ✅ 数据库:MySQL…

MaxKB 使用 MCP 连接 Oracle (免安装 cx_Oracle 和 Oracle Instant Client)

一、背景 安装cx_Oracle包和Oracle Instant Client来操作数据库,比较繁琐同时容易冲突,不同的 Oracle 版本都需要安装不同的插件。这篇文章将介绍使用 MCP 协议的连接方法。 二、操作步骤 1、使用 1Panel 安装 DBhub a) 数据库类型选择 Oracle 类型。…

基于Python的超声波OFDM数字通信链路设计与实现

基于Python的超声波OFDM数字通信链路设计与实现 摘要 本文详细介绍了使用Python实现的超声波OFDM(正交频分复用)数字通信链路系统。该系统能够在标准音响设备上运行,利用高于15kHz的超声波频段进行数据传输,采用48kHz采样率。文章涵盖了从OFDM基本原理、…

滑动窗口相关题目

近些年来,我国防沙治沙取得显著成果。某沙漠新种植N棵胡杨(编号1-N),排成一排。一个月后,有M棵胡杨未能成活。现可补种胡杨K棵,请问如何补种(只能补种,不能新种)&#xf…

Java 工具类的“活化石”:Apache Commons 核心用法、性能陷阱与现代替代方案

在上一篇文章中,我们回顾了 Apache Commons 的经典组件。但作为 Java 世界中资历最老、影响最深远的工具库,它的价值远不止于此。许多开发者可能只使用了它 10% 的功能,却忽略了另外 80% 能极大提升代码质量的“隐藏宝石”。本文将提供一个更…

数据结构——图及其C++实现 多源最短路径 FloydWarshall算法

目录 一、前言 二、算法思想 三、代码实现 四、测试 五、源码 一、前言 前两篇学习的Dijkstra算法和Bellman-Ford算法都是用来求解图的单源最短路径,即从图中指定的一个源点出发到图中其他任意顶点的最短路径。Dijkstra算法不能求解带有负权重的图的最短路径&…

解决微软应用商店 (Microsoft store) 打不开,无网络连接的问题!

很多小伙伴都会遇见微软应用商店 (Microsoft store)打开后出现无网络的问题,一般出现这种问题基本都是因为你的电脑安装了某些银行的网银工具,因为网银工具为了安全会关闭Internet 选项中的最新版本的TLS协议,而微软商店又需要最新的TLS协议才…

Android—服务+通知=>前台服务

文章目录1、Android服务1.1、定义1.2、基本用法1.2.1、定义一个服务1.2.2、服务注册1.2.3、启动和停止服务1.2.4、活动和服务进行通信1.3、带绑定的服务示例1.3.1、定义服务类1.3.2、客户端(Activity)绑定与交互​1.3.3、AndroidManifest.xml 注册​1.3.…

从基础功能到自主决策, Agent 开发进阶路怎么走

Agent 开发进阶路线大纲基础功能实现核心模块构建环境感知:传感器数据处理(视觉、语音、文本等输入)基础动作控制:API调用、硬件驱动、简单反馈机制状态管理:有限状态机(FSM)或行为树&#xff0…

《动手学深度学习》读书笔记—9.6编码器-解码器架构

本文记录了自己在阅读《动手学深度学习》时的一些思考,仅用来作为作者本人的学习笔记,不存在商业用途。 正如我们在9.5机器翻译中所讨论的,机器翻译是序列转换模型的一个核心问题,其输入和输出都是长度可变的序列。为了处理这种类…

DocBench:面向大模型文档阅读系统的评估基准与数据集分析

本文由「大千AI助手」原创发布,专注用真话讲AI,回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我,一起撕掉过度包装,学习真实的AI技术! 一、数据集概述与核心目标 DocBench 是由研究团队于2024年提出的首个…

Python高级排序技术:非原生可比对象的自定义排序策略详解

引言:超越原生比较操作的排序挑战在Python数据处理中,我们经常需要处理不原生支持比较操作的对象。根据2024年《Python开发者生态系统报告》,在大型项目中,开发者平均需处理28%的自定义对象排序需求,这些对象包括&…