Contents

软件开发概念配套练习

以下是一个为期 10 周、每周末练习一个核心概念的实战任务清单。我们以 Python + FastAPI 为技术栈,从零开始构建一个“简易订单系统”,逐步把前面学到的理论点全部落地。


项目初始化(一次性准备,不占周末)

在第一个周末之前,先用最短时间搭好项目骨架:

1
2
3
4
mkdir order-system && cd order-system
python -m venv venv && source venv/bin/activate   # Windows: venv\Scripts\activate
pip install fastapi uvicorn sqlalchemy pydantic pytest httpx
git init

创建文件 main.py,写一个最简 Hello World,验证可运行:

1
2
3
4
5
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
    return {"message": "Hello"}

运行 uvicorn main:app --reload,看到页面即表示就绪。之后每个周末都在这个项目上继续。


第 1 周:RESTful API 设计

目标
用 FastAPI 实现符合 REST 规范的订单 API,体会资源、HTTP 方法、状态码、路径参数的意义。

任务

  • 设计 Order 资源模型(使用 Pydantic)。
  • 实现以下端点,并严格遵守 REST 语义:
    • POST /orders — 创建订单(返回 201 Created
    • GET /orders/{order_id} — 获取单个订单(200 / 404
    • GET /orders — 获取订单列表(可加查询参数 ?status=paid
    • DELETE /orders/{order_id} — 删除订单
  • 暂时用内存列表存储订单,不涉及数据库。

所需 FastAPI 特性
APIRouter,路径参数/查询参数,Response(status_code=...),Pydantic 请求体与响应模型。

验收标准
能用 curlhttpie 完成 CRUD,并通过返回的 HTTP 状态码区分成功/失败。


第 2 周:依赖注入(DI)

目标
借助 FastAPI 的 Depends,将服务层从路由函数中解耦,并通过 DI 灵活切换实现。

任务

  • 创建 IOrderRepository 接口(抽象基类),以及内存实现 InMemoryOrderRepository

  • 创建 OrderService 类,接收 IOrderRepository 作为构造参数。

  • 使用 DependsOrderService 注入到路由中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    def get_repo() -> IOrderRepository:
        return InMemoryOrderRepository()
    
    def get_service(repo: IOrderRepository = Depends(get_repo)) -> OrderService:
        return OrderService(repo)
    
    @router.post("/orders")
    def create(order: OrderCreate, service: OrderService = Depends(get_service)):
        ...
    
  • 额外准备一个 FakeOrderRepository(打印日志的实现),通过修改 get_repo 函数一行代码切换存储方式,体验“可插拔”。

验收标准
不修改路由函数,只改一个 Depends 工厂函数,就能让订单服务使用不同的存储实现。


第 3 周:单一职责原则(SRP) & 开闭原则(OCP)

目标
将接口层的验证、业务逻辑、数据访问彻底分离,使各模块职责单一;通过抽象扩展新功能而不改旧代码。

任务

  • 拆分 OrderService:将输入验证逻辑迁移到独立的 OrderValidator 类中。
  • Order 生成逻辑与持久化逻辑分离。
  • 引入“支付方式”概念,定义 PaymentMethod 抽象类,实现 CreditCardPaymentPayPalPayment
  • CheckoutService 中依赖 PaymentMethod 接口(通过构造注入),增加新的支付方式时只需新增类,CheckoutService 代码不变。

验收标准
每个类的职责清晰可描述(一句话说明);新增 BitcoinPayment 时,只需要增加一个文件,原 CheckoutService 不需改动。


第 4 周:设计模式(工厂、策略、观察者)

目标
用组合代替条件分支,实现运行时动态切换算法,并用观察者实现支付成功的事件通知。

任务

  • 策略模式:实现 DiscountStrategy 接口,提供 NoDiscountBlackFridayDiscountVIPDiscountOrderPricingService 通过 DI 或 Setter 接收当前策略,计算最终价格。
  • 工厂模式:创建 PaymentFactory,根据请求参数(如支付类型字符串)返回对应的 PaymentMethod 实例。工厂内部可使用简单判断或字典映射。
  • 观察者模式:当支付成功后,PaymentProcessor 调用已注册的 PaymentObserver 列表(如 StockObserver 扣库存、PointsObserver 赠积分)。观察者列表通过构造函数注入。

验收标准
能通过传入不同折扣策略获得不同价格;支付成功后控制台能看到各观察者触发信息;新增观察者只需添加一个类并注入即可。


第 5 周:单元测试与 TDD

目标
为业务逻辑编写测试,体验“先写测试、再写实现、最后重构”的 TDD 节奏。

任务

  • OrderValidator 编写单元测试(使用 pytest):
    • 空金额订单应抛出异常
    • 负数金额应抛出异常
    • 有效订单通过验证
  • TDD 实现一个新功能:结算时,若订单金额 < 0.01,不允许支付。先写失败的测试,再用最少代码通过,最后重构。
  • CheckoutService 写测试,用 unittest.mock 模拟 PaymentMethodRepository,确保不连接真实支付和数据库。
  • 使用 pytesthttpxTestClient 对 FastAPI 端点进行集成测试,注入假存储。

验收标准
pytest 覆盖核心服务逻辑;所有测试秒级运行;能通过 pytest --cov 看到覆盖率报告。


第 6 周:重构

目标
在测试的保护下,识别代码坏味道并系统性地改善内部结构。

任务

  • 检查现有代码,找出以下坏味道并修复:
    • 长函数 → 提取函数(Extract Method)
    • 参数列表过长 → 引入参数对象(如用 OrderPricingRequest 封装价格计算参数)
    • 条件表达式过多 → 用多态替换条件(支付方式、折扣策略已经是多态,可验证)
    • 重复代码 → 提取公共逻辑
  • 重构过程中持续运行 pytest,确保测试始终绿灯。
  • 使用 git 在重构前后分别打 tag(v0.1, v0.2),对比变化。

验收标准
重构前后功能完全不变,代码行数减少或结构更清晰,测试全部通过。


第 7 周:版本控制(Git)工作流

目标
模拟团队协作流程,掌握分支管理、Pull Request、提交规范。

任务

  • 在 GitHub/GitLab 创建远程仓库,推送当前代码。
  • 采用简化版 Git Flow:
    • main 创建 develop 分支。
    • 每个新功能从 developfeature/xxx,开发后合并回 develop
    • 热修复从 mainhotfix/xxx,修复后合并到 maindevelop
  • 提交信息遵守规范:feat: 添加订单备注字段, fix: 空金额验证未生效
  • feature/logging 分支上添加一个日志功能,发起 Pull Request(自己 review 自己),利用 Diff 审查改动,然后合并。
  • 使用 git rebase -i 整合历史(例如压缩多个微小提交)。

验收标准
仓库中有 maindevelop 和至少两个已合并的功能分支;提交历史清晰可读;完成一次 PR 流程。


第 8 周:CI/CD

目标
为项目配置自动化构建、测试和代码检查流水线。

任务

  • 使用 GitHub Actions(或 GitLab CI)编写 CI 配置文件(.github/workflows/ci.yml):
    • 触发条件:推送 maindevelop 或任何 PR。
    • 步骤:安装 Python,安装依赖,运行 pytest,运行 flake8ruff 代码风格检查,运行 mypy 静态类型检查(可选)。
  • 配置自动生成测试覆盖率报告,并上传到 Codecov。
  • (可选)构建 Docker 镜像,推送到 Docker Hub。
  • 提交代码,观察 GitHub Actions 自动运行并收到通过/失败反馈。

验收标准
当推送代码到 GitHub 时,自动运行测试和 lint,失败会收到通知。保证代码在合并前经过自动化验证。


第 9 周:领域驱动设计(DDD)基础

目标
用 DDD 术语重新组织订单领域,明确实体、值对象、聚合、仓储和领域服务。

任务

  • 定义值对象:Money(金额+币种,不可变)、Address(城市+街道,不可变)。
  • 定义实体:Order(有唯一 ID,生命周期状态可变,包含 MoneyAddress),OrderLine(订单行,属于订单聚合内部)。
  • 明确聚合:Order 为聚合根,外部只能通过 OrderRepository 访问订单,且不能直接修改内部行。
  • 仓储接口:IOrderRepository 定义 save(Order)find_by_id(UUID) 等方法,返回领域模型而非数据库模型。
  • 领域服务:PricingService 封装价格计算逻辑(组合各种策略),属于无状态领域服务。
  • 应用层通过 DI 获取仓储和领域服务,协调流程。

验收标准
领域模型与持久化模型分离;聚合根保证内部一致性(如添加订单行必须通过 Order.add_item,并校验金额);值对象不可变,使用 __eq__ 进行值比较。


第 10 周:数据库事务与隔离级别 + 架构模式

目标
为系统接入真实数据库,处理并发事务问题,并按整洁架构 / MVC 思想分层整理代码。

任务

  • 用 SQLAlchemy 实现 SqlOrderRepository,通过 DI 替换内存仓储。
  • 设置 SQLite(或 PostgreSQL)数据库,在 FastAPI 中使用 Database.dependency 管理会话。
  • 实现“库存扣减”逻辑:StockService.decrease 需要与订单创建在同一事务中(使用 @transactwith Session.begin())。
  • 模拟并发下单:
    • 编写一个并发测试,多线程同时抢购同一商品,验证不会超卖(可设置事务隔离级别 SERIALIZABLE 或用 SELECT ... FOR UPDATE 锁)。
    • 测试不同隔离级别下的表现,记录差异。
  • 最后重构项目目录为 Clean Architecture 风格(或 MVC):
    1
    2
    3
    4
    5
    
    app/
      api/          # 路由、依赖
      domain/       # 实体、值对象、仓储接口
      application/  # 用例/应用服务
      infrastructure/ # 数据库实现、外部服务
    
  • 确认依赖方向:apiapplicationdomaininfrastructureinfrastructure 实现 domain 接口,通过 DI 注入。

验收标准
并发测试中库存不会变成负数;换用不同隔离级别时可演示幻读或不可重复读现象(用日志/打印证明);最终项目结构分层清晰,内层不依赖外层。


总结表

周数 概念 核心实践 FastAPI/工具
1 RESTful API 设计 资源建模、HTTP 方法、状态码 FastAPI 路由、Pydantic
2 依赖注入(DI) Depends、解耦路由与服务 FastAPI Depends
3 SRP & OCP 拆分验证器、支付方式接口扩展 抽象基类
4 工厂、策略、观察者 折扣策略、支付工厂、支付事件观察者 设计模式实现
5 单元测试 & TDD 编写测试、mock、TDD 一个新功能 pytest、TestClient
6 重构 提取函数、以多态替换条件、消除重复 git tag
7 Git 工作流 分支模型、PR、rebase、提交规范 GitHub / GitLab
8 CI/CD GitHub Actions 自动测试与 lint GitHub Actions
9 DDD 基础 值对象、实体、聚合、仓储、领域服务 领域建模
10 事务/隔离级别 & 架构模式 并发扣库存、隔离级别验证、分层架构重构 SQLAlchemy、Clean Arch

参考答案

完全理解!之前那个清单看起来有点抽象,现在我们直接用 Python/FastAPI 写出来,每个练习都附带一个“参考答案”,你可以边看边改、边跑边理解。下面是 10 周的周末练习计划,每周一个核心概念,包含目标、说明和可运行的代码骨架。


第 1 周:单一职责原则(SRP)

目标:把“创建依赖 + 业务逻辑 + 发通知”混在一起的大函数拆开。

练习:重构下面的 order_router,让它只负责接收请求和调用服务。

参考答案:拆成三个独立的类。

 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
# main.py
from fastapi import FastAPI, Depends

app = FastAPI()

# ---------- 职责分离 ----------
class OrderValidator:
    def validate(self, order: dict):
        if order.get("total", 0) <= 0:
            raise ValueError("金额必须大于0")

class OrderRepository:
    def save(self, order: dict):
        print(f"订单 {order['id']} 已保存到数据库")

class EmailSender:
    def send(self, to: str, message: str):
        print(f"邮件发送至 {to}: {message}")

# ---------- 服务层(只做调度) ----------
class OrderService:
    def __init__(self, validator: OrderValidator,
                 repository: OrderRepository,
                 email_sender: EmailSender):
        self.validator = validator
        self.repository = repository
        self.email_sender = email_sender

    def process(self, order: dict):
        self.validator.validate(order)
        self.repository.save(order)
        self.email_sender.send(order["email"], "订单已创建")

# ---------- DI 容器(手动模拟) ----------
def get_order_service():
    return OrderService(OrderValidator(), OrderRepository(), EmailSender())

# ---------- 路由 ----------
@app.post("/orders")
def create_order(order: dict, service: OrderService = Depends(get_order_service)):
    service.process(order)
    return {"status": "ok"}

关键点OrderService 不再自己 new 任何东西,每个类只做一件事。


第 2 周:开闭原则(OCP)

目标:增加一种支付方式时,不改动 Checkout 类。

练习:实现 PaymentMethod 接口,并通过 DI 注入到 Checkout

参考答案

 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
from abc import ABC, abstractmethod

# ---------- 抽象接口 ----------
class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount: float) -> str:
        pass

# ---------- 具体实现 ----------
class CreditCard(PaymentMethod):
    def pay(self, amount: float):
        return f"信用卡支付 {amount} 元"

class PayPal(PaymentMethod):
    def pay(self, amount: float):
        return f"PayPal 支付 {amount} 元"

# ---------- 使用方(对修改关闭) ----------
class Checkout:
    def __init__(self, payment: PaymentMethod):
        self.payment = payment

    def execute(self, amount: float):
        return self.payment.pay(amount)

# FastAPI 示例:通过查询参数选择支付方式
from fastapi import FastAPI, Query, Depends

app = FastAPI()

def get_payment(method: str = Query("credit")):
    if method == "credit":
        return CreditCard()
    elif method == "paypal":
        return PayPal()
    else:
        raise ValueError("不支持的支付方式")

@app.get("/pay")
def pay(amount: float, checkout: Checkout = Depends(lambda: Checkout(get_payment()))):
    # 实际项目中 Depends 里应根据请求参数构建 checkout
    return {"result": checkout.execute(amount)}

当你想加比特币支付时:只需新增 BitcoinPayment 类,并在工厂里加一个分支,Checkout 一行不改。


第 3 周:依赖倒置原则(DIP) + 依赖注入

目标:高层模块 OrderService 只依赖抽象 Database 接口,不依赖具体的 MysqlDatabase

练习:将数据库访问抽象为 Database 接口,并通过构造函数注入。

参考答案

 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
from abc import ABC, abstractmethod

# ---------- 抽象(高层模块依赖它) ----------
class Database(ABC):
    @abstractmethod
    def save(self, data: dict):
        pass

# ---------- 具体实现(低层模块) ----------
class PostgresDatabase(Database):
    def save(self, data: dict):
        print(f"Postgres 保存: {data}")

class InMemoryDatabase(Database):
    def __init__(self):
        self.storage = []

    def save(self, data: dict):
        self.storage.append(data)
        print("内存保存")

# ---------- 业务逻辑 ----------
class UserService:
    def __init__(self, db: Database):
        self.db = db

    def create_user(self, name: str):
        self.db.save({"name": name})

# FastAPI 使用依赖注入选择实现
from fastapi import FastAPI, Depends

app = FastAPI()

# 切换数据库只需改这里
def get_database() -> Database:
    return InMemoryDatabase()   # 或 PostgresDatabase()

@app.post("/users")
def create_user(name: str, service: UserService = Depends(lambda: UserService(get_database()))):
    service.create_user(name)
    return {"user": name}

关键点:依赖方向反转了——UserService 不 import 具体数据库。


第 4 周:工厂模式

目标:将对象的复杂创建逻辑从业务代码里移走。

练习:创建支付方式的工厂,支持从配置文件读取类型。

参考答案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 支付工厂
class PaymentFactory:
    @staticmethod
    def create(payment_type: str) -> PaymentMethod:
        if payment_type == "credit":
            return CreditCard()
        elif payment_type == "paypal":
            return PayPal()
        else:
            raise ValueError(f"未知支付方式: {payment_type}")

# 在 FastAPI 中使用
def get_payment_from_factory(method: str = "credit"):
    return PaymentFactory.create(method)

# 路由不变,工厂负责创建

如果引入新支付方式,只需修改工厂(或将其升级为注册表模式,完全符合 OCP)。


第 5 周:策略模式

目标:动态切换折扣算法,比如“无折扣”、“黑五大促”。

参考答案

 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
from abc import ABC, abstractmethod

# ---------- 策略接口 ----------
class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, price: float) -> float:
        pass

class NoDiscount(DiscountStrategy):
    def apply(self, price):
        return price

class BlackFridayDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.5

# ---------- 上下文 ----------
class PriceCalculator:
    def __init__(self, strategy: DiscountStrategy):
        self.strategy = strategy

    def calculate(self, price: float):
        return self.strategy.apply(price)

# FastAPI 依赖注入 + 工厂
from fastapi import FastAPI, Query

app = FastAPI()

def get_discount_strategy(campaign: str = Query("none")):
    if campaign == "blackfriday":
        return BlackFridayDiscount()
    return NoDiscount()

@app.get("/price")
def get_price(original: float, calculator: PriceCalculator = Depends(
    lambda campaign=Query("none"): PriceCalculator(get_discount_strategy(campaign))
)):
    # 实际中需要配合 Depends 传参,这里为了简洁直接 Lambda
    return {"final_price": calculator.calculate(original)}

真正运行时需要改造 Depends,但策略模式的核心结构就是这样。


第 6 周:观察者模式

目标:订单支付成功后,通知库存、积分、物流系统。

参考答案

 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
from typing import List

# ---------- 观察者接口 ----------
class OrderObserver:
    def on_payment_success(self, order_id: str, amount: float):
        pass

class InventoryObserver(OrderObserver):
    def on_payment_success(self, order_id, amount):
        print(f"[库存] 扣减库存,订单 {order_id}")

class PointsObserver(OrderObserver):
    def on_payment_success(self, order_id, amount):
        print(f"[积分] 增加积分,订单 {order_id}")

# ---------- 被观察者 ----------
class PaymentProcessor:
    def __init__(self, observers: List[OrderObserver] = None):
        self.observers = observers or []

    def add_observer(self, observer: OrderObserver):
        self.observers.append(observer)

    def pay(self, order_id: str, amount: float):
        print(f"支付成功,订单 {order_id}")
        for obs in self.observers:
            obs.on_payment_success(order_id, amount)

# FastAPI 组装
from fastapi import FastAPI, Depends

app = FastAPI()

def get_payment_processor():
    processor = PaymentProcessor()
    processor.add_observer(InventoryObserver())
    processor.add_observer(PointsObserver())
    return processor

@app.post("/pay/{order_id}")
def pay(order_id: str, amount: float,
        processor: PaymentProcessor = Depends(get_payment_processor)):
    processor.pay(order_id, amount)
    return {"status": "paid"}

新增观察者只需加一个类,并在组装函数里添加,不影响 PaymentProcessor


第 7 周:TDD 与单元测试

目标:为折扣计算写测试,体验“先写测试,再写代码”。

参考答案:使用 pytest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# test_price_calculator.py
import pytest
from main import PriceCalculator, NoDiscount, BlackFridayDiscount

def test_no_discount():
    calc = PriceCalculator(NoDiscount())
    assert calc.calculate(100) == 100

def test_black_friday():
    calc = PriceCalculator(BlackFridayDiscount())
    assert calc.calculate(100) == 50

运行:pytest test_price_calculator.py

TDD 流程

  1. 先写上面的测试(此时可能对应的类还没写,测试失败)
  2. 再创建 NoDiscountBlackFridayDiscount 使测试通过
  3. 重构代码,保持测试绿色

第 8 周:DDD 基础 – 实体与值对象

目标:用实体(具有唯一标识)和值对象(不可变)建模订单。

参考答案

 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
from dataclasses import dataclass
from uuid import uuid4

# ---------- 值对象(不可变) ----------
@dataclass(frozen=True)
class Address:
    street: str
    city: str

@dataclass(frozen=True)
class Money:
    amount: float
    currency: str = "CNY"

# ---------- 实体(有 ID,可变) ----------
class Order:
    def __init__(self, customer_name: str, address: Address):
        self.id = str(uuid4())
        self.customer_name = customer_name
        self.shipping_address = address
        self.items = []
        self.status = "NEW"

    def add_item(self, product: str, price: Money):
        self.items.append({"product": product, "price": price})

    def total(self) -> Money:
        total_amount = sum(item["price"].amount for item in self.items)
        return Money(total_amount)

# ---------- 仓储接口(领域层定义) ----------
class OrderRepository(ABC):
    @abstractmethod
    def save(self, order: Order):
        pass

# ---------- 基础设施实现 ----------
class InMemoryOrderRepository(OrderRepository):
    def __init__(self):
        self._store = {}

    def save(self, order: Order):
        self._store[order.id] = order

FastAPI 里可以通过 DI 注入 OrderRepository,完成聚合的持久化。


第 9 周:RESTful API 设计

目标:设计一组符合 REST 规范的订单 API。

参考答案:完整的 CRUD。

 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
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel

app = FastAPI()

# ---------- 模型 ----------
class OrderCreate(BaseModel):
    customer_name: str
    amount: float

# ---------- 模拟数据库 ----------
orders_db = {}

# ---------- 路由 ----------
@app.post("/orders", status_code=201)
def create_order(order: OrderCreate):
    order_id = str(uuid4())
    orders_db[order_id] = order.dict()
    return {"id": order_id, **orders_db[order_id]}

@app.get("/orders/{order_id}")
def get_order(order_id: str):
    if order_id not in orders_db:
        raise HTTPException(status_code=404, detail="未找到")
    return orders_db[order_id]

@app.put("/orders/{order_id}")
def update_order(order_id: str, order: OrderCreate):
    if order_id not in orders_db:
        raise HTTPException(status_code=404, detail="未找到")
    orders_db[order_id] = order.dict()
    return {"id": order_id, **orders_db[order_id]}

@app.delete("/orders/{order_id}", status_code=204)
def delete_order(order_id: str):
    if order_id in orders_db:
        del orders_db[order_id]
    return

使用合适的 HTTP 方法、状态码、名词资源。


第 10 周:数据库事务与隔离级别

目标:模拟转账操作,使用事务保证数据一致性。

参考答案:使用 sqlite3FastAPI 的路由。

 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
import sqlite3
from fastapi import FastAPI, HTTPException

app = FastAPI()

def get_connection():
    conn = sqlite3.connect("bank.db")
    conn.execute("PRAGMA journal_mode=WAL")
    return conn

@app.post("/transfer")
def transfer(from_acc: str, to_acc: str, amount: float):
    conn = get_connection()
    try:
        conn.execute("BEGIN")
        # 检查余额
        cur = conn.execute("SELECT balance FROM accounts WHERE name=?", (from_acc,))
        row = cur.fetchone()
        if not row or row[0] < amount:
            raise ValueError("余额不足")
        # 扣减
        conn.execute("UPDATE accounts SET balance = balance - ? WHERE name=?", (amount, from_acc))
        # 增加
        conn.execute("UPDATE accounts SET balance = balance + ? WHERE name=?", (amount, to_acc))
        conn.commit()
        return {"status": "success"}
    except Exception as e:
        conn.rollback()
        raise HTTPException(status_code=400, detail=str(e))
    finally:
        conn.close()

测试隔离级别:可以开启两个终端,用 sqlite3 命令行并发操作,观察 repeatable read 行为。