解锁数据库简洁之道:FastAPI与SQLModel实战指南

news2025/6/12 21:11:26

在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可维护性可能会面临挑战。本文将引领你进入对象关系映射器(ORM) 的世界,并重点介绍FastAPI官方推荐的SQLModel,展示如何在FastAPI应用中以更高效、更Pythonic、类型更安全的方式驾驭数据库操作。

1. ORM 是什么?为何选择它?

ORM,即对象关系映射器(Object Relational Mapper),是一种巧妙的编程技术。它在你的关系型数据库(如PostgreSQL)和你使用的面向对象编程语言(如Python)之间搭建了一座桥梁,一个抽象层。简单来说,ORM允许你:

  • 用Python的类 (Class) 来代表数据库中的表 (Table)
  • 用这些类的实例 (Instance/Object) 来代表表中的行 (Row)
  • 用类实例的属性 (Attribute) 来代表行中的列 (Column) 数据。

ORM如何作为“翻译官”:

在没有ORM的情况下,你的FastAPI应用需要通过数据库驱动(如psycopg2)直接向数据库管理系统(DBMS)发送原始的SQL命令。DBMS执行这些命令后,将结果返回给应用。

引入ORM(特别是SQLModel)后,情况大为改观。你的FastAPI应用不再需要直接“说”SQL。取而代之的是,你使用Python代码和ORM提供的API(比如创建Python对象、调用对象方法)来表达你的数据库操作意图。ORM(SQLModel在底层利用了SQLAlchemy Core的功能)会接收这些Python指令,并将它们翻译成相应的SQL语句,然后通过数据库驱动发送给DBMS。

使用ORM(特别是SQLModel)的核心优势:

  1. 告别手写SQL,提升开发效率:

    • 开发者可以直接通过操作Python对象来完成增删改查(CRUD),而无需记忆复杂的SQL语法或担心表/列名的拼写错误。
    • SQLModel与Pydantic的深度集成意味着你的数据库模型同时也是数据校验模型,减少了重复定义。
  2. 增强的类型安全与编辑器支持:

    • SQLModel基于Python类型提示构建。这意味着你的IDE(如VS Code)可以为你提供强大的自动补全、类型检查和重构支持,在编码阶段就能发现潜在错误。
    • FastAPI会利用这些类型提示进行自动的数据校验和文档生成。
  3. 有效防止SQL注入:

    • SQL注入是常见的Web安全漏洞。ORM在将Python操作转换为SQL时,通常会使用参数化查询等机制,自动安全地处理用户输入,从而有效防止此类攻击。
  4. 更Pythonic的代码风格:

    • ORM使数据库操作代码更符合Python的编程习惯。查询数据、创建记录等操作变得像操作普通Python对象和列表一样自然,代码更易读、易维护。
  5. 潜在的数据库后端灵活性:

    • SQLModel底层依赖SQLAlchemy Core,这意味着它继承了SQLAlchemy对多种数据库后端的支持。理论上,如果将来需要更换数据库类型(如从PostgreSQL到MySQL或SQLite),主要修改的是数据库连接字符串,大部分业务逻辑代码可能无需大的改动。

2. SQLModel:FastAPI的现代数据层伙伴

SQLModel由FastAPI的作者Sebastián Ramírez (tiangolo) 创建,旨在提供一个与FastAPI和Pydantic无缝集成的数据库操作库。它巧妙地结合了Pydantic的数据校验能力和SQLAlchemy的数据库交互能力。

核心特性:

  • 一个模型,多种用途: SQLModel定义的类既是Pydantic模型(用于数据校验、序列化、API文档),也是SQLAlchemy模型(用于数据库表映射和操作)。
  • 基于类型提示: 充分利用Python的类型提示,提供卓越的编辑器支持和运行时数据校验。
  • 简洁易用: API设计力求简单直观,易于上手。

3. SQLModel核心配置:连接你的数据库

在FastAPI应用中集成SQLModel,首先需要配置数据库连接。

3.1 创建数据库引擎(Engine)

引擎(Engine) 是SQLModel (通过SQLAlchemy Core)与特定数据库建立连接的入口点。它负责管理数据库连接池和处理特定数据库的方言(SQL翻译规则)。

# database.py
from sqlmodel import create_engine

# 数据库连接URL格式:postgresql://用户名:密码@主机:端口/数据库名
# 确保替换为你的实际数据库凭据和名称
SQLALCHEMY_DATABASE_URL = "postgresql://[你的用户名]:[你的密码]@localhost:5432/[你的数据库名]"

engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True) # echo=True 会打印执行的SQL语句,便于调试
  • create_engine(): 根据提供的数据库URL创建引擎实例。URL中清晰地指明了数据库类型 (postgresql)、用户名、密码、主机、端口和数据库名称。
  • echo=True: 在开发时非常有用,它会让你在控制台看到SQLModel实际执行的SQL语句,帮助理解其工作方式和调试问题。生产环境中通常会关闭它。
3.2 创建数据库和表(一次性操作)

SQLModel可以根据你定义的模型类自动在数据库中创建相应的表结构。这通常在应用启动时执行一次。

# database.py (接上文)
from sqlmodel import SQLModel # 导入SQLModel基类

def create_db_and_tables():
    # SQLModel.metadata.create_all() 会检查数据库中是否存在表
    # 如果不存在,则根据定义的SQLModel模型创建它们
    SQLModel.metadata.create_all(engine)

然后在你的主应用文件 (main.py) 中,可以在应用启动时调用这个函数:

# main.py
from fastapi import FastAPI
from .database import create_db_and_tables

app = FastAPI()

@app.on_event("startup") # FastAPI应用启动时执行
def on_startup():
    create_db_and_tables()

# ... 其他应用代码 ...

重要提示: SQLModel.metadata.create_all(engine) 只会创建不存在的表。如果你的模型后续发生了更改(如添加新字段、修改字段类型),create_all 不会自动更新已存在表的结构。对于生产环境中的数据库结构变更,你需要使用数据库迁移工具,如 Alembic

3.3 数据库会话(Session)与依赖注入

会话(Session) 是执行所有数据库操作(增删改查)的“工作区”。每个数据库请求通常在它自己的会话中处理。FastAPI通过**依赖注入(Dependency Injection)**来优雅地管理会话的生命周期。

# main.py (或一个专门的 dependencies.py 文件)
from fastapi import Depends
from sqlmodel import Session # 注意这里导入的是SQLModel的Session
from .database import engine # 导入之前创建的engine

# 定义一个依赖项函数,用于获取数据库会话
def get_session(): # FastAPI 教程中常命名为 get_db
    with Session(engine) as session: # 使用SQLModel的Session,并确保引擎被传入
        yield session
        # 当请求处理完毕后,with语句会自动处理session.close()
        # 如果在会话块内发生异常,事务会自动回滚
        # 如果没有异常,且你调用了 session.commit(),事务会被提交

这个get_session函数将在你的路径操作函数中作为依赖项使用。FastAPI会在处理每个请求前调用它来获取一个新的数据库会话,并在请求结束后(无论成功或失败)确保会话被正确关闭。with Session(engine) as session: 这种用法是推荐的,它能更好地管理会话的生命周期和事务。

4. 定义SQLModel模型:Python类映射数据库表

使用SQLModel,你可以通过定义Python类来描述数据库表的结构,这些类同时也是Pydantic模型。

以一个Post(社交媒体帖子)模型为例:

# models.py
from datetime import datetime
from typing import Optional
from sqlmodel import SQLModel, Field # 导入SQLModel基类和Field
from sqlalchemy import text # 用于定义服务器端默认值

# 定义Post模型,它既是Pydantic模型,也是SQLAlchemy表模型
class Post(SQLModel, table=True): # table=True 表明这是一个数据库表模型
    __tablename__ = "posts" # 可选,如果省略,SQLModel会尝试根据类名推断表名

    # 定义字段,Field用于提供额外的数据库列信息和Pydantic校验信息
    id: Optional[int] = Field(default=None, primary_key=True, index=True) # 主键,自动生成,建立索引
    title: str = Field(index=True) # 标题,建立索引以便快速搜索
    content: str = Field(nullable=False) # nullable=False 表示该字段不能为空
    published: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")}) # 是否发布,默认True
    created_at: Optional[datetime] = Field(
        default=None, # Pydantic层面允许不传,数据库层面有默认值
        sa_column_kwargs={"server_default": text("now()")} # 数据库级别默认值为当前时间
    )

在这个模型中:

  • class Post(SQLModel, table=True): 表明Post是一个SQLModel表模型。
  • id: Optional[int] = Field(default=None, primary_key=True, index=True):
    • Optional[int]: 类型提示,ID是整数,在创建时可以不提供(由数据库生成)。
    • default=None: Pydantic层面的默认值。
    • primary_key=True: 声明此字段为主键。
    • index=True: 建议为常用于查询条件的字段创建数据库索引以提高性能。
  • title: str = Field(index=True): 普通字符串字段,也创建了索引。
  • published: bool = Field(default=True, sa_column_kwargs={"server_default": text("true")}):
    • default=True: Pydantic层面的默认值。
    • sa_column_kwargs={"server_default": text("true")}: 使用sa_column_kwargs传递SQLAlchemy特定的列参数。这里通过text("true")设置了数据库服务器端的默认值为TRUE
  • created_at: Optional[datetime] = Field(default=None, sa_column_kwargs={"server_default": text("now()")}): 创建时间,数据库服务器端默认为当前时间。

5. 使用SQLModel实现CRUD操作

现在,让我们看看如何在FastAPI路径操作中使用SQLModel和get_session依赖来实现数据的增删改查。

5.1 创建(Create)帖子

目标:posts表中插入一条新帖子。

# main.py
from fastapi import FastAPI, Response, status, HTTPException, Depends
from typing import List
from sqlmodel import Session, select # 导入SQLModel的Session和select

# ... (app, on_startup, get_session 定义如前) ...
from .models import Post # 导入Post模型

@app.post("/posts", status_code=status.HTTP_201_CREATED, response_model=Post)
def create_new_post(post_payload: Post, session: Session = Depends(get_session)):
    # post_payload 是一个已经通过Pydantic校验的Post实例
    # 注意:如果Post模型中的id是Optional且primary_key=True, default=None
    # 在创建时,我们不应该给id赋值,数据库会自动生成。
    # 如果post_payload传入了id,你可能需要:
    # db_post = Post.model_validate(post_payload) # Pydantic v2
    # 或 db_post = Post(**post_payload.dict(exclude_unset=True, exclude={'id'})) # 确保不传入id

    db_post = Post.model_validate(post_payload) # 确保从请求体创建的实例符合模型定义
                                                # 如果ID是可选的,且数据库自动生成,
                                                # 传入的post_payload不应包含ID,或者需要处理它

    session.add(db_post)  # 将新帖子对象添加到会话中,准备插入
    session.commit()     # 提交事务,将更改写入数据库
    session.refresh(db_post) # 从数据库刷新对象,获取自动生成的值 (如ID, created_at)
    return db_post
  • post_payload: Post: FastAPI会自动将请求体JSON转换为PostPydantic模型实例,并进行校验。
  • session.add(db_post): 将新创建的(或从请求体转换来的)SQLModel对象添加到当前数据库会话中,标记为待插入。
  • session.commit(): 提交当前会话中的所有更改(新增、修改、删除),这些更改将真正写入数据库。
  • session.refresh(db_post): 在提交后,如果对象有数据库自动生成或更新的字段(如自增ID、服务器端默认的时间戳),此方法会从数据库重新加载这些值到db_post对象中。
5.2 读取(Read)帖子

获取所有帖子:

# main.py (接上文)

@app.get("/posts", response_model=List[Post])
def get_all_existing_posts(session: Session = Depends(get_session)):
    statement = select(Post) # 创建一个SQLModel查询语句
    results = session.exec(statement) # 执行查询语句
    posts_list = results.all() # 获取所有结果行
    return posts_list
  • select(Post): SQLModel(以及SQLAlchemy 2.0+风格)使用select()函数来构建查询。select(Post)表示查询Post表的所有列。
  • session.exec(statement): 执行构建好的查询语句。
  • results.all(): 从执行结果中获取所有匹配的行,每行都会被转换为一个Post模型实例。

获取单个帖子:

# main.py (接上文)

@app.get("/posts/{post_id}", response_model=Post)
def get_single_post(post_id: int, session: Session = Depends(get_session)):
    # db.get(ModelClass, primary_key_value) 是获取单个对象最高效的方式
    post = session.get(Post, post_id)
    if not post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail=f"ID为 {post_id} 的帖子未找到")
    return post
  • session.get(Post, post_id): 这是SQLModel (和SQLAlchemy) 通过主键获取单个对象的推荐方式,非常简洁高效。
5.3 更新(Update)帖子

目标: 更新posts表中特定ID的帖子。

# main.py (接上文)

# 为了更新,通常我们会定义一个不包含只读字段(如id, created_at)的Pydantic模型
class PostUpdate(SQLModel): # Pydantic模型,用于更新,字段都设为Optional
    title: Optional[str] = None
    content: Optional[str] = None
    published: Optional[bool] = None

@app.put("/posts/{post_id}", response_model=Post)
def update_existing_post(
    post_id: int,
    post_update_payload: PostUpdate, # 使用专门的Update模型
    session: Session = Depends(get_session)
):
    db_post = session.get(Post, post_id) # 获取要更新的帖子对象
    if not db_post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail=f"ID为 {post_id} 的帖子未找到")

    # post_update_payload.model_dump(exclude_unset=True) 获取请求中实际传递的字段值
    # exclude_unset=True 确保只获取客户端明确设置的字段,用于部分更新(PATCH)
    # 对于PUT,客户端应该提供所有可修改字段的新值
    update_data = post_update_payload.model_dump(exclude_unset=True)

    for key, value in update_data.items():
        setattr(db_post, key, value) # 更新帖子对象的属性

    session.add(db_post) # 即使是更新,SQLModel也需要add来追踪对象变化
    session.commit()
    session.refresh(db_post)
    return db_post
  • PostUpdate模型:通常为了更新操作(尤其是部分更新/PATCH),我们会定义一个所有字段都是Optional的Pydantic模型。这样,客户端可以只发送他们想修改的字段。
  • post_update_payload.model_dump(exclude_unset=True): 这个方法非常关键。它将Pydantic模型实例转换为字典,但exclude_unset=True确保了只有那些在请求中被客户端实际设置了值的字段才会包含在字典中。
  • setattr(db_post, key, value): 遍历包含更新数据的字典,并使用setattr动态更新db_post对象的相应属性。
  • session.add(db_post): SQLModel (和SQLAlchemy) 通过将会话中的对象标记为“脏”(dirty)来追踪更改。即使是更新,也需要将修改后的对象add回会话(如果它之前已存在于会话中且被修改,它已经被追踪了,但显式add无害且有时是必要的,特别是如果对象是从会话分离后又重新附加的场景)。
  • 然后是熟悉的session.commit()session.refresh().
5.4 删除(Delete)帖子

目标: 删除posts表中特定ID的帖子。

# main.py (接上文)

@app.delete("/posts/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_existing_post(post_id: int, session: Session = Depends(get_session)):
    db_post = session.get(Post, post_id)
    if not db_post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail=f"ID为 {post_id} 的帖子未找到")

    session.delete(db_post) # 从会话中标记此对象为待删除
    session.commit()       # 提交事务,将删除操作写入数据库

    # 对于204 No Content,不应返回任何响应体
    return Response(status_code=status.HTTP_204_NO_CONTENT)
  • session.delete(db_post): 将指定的SQLModel对象标记为待删除。
  • session.commit(): 真正执行删除操作。

6. SQLModel的额外优势

  • 与Pydantic的无缝集成:由于SQLModel类本身就是Pydantic模型,你可以直接在FastAPI的路径操作函数中使用它们进行请求体验证和响应模型定义,无需创建额外的schemas.py文件(除非你有特定理由需要分离)。
  • 编辑器极致体验:得益于类型提示,VS Code等现代编辑器能提供非常棒的自动补全、错误检查和代码导航。
  • SQLAlchemy的强大后盾:虽然SQLModel提供了更简洁的API,但它底层利用了SQLAlchemy Core。这意味着当你需要更复杂或底层的数据库操作时,仍然可以利用SQLAlchemy的强大功能。

总结:拥抱SQLModel,简化FastAPI数据库开发

SQLModel为FastAPI开发者提供了一种现代、Pythonic且类型安全的方式来与数据库交互。它通过将Pydantic的数据模型能力与SQLAlchemy的数据库操作能力相结合,显著简化了从模型定义到CRUD操作的整个流程。

通过使用SQLModel,你可以:

  • 减少模板代码。
  • 提高开发效率。
  • 增强代码的可读性和可维护性。
  • 获得出色的编辑器支持和类型安全性。

对于希望以更高效、更简洁的方式构建数据驱动的FastAPI应用的开发者而言,SQLModel无疑是一个值得投入学习和使用的优秀工具。

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

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

相关文章

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…

高频面试之3Zookeeper

高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个&#xff1f;3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制&#xff08;过半机制&#xff0…

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…

CentOS下的分布式内存计算Spark环境部署

一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架&#xff0c;相比 MapReduce 具有以下核心优势&#xff1a; 内存计算&#xff1a;数据可常驻内存&#xff0c;迭代计算性能提升 10-100 倍&#xff08;文档段落&#xff1a;3-79…

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…