FastAPI
前言
因为我本身是学习Java的,如果你也是,那么你学习 FastAPI 会非常顺畅!很多概念几乎可以无缝迁移。
让我用熟悉的 Spring Boot 视角来帮你快速建立对应关系:
核心概念对应表(Spring Boot → FastAPI)
Spring Boot 概念
FastAPI 对应概念
相似度
@RestController
@app.get()/@app.post() 等路由装饰器
★★★★★
@RequestMapping
路径参数和装饰器(@app.api_route)
★★★★☆
@Autowired
Depends() 依赖注入
★★★★☆
@Component/@Service
普通函数 + Depends()
★★★★☆
@Repository
数据库操作函数/类
★★★★☆
@Configuration
启动时初始化代码/依赖项
★★★☆☆
@Bean
使用 lifespan 或单例依赖
★★★☆☆
Interceptor/Filter
依赖项或中间件
★★★★☆
@ControllerAdvice
异常处理器(@app.exception_handler)
★★★★☆
@Valid 和 DTO
Pydantic 模型验证
★★★★★
application.properties
环境变量或 .env 文件
★★★★☆
Spring Security
依赖项 + OAuth2 工具
★★★★☆
你会立即感到熟悉的几个方面
1. 注解式开发 → 装饰器开发
1 2 3 4 @app.get("/users/{id}" ) def get_user (id : int , db: Session = Depends(get_db ) ): return db.query(User).filter (User.id == id ).first()
2. 依赖注入的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 class UserService : def get_user (self, user_id: int ): return {"id" : user_id, "name" : "John" } user_service = UserService() @app.get("/users/{id}" ) def read_user (id : int , service: UserService = Depends(lambda : user_service ) ): return service.get_user(id )
3. 数据验证(DTO → Pydantic)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pydantic import BaseModelclass UserCreate (BaseModel ): username: str email: str age: int @validator('email' ) def validate_email (cls, v ): if '@' not in v: raise ValueError('Invalid email' ) return v @app.post("/users/" ) def create_user (user: UserCreate ): return {"message" : "User created" }
需要稍作调整的思维模式
更函数式,更少“仪式感”
不需要定义接口
不需要 getter/setter
没有复杂的 XML 配置
更直接的请求-响应
参数直接从函数参数获取
返回值直接就是响应体
异步支持是天生的(async/await)
更灵活的依赖注入
可以是函数,可以是类,都可以注入
可以基于路径参数、查询参数、header 等动态创建依赖
学习建议
直接找对应关系 :每当你想“在 Spring Boot 里我会怎么做”,就去查 FastAPI 的对应方案
从 Pydantic 开始 :这是 FastAPI 的“杀手锏”,类似 Lombok + Validation 的超强组合
理解 Python 的特性 :类型提示、装饰器、async/await - 这些是 FastAPI 强大的基础
别怕写“简单”代码 :Python 代码看起来比 Java 简单,但功能一样强大
快速入门路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @app.get("/hello" ) def hello (name: str = "World" ): return {"message" : f"Hello {name} " } def get_db (): db = SessionLocal() try : yield db finally : db.close() @app.get("/items" ) def get_items (db: Session = Depends(get_db ) ): return db.query(Item).all () oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token" ) def get_current_user (token: str = Depends(oauth2_scheme ) ): return user @app.get("/users/me" ) def read_me (current_user = Depends(get_current_user ) ): return current_user
好消息是…
你的 Spring Boot 经验有 80% 可以直接迁移!
REST API 设计
分层架构(Controller/Service/Repository)
依赖注入的价值
数据验证的重要性
配置管理
错误处理
FastAPI 只是用更 Pythonic、更简洁的方式实现了相同的理念。你会发现在 FastAPI 中实现同样的功能,代码量可能只有 Spring Boot 的 1/3 到 1/2,但功能和性能一样强大。
我们学习java学的是理念,FastAPI 只是换了种更轻量、更现代的语言和语法来实现它。
下面是我的笔记~
FastAPI 入门 - FastAPI 框架
Python 版本最低3.8 推荐3.10以上 我是用3.13
Python环境
为了适应各种Python版本,这里使用MiniConda 来作为Python环境,MiniConda 可以在创建虚拟环境的时候指定Python 版本号,并且没有Anaconda 那么多乱七八糟的第三方库
一:MiniConda
Miniconda - Anaconda
Download Success - post download | Anaconda
安装完后要将路径添加到环境变量当中
1 2 3 4 5 6 # 根路径 D: \miniconda# Scripts 路径 D: \miniconda\Scripts# bin路径 D: \miniconda\Library\bin
验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 C:\Users \Lenovo >conda <==== 输入conda usage : conda -script.py [-h ] [-v ] [--no -plugins ] [-V ] COMMAND ...conda is a tool for managing and deploying applications , environments and packages .options : -h , --help Show this help message and exit . -v , --verbose Can be used multiple times . Once for detailed output , twice for INFO logging , thrice for DEBUG logging , four times for TRACE logging . --no -plugins Disable all plugins that are not built into conda . -V , --version Show the conda version number and exit . commands : The following built -in and plugins subcommands are available . COMMAND activate Activate a conda environment . clean Remove unused packages and caches . commands List all available conda subcommands (including those from plugins ). Generally only used by tab -completion . compare Compare packages between conda environments . config Modify configuration values in .condarc . content -trust Signing and verification tools for Conda create Create a new conda environment from a list of specified packages . deactivate Deactivate the current active conda environment . doctor Display a health report for your environment . env Create and manage conda environments . export Export a given environment info Display information about current conda install . init Initialize conda for shell interaction . install Install a list of packages into a specified conda environment . list List installed packages in a conda environment . menuinst A subcommand for installing and removing shortcuts via menuinst . notices Retrieve latest channel notifications . package Create low -level conda packages . (EXPERIMENTAL ) remove (uninstall ) Remove a list of packages from a specified conda environment . rename Rename an existing environment . repoquery Advanced search for repodata . run Run an executable in a conda environment . search Search for packages and display associated information using the MatchSpec format . token Set repository access token and configure default_channels tos A subcommand for viewing , accepting , rejecting , and otherwise interacting with a channel 's Terms of Service (ToS ). This plugin periodically checks for updated Terms of Service for the active /selected channels . Channels with a Terms of Service will need to be accepted or rejected prior to use . Conda will only allow package installation from channels without a Terms of Service or with an accepted Terms of Service . Attempting to use a channel with a rejected Terms of Service will result in an error . update (upgrade ) Update conda packages to the latest compatible version . C :\Users \Lenovo >
二:虚拟环境
默认情况下,我们直接通过pip install 的方式将包安装在系统级别的Python环境中,这样存在一个问题,就是如果有个老项目用FastAPI是老版本做的,然后你现在想用最新的FastAPI版本来开发新项目,这时候就会碰到一个问题,如何在电脑中同事拥有两套环境?这个时候就是使用我们的虚拟环境来解决这个问题。
首先,我们创建一个基于Python 3.13 版本的虚拟环境:
1 2 conda create -n fastapi-env python=3.13
通过下列命令可以管理虚拟环境:
1 2 3 4 5 6 7 8 9 10 11 conda env list conda env remove -n [虚拟环境名称] conda activate [虚拟环境名称] conda deactivate
三:设置pip源
默认情况下,使用pip命令会从python官网上下载,由于Python官网服务器在国外,下载速度有点慢,我们可以改为国内滴,比如清华,搜索引擎搜索一下也有很多其他的
1 pip config set global .index-url https://pypi.tuna.tsinghua.edu.cn/simple
结果:
1 2 3 4 C:\Users \Lenovo >conda activate fastapi -env (fastapi -env ) C :\Users \Lenovo >pip config set global.index -url https ://pypi.tuna.tsinghua.edu.cn /simple Writing to C :\Users \Lenovo \AppData \Roaming \pip \pip.ini
开发工具
PyCharm
这个不多说啦
我是教育邮箱申请的pro
或者你可以去某宝购买。
Mysql
javaer应该都会的
Navicat
也可以使用其他的数据库可视化软件
FastAPI 介绍
一:FastAPI 简介
FastAPI 是一个高性能的 Python Web 框架,旨在为开发人员提供快速、简洁且强大的 API 开发体验。它于 2017 年由 Sebastian Ramirez 发布,旨在解决当时市场上现有框架在处理 HTTP 请求和响应时的复杂性和维护困难问题。
Sebastian Ramirez 在开发 FastAPI 时,面临着构建 API 的挑战,他发现现有的框架在处理 HTTP 请求和响应时过于复杂,难以维护。因此,他决定开发一个更简洁、更易于使用的框架,以帮助其他开发人员更专注于业务逻辑,而不是处理 HTTP 请求和响应。
FastAPI 支持异步编程 ,这意味着它可以更有效地处理并发请求,提高应用程序的性能。此外,FastAPI 还支持多种数据库和数据存储后端,如 PostgreSQL、MySQL、SQLite、MongoDB 等,以及 ORM 框架,如 SQLAlchemy。这使得开发者可以轻松地将 FastAPI 与各种数据库集成,构建强大的数据驱动应用程序。
许多知名的公司和项目已经开始使用 FastAPI,包括 Google、Netflix、Amazon、Dropbox 等。这些公司在使用 FastAPI 时,都对其高性能和简洁性给予了高度评价。
FastAPI 的基准测试成绩也证明了其高性能。根据官方的基准测试结果,FastAPI 在处理并发请求时,其性能优于其他流行的 Web 框架,如 Flask 和 Django。这使得 FastAPI 成为构建高性能 Web 应用程序和 API 的理想选择。
Django、Flask 和 FastAPI 三大框架的特点:
功能:Django > Flask > FastAPI
性能:FastAPI > Flask > Django
二:FastAPI的优点
快速 :与NodeJS和Go性能相当,是最快的Python web框架之一。
高效编码 :提升功能开发速度200%至300%。
更少bug :减少约40%开发者人为错误。
智能 :编辑器自动补全以减少调试时间。
简单 :易使用和学习,读文档时间更短。
简短 :代码重复最小化,通过参数声明实现丰富功能,bug更少。
健壮 :生产可用级别代码,含自动生成的交互式文档。
标准化 :基于OpenAPI和JSON Schema。
三:安装
使用下面这个命令即可安装:
1 pip install "fastapi[standard]"
四:一个最简单的FastAPI程序
在Pycharm中选择FastAPI项目新建
当然了记得创建.gitignore 因为ide会自带一些文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from fastapi import FastAPIapp = FastAPI() @app.get("/" ) async def root (): return {"message" : "Hello World" } @app.get("/hello/{name}" ) async def say_hello (name: str ): return {"message" : f"Hello {name} " }
Uvicorn
uvicorn是一个高性能的异步web服务器。我们也可以使用uvicorn来直接运行FastAPI项目。首先通过以下命令安装uvicorn:
1 pip install "uvicorn[standard]"
然后可以通过下面命令运行项目:
1 uvicorn main:app --reload
测试API
另外我们可以在项目根路径下test_main.http 中测试我们写的API是否有错误
具体项目代码(后续所有代码都放在这里):yjyrichard/fastapi-learning: fastapi学习代码
Pydantic介绍
在其他web框架中,比如Django或Flask,我们通过定义表单类来校验数据。而在FastAPI中,我们使用Pydantic来实现这一功能。
Pydantic的特点:
由类型提示支持 :少学习、代码少、与IDE工具集成。
速度快 :Rust核心验证,Python最快数据验证库之一。
JSON模式 :与其他工具集成。
生态系统丰富 :PyPI约8000个包,含FastAPI等流行库。
Battle测试 :月下载超7000万次,被多家大型科技公司使用。
安装
由于FastAPI依赖Pydantic,因此在安装FastAPI时会把pydantic也一起安装了。
基本使用
Pydanyic 是通过定义模型,以及在模型中指定字段来对值进行校验的,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 from datetime import datefrom typing import List from pydantic import BaseModel, ValidationErrorclass User (BaseModel ): """ 定义一个名为User的模型,它继承自pydantic.BaseModel。 这个类定义了我们期望的数据结构、类型和约束。 """ id : int name: str = 'John Doe' date_joined: date | None departments: List [str ] | None print ("--- 场景一:使用有效数据 ---" )valid_data = { 'id' : 123 , 'date_joined' : '2023-06-01' , 'departments' : ['技术部' , '产品部' ] } try : user = User(**valid_data) print ("✅ 数据验证成功!" ) print (f"用户ID: {user.id } " ) print (f"用户姓名: {user.name} " ) print (f"加入日期: {user.date_joined} (类型: {type (user.date_joined)} )" ) except ValidationError as e: print ("❌ 数据验证失败!" ) print (e) print ("\n" + "=" *50 + "\n" )print ("--- 场景二:使用无效数据并捕获错误 ---" )invalid_data = { 'id' : 'abc' , 'name' : 456 , 'date_joined' : '2023-13-01' , 'departments' : '不是列表' } try : user_invalid = User(**invalid_data) print ("✅ 数据验证成功!" ) except ValidationError as e: print ("❌ 数据验证失败!捕获到 ValidationError。" ) print ("\n--- 错误摘要 ---" ) print (e) print ("\n--- 详细错误列表 ---" ) error_list = e.errors() for i, error in enumerate (error_list, 1 ): print (f"错误 {i} :" ) print (f" - 位置: {' -> ' .join(map (str , error['loc' ]))} " ) print (f" - 信息: {error['msg' ]} " ) print (f" - 类型: {error['type' ]} " ) print ("-" * 20 )
请求数据
请求数据包括有路由参数,Body参数,以下分别进行讲解:
这个跟java没有什么区别 直接来看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 from fastapi import FastAPI, Body, Query, Path, Form, File, UploadFilefrom pydantic import BaseModel, Fieldfrom typing import Annotatedapp = FastAPI() @app.get("/items/{item_id}" ) async def read_item (item_id: int ): return {"item_id" : item_id} @app.get("/users/" ) async def read_users (q: Annotated[str | None , Query(max_length=50 )] = None ): if q: return {"query" : q} return {"message" : "No query parameter provided" } class Item (BaseModel ): name: str description: str | None = None price: float tax: float | None = None @app.post("/items/" ) async def create_item (item: Item ): return item @app.put("/items/{item_id}" ) async def update_item ( item_id: Annotated[int , Path(title="The ID of the item to update" , ge=1 )], q: str | None = None , item: Annotated[Item, Body(embed=True )] ): results = {"item_id" : item_id, "item" : item} if q: results["q" ] = q return results @app.post("/login/" ) async def login (username: str = Form( ), password: str = Form( ) ): return {"username" : username} @app.post("/uploadfile/" ) async def create_upload_file (file: UploadFile = File(... ) ): return { "filename" : file.filename, "content_type" : file.content_type } @app.post("/files/" ) async def create_file ( file: bytes = File( ), description: str = Form(... ) ): return { "file_size" : len (file), "description" : description }
依赖注入
依赖注入可以让我们的视图函数在执行之前先执行一段逻辑代码,这段逻辑代码可以返回新的值给视图函数。在以下场景中可以使用依赖注入:
共享业务逻辑(复用相同的代码逻辑)
共享数据库连接
实现安全、验证、角色权限
等等。
总之,就是将一些重复性的代码单独写成依赖,然后在需要的视图函数中注入这个依赖。
因为我之前是学习Java的 这样解释我反而有点看不太懂
跟spring的核心理念是相同的意思是:
“控制反转(IoC)”和“依赖倒置(DIP)”:将对象的创建和管理权交给框架,使用者只声明“我需要什么”,框架负责“提供什么”。
springboot这样写嘛:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Service public class UserService { public User getUser (Long id) { } } @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public User getUser (@PathVariable Long id) { return userService.getUser(id); } }
fastapi这样写:
1 2 3 4 5 6 7 8 9 10 def get_current_user (token: str = Depends(oauth2_scheme ) ): return user @app.get("/users/me" ) def read_users_me (current_user: User = Depends(get_current_user ) ): return current_user
总之:就是别总是 “new ”啦 找框架要吧。
将一些重复性的代码单独写成以来,任何在需要的视图函数中,注入这个依赖。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 from fastapi import Dependsfrom typing import Dict async def page_common (page: int =0 ,size: int =10 ): return {"page" : page,"size" : size} @app.get("/user/list" ) async def user_list (page_params: Dict =Depends(page_common ) ): page = page_params["page" ] size = page_params["size" ] return {"page" : page,"size" : size}
APIRouter
在FastAPI项目中,我们不可能把所有视图都放到main.py 文件中,这时候久需要将视图进行分类,任何同类的放到一个单独的子路由下。在FastAPI 中可以使用 APIRouter 类进行实现。【好比 java里面的 [业务名称]Controller 这样看起来更加清晰嘛】
新建一个包:routers
里面新建两个py文件分别为:article.py 和 user.py
user.py
1 2 3 4 5 6 7 8 9 10 11 12 13 from fastapi import APIRouterrouter = APIRouter(prefix="/user" , tags=["user" ]) @router.get("/list" ) async def user_list (): return {"users" : ["zs" ,"ls" ]} @router.get('/{user_id}' ) async def user_detail (user_id ): return {"user_id" : user_id}
article.py 也差不多 就不粘贴了
现在还没有跟main.py进行关联
1 2 3 4 5 6 from fastapi import FastAPIfrom routers.user import router as user_routerfrom routers.article import router as article_router app = FastAPI() app.include_router(user_router) app.include_router(article_router)
这个配置方式让我感觉跟vue很像
想象成组装一台电脑:
FastAPI/Vue 方式 (模块化组装):
1 2 3 4 1. 买一个空机箱(app = FastAPI() / createApp())2. 买独立显卡(user_router) 3. 买独立内存条(article_ router)4. 组装起来(app.include_router())
灵活 :需要什么装什么
Spring Boot 方式 (一体化方案):
1 2 3 1. 买品牌整机(@SpringBootApplication)2. 开机即用(自动扫描所有组件)3. 配置都在说明书里(application.properties)
省心 :开箱即用,功能齐全
学习迁移建议
FastAPI 的 app ≈ Vue 的 app ≈ Spring Boot 的启动类
都是应用的核心容器
都负责全局配置
都是模块注册的中心
FastAPI 的 APIRouter ≈ Vue 的组件 ≈ Spring Boot 的 @Controller
都是功能模块
都可以独立开发和测试
都可以被主应用"挂载"
最大的区别 :
FastAPI/Vue : 显式注册(你要手动 include_router)
Spring Boot : 隐式注册(自动扫描 @Controller)
现代框架都在向 “核心应用 + 模块化插件” 的模式发展。这种模式让代码更清晰、更易维护,也让学习曲线更平滑——学会一个,其他的就很容易理解了。
app = FastAPI() 就是 FastAPI 世界的 “Spring Boot Application”,是整个应用的启动器和配置中心。**
数据库连接和模型
在Python框架中,我们通常会使用ORM(Object Relationship Mapping)框架来操作数据库。目前主流的ORM框架有:SQLALchemy、Peewee ORM、Pony ORM、GINO、Tortoise ORM、orman等。而SQLALchemy是功能最强大,且社区最活跃的ORM库,并且还支持异步,因此我选择用SQLALchemy作为操作数据库的ORM框架。
另外,FastAPI作者开发了一个ORM框架叫做SQLModel,但是这个框架目前来说还不完善,文档都还处于开发期间,它底层也是基于SQLALchemy Core来实现的。SQLAlchemy文档地址为:https://www.sqlalchemy.org/
安装
1.安装sqlalchemy
通过以下命令安装:
1 pip install "sqlalchemy[asyncio]"
将会安装异步版本的sqlalchemy
然后再执行以下命令安装aiomysql 驱动:
1 2 3 # 同步:mysqlclient # 异步:aimysql pip install aiomysql
2.安装cryptography
使用Python连接MySQL需要用cryptography对密码进行加密,所以还需要安装以下包:
1 pip install cryptography
创建连接
配置连接参数
使用SQLAlchemy 连接数据库,是通过设置一个固定格式的字符串来实现的。mysql+aiomysql 为例,那么其连接格式如下:
1 DB_URL = "mysql+aiomysql://用户名:密码@主机名:端口号/数据库名称?charset=utf8mb4"
示例如下:
1 DB_URL = "mysql+aiomysql://root:root@127.0.0.1:3306/bookdb?charset=utf8mb4"
创建Engine 对象
SQLAlchemy 中的 Engine 对象,它负责管理数据库连接的创建(并不直接操作数据库),连接池的维护,SQL语句的翻译等。Engine对象在整个程序中只能有一个。创建Engine对象的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from sqlalchemy.ext.asyncio import create_async_engineengine = create_async_engine( DB_URI, echo=True , pool_size=10 , max_overflow=20 , pool_timeout=10 , pool_recycle=3600 , pool_pre_ping=True , )
创建会话工厂
使用 sqlalchemy_orm.sessionmarker 类来创建会话工厂,这个会话工厂实际就是Session 或者它的子类,以后如果要操作数据库,那么就需要创建一个会话工厂的对象 (即:Session类的对象),来完成相关操作。示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 from sqlalchemy.orm import sessionmakerfrom sqlalchemy.ext.asyncio import AsyncSessionAsyncSessionFactory = sessionmaker( bind=engine, class_=AsyncSession, autoflush=True , expire_on_commit=False )
创建模型
定义Base 类
Base 类是所有ORM Model 类的父类,一个ORM Model 类对应数据库中的一张表。ORM Model 中的一个Column 类属性对应数据库表中的一个字段。Base类的生成可以使用 sqlalchemy.ext.declarative.declarative_base 函数来实现,也可以继承 sqlalchemy.MetaData 类,实现自己的子类,并在子类中编写约束规范。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from sqlalchemy.orm import DeclarativeBasefrom sqlalchemy import MetaDataclass Base (DeclarativeBase ): metadata = MetaData(naming_conventions={ "ix" : 'ix_x(column_0_label)s' , "uq" : "uq_x(table_name)s_x(column_0_name)s" , "ck" : "ck_x(table_name)s_x(constraint_name)s" , "fk" : "fk_x(table_name)s_x(column_0_name)s_x(referred_table_name)s" , "pk" : "pk_x(table_name)s" })
创建ORM模型
这里我们以创建一个User模型为例来说明模型的创建:
1 2 3 4 5 6 7 8 9 10 11 from typing import Optional from sqlalchemy import Integer, String, selectfrom sqlalchemy.orm import Mapped, mapped_columnclass User (Base ): __tablename__ = 'user' id : Mapped[int ] = mapped_column(Integer, primary_key=True , autoincrement=True ) email: Mapped[str ] = mapped_column(String(100 ), unique=True , index=True ) username: Mapped[str ] = mapped_column(String(100 )) password: Mapped[str ] = mapped_column(String(200 ))
模型关系
这里我们使用外键来实现一对一,一对多和多对多,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 """ 用户相关模型 - SQLAlchemy ORM 关系映射示例 === 对比 MyBatis/MyBatis-Plus 的核心区别 === 1. MyBatis: 需要手写 XML 或注解来定义 SQL 和结果映射 MyBatis-Plus: 通过继承 BaseMapper 自动生成单表 CRUD SQLAlchemy: 通过 Python 类定义,自动生成 SQL,关联查询也自动处理 2. MyBatis: resultMap 手动配置关联查询 (association/collection) SQLAlchemy: relationship() 自动处理关联加载,不需要写 XML 3. MyBatis-Plus: @TableName, @TableId, @TableField 注解 SQLAlchemy: __tablename__, mapped_column() 定义 """ from typing import List , Optional , TYPE_CHECKINGfrom . import Basefrom sqlalchemy.orm import Mapped, mapped_column, relationshipfrom sqlalchemy import Integer, String, ForeignKeyif TYPE_CHECKING: from .article import Article class User (Base ): """ 用户表 - 演示一对一、一对多关系 === MyBatis-Plus 对比 === @TableName("user") public class User { @TableId(type = IdType.AUTO) private Long id; private String email; private String username; private String password; // MyBatis-Plus 不直接支持关联映射 // 需要手动查询或使用 XML 配置 @TableField(exist = false) private UserExtension userExtension; @TableField(exist = false) private List<Article> articles; } === MyBatis XML 关联查询对比 === <resultMap id="UserResultMap" type="User"> <id property="id" column="id"/> <result property="email" column="email"/> <result property="username" column="username"/> <!-- 一对一关联 --> <association property="userExtension" javaType="UserExtension" column="id" select="selectUserExtensionByUserId"/> <!-- 一对多关联 --> <collection property="articles" ofType="Article" column="id" select="selectArticlesByAuthorId"/> </resultMap> """ __tablename__ = 'user' id : Mapped[int ] = mapped_column(Integer, primary_key=True , autoincrement=True ) email: Mapped[str ] = mapped_column(String(100 ), unique=True , index=True ) username: Mapped[str ] = mapped_column(String(100 ), unique=True , index=True ) password: Mapped[str ] = mapped_column(String(200 )) """ 【一对一关系详解】 MyBatis 写法: <association property="userExtension" javaType="UserExtension" column="id" select="selectUserExtensionByUserId"/> // 或者嵌套结果映射 <association property="userExtension" javaType="UserExtension"> <id property="id" column="ext_id"/> <result property="university" column="university"/> </association> MyBatis-Plus 写法: // MP 不支持自动关联,需要手动查询 User user = userMapper.selectById(1); UserExtension ext = userExtensionMapper.selectOne( new QueryWrapper<UserExtension>().eq("user_id", user.getId()) ); user.setUserExtension(ext); SQLAlchemy 写法: relationship() 自动处理!访问 user.user_extension 时自动查询 关键点: - 外键在 UserExtension 表中 (user_id) - back_populates 实现双向绑定 - uselist=False 表示一对一(返回单个对象,不是列表) """ user_extension: Mapped[Optional ["UserExtension" ]] = relationship( "UserExtension" , back_populates="user" , uselist=False , cascade="all, delete-orphan" ) """ 【一对多关系详解】 MyBatis 写法: <collection property="articles" ofType="Article" column="id" select="selectArticlesByAuthorId"/> // 或者嵌套结果映射 <collection property="articles" ofType="Article"> <id property="id" column="article_id"/> <result property="title" column="title"/> </collection> MyBatis-Plus 写法: // MP 不支持自动关联,需要手动查询 User user = userMapper.selectById(1); List<Article> articles = articleMapper.selectList( new QueryWrapper<Article>().eq("author_id", user.getId()) ); user.setArticles(articles); SQLAlchemy 写法: relationship() 自动处理!访问 user.articles 时自动查询 关键点: - 外键在 Article 表中 (author_id) - List 类型表明这是"一"的一方,持有"多"的集合 - lazy 控制加载时机,类似 MyBatis 的懒加载配置 """ articles: Mapped[List ["Article" ]] = relationship( "Article" , back_populates="author" , lazy="selectin" ) class UserExtension (Base ): """ 用户扩展信息表 - 演示一对一关系的"从"方(持有外键的一方) === MyBatis-Plus 对比 === @TableName("user_extension") public class UserExtension { @TableId(type = IdType.AUTO) private Long id; private String university; private Long userId; // 外键字段 @TableField(exist = false) // 非数据库字段 private User user; } === MyBatis XML 对比 === <resultMap id="UserExtensionResultMap" type="UserExtension"> <id property="id" column="id"/> <result property="university" column="university"/> <result property="userId" column="user_id"/> <association property="user" javaType="User" column="user_id" select="selectUserById"/> </resultMap> """ __tablename__ = 'user_extension' id : Mapped[int ] = mapped_column(Integer, primary_key=True , autoincrement=True ) university: Mapped[str ] = mapped_column(String(100 )) """ 外键定义 - 这是关系的"物理连接" MyBatis-Plus: 就是普通字段 private Long userId; MyBatis XML: column="user_id" 在 association 中指定 SQLAlchemy 区别: - ForeignKey('user.id'): 数据库层面的外键约束 - relationship(): ORM 层面的对象关联 - 两者分开定义,更清晰 unique=True 保证一对一(一个 user_id 只能出现一次) """ user_id: Mapped[int ] = mapped_column( Integer, ForeignKey('user.id' ), unique=True ) """ 【反向关联】 MyBatis: 通过 association + select 实现 SQLAlchemy: relationship + back_populates 实现双向绑定 back_populates vs backref: - back_populates: 两边都要写 relationship(推荐,更清晰) - backref: 只在一边写,自动在另一边创建属性(简洁但隐式) """ user: Mapped["User" ] = relationship( "User" , back_populates="user_extension" ) """ 【查询方式对比】 1. 单表查询: MyBatis-Plus: userMapper.selectById(1) SQLAlchemy: session.get(User, 1) 2. 条件查询: MyBatis-Plus: userMapper.selectList(new QueryWrapper<User>().eq("email", "test@test.com")) SQLAlchemy: session.execute(select(User).where(User.email == "test@test.com")) 3. 关联查询(最大区别!): MyBatis-Plus: 需要手动写两次查询,再 set 进去 MyBatis XML: 需要配置 association/collection SQLAlchemy: 直接访问 user.articles,自动查询! 【为什么 SQLAlchemy 更方便?】 MyBatis/MyBatis-Plus 的关联查询: User user = userMapper.selectById(1); // 还要再查一次! List<Article> articles = articleMapper.selectList( new QueryWrapper<Article>().eq("author_id", 1) ); user.setArticles(articles); SQLAlchemy 的关联查询: user = session.get(User, 1) # 直接用!SQLAlchemy 自动处理关联查询 for article in user.articles: print(article.title) """
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 """ 文章模型 - 演示多对一关系 (ManyToOne) 和多对多关系 (ManyToMany) === 关系概览 === - Article -> User: 多对一 (多篇文章属于一个作者) - Article <-> Tag: 多对多 (一篇文章可以有多个标签,一个标签可以标记多篇文章) """ from typing import List , TYPE_CHECKINGfrom . import Basefrom sqlalchemy.orm import Mapped, mapped_column, relationshipfrom sqlalchemy import Integer, String, ForeignKey, Text, Table, Columnif TYPE_CHECKING: from .user import User """ 【多对多关系 - 中间表定义】 这是 SQLAlchemy 处理多对多关系的方式,需要一个中间表。 === Java/JPA 对比 === @Entity public class Article { @ManyToMany @JoinTable( name = "article_tag", joinColumns = @JoinColumn(name = "article_id"), inverseJoinColumns = @JoinColumn(name = "tag_id") ) private List<Tag> tags; } JPA 中使用 @JoinTable 注解,SQLAlchemy 需要显式定义 Table 对象。 === MyBatis 对比 === MyBatis 需要手写中间表的查询: <resultMap id="ArticleWithTagsMap" type="Article"> <id property="id" column="id"/> <collection property="tags" ofType="Tag"> <id property="id" column="tag_id"/> <result property="name" column="tag_name"/> </collection> </resultMap> <select id="selectArticleWithTags" resultMap="ArticleWithTagsMap"> SELECT a.*, t.id as tag_id, t.name as tag_name FROM article a LEFT JOIN article_tag at ON a.id = at.article_id LEFT JOIN tag t ON at.tag_id = t.id WHERE a.id = #{id} </select> """ article_tag = Table( 'article_tag' , Base.metadata, Column('article_id' , Integer, ForeignKey('article.id' ), primary_key=True ), Column('tag_id' , Integer, ForeignKey('tag.id' ), primary_key=True ) ) class Article (Base ): """ 文章表 - 演示多对一关系的"多"方,以及多对多关系 === Java/JPA 对比 === @Entity @Table(name = "article") public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(length = 100) private String title; @Lob // 大文本 private String content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "author_id") private User author; @ManyToMany @JoinTable(name = "article_tag", ...) private List<Tag> tags; } === MyBatis 对比 === <resultMap id="ArticleResultMap" type="Article"> <id property="id" column="id"/> <result property="title" column="title"/> <result property="content" column="content"/> <!-- 多对一关联 --> <association property="author" column="author_id" javaType="User" select="selectUserById"/> <!-- 多对多关联 --> <collection property="tags" column="id" ofType="Tag" select="selectTagsByArticleId"/> </resultMap> """ __tablename__ = 'article' id : Mapped[int ] = mapped_column(Integer, primary_key=True , autoincrement=True ) title: Mapped[str ] = mapped_column(String(100 )) content: Mapped[str ] = mapped_column(Text) """ 外键定义 - 多对一关系中,外键在"多"的一方 JPA: @JoinColumn(name = "author_id") MyBatis: 在查询中通过 column="author_id" 关联 """ author_id: Mapped[int ] = mapped_column( Integer, ForeignKey('user.id' ) ) """ 【多对一关系详解】 SQLAlchemy 写法: Mapped["User"] (单个对象,不是 List) 表示这是"多对一"中的"多"方 JPA 写法: @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "author_id") private User author; MyBatis 写法: <association property="author" column="author_id" select="selectUserById"/> 关键点: - 外键 author_id 在本表中 - Mapped["User"] 是单个对象,表示"多对一" - 对比 User 中的 Mapped[List["Article"]],那是"一对多" """ author: Mapped["User" ] = relationship( "User" , back_populates="articles" ) """ 【多对多关系详解】 SQLAlchemy 写法: 使用 secondary 参数指定中间表 JPA 写法: @ManyToMany @JoinTable( name = "article_tag", joinColumns = @JoinColumn(name = "article_id"), inverseJoinColumns = @JoinColumn(name = "tag_id") ) private List<Tag> tags; MyBatis 写法: 需要手写关联查询,通过中间表 JOIN 关键点: - 多对多需要中间表 (article_tag) - 两边都是 List 类型 - secondary 参数指定中间表 - 双向多对多,两边都需要定义 relationship """ tags: Mapped[List ["Tag" ]] = relationship( "Tag" , secondary=article_tag, back_populates="articles" ) class Tag (Base ): """ 标签表 - 演示多对多关系 === Java/JPA 对比 === @Entity @Table(name = "tag") public class Tag { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String name; @ManyToMany(mappedBy = "tags") // 被动方使用 mappedBy private List<Article> articles; } === MyBatis 对比 === <resultMap id="TagResultMap" type="Tag"> <id property="id" column="id"/> <result property="name" column="name"/> <collection property="articles" column="id" ofType="Article" select="selectArticlesByTagId"/> </resultMap> """ __tablename__ = 'tag' id : Mapped[int ] = mapped_column(Integer, primary_key=True , autoincrement=True ) name: Mapped[str ] = mapped_column(String(50 ), unique=True ) """ 【多对多 - 双向定义】 多对多关系两边都要定义 relationship,使用相同的 secondary 中间表 JPA 中: - 一方使用 @JoinTable 定义(主动方) - 另一方使用 mappedBy(被动方) SQLAlchemy 中: - 两边都使用 secondary 指定同一个中间表 - back_populates 建立双向关联 """ articles: Mapped[List ["Article" ]] = relationship( "Article" , secondary=article_tag, back_populates="tags" ) """ 【高级用法 - 中间表有额外字段】 如果中间表需要存储额外信息(如:用户收藏文章的时间), 就不能用简单的 Table,需要定义完整的 Model 类。 === 场景示例 === 用户收藏文章,需要记录收藏时间 === Java/JPA 对比 === // 需要创建一个中间实体 @Entity public class UserFavorite { @EmbeddedId private UserFavoriteId id; @ManyToOne @MapsId("userId") private User user; @ManyToOne @MapsId("articleId") private Article article; @Column(name = "created_at") private LocalDateTime createdAt; } === SQLAlchemy 写法 === from datetime import datetime class UserFavorite(Base): __tablename__ = 'user_favorite' user_id: Mapped[int] = mapped_column(ForeignKey('user.id'), primary_key=True) article_id: Mapped[int] = mapped_column(ForeignKey('article.id'), primary_key=True) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) # 关联 user: Mapped["User"] = relationship(back_populates="favorites") article: Mapped["Article"] = relationship(back_populates="favorited_by") class User(Base): favorites: Mapped[List["UserFavorite"]] = relationship(back_populates="user") class Article(Base): favorited_by: Mapped[List["UserFavorite"]] = relationship(back_populates="article") 这种模式叫做 Association Object Pattern, 类似 JPA 中把多对多拆成两个一对多的做法。 """
迁移模型
模型定义好后,要将模型映射到数据库中生成表,或者以后模型上的字段名,字段类型等发生改变了,可以非常方便的使用 alembic 来进行迁移
安装alembic
通过下列命令来安装alembic:
1 pip install alembic==1.13 .2
迁移
创建迁移仓库
alembic 的使用类似git,也可以进行版本回退,并且都需要先创建好一个迁移仓库。在项目根路径下,使用以下命令生成仓库:
1 alembic init alembic --template async
如果项目Mysql驱动不是异步欸都,比如pymysql,那么就不需要执行 --template async
修改alembic.ini
要将模型迁移到仓库中,还需要修改alembic.ini 下连接数据库的配置,修改代码如下:
1 2 # 注释sqlalchemy.url # sqlalchemy.url=
修改env.py
将alembic/env.py文件夹中的target_metadata修改为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import settingsfrom models import Baseconfig = context.config database_url = settings.DB_URI if database_url is None : raise ValueError("No database URL provided" ) config.set_main_option("sqlalchemy.url" , database_url) if config.config_file_name is not None : fileConfig(config.config_file_name) target_metadata = Base.metadata
生成迁移脚本
如果模型发生改变了,那么需要先将模型生成迁移脚本,执行以下命令:
1 alembic revision --autogenerate -m "修改的内容"
这样就会在alembic/versions 下生成迁移脚本文件。
执行迁移脚本
在alembic/versions 下生成迁移脚本后,模型的修改并没有同步到数据库中,因此还需要执行以下命令:
MyBatis 是以 SQL 为中心 (写 SQL -> 映射结果),而 SQLAlchemy 是以对象为中心 (操作对象 -> 自动生成 SQL)。
如果想要回到上一次的版本,那么可以使用以下命令来实现:
1 2 3 4 5 6 7 8 alembic downgrade -1 alembic downgrade base
CRUD 操作 (增删改查)
配置好模型和数据库连接后,接下来就是实际的业务操作。这里我们使用异步会话 (AsyncSession) 来演示。
核心思维转换 :
MyBatis : 你调用 Mapper 接口的方法,MyBatis 帮你执行绑定的 SQL。
SQLAlchemy : 你操作 Python 对象(创建对象、修改属性),然后告诉 Session “提交”,SQLAlchemy 帮你翻译成 SQL 并执行。
1. 新增 (Create)
在 SQLAlchemy 中,新增数据就是创建一个对象,然后 add 到会话中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import asynciofrom sqlalchemy.ext.asyncio import AsyncSessionfrom models import User, UserExtensionasync def create_user_demo (session: AsyncSession ): """ 新增用户 === MyBatis 对比 === User user = new User(); user.setUsername("admin"); userMapper.insert(user); // 此时数据库已有数据 === SQLAlchemy 逻辑 === 1. 实例化对象 (内存中) 2. session.add (加入到事务暂存区,数据库还没提交) 3. session.commit (提交事务,真正写入数据库) 4. session.refresh (可选,为了拿回数据库生成的自增 ID 或默认值) """ new_user = User( username="admin" , email="admin@example.com" , password="hashed_password_123" ) user_ext = UserExtension(university="Peking University" ) new_user.user_extension = user_ext session.add(new_user) try : await session.commit() await session.refresh(new_user) print (f"用户创建成功,ID: {new_user.id } " ) except Exception as e: await session.rollback() print (f"创建失败: {e} " )
2. 查询 (Read)
这是与 MyBatis 差异最大的地方。SQLAlchemy 2.0 统一使用 select() 构建查询语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from sqlalchemy import selectfrom sqlalchemy.orm import selectinloadasync def get_user_demo (session: AsyncSession ): """ 查询用户 === MyBatis 对比 === User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", "admin")); === SQLAlchemy 逻辑 === 构建 select 语句 -> 执行(execute) -> 获取结果(scalars) """ user = await session.get(User, 1 ) if user: print (f"找到用户: {user.username} " ) stmt = select(User).where(User.username == "admin" ) result = await session.execute(stmt) user_obj = result.scalars().first() stmt = ( select(User) .where(User.id == 1 ) .options( selectinload(User.articles), selectinload(User.user_extension) ) ) result = await session.execute(stmt) u = result.scalars().first() if u: print (f"用户文章数: {len (u.articles)} " ) print (f"用户学校: {u.user_extension.university if u.user_extension else '无' } " )
3. 更新 (Update)
SQLAlchemy 有两种更新方式。
方式一:对象操作(推荐,符合 ORM 直觉)
先查出来,改属性,再提交。SQLAlchemy 会自动监测哪些字段变了(Dirty Checking)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 async def update_user_orm_style (session: AsyncSession ): """ 先查后改 === MyBatis 对比 === User user = userMapper.selectById(1); user.setEmail("new@test.com"); userMapper.updateById(user); """ user = await session.get(User, 1 ) if user: user.email = "new_email@example.com" await session.commit()
方式二:批量更新(类似 SQL UPDATE 语句)
适合批量操作,不需要先把对象查到内存里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from sqlalchemy import updateasync def update_user_sql_style (session: AsyncSession ): """ 直接构建 Update 语句 === MyBatis 对比 === UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("id", 1).set("email", "direct@test.com"); userMapper.update(null, updateWrapper); """ stmt = ( update(User) .where(User.id == 1 ) .values(email="direct_update@example.com" ) ) await session.execute(stmt) await session.commit()
4. 删除 (Delete)
同样有两种方式。
方式一:对象操作
1 2 3 4 5 6 7 8 async def delete_user_orm_style (session: AsyncSession ): user = await session.get(User, 1 ) if user: await session.delete(user) await session.commit()
方式二:批量删除
1 2 3 4 5 6 7 8 9 10 from sqlalchemy import deleteasync def delete_user_sql_style (session: AsyncSession ): """ === MyBatis 对比 === userMapper.delete(new QueryWrapper<User>().eq("id", 1)); """ stmt = delete(User).where(User.id == 1 ) await session.execute(stmt) await session.commit()
总结:MyBatis vs SQLAlchemy 转习惯
为了让你更顺滑地过渡,请记住以下几点:
Session 是核心 :MyBatis 里你注入的是 UserMapper,SQLAlchemy 里你注入的是 AsyncSession。Session 是所有操作的入口。
Explicit vs Implicit :
MyBatis 很明确:你调用 select 它就 select,你调用 update 它就 update。
SQLAlchemy ORM 模式下:你修改了对象的属性,必须显式调用 session.commit(),它才会根据差异生成 UPDATE 语句。
Await 一切 :因为你选用了 asyncio,记得所有的 session.execute, session.commit, session.get 等涉及 I/O 的操作前都要加 await。
关于 Relationship :这是上面模型代码里写得最好的部分。在 MyBatis 里处理关联表(比如查用户带出文章列表)通常很痛苦(写 XML 嵌套查询)。在 SQLAlchemy 里,只要模型定义好了 relationship,查询时加上 .options(selectinload(...)) 就可以自动组装数据,这是 ORM 最大的优势。
如果你有后端(Python/Java)背景,理解前端框架会非常快。uni-app 的核心思想是**“一套代码,多端运行”**,这就好比 Java 的“一次编写,到处运行”,uni-app 将 Vue 代码编译成 iOS、Android、H5 以及各种小程序(微信、支付宝等)的原生代码。
uni-app 快速入门讲义
一、uni-app 介绍
1.1 什么是 uni-app?
uni-app 是一个使用 Vue.js 开发所有前端应用的框架。开发者编写一套代码,可发布到 iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。
1.2 核心优势
跨平台 :真正的“一套代码,多端发行”。
学习成本低 :基于通用的前端技术栈(Vue 语法 + 微信小程序 API)。只要懂 Vue,基本上就学会了 uni-app。
生态丰富 :拥有插件市场,有大量现成的组件和模板。
性能优秀 :App 端基于 weex/nvue 渲染,性能接近原生。
1.3 技术栈对比(给后端开发者的类比)
HTML/模板 :在 uni-app 中是 <template>,类似 Python 的 Jinja2 或 Java 的 JSP/Thymeleaf。
CSS/样式 :在 uni-app 中是 <style>,支持 SCSS/LESS。
JS/逻辑 :在 uni-app 中是 <script>,处理业务逻辑。
API :uni-app 将各端(如微信小程序和浏览器)的差异封装成了统一的 uni.xxx 方法(例如 uni.request 发送 HTTP 请求)。
二、开发环境搭建 (HBuilderX)
虽然 VS Code 也能开发 uni-app,但官方推荐使用 HBuilderX 。它内置了 uni-app 的编译器、调试环境和代码提示,是目前开发 uni-app 效率最高的工具。
2.1 安装步骤
下载 :访问 DCloud 官网 下载 HBuilderX。
注意:请下载 App开发版 (内置了相关环境),不要下载标准版。
安装 :Windows 解压即可使用;Mac 拖入应用程序。
注册 DCloud 账号 :首次启动需要注册一个账号,用于后续发包和云端打包。
2.2 创建第一个项目
打开 HBuilderX。
点击菜单栏 文件 -> 新建 -> 项目。
选择 uni-app 类型。
输入项目名称(例如 hello-uni)。
模板选择 “默认模板” (适合从零开始),或者选择 “Hello uni-app” (官方示例,包含大量组件演示,推荐新手看一看)。
Vue 版本选择:推荐 Vue 3 (主流),但老项目可能是 Vue 2。
点击 创建。
2.3 运行项目
运行到浏览器 :菜单栏 运行 -> 运行到浏览器 -> Chrome。
运行到小程序 :需要先安装微信开发者工具,并在 HBuilderX 设置中配置微信开发者工具的路径。
运行到手机 :连接手机,开启 USB 调试,选择 运行到手机或模拟器。
三、项目结构介绍
uni-app 的目录结构非常规范,类似于 Spring Boot 或 Django 的约定大于配置。
3.1 目录树示意图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ┌─ components // 自定义组件目录(类似后端封装的公共类/工具类) │ └─ my-comp │ └─ my-comp.vue ├─ pages // 页面存放目录(类似于 View 层或 Controller 对应的页面) │ ├─ index │ │ └─ index.vue // index页面 │ └─ login │ └─ login.vue // 登录页面 ├─ static // 静态资源目录(图片、字体等,不参与编译) ├─ unpackage // 编译后的包存放目录(类似 target 或 build 目录,不要手动改) ├─ App.vue // 应用配置,用来配置App全局样式以及监听 应用生命周期 ├─ main.js // Vue初始化入口文件(类似 main.py 或 SpringBootApplication 类) ├─ manifest.json // 配置应用名称、appid、Logo、权限等打包信息 ├─ pages.json // 【重要】全局路由配置、导航栏、底部 TabBar 配置 └─ uni.scss // 全局 SCSS 变量文件
3.2 核心文件详解
1. pages.json (路由与外观配置)
这是 uni-app 中最重要的配置文件。它决定了应用由哪些页面组成,以及窗口的外观。
pages 数组 :注册应用中所有的页面。如果你写了一个新页面但没在这里注册,它就无法访问(类似 Django 的 urls.py)。
globalStyle :定义导航栏颜色、标题文字颜色等全局样式。
tabBar :配置底部的 Tab 切换栏(如微信底部的“首页”、“我的”)。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 { "pages" : [ { "path" : "pages/index/index" , "style" : { "navigationBarTitleText" : "首页" } } , { "path" : "pages/login/login" , "style" : { "navigationBarTitleText" : "登录" } } ] , "globalStyle" : { "navigationBarTextStyle" : "black" , "navigationBarBackgroundColor" : "#F8F8F8" } }
2. App.vue (应用入口组件)
这不是一个页面,而是应用的根组件 。
应用生命周期 :
onLaunch: 应用初始化完成时触发(全局只触发一次)。常用于检查登录状态、获取全局配置。
onShow: 应用启动,或从后台进入前台显示时触发。
onHide: 应用从前台进入后台时触发。
全局样式 :在 <style> 标签中写的样式,对所有页面生效。
3. main.js (程序入口)
初始化 Vue 实例,引入全局插件。
类似 Java 的 main 方法或 Python 的 if __name__ == '__main__':。
4. manifest.json (发布配置)
图形化配置界面。
配置 App 的名称、版本号。
配置 App 的图标(自动生成不同尺寸)。
配置微信小程序的 AppID。
配置各种 SDK(地图、支付、登录)的 Key。
5. .vue 页面文件
标准的 Vue 单文件组件结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template > <div class ="content" > <text > {{ title }}</text > <button @click ="sayHello" > 点击我</button > </div > </template > <script > export default { data ( ) { return { title : 'Hello uni-app' } }, onLoad ( ) { console .log ('页面加载了' ); }, methods : { sayHello ( ) { uni.showToast ({ title : '你好!' }) } } } </script > <style > .content { padding : 20px ; } </style >
四、后端开发者快速上手建议
关于 API 请求 :
uni-app 提供了 uni.request,用法类似 Python 的 requests 或 jQuery 的 ajax。
1 2 3 4 5 6 7 uni.request ({ url : 'http://127.0.0.1:8000/api/users' , method : 'GET' , success : (res ) => { console .log (res.data ); } });
注意:小程序开发需要配置服务器域名白名单,且必须是 HTTPS(开发环境可以在详情里勾选“不校验合法域名”)。
关于数据绑定 :
Vue 是双向绑定(MVVM)。修改 JS 中的变量 (this.title = '新标题'),界面会自动更新。不需要像操作 DOM 那样手动去改 HTML。
关于单位 :
uni-app 推荐使用 rpx (responsive pixel) 作为 CSS 单位。
规定屏幕宽为 750rpx。
在 iPhone6/7/8 上,1px = 2rpx。
使用 rpx 可以自动适配不同宽度的手机屏幕。
core: 程序的一些核心模块:比如智能体,授权,发送邮件模块等。
models: SQLAlchemy 的ORM模型
repository:操作数据库的逻辑层
routers: APIRouter 分层模型
settings : 项目配置模块 ,可以按照dev pro进行区分
dependencies: FastAPI 项目的依赖项
main: 程序入口文件
schemas: Pydantic 模型
ORM模型