AI Agent Python 学习路线
首页
  • Month 1 概览
  • Week 1 · Python 语法速成
  • Week 2-3 · FastAPI 实战
  • 进阶 · FastAPI 业务架构详解
  • 实战 · 会员管理系统(商业级标准)
  • Week 4 · AI / LLM 基础
  • Month 2 概览
  • Week 5-6 · LangChain / LangGraph
  • Week 7-8 · RAG 检索增强生成
  • Month 3 概览
  • Week 9-10 · 架构设计
  • Week 11 · 实现 + 调试
  • Week 12 · 部署 + 复盘
  • 基建 · 选型指南(SQLite/PG/MySQL/Redis/Nginx)
📚 资源 & 避坑
首页
  • Month 1 概览
  • Week 1 · Python 语法速成
  • Week 2-3 · FastAPI 实战
  • 进阶 · FastAPI 业务架构详解
  • 实战 · 会员管理系统(商业级标准)
  • Week 4 · AI / LLM 基础
  • Month 2 概览
  • Week 5-6 · LangChain / LangGraph
  • Week 7-8 · RAG 检索增强生成
  • Month 3 概览
  • Week 9-10 · 架构设计
  • Week 11 · 实现 + 调试
  • Week 12 · 部署 + 复盘
  • 基建 · 选型指南(SQLite/PG/MySQL/Redis/Nginx)
📚 资源 & 避坑

实战项目:会员管理系统(商业级标准)

🎯 项目定位:用 商业项目的工程标准 实现一个会员管理后台(单体架构),把 FastAPI 业务架构详解 的分层真正落地。

"商业标准"≠ 多租户/微服务,而是:数据库有迁移、API 有版本、操作有审计、并发有保护、测试有覆盖、部署有 CI/CD。这些是学习 demo 和能上生产系统的真正差距,也是面试时最能体现工程素养的部分。


1. 学习版 vs 商业版:差距清单

先看清我们要补什么(✅ = 本项目实现):

维度学习 demo商业标准本项目
表结构变更手动删库重建Alembic 迁移脚本,可升级可回滚✅
API 契约想到就改版本化 /api/v1 + OpenAPI 规范 + 统一响应信封✅
并发安全没想过事务 + 行锁防超卖/重复扣款✅
操作审计无审计日志表:谁在什么时间改了什么✅
认证一个 access token双 token(access 15min + refresh 7d)+ 注销黑名单✅
配置管理.env 塞所有分环境配置 + secrets 不进 git + 启动时校验✅
日志print结构化日志 + request_id 链路追踪✅
测试想到才测测试金字塔 + coverage ≥ 80% 核心业务✅
CI/CD本地跑跑GitHub Actions:lint → type check → test → Docker 构建✅
部署裸跑 uvicorn多阶段 Docker 构建 + 环境分离✅
代码规范随手写ruff + mypy + pre-commit 强制执行✅
多租户—租户隔离、订阅计费演进路线(见 §9)

2. 功能范围(单租户形态)

模块功能商业级要求点
认证授权登录、刷新 token、注销、RBAC(superadmin/operator/viewer)双 token、bcrypt、登录失败限流
会员管理CRUD、搜索分页、冻结/注销软删除、注销保留期、手机号唯一
套餐管理套餐 CRUD、上下架价格用分存储(整数),禁用 float
订单/会员卡购卡、续卡、退卡、流水幂等键、事务+行锁、金额不可变
到期提醒查询 N 天内到期会员为定时任务/Agent 预留接口
审计日志所有写操作自动记录中间件级自动采集,业务代码零侵入

不做:多租户、前端页面(Swagger UI 即界面)、真实支付、消息通知。


3. 数据库设计(商业版差异)

3.1 ER 图

erDiagram
    ADMIN ||--o{ ORDER : operates
    ADMIN ||--o{ AUDIT_LOG : "writes 写入"
    MEMBER ||--o{ ORDER : has
    PLAN ||--o{ ORDER : referenced_by
    ORDER ||--o| REFUND : "may_have 退款"
    MEMBER ||--o| MEMBER_CARD : holds

    ADMIN {
        int id PK
        string username UK
        string password_hash "bcrypt"
        string role "superadmin/operator/viewer"
        bool is_active "禁用后token立即失效"
        datetime created_at
        datetime last_login_at
    }
    MEMBER {
        int id PK
        string name
        string phone UK "唯一索引"
        string status "active/frozen/deleted"
        datetime deleted_at "软删除时间"
        datetime created_at
    }
    PLAN {
        int id PK
        string name
        int duration_days
        int price_cents "★价格用分!整数"
        string description
        bool is_active
        datetime created_at
    }
    ORDER {
        int id PK
        string order_no UK "业务单号 O+雪花/时间戳"
        string idempotency_key UK "★幂等键"
        int member_id FK
        int plan_id FK
        int admin_id FK
        int amount_cents "实付(分)"
        string type "purchase/renew/refund"
        datetime created_at
    }
    MEMBER_CARD {
        int id PK
        int member_id FK "UK 一人一卡"
        datetime expire_at
        int version "★乐观锁版本号"
        datetime updated_at
    }
    AUDIT_LOG {
        int id PK
        int admin_id FK
        string action "member.update / order.create..."
        string resource_type
        int resource_id
        jsonb changes "before/after 快照"
        string request_id "★链路追踪ID"
        string ip
        datetime created_at
    }

3.2 商业级设计决策(每条都是面试题)

决策学习版做法商业版做法为什么
金额float priceint price_cents(分)float 精度丢失:0.1+0.2≠0.3,财务对账会出人命。TS 里你见过 0.1+0.2===0.30000000000000004
表结构变更直接改 db.py + 删库Alembic 迁移链,每个变更可回滚生产库不能删,必须 alembic upgrade head 增量演进
并发购卡无保护事务 + SELECT ... FOR UPDATE 行锁(或 version 乐观锁)两个操作员同时给同一会员续卡,到期时间会互相覆盖
重复提交无保护客户端传 Idempotency-Key 头,服务端唯一约束网络超时重试 → 重复扣款,是真实商业事故 Top3
业务单号数据库自增 id 对外暴露order_no 独立业务单号(日期+序列)自增 id 泄露经营数据(竞对遍历你的订单量)
审计无audit_log 表 + changes JSON 存 before/after商业系统必须回答"这数据谁改的"
时间datetime.now() 本地时间全库 UTC,展示层转时区服务器时区不同导致订单时间错乱
约束下沉只靠 Pydantic 校验DB 层加 唯一索引/外键/CHECK 约束应用层校验可被绕过(并发窗口、其他写入方)

3.3 Alembic 迁移工作流

# 初始化(一次性)
.venv\Scripts\python -m alembic init alembic

# 模型变更后的标准流程
.venv\Scripts\python -m alembic revision --autogenerate -m "add audit_log table"
.venv\Scripts\python -m alembic upgrade head        # 应用
.venv\Scripts\python -m alembic downgrade -1        # 回滚一步(演练过才算会)

💡 商业铁律

models/db.py 的每次修改,必须配套一个迁移脚本才能合并。直接改生产库结构 = 事故。


4. API 设计规范

4.1 版本化 + 统一响应信封

所有路由挂 /api/v1。商业接口永远留版本逃生门:契约变了就发 /api/v2,老客户端不炸。

统一响应信封(相当于 TS 的 Result<T> 包装):

// 成功
{ "code": 0, "message": "ok", "data": { "items": [...], "total": 42 } }
// 失败 —— code 是业务错误码,不是 HTTP 状态码
{ "code": "ORDER_MEMBER_FROZEN", "message": "会员已冻结,无法购卡", "request_id": "req-8f3a" }

4.2 错误码表(docs/error-codes.md 与代码同源)

错误码HTTP含义
AUTH_INVALID_CREDENTIALS401用户名或密码错误(不区分哪个错,防撞库)
AUTH_TOKEN_EXPIRED401access token 过期,用 refresh 换新
AUTH_FORBIDDEN403角色权限不足
MEMBER_PHONE_DUP409手机号已注册
MEMBER_INVALID400会员不存在/已冻结/已注销
ORDER_IDEMPOTENT_REPLAY200幂等重放,返回原订单(不是错误!)
ORDER_CARD_CONFLICT409并发冲突,请重试

4.3 幂等设计(商业系统核心课)

# app/services/order_service.py — 幂等 + 行锁,本项目最值钱的 20 行
from sqlalchemy import select
from app.core.exceptions import BusinessError

async def purchase(self, req: OrderCreate, idempotency_key: str | None) -> OrderOut:
    async with self.db.begin():                       # 1. 开启事务
        # 2. 幂等检查:同 key 直接返回原订单(重试安全)
        if idempotency_key:
            existing = await self.orders.get_by_idem_key(idempotency_key)
            if existing:
                return existing                        # ORDER_IDEMPOTENT_REPLAY

        # 3. 行锁:锁定会员卡行,杜绝并发续卡互相覆盖
        card = await self.db.execute(
            select(MemberCard).where(MemberCard.member_id == req.member_id)
            .with_for_update()                         # ★ SELECT ... FOR UPDATE
        )
        ...
        # 4. 事务提交时全部生效;中途任何一步抛错 → 全部回滚

💡 TS 对照

你在大厂前端做过防重复点击(按钮 loading + disable)——但那只能防手抖,防不了网络重试和并发。商业系统的幂等必须在服务端做,前端 loading 只是体验优化。

4.4 审计日志:零侵入采集

# app/core/audit.py — 中间件级审计,业务代码不写一行审计代码
from app.dao.audit_dao import AuditDao
from app.core.context import current_admin, current_request_id

class AuditMiddleware(BaseHTTPMiddleware):
    WRITE_METHODS = {"POST", "PUT", "PATCH", "DELETE"}

    async def dispatch(self, request, call_next):
        response = await call_next(request)
        if request.method in self.WRITE_METHODS and response.status_code < 400:
            admin = current_admin.get(None)
            if admin:
                await AuditDao.log(
                    admin_id=admin.id,
                    action=f"{request.path_params}.{request.method}",
                    request_id=current_request_id.get(),       # 关联日志链路
                    ip=request.client.host,
                    changes=getattr(request.state, "audit_changes", None),
                )
        return response

💡 配合 request_id 中间件:每个请求生成唯一 ID,塞进所有日志和审计记录。线上排查问题就是靠它把散落的日志串成一条线(相当于前端的 traceId)。


5. 认证:双 Token + 注销黑名单

学习版一个 access token 用 7 天 = 泄露了就完蛋。商业标准:

Token有效期存哪用途
access15 分钟内存(前端)调业务接口
refresh7 天HttpOnly Cookie / 安全存储只干一件事:换新 access
# app/routes/auth.py
@router.post("/refresh")
async def refresh(refresh_token: str):
    payload = decode_token(refresh_token)
    if await is_blacklisted(refresh_token):          # 已注销的 refresh 不可再用
        raise BusinessError("AUTH_TOKEN_EXPIRED", http_status=401)
    return {"access_token": create_access_token(int(payload["sub"]), payload["role"])}

@router.post("/logout")
async def logout(refresh_token: str, admin = Depends(get_current_admin)):
    await add_to_blacklist(refresh_token, ttl=7 * 24 * 3600)  # 剩余有效期
    return {"code": 0, "message": "ok"}

安全清单(上线前逐项自查):

  • [ ] 密码 bcrypt(cost ≥ 12),登录错误信息不区分用户名/密码
  • [ ] 登录接口限流:同 IP 5 次/分钟(slowapi 库)
  • [ ] JWT secret ≥ 32 位随机串,来自环境变量,绝不进 git
  • [ ] 管理员禁用(is_active=False)后,已签发 access token 立即失效(依赖里查库校验)
  • [ ] 全站 HTTPS;CORS 白名单精确到 origin,不用 *
  • [ ] Swagger 文档在生产环境关闭(docs_url=None)

6. 工程化基建

6.1 目录结构(商业版增量用 ★ 标注)

member-system/
├── app/
│   ├── main.py                  # 入口组装
│   ├── config.py                # ★ 分环境配置 + 启动时必填项校验
│   ├── models/
│   │   ├── schemas.py
│   │   └── db.py
│   ├── routes/                  # /api/v1/*  薄路由层
│   ├── services/                # 业务规则(幂等、行锁、退卡规则)
│   ├── dao/                     # 数据访问
│   ├── core/
│   │   ├── security.py          # 双 token、bcrypt
│   │   ├── exceptions.py        # BusinessError + 错误码表
│   │   ├── audit.py             # ★ 审计中间件
│   │   ├── context.py           # ★ request_id / current_admin 上下文
│   │   └── logging.py           # ★ 结构化日志(JSON 格式)
│   └── utils/
├── alembic/                     # ★ 数据库迁移
│   └── versions/                #   迁移脚本链
├── tests/
│   ├── unit/                    # ★ 单元测试(services,Mock dao,快)
│   ├── integration/             # ★ 集成测试(真实 SQLite,走 HTTP 层)
│   └── conftest.py              # fixture:测试库、登录态
├── scripts/
│   └── init_superadmin.py       # 初始化超管(密码走环境变量)
├── deploy/
│   ├── Dockerfile               # ★ 多阶段构建
│   └── docker-compose.yml       # ★ 本地一键起 app + 库
├── .github/workflows/ci.yml     # ★ CI 流水线
├── .pre-commit-config.yaml      # ★ 提交前钩子
├── docs/
│   └── error-codes.md           # 错误码表(与代码同源)
├── pyproject.toml               # ★ 含 dev 依赖组:pytest/ruff/mypy/coverage
└── .env.example                 # 环境变量模板(.env 本身 gitignore)

6.2 代码规范强制执行

# .pre-commit-config.yaml — git commit 时自动跑,不合规提交不上去
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.4.0
    hooks:
      - id: ruff         # lint + import 排序
      - id: ruff-format  # 格式化(相当于 prettier)
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.10.0
    hooks:
      - id: mypy         # 静态类型检查(相当于 tsc --noEmit)

💡 TS 对照:ruff ≈ eslint + prettier,mypy ≈ typescript 编译检查,pre-commit ≈ husky + lint-staged。Python 圈 2024 年后的事实标准就是 ruff(一个工具顶过去十几个)。

6.3 配置管理:启动即校验

# app/config.py — 缺配置直接启动失败,而不是跑到一半才炸
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    env: Literal["dev", "staging", "prod"]
    jwt_secret: str
    jwt_expire_hours: int = 15 / 60 + 1     # prod 用分钟级
    database_url: str = "sqlite+aiosqlite:///./data/member.db"
    docs_enabled: bool = True               # prod 里覆盖为 False

    @model_validator(mode="after")
    def check_prod(self):
        if self.env == "prod":
            assert len(self.jwt_secret) >= 32, "prod 环境 jwt_secret 必须 ≥32 位"
        return self

settings = Settings()   # env=prod 且配置缺失 → 进程启动即报错退出

6.4 CI/CD(GitHub Actions)

# .github/workflows/ci.yml — 每次 push 自动执行
name: CI
on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install -e ".[dev]"
      - run: ruff check app tests          # ① lint
      - run: mypy app                      # ② 类型检查
      - run: pytest --cov=app --cov-fail-under=80   # ③ 测试+覆盖率门槛
  docker:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -f deploy/Dockerfile -t member-system:${{ github.sha }} .

6.5 测试金字塔

层级测什么依赖数量目标
单元(多)services 业务规则:幂等重放、冻结会员拒购、退卡折算Mock 全部 dao核心规则 100% 覆盖
集成(中)路由 → service → 真库:401/403/409 状态码、分页契约真实 SQLite + TestClient每个接口正反用例
并发(少而精)两协程同时购同一会员卡 → 断言无覆盖真 SQLite + asyncio.gather1-2 个关键用例
# tests/integration/test_concurrent_purchase.py — 并发测试,商业级必须有
async def test_concurrent_renew_no_overwrite(client, member_with_card):
    async def renew(key):
        return await client.post("/api/v1/orders",
            json={"member_id": 1, "plan_id": 1},
            headers={"Authorization": "...", "Idempotency-Key": key})

    r1, r2 = await asyncio.gather(renew("k1"), renew("k2"))
    card = await get_card(1)
    # 两次续费都成功,到期时间 = 原到期 + 2 × 时长(而不是互相覆盖)
    assert card.expire_at == expected_base + timedelta(days=plan_days * 2)

6.6 部署:多阶段 Docker

# deploy/Dockerfile — 多阶段:builder 装依赖,runtime 只带产物(镜像小 60%+)
FROM python:3.12-slim AS builder
WORKDIR /build
COPY pyproject.toml ./
RUN pip install --prefix=/install .

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY app ./app
COPY alembic ./alembic
COPY alembic.ini .
RUN useradd -r appuser && chown -R appuser /app   # ★ 不用 root 跑容器
USER appuser
CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"]

💡 注意 CMD:先跑迁移再起服务——商业部署的标准动作,保证库结构永远和代码版本匹配。


7. 分阶段开发计划(商业版,建议 3-4 周)

阶段内容验收标准(不达标不进下一阶段)
P1 地基目录骨架、ruff/mypy/pre-commit 跑通、config 分环境、Alembic 初始迁移、init_superadmin 脚本pre-commit run --all-files 全绿;空库 → alembic upgrade head 建表成功
P2 认证双 token、注销黑名单、RBAC、登录限流access 过期后 refresh 可换新;viewer 删会员 → 403
P3 会员+套餐CRUD、分页搜索、软删除、错误码表、审计中间件任何写操作在 audit_log 有记录(含 request_id)
P4 订单(难点)幂等键、事务+行锁、退卡折算、并发测试并发双购卡测试通过;重放同 key 返回原订单不重复扣
P5 质量收尾coverage ≥ 80%、CI 流水线、Dockerfile、READMEGitHub Actions 全绿;docker compose up 一键可跑

每个阶段 = 一个 PR + conventional commit(feat(auth): add refresh token rotation),给自己做 code review 的习惯从现在养起。


8. 验收清单(商业级)

架构

  • [ ] routes 无业务、services 无 SQL、dao 无 HTTP 概念
  • [ ] 表结构变更全部走 Alembic,可 upgrade 也可 downgrade

API

  • [ ] 所有接口挂 /api/v1,统一响应信封 + 错误码表
  • [ ] 购卡接口支持 Idempotency-Key,重放返回原订单

安全

  • [ ] 双 token + 注销黑名单;管理员禁用后 token 即刻失效
  • [ ] bcrypt + 登录限流 + prod 关 Swagger + secret 不进 git
  • [ ] 审计日志记录所有写操作(谁、何时、改了什么、request_id)

质量

  • [ ] ruff / mypy / pre-commit 全绿
  • [ ] 核心业务单测 + 接口集成测试 + 并发测试,coverage ≥ 80%
  • [ ] GitHub Actions:lint → mypy → pytest(带门槛) → docker build 全通过

部署

  • [ ] 多阶段 Docker 构建,非 root 运行,启动自动跑迁移
  • [ ] dev / prod 环境分离,配置启动时校验

9. 演进路线(商业标准之上:SaaS 化预演)

按商业标准建好地基后,SaaS 化是增量重构而非推倒重来:

步骤改动依赖的地基
① 租户表 + 全表加 tenant_idAlembic 迁移 + dao 层自动注入租户过滤迁移链(P1)
② JWT 加 tenant_id,依赖统一校验归属只改 deps.py + security.py依赖注入链(P2)
③ 套餐→订阅:SUBSCRIPTION 表 + 计费周期任务纯增量,动 services 层错误码/幂等(P4)
④ 支付网关接入新增 services/payment/,订单状态机金额用分、流水不可变(P4)
⑤ SQLite → PostgreSQL只改连接串 + 行锁语义验证dao 隔离(P1)

💡 和 Month 3 的呼应:Agent 的 Tool 直接复用本项目的 OrderService(带幂等和行锁的版本)。商业级的地基让 Agent 调用下单时天然防重复扣款——这就是"按商业标准学习"复利最大的一笔投资。