【FDE系列】阶段2:Day 49:OpenAPI 文档与接口测试

📚前言

📒FDE系列内容总纲:

【大纲】FDE 前沿部署工程师学习系列教程-CSDN博客

🚄前置课程列表:

见文档结尾附录。


🚀阶段2·Day 49:OpenAPI 文档与接口测试

FDE 学习系列教程 · 第二阶段 · 第 6 周 · Day 4 预计时长:3 小时 | 难度:★★★☆☆ | 前置知识:Day 46 RESTful 与认证、Pydantic 模型、pytest 基础
📌 一句话目标:学会用 FastAPI/Pydantic 写出"自带说明"的接口文档(OpenAPI/Swagger UI),并用 pytest + TestClient 给 API 编写覆盖正常与异常路径的自动化测试。


🧑‍🤝‍🧑 开场:代码写完了,怎么让别人会用、怎么保证没改坏

经过前三天,你的系统已经能:对外部提供 RESTful 接口(还带认证)、主动调飞书、接收 Webhook 事件。现在要解决两个交付层面的现实问题。

问题一:别人怎么知道你有哪些接口、参数怎么传?

没有文档的项目,对接就是灾难现场:

复制代码
  同事:你这个创建工单的接口,URL 是啥?要传哪些字段?
  你:呃...... /api/ticket?字段我想想...... title、content 还有个啥来着,我看下代码
  同事:返回 422 是啥意思?
  你:我翻翻......

更糟的是手写文档 :你今天写了一份 Word/Markdown,下周改了接口忘了同步,文档立刻变成"骗人的东西"。FastAPI 的杀手锏是------文档由代码自动生成,永远不会过期

问题二:改了代码,怎么确定没把旧功能改坏?

现在你只有 5 个接口,手动点一遍还行。等项目有 30 个接口、对接 3 个平台,每次上线都手测一遍是不可能的。自动化测试让你改完代码跑一条命令,几秒钟就知道有没有"按下葫芦浮起瓢"。

复制代码
今天两件事,正好对应"可交付"和"可维护":

  ① OpenAPI 文档 ──► 让接口会说话,别人(和未来的你)看得懂、点一下就能试
  ② pytest 测试 ──► 给接口装上安全网,改代码后一条命令验证有没有改坏

📖 一、认识 FastAPI 自动文档

FastAPI 底层基于一个叫 OpenAPI 的开放规范------它用一份 JSON 把"你有哪些接口、每个接口要什么参数、返回什么结构"完整描述出来。然后有两种网页把这份 JSON 渲染成人类友好的界面:

复制代码
  你的 FastAPI 代码
       │(框架自动扫描路由、Pydantic 模型)
       ▼
  openapi.json(机器可读的接口描述)
       │
       ├──► /docs      Swagger UI:可交互,能直接在网页上发请求试接口 ⭐
       │
       └──► /redoc     ReDoc:排版漂亮,适合阅读/对外展示
地址 名称 特点 用途
/docs Swagger UI 可交互、能在线调试、有 "Try it out" 开发调试、内部联调最常用
/redoc ReDoc 三栏排版、只读、更美观 给客户/非技术方阅读
/openapi.json 原始规范 纯 JSON 导入 Postman/Apifox、生成客户端 SDK

💡 这不是 FastAPI 独有的魔法,而是行业标准(OpenAPI 规范,前身叫 Swagger)。Java 的 SpringDoc、Go 的 swag 都产出同一份规范,所以今天学的概念在任何技术栈都通用。


🖥️ 二、写一个"文档丰富"的工单 API

先建一个待完善的 API,再一步步看文档是怎么自动长出来的。新建 app_docs.py

复制代码
"""
带完整 OpenAPI 文档元信息的工单 API
运行:uvicorn app_docs:app --reload
访问:http://127.0.0.1:8000/docs
"""
from fastapi import FastAPI, HTTPException, Header
from pydantic import BaseModel, Field
from typing import Literal
from datetime import datetime

app = FastAPI(
    title="FDE 设备工单系统 API",
    description=(
        "面向工厂数字化工程师(FDE)的设备工单管理服务。\n\n"
        "- 支持工单的创建、查询、状态流转\n"
        "- 使用 API Key 鉴权(请求头 X-API-Key)\n"
        "- 告警工单可由飞书 Webhook 自动创建(见 /webhook)"
    ),
    version="1.2.0",
    contact={"name": "FDE 平台组", "email": "fde@example.com"},
    license_info={"name": "内部使用"},
)

API_KEY = "fde-secret-2026"
tickets: list[dict] = []


# ---------- 请求模型 ----------
class TicketCreate(BaseModel):
    title: str = Field(
        ..., min_length=2, max_length=50,
        description="工单标题,简明描述问题,如:注塑机A3温度超标",
        examples=["注塑机A3 温度超标"],
    )
    device_id: str = Field(
        ..., pattern=r"^[A-Z]+\d+$",
        description="设备编号,格式为 字母+数字,如 A3、B12",
        examples=["A3"],
    )
    priority: Literal["low", "medium", "high"] = Field(
        "medium",
        description="优先级,默认 medium",
    )
    description: str | None = Field(
        None, max_length=500,
        description="详细描述,选填",
    )


# ---------- 响应模型 ----------
class Ticket(BaseModel):
    id: int = Field(..., description="工单自增 ID")
    title: str = Field(..., description="工单标题")
    device_id: str = Field(..., description="设备编号")
    priority: str = Field(..., description="优先级")
    status: Literal["open", "processing", "closed"] = Field(
        ..., description="工单状态")
    created_at: datetime = Field(..., description="创建时间 ISO 格式")


class ErrorResponse(BaseModel):
    detail: str = Field(..., description="错误说明")


def _auth(x_api_key: str | None):
    if x_api_key != API_KEY:
        raise HTTPException(status_code=401, detail="无效或缺失的 API Key")


# ---------- 接口 ----------
@app.post(
    "/api/tickets",
    response_model=Ticket,
    status_code=201,
    tags=["工单管理"],
    summary="创建工单",
    response_description="创建成功,返回完整工单对象",
    responses={
        401: {"model": ErrorResponse, "description": "API Key 无效"},
        422: {"description": "请求参数校验失败"},
    },
)
def create_ticket(payload: TicketCreate, x_api_key: str | None = Header(None)):
    """创建一张设备工单。

    ## 业务规则
    - 标题 2~50 字,设备编号必须符合 **字母+数字** 格式
    - 优先级 high 的工单会进入值班群加急通道
    - 创建后初始状态为 `open`

    ## 示例场景
    车间巡检发现注塑机温度超标时调用本接口,随后可通过
    `PATCH /api/tickets/{id}` 推进状态。
    """
    _auth(x_api_key)
    ticket = {
        "id": len(tickets) + 1,
        "title": payload.title,
        "device_id": payload.device_id,
        "priority": payload.priority,
        "status": "open",
        "created_at": datetime.now(),
    }
    tickets.append(ticket)
    return ticket


@app.get(
    "/api/tickets",
    response_model=list[Ticket],
    tags=["工单管理"],
    summary="查询工单列表",
)
def list_tickets(
    x_api_key: str | None = Header(None),
    status: Literal["open", "processing", "closed"] | None = None,
):
    """查询工单,可按 `status` 过滤;不传则返回全部。"""
    _auth(x_api_key)
    if status:
        return [t for t in tickets if t["status"] == status]
    return tickets


@app.patch(
    "/api/tickets/{ticket_id}",
    response_model=Ticket,
    tags=["工单管理"],
    summary="更新工单状态",
    responses={404: {"model": ErrorResponse, "description": "工单不存在"}},
)
def update_ticket(
    ticket_id: int,
    status: Literal["open", "processing", "closed"],
    x_api_key: str | None = Header(None),
):
    """推进工单状态,例如 open → processing → closed。"""
    _auth(x_api_key)
    for t in tickets:
        if t["id"] == ticket_id:
            t["status"] = status
            return t
    raise HTTPException(404, f"工单 {ticket_id} 不存在")

启动后打开 High Performance Web Crawler API - Swagger UI ,你会看到:

  • 顶部是标题、版本、说明(来自 FastAPI(...) 的元信息)

  • "工单管理" 标签把接口分组(来自 tags

  • 每个接口可点开,看到参数说明、示例值、响应结构

  • 点右上角 Authorize 或直接在接口里填 X-API-Key 请求头,再点 Try it out → Execute,能直接在网页上发真实请求!


📖 三、让文档变好的关键写法

对比"裸奔接口"和"完善接口",文档质量由这些要素决定:

写法 作用 出现在文档哪里
FastAPI(title=..., version=...) 项目级标题/版本/说明 文档顶部
tags=["工单管理"] 接口分组归类 按标签折叠分组
summary= 一句话接口名 接口标题栏
函数 docstring 详细说明(支持 Markdown 标题/列表) 接口详情的 Description
Field(..., description=, examples=) 字段含义、示例、校验规则 请求体/响应体每个字段旁
Literal["low","medium"] 枚举可选值 文档显示下拉可选,非法值 422
response_model=Ticket 明确返回结构 Responses 区展示返回 JSON 结构
responses={401:..., 404:...} 补充错误响应说明 文档列出各状态码及含义
status_code=201 默认成功状态码 文档标注
复制代码
  文档质量阶梯:

  裸奔:@app.post("/api/tickets")      → 文档只有个路径,参数靠猜
   │
  加模型:payload: TicketCreate         → 自动有了字段结构和类型
   │
  加 Field:description + examples      → 每个字段有中文解释和示例 ⭐
   │
  加 tags/summary/responses             → 分组清晰、错误码齐全,可对外交付

📌 核心理念:文档即代码(Docs as Code) 。你不是"另外写一份文档",而是在定义模型和接口时顺手把描述、示例、校验写进去,文档自动生成、随代码更新。这彻底解决了"文档和代码不一致"的老问题。

导出规范 & 导入 Postman/Apifox

  1. 浏览器访问 http://127.0.0.1:8000/openapi.json,保存为 JSON 文件

  2. 在 Postman:Import → 选择该文件 → 自动生成整个接口集合

  3. 在 Apifox:导入数据 → OpenAPI/Swagger → 选文件,还能一键生成 Mock 服务

团队对接时,把这份 JSON(或直接给 /docs 地址)发给前端/第三方即可,不用再口头讲接口。


📖 四、接口测试基础:为什么用 TestClient

写 API 测试最朴素的想法是:先启动 uvicorn,再用 httpx 发请求断言。但这样测试依赖服务器在运行,麻烦又慢。

FastAPI 提供 TestClient (基于 httpx),它能不启动真实服务器、在测试进程内部直接"模拟"发请求:

复制代码
from fastapi.testclient import TestClient
from app_docs import app

client = TestClient(app)

resp = client.post("/api/tickets", json={"title": "测试工单", "device_id": "A1"},
                   headers={"X-API-Key": "fde-secret-2026"})
assert resp.status_code == 201
assert resp.json()["status"] == "open"

TestClient 的方法和 httpx 一模一样(.get/.post/.patchjson=headers=params=),所以你已经会用了。


🖥️ 五、编写完整测试套件

安装:

复制代码
pip install pytest

项目结构(测试文件以 test_ 开头,pytest 才能自动发现):

复制代码
fde-api/
├── app_docs.py
└── tests/
    ├── __init__.py
    ├── conftest.py        # 夹具(fixture)放这里
    └── test_tickets.py    # 测试用例

夹具 conftest.py------准备可复用的"测试环境"

复制代码
"""pytest 夹具:多个测试文件共享的初始化逻辑"""
import pytest
from fastapi.testclient import TestClient
from app_docs import app, tickets


@pytest.fixture
def client():
    """每个测试用例拿到一个干净的 TestClient"""
    return TestClient(app)


@pytest.fixture(autouse=True)
def reset_db():
    """autouse=True:每个用例执行前自动清空内存工单,保证用例互不干扰"""
    tickets.clear()
    yield          # 这里把控制权交给测试用例
    # yield 之后是"收尾",本例无需处理


@pytest.fixture
def auth_headers():
    """带正确 API Key 的请求头"""
    return {"X-API-Key": "fde-secret-2026"}

💡 yield 把 fixture 分成"准备"和"清理"两半。autouse=True 表示不用在每个测试里显式声明,它自动生效------这里用来在每个用例前重置数据,避免"上一个用例创建的工单影响下一个用例的断言",即测试隔离

测试用例 test_tickets.py

复制代码
"""工单 API 测试:正常路径 + 异常路径都要覆盖"""
import pytest


# ---------- 创建工单 ----------
def test_create_ticket_success(client, auth_headers):
    """正常创建:201 + 返回结构正确"""
    resp = client.post(
        "/api/tickets",
        json={"title": "注塑机A3温度超标", "device_id": "A3", "priority": "high"},
        headers=auth_headers,
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["id"] == 1
    assert data["status"] == "open"          # 业务默认值
    assert data["device_id"] == "A3"
    assert "created_at" in data


def test_create_ticket_default_priority(client, auth_headers):
    """不传 priority,应默认 medium"""
    resp = client.post(
        "/api/tickets",
        json={"title": "普通巡检问题", "device_id": "B2"},
        headers=auth_headers,
    )
    assert resp.status_code == 201
    assert resp.json()["priority"] == "medium"


# ---------- 鉴权 ----------
def test_create_without_api_key(client):
    """没有 API Key:401"""
    resp = client.post("/api/tickets",
                       json={"title": "测试", "device_id": "A1"})
    assert resp.status_code == 401


def test_create_with_wrong_api_key(client):
    """错误 API Key:401"""
    resp = client.post(
        "/api/tickets",
        json={"title": "测试", "device_id": "A1"},
        headers={"X-API-Key": "wrong-key"},
    )
    assert resp.status_code == 401


# ---------- 参数校验 ----------
def test_invalid_device_id_format(client, auth_headers):
    """设备编号不符合 字母+数字:422"""
    resp = client.post(
        "/api/tickets",
        json={"title": "测试", "device_id": "设备-@#"},
        headers=auth_headers,
    )
    assert resp.status_code == 422


def test_title_too_short(client, auth_headers):
    """标题少于 2 字:422"""
    resp = client.post(
        "/api/tickets",
        json={"title": "A", "device_id": "A1"},
        headers=auth_headers,
    )
    assert resp.status_code == 422


def test_invalid_priority_enum(client, auth_headers):
    """priority 不在枚举内:422"""
    resp = client.post(
        "/api/tickets",
        json={"title": "测试工单", "device_id": "A1", "priority": "urgent!!!"},
        headers=auth_headers,
    )
    assert resp.status_code == 422


# ---------- 查询与状态流转 ----------
def test_list_and_filter(client, auth_headers):
    """创建 2 张,按 status 过滤"""
    client.post("/api/tickets", json={"title": "工单一", "device_id": "A1"},
                headers=auth_headers)
    client.post("/api/tickets", json={"title": "工单二", "device_id": "A2"},
                headers=auth_headers)
    client.patch("/api/tickets/2?status=closed", headers=auth_headers)

    all_tickets = client.get("/api/tickets", headers=auth_headers).json()
    assert len(all_tickets) == 2

    closed = client.get("/api/tickets", params={"status": "closed"},
                        headers=auth_headers).json()
    assert len(closed) == 1
    assert closed[0]["id"] == 2


def test_update_nonexistent_ticket(client, auth_headers):
    """更新不存在的工单:404"""
    resp = client.patch("/api/tickets/999?status=closed", headers=auth_headers)
    assert resp.status_code == 404


# ---------- 参数化:一个测试逻辑跑多组数据 ----------
@pytest.mark.parametrize("device_id,expected_ok", [
    ("A3", True),
    ("B12", True),
    ("ABC", False),       # 没有数字
    ("12A", False),       # 数字开头
    ("a3", False),        # 小写字母
])
def test_device_id_pattern(client, auth_headers, device_id, expected_ok):
    resp = client.post(
        "/api/tickets",
        json={"title": "编号校验", "device_id": device_id},
        headers=auth_headers,
    )
    if expected_ok:
        assert resp.status_code == 201
    else:
        assert resp.status_code == 422

运行:

复制代码
pytest -v

test_tickets.py::test_create_ticket_success PASSED
test_tickets.py::test_create_ticket_default_priority PASSED
test_tickets.py::test_create_without_api_key PASSED
test_tickets.py::test_create_with_wrong_api_key PASSED
test_tickets.py::test_invalid_device_id_format PASSED
...
test_tickets.py::test_device_id_pattern[A3-True] PASSED
test_tickets.py::test_device_id_pattern[12A-False] PASSED
============= 13 passed in 0.5s =============

📖 六、测试设计的核心原则

一个测试只验证一件事 + 测试结构 AAA

复制代码
  每个测试用例遵循 AAA 三段式:

  Arrange(准备)  构造数据、请求头
      │
  Act(行动)      发起一次请求
      │
  Assert(断言)   检查状态码 + 返回内容

  例:
  resp = client.post(...)              # Act
  assert resp.status_code == 201       # Assert
  assert resp.json()["status"]=="open" # Assert

正常路径 vs 异常路径(测试价值的分水岭)

新手最容易犯的错:只测"一切正常"的情况。但生产事故几乎都发生在异常分支。看这份覆盖清单:

路径类型 用例 为什么必须测
✅ 正常 合法参数创建成功 201 基本功能
✅ 正常 默认值、查询过滤、状态流转 业务规则
❌ 鉴权异常 无 Key / 错 Key → 401 防止安全裸奔
❌ 入参异常 格式错/枚举错/长度错 → 422 Pydantic 校验是否生效
❌ 资源异常 更新不存在工单 → 404 边界条件
🔁 副作用 幂等、重复提交 Day 48 的重点

📌 经验法则 :写接口时每写一个 raise HTTPException 或加一条校验规则,就该有对应测试证明它真的会触发。测试不是证明"代码能跑",而是证明"错误被正确拦截"。

参数化测试:消除复制粘贴

@pytest.mark.parametrize 让一组数据跑同一个测试逻辑,上面设备编号的例子用 1 个函数覆盖了 5 种输入。新增边界值只要往列表里加一行,不要复制 5 个几乎相同的函数。

测试隔离

reset_db 夹具在每个用例前清空数据,这很关键。如果测试共享状态,就会出现"单独跑能过、一起跑就挂"的诡异问题------通常是前一个用例的数据污染了后一个。


📖 七、测试覆盖率:知道自己漏测了什么

覆盖率工具统计"测试运行时,代码有多少比例的行被执行到":

复制代码
pip install pytest-cov
pytest --cov=app_docs --cov-report=term-missing

Name          Stmts   Miss  Cover
---------------------------------
app_docs.py      45      3    93%

--cov-report=term-missing 还会告诉你具体哪几行没被覆盖,直接定位需要补测试的分支。

理性看待覆盖率:

复制代码
  覆盖率 ≠ 测试质量。100% 覆盖也可能断言很弱(只断言不报错)。
  它的价值是【发现明显没测到的代码】,而不是追求数字。
  FDE 项目建议:核心业务逻辑 80%+,对接/工具代码可适当放宽。

📝 本课小结

知识点 一句话记住
OpenAPI 用 JSON 标准描述接口,跨语言通用
/docs Swagger UI,可交互在线调试
/redoc 只读美观文档,适合对外
openapi.json 可导入 Postman/Apifox
文档元信息 title/version/tags/summary/docstring
Field description + examples + 校验,字段级文档
response_model 明确返回结构,自动进文档
responses 补充 401/404/422 等错误响应说明
文档即代码 文档随代码自动生成,永不过期
TestClient 不起服务器即可测试 API,用法同 httpx
fixture 可复用的准备/清理逻辑,autouse 做隔离
AAA Arrange 准备 → Act 执行 → Assert 断言
异常路径 401/422/404 必须测,事故都在异常分支
参数化 一组数据跑同一逻辑,加边界值只加一行
覆盖率 发现漏测代码,理性看待数字

🧠 核心认知 :在 FDE 的交付现场,文档和测试不是"锦上添花",而是项目能不能交给别人、能不能长期维护的分水岭。用 FastAPI,写好 Pydantic 模型和字段描述,文档是白送的;养成"每写一个接口就补正常+异常测试"的肌肉记忆,你就能放心地反复修改系统------因为有一张自动化的安全网兜底,改坏了立刻报警。


📋 课后练习

练习 1:玩转自动文档(约 20 分钟)

  1. 启动 app_docs,打开 /docs,用 Authorize 或手动填请求头完成一次创建工单的 Try it out

  2. 故意在网页上提交一个非法 device_id(如 @@@),观察返回的 422 错误结构

  3. 打开 /redoc 对比两种文档风格;导出 openapi.json,尝试导入 Postman 或 Apifox

练习 2:补全测试(约 40 分钟)

  1. list_tickets 补一个测试:传非法的 status 枚举值应返回 422

  2. 给鉴权补测试:不带 Key 查询工单列表 应返回 401

  3. 用参数化写一个测试:标题长度分别为 1、2、50、51,验证边界(2 和 50 成功,1 和 51 返回 422)

  4. pytest --cov=app_docs --cov-report=term-missing,查看是否还有未覆盖的行

练习 3:给昨天的 Webhook 写测试(约 40 分钟)

  1. 用 TestClient 对 Day 48 的 /webhook/feishu 写测试:

    • 正确 challenge 握手返回原值

    • 错误 token 返回 403

    • 同一 event_id 连发两次,第二次是 duplicate,工单不重复

  2. 思考:为什么 Webhook 这种"依赖外部平台推送"的接口,尤其需要自动化测试?(提示:你没法等飞书真实推送来验证每次改动)


🔭 下节预告:第二阶段大收官 🎉

第 3 周到第 6 周,你从 SQL 一路打到了 API 双向集成。明天是综合项目日,我们把这四周学的东西串成一个完整作品:

设备告警自动化工单闭环系统:设备数据入库(SQL)→ 超阈值触发告警 → 飞书 API 推送消息(主动调用)→ 群里回复自动建单(Webhook 接收)→ 工单状态流转(RESTful + JWT)→ Docker Compose 一键部署 → OpenAPI 文档 + pytest 测试。

明天不仅是写代码,更重要的是看一个真实 FDE 项目的完整目录结构、模块分层和端到端联调流程,以及第二阶段整体复盘和第三阶段的路线预告。带着这四周的笔记,明天见!


🌍附录:前置课程列表

阶段一:认知启蒙(AI 认知与 FDE 角色)
AI 认知

【FDE系列】阶段1Day 1:AI 层级关系 --- 四个嵌套的圈-CSDN博客

【FDE系列】阶段1Day 2:AI 三阶段发展史 --- 会认 → 会判断 → 会创造-CSDN博客

【FDE系列】阶段1Day 3:符号 AI vs 机器学习 --- 两条路线的本质区别-CSDN博客

【FDE系列】阶段1Day 4:Transformer 的历史意义 --- 2017 年的分水岭-CSDN博客

【FDE系列】阶段1Day 5:本周复习与自测 --- 检验你的 AI 认知地基-CSDN博客

【FDE系列】阶段1Day 6:Transformer 架构 --- 一张图纸盖出千千万万栋楼-CSDN博客

【FDE系列】阶段1Day 7:LLM 本质 --- 文字接龙机器-CSDN博客

【FDE系列】阶段1Day 8:Token --- 模型眼中的最小单位-CSDN博客

【FDE系列】阶段1Day 9:AI 幻觉 --- 为什么会一本正经地胡说八道-CSDN博客

【FDE系列】阶段1Day 10:上下文窗口 --- 模型的记忆力上限 + 本周复习-CSDN博客

【FDE系列】阶段1Day 11:Prompt --- 给模型立规矩-CSDN博客

【FDE系列】阶段1Day 12:Memory --- 让模型记住上下文

【FDE系列】阶段1Day 13:RAG --- 给模型配图书管理员-CSDN博客

【FDE系列】阶段1Day 14:Tool Use --- 让模型动手操作-CSDN博客

【FDE系列】阶段1Day 15:MCP --- 统一的工具接口标准 + 第三周复习-CSDN博客

FDE 基础概念

【FDE系列】阶段1Day 16:什么是 FDE --- 把 AI 变成客户结果的人-CSDN博客

【FDE系列】阶段1Day 17:FDE vs 传统实施 --- 三大本质区别-CSDN博客

【FDE系列】阶段1Day 18:FDE 三重身份 + C6 胜任力模型-CSDN博客

【FDE系列】阶段1Day 19:七阶段行动路径 + 行业经验的价值-CSDN博客

【FDE系列】阶段1Day 20:阶段总结与产出物 --- 第一阶段收官-CSDN博客


阶段二:技术地基(Python + FastAPI + SQL + Docker + API 集成)
Python基础

【FDE系列】阶段2:Day 21:Python 环境搭建 --- 写出你的第一行代码-CSDN博客

【FDE系列】阶段2:Day 22:变量、数据类型、条件判断 --- Python 的"记忆"和"判断"-CSDN博客

【FDE系列】阶段2:Day 23:循环与函数 --- 让代码跑 100 遍、把逻辑打包复用-CSDN博客

【FDE系列】阶段2:Day 24:数据结构 --- 列表、字典、集合、元组-CSDN博客

【FDE系列】阶段2:Day 25:文件读写与 JSON --- 让程序连通外部数据(第一周收官)-CSDN博客

【FDE系列】阶段2:Day 26:模块化编程 --- 把代码拆成"抽屉柜"-CSDN博客

【FDE系列】阶段2:Day 27:异常处理与日志 --- 让程序"摔不烂、查得到"-CSDN博客

FastAPI入门到进阶

【FDE系列】阶段2:Day 28:FastAPI 入门 --- 把你的函数变成 API 服务-CSDN博客

【FDE系列】阶段2:Day 29:FastAPI 进阶 --- Pydantic 模型与完整 CRUD 实战-CSDN博客

【FDE系列】阶段2:Day 30:生产代码规范 --- 测试、类型注解、配置管理(第二周收官)-CSDN博客

SQL基础

【FDE系列】阶段2:Day 31:SQL 基础 --- 增删改查一把梭-CSDN博客

【FDE系列】阶段2:Day 32:多表查询 --- JOIN 与聚合-CSDN博客

【FDE系列】阶段2:Day 33:进阶查询 --- 窗口函数与 CTE-CSDN博客

【FDE系列】阶段2:Day 34:数据清洗 --- 把脏数据捋干净-CSDN博客

【FDE系列】阶段2:Day 35:Python + SQL --- 工单接入 MySQL + 本周收官-CSDN博客

Linux基础

【FDE系列】阶段2:Day 36:Linux 入门与文件操作 --- 扔掉鼠标的第一天-CSDN博客

【FDE系列】阶段2:Day 37:权限、进程与文本三剑客-CSDN博客

【FDE系列】阶段2:Day 38:Shell 脚本 --- 把命令串起来自动跑-CSDN博客

【FDE系列】阶段2:Day 39:Linux 综合实战 --- 让服务无人值守-CSDN博客

【FDE系列】阶段2:Day 40:Shell 进阶 --- 生产级脚本与本周收官-CSDN博客

Docker

【FDE系列】阶段2:Day 41:Docker 入门 --- 把环境装进盒子-CSDN博客

【FDE系列】阶段2:Day 42:Dockerfile 实战 --- 把你的应用打包成镜像-CSDN博客

【FDE系列】阶段2:Day 43:Docker Compose --- 多容器一键编排-CSDN博客

相关推荐
czxxxc1 小时前
从直播工具到经营系统,创客匠人SaaS与AI融合带来运营新变化
人工智能·saas
龙亘川1 小时前
水利工程决策分析报表业务建模:从 “定时报表 + 自定义报表“ 到驾驶舱钻取闭环
人工智能·智慧城市·开源软件·数据可视化·水利水务
oioihoii1 小时前
监控平台能启动却连不上数据库?从 WGCLOUD 部署到多主机监控把链路跑通
人工智能
`流年づ1 小时前
人工智能学习笔记 - 补充
人工智能·笔记·深度学习·学习
鸽芷咕1 小时前
Claude Code 报 429 “Rate limit reached“?先分清是限速还是额度用完,对症下药
人工智能·报错解决·编程工具·claude code
大东(AIP内容运营专员)1 小时前
AIPHarness 与 DeepSeekHarness 的深度对比
人工智能·ai写作
LorryJovens1 小时前
【LAAP科研】双系统具身AGI范式研究——基于LAAP认知架构与Jev概率决策模型的系统性技术调研与范式验证
人工智能·gpt·安全·架构
小程故事多_801 小时前
Claude‑Code Agent Harness,核心架构与源码洞察
人工智能
鲜于言悠9051 小时前
飞书客服Agent接入实战:解决消息归属与重复投递问题
人工智能