【Python全栈开发】第10讲 | 网络协议、API 设计与全栈项目总结
1. 终章:连接一切的桥梁
兄弟们,恭喜你走到了这里!这是《Python 从零到精通》系列的最后一讲。
在前面的课程里,我们学会了写逻辑、存数据、做 Web。但这些东西是怎么在互联网上飞来飞去的?为什么你输入 google.com 就能看到网页?
这一讲,我们要揭开网络协议的神秘面纱,并聊聊如何设计出让前端同学感激涕零的 RESTful API。
2. TCP/IP 协议栈详解
在深入讨论 HTTP 之前,我们需要先理解它的根基——TCP/IP 协议栈。这是一套分层网络通信模型,是现代互联网的基石。
2.1 四层模型概览
TCP/IP 协议栈分为四层,每一层各司其职:
| 层级 | 名称 | 核心功能 | 典型协议/设备 |
|---|---|---|---|
| 第4层 | 应用层 | 为应用程序提供网络服务 | HTTP、FTP、SMTP、DNS |
| 第3层 | 传输层 | 端到端的数据传输控制 | TCP、UDP |
| 第2层 | 网络层 | 寻址和路由选择 | IP、ICMP、路由器 |
| 第1层 | 网络接口层 | 物理数据传输 | 以太网、WiFi、网卡 |
数据封装过程:当数据从应用层向下传递时,每一层都会添加自己的头部信息(封装),到达目标主机后再逐层解封。
2.2 网络层:IP 协议
IP(互联网协议) 负责将数据包从源地址传送到目标地址。
IPv4 vs IPv6:
| 特性 | IPv4 | IPv6 |
|---|---|---|
| 地址长度 | 32位(约42亿个地址) | 128位(约340涧个地址) |
| 地址格式 | 192.168.1.1 | 2001:0db8:85a3::8a2e:0370:7334 |
| 安全性 | 可选IPSec | 内置IPSec |
| 现状 | 地址枯竭,逐步过渡 | 未来主流 |
2.3 传输层:TCP 与 UDP
在聊 HTTP 之前,我们必须先了解它的底层——传输层协议。TCP 和 UDP 是互联网传输数据的两种方式,各有千秋。
2.3.1 TCP:可靠的老实人
TCP(传输控制协议) 就像一个负责任的快递员,必须确认你签收了才算送达。
核心特点:
- 三次握手:建立连接时,客户端和服务端要来回确认三次
- 第一次:客户端发送 SYN(请求建立连接)
- 第二次:服务端回复 SYN+ACK(确认收到,同意建立)
- 第三次:客户端发送 ACK(确认建立连接)
- 四次挥手:断开连接时,也要确认四次
- 确保双方数据都传输完毕才关闭
- 可靠传输:数据丢失会自动重传,保证数据完整
- 有序到达:数据包按顺序重组,不会乱序
- 流量控制:通过滑动窗口机制防止发送方淹没接收方
- 拥塞控制:根据网络状况动态调整发送速率
适用场景:网页浏览、文件传输、邮件发送——凡是不能丢数据的场景。
2.3.2 UDP:潇洒的急先锋
UDP(用户数据报协议) 就像寄明信片,扔出去就不管了,能不能收到看缘分。
核心特点:
- 无连接:不需要建立连接,直接发
- 不可靠:数据丢了就丢了,不重传
- 速度快:没有确认机制,延迟极低
- 无序:数据包可能乱序到达
- 头部开销小:仅8字节,TCP需要20字节
适用场景:视频直播、语音通话、在线游戏——宁可丢几帧,也不能卡顿。
2.4 TCP vs UDP 对比表
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 高,保证送达 | 低,可能丢包 |
| 速度 | 较慢 | 快 |
| 有序性 | 保证顺序 | 不保证 |
| 头部大小 | 20字节 | 8字节 |
| 拥塞控制 | 有 | 无 |
| 典型应用 | HTTP、FTP、SMTP | DNS、视频直播、游戏 |
一句话总结:TCP 稳但慢,UDP 快但野。选谁取决于你的业务需求。
3. HTTP:互联网的"通用语言"
所有的 Web 开发,本质上都是在玩 HTTP。
3.1 HTTP 版本演进:1.1 vs 2 vs 3
HTTP 协议经历了多次重大升级,每个版本都解决了前代的痛点:
3.1.1 HTTP/1.1(1997年)
特点:
- 持久连接(Keep-Alive):复用 TCP 连接,减少握手开销
- 管道化(Pipelining):允许连续发送多个请求,无需等待响应
- 分块传输编码:支持流式传输大文件
- 缓存控制:引入
Cache-Control等头部
痛点:
- 队头阻塞(Head-of-Line Blocking):一个请求阻塞,后续请求都得等
- 每个请求都需要完整的 HTTP 头部,冗余传输
- 同一域名下浏览器限制6-8个并发连接
3.1.2 HTTP/2(2015年)
核心改进:
| 特性 | 说明 | 优势 |
|---|---|---|
| 二进制分帧 | 数据以二进制帧传输,而非文本 | 解析更高效,错误更少 |
| 多路复用 | 单一 TCP 连接上并行传输多个请求 | 彻底解决队头阻塞 |
| 头部压缩 | HPACK 算法压缩请求头 | 减少冗余数据传输 |
| 服务器推送 | 服务端主动推送资源 | 提前加载关键资源 |
| 流优先级 | 设置请求优先级 | 关键资源优先加载 |
一句话总结:HTTP/2 让网页加载速度大幅提升,是现代 Web 的主流选择。
3.1.3 HTTP/3(2022年)
革命性变化:基于 QUIC 协议(基于 UDP),而非 TCP。
核心优势:
- 零 RTT 连接:首次连接只需1个 RTT,重连时0 RTT
- 彻底解决队头阻塞:QUIC 在传输层解决,单个流阻塞不影响其他流
- 连接迁移:网络切换(WiFi转4G)时连接不中断
- 内置加密:TLS 1.3 集成,握手更快
适用场景:移动应用、实时通信、对延迟敏感的应用。
3.1.4 版本对比总结
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输层 | TCP | TCP | QUIC (UDP) |
| 队头阻塞 | 有 | 应用层解决 | 彻底解决 |
| 多路复用 | 无 | 有 | 有 |
| 头部压缩 | 无 | HPACK | QPACK |
| 服务器推送 | 无 | 有 | 有 |
| 连接建立 | 2-3 RTT | 2-3 RTT | 0-1 RTT |
| 连接迁移 | 不支持 | 不支持 | 支持 |
3.2 请求 (Request) 与响应 (Response)
- 请求:你跟服务器说:“给我看一眼那张图片。”
- 响应:服务器说:"行,给你。“或者"对不起,没找到。”
一个 HTTP 请求由三部分组成:请求行、请求头、请求体。响应也是类似的结构。
3.3 常见的 HTTP 方法(四大天王)
| 方法 | 用途 | 是否有请求体 | 是否幂等 |
|---|---|---|---|
| GET | 查询资源 | 无 | 是 |
| POST | 创建资源 | 有 | 否 |
| PUT | 更新资源(全量) | 有 | 是 |
| DELETE | 删除资源 | 可选 | 是 |
| PATCH | 更新资源(部分) | 有 | 否 |
幂等性解释:同一个请求执行一次和执行多次,效果相同。GET、PUT、DELETE 是幂等的,POST 不是。
3.4 状态码:服务器的"微表情"
| 状态码 | 类别 | 含义 | 常见示例 |
|---|---|---|---|
| 2xx | 成功 | 请求被成功处理 | 200 OK, 201 Created |
| 3xx | 重定向 | 需要进一步操作 | 301 永久移动, 302 临时移动 |
| 4xx | 客户端错误 | 请求有问题 | 400 Bad Request, 404 Not Found |
| 5xx | 服务端错误 | 服务器出问题了 | 500 Internal Error, 502 Bad Gateway |
4. HTTP 缓存机制:让网站飞起来
每次请求都从服务器拉数据太慢了。HTTP 缓存让浏览器把资源存下来,下次直接用。
4.1 强缓存:不问服务器,直接用
强缓存期间,浏览器根本不发请求,直接用本地缓存。
关键响应头:
| 响应头 | 说明 | 示例 |
|---|---|---|
Cache-Control |
缓存控制(HTTP/1.1) | max-age=3600 |
Expires |
过期时间(HTTP/1.0,已过时) | Wed, 21 Oct 2026 07:28:00 GMT |
Cache-Control 常用指令:
max-age=3600:缓存 3600 秒no-cache:每次使用前要验证(不是不缓存!)no-store:完全不缓存public:可以被任何缓存(包括 CDN)存储private:只能被浏览器缓存
4.2 协商缓存:问一下服务器,能不能用
强缓存过期后,浏览器会问服务器:“我这里有个缓存,还能用吗?”
两种验证方式:
| 方式 | 请求头 | 响应头 | 原理 |
|---|---|---|---|
| Last-Modified | If-Modified-Since |
Last-Modified |
比较修改时间 |
| ETag | If-None-Match |
ETag |
比较内容哈希值 |
ETag 更精确:因为文件可能内容没变但修改时间变了,或者修改时间没变但内容变了。
协商缓存命中时:服务器返回 304 Not Modified,不返回内容,省带宽。
5. RESTful API 设计深度解析
全栈开发里,后端和前端的沟通就靠 API。如果你乱写,前端同学可能会拿着键盘找你拼命。
5.1 REST 架构核心原则
REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在2000年提出。
5.1.1 资源(Resources)
REST 的核心是资源,任何可以命名的事物都可以是资源:用户、订单、文章、图片等。
- 错误示范:
/get_all_users,/delete_user_by_id?id=1(动作导向) - 正确示范:
GET /users,DELETE /users/1(名词导向)
5.1.2 统一接口(Uniform Interface)
| 原则 | 说明 | 实践 |
|---|---|---|
| 资源标识 | 每个资源有唯一标识符 | URL 作为资源标识 |
| 自描述消息 | 请求包含足够信息 | 使用标准 HTTP 方法和头部 |
| 资源的自描述 | 响应包含资源表示 | JSON/XML 表示资源状态 |
| HATEOAS | 超媒体作为应用状态引擎 | 响应中包含相关资源链接 |
5.1.3 无状态(Stateless)
服务器不保存客户端的上下文状态。每个请求都必须包含所有必要信息。
好处:
- 服务端容易扩展(任意服务器实例都可以处理请求)
- 请求可以被缓存
- 系统更可靠(没有会话状态丢失的问题)
5.2 RESTful 风格要点
- URL 用名词,不用动词
- HTTP 方法表达操作类型
- 资源用复数形式(
/users而非/user) - 层级关系用路径表达(
/users/1/orders)
5.3 状态码使用规范
| 场景 | 状态码 | 说明 |
|---|---|---|
| 查询成功 | 200 OK | 返回资源列表或详情 |
| 创建成功 | 201 Created | 返回新创建的资源 |
| 删除成功 | 204 No Content | 无需返回内容 |
| 参数错误 | 400 Bad Request | 请求参数校验失败 |
| 未认证 | 401 Unauthorized | 缺少或无效的认证信息 |
| 无权限 | 403 Forbidden | 已认证但无权访问 |
| 资源不存在 | 404 Not Found | URL 对应的资源不存在 |
| 资源冲突 | 409 Conflict | 如重复创建已存在资源 |
| 服务器错误 | 500 Internal Server Error | 服务端异常 |
5.4 响应格式标准化
{
"code": 200,
"msg": "Success",
"data": {
"id": 1,
"name": "张三"
},
"timestamp": 1709012345
}
统一结构的好处:前端可以用同一套逻辑处理所有响应,不用为每个接口写特殊处理。
6. 认证与授权机制
API 的安全性至关重要。我们需要区分认证(你是谁)和授权(你能做什么)。
6.1 JWT(JSON Web Token)
JWT 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。
6.1.1 JWT 结构
JWT 由三部分组成,用点号分隔:
xxxxx.yyyyy.zzzzz
| | |
头部 载荷 签名
头部(Header):
{
"alg": "HS256",
"typ": "JWT"
}
载荷(Payload):
{
"sub": "1234567890",
"name": "张三",
"iat": 1516239022,
"exp": 1516242622
}
签名(Signature):
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
6.1.2 JWT 工作流程
- 用户登录,服务端验证成功后生成 JWT
- 服务端将 JWT 返回给客户端
- 客户端将 JWT 存储(LocalStorage 或 Cookie)
- 后续请求携带 JWT(通常放在 Authorization 头部)
- 服务端验证 JWT 签名和有效期
6.1.3 JWT Python 实现
import jwt
from datetime import datetime, timedelta, timezone
# 生成 JWT
def create_token(user_id: str, secret: str, expires_hours: int = 24):
now = datetime.now(timezone.utc)
payload = {
"user_id": user_id,
"exp": now + timedelta(hours=expires_hours),
"iat": now
}
token = jwt.encode(payload, secret, algorithm="HS256")
return token
# 验证 JWT
def verify_token(token: str, secret: str):
try:
payload = jwt.decode(token, secret, algorithms=["HS256"])
return payload
except jwt.ExpiredSignatureError:
return None # Token 过期
except jwt.InvalidTokenError:
return None # Token 无效
# 使用示例
secret_key = "your-secret-key-must-be-at-least-32-bytes-long"
token = create_token("user_123", secret_key)
print(f"生成的 Token: {token}")
payload = verify_token(token, secret_key)
print(f"验证结果: {payload}")
6.1.4 JWT 最佳实践
| 建议 | 说明 |
|---|---|
| 设置合理的过期时间 | 建议访问令牌15分钟,刷新令牌7天 |
| 使用 HTTPS | 防止 Token 被截获 |
| 不要在 JWT 中存放敏感信息 | Payload 只是 Base64 编码,可被解码 |
| 使用强密钥 | 至少256位的密钥 |
| 实现 Token 黑名单 | 用于登出等场景 |
6.2 OAuth 2.0
OAuth 2.0 是一种授权框架,允许第三方应用获取用户资源的有限访问权限,而不暴露用户密码。
6.2.1 核心角色
| 角色 | 说明 | 示例 |
|---|---|---|
| 资源所有者 | 拥有资源的用户 | 微信用户 |
| 客户端 | 请求访问资源的应用 | 某第三方网站 |
| 授权服务器 | 验证用户身份并颁发令牌 | 微信授权服务器 |
| 资源服务器 | 托管受保护资源的服务器 | 微信用户信息接口 |
6.2.2 授权流程(授权码模式)
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
6.2.3 FastAPI 集成 OAuth2
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
# Token URL
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 模拟用户数据库
fake_users_db = {
"zhangsan": {
"username": "zhangsan",
"password": "secret_password",
"disabled": False
}
}
class User(BaseModel):
username: str
disabled: bool = False
def get_user(db, username: str):
if username in db:
return User(**db[username])
return None
async def get_current_user(token: str = Depends(oauth2_scheme)):
# 实际项目中这里需要验证 JWT
user = get_user(fake_users_db, token)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"}
)
return user
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or user["password"] != form_data.password:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password"
)
return {"access_token": user["username"], "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
6.3 JWT vs OAuth2 对比
| 特性 | JWT | OAuth2 |
|---|---|---|
| 定位 | Token 格式标准 | 授权框架 |
| 用途 | 携带用户身份信息 | 第三方授权访问 |
| 复杂度 | 简单 | 较复杂 |
| 适用场景 | 单应用认证 | 第三方登录、API 开放平台 |
| 关系 | OAuth2 可以使用 JWT 作为 Token 格式 | OAuth2 是框架,JWT 是工具 |
7. API 版本控制策略
你的 API 以后肯定会升级。为了不让旧版本的前端崩掉,需要设计好版本控制策略。
7.1 为什么需要版本控制
- 功能迭代:新增功能可能改变响应结构
- 字段变更:删除或修改字段会破坏旧客户端
- Bug 修复:修复可能改变原有行为
- 性能优化:查询参数或路径可能调整
7.2 版本控制策略
7.2.1 URL 路径版本(推荐)
https://api.myapp.com/v1/users
https://api.myapp.com/v2/users
优点:
- 直观清晰,一眼看出版本
- 易于缓存和路由
- 适合重大版本变更
缺点:
- URL 变更可能影响书签和文档
7.2.2 查询参数版本
https://api.myapp.com/users?version=1
https://api.myapp.com/users?version=2
优点:
- URL 结构保持稳定
- 适合小版本变更
缺点:
- 不够直观
- 可能被缓存策略忽略
7.2.3 请求头版本
Accept: application/vnd.myapp.v1+json
Accept-Version: v1
优点:
- URL 保持干净
- 符合 REST 纯化论
缺点:
- 不够直观,调试困难
- 需要额外的文档说明
7.3 FastAPI 实现 API 版本控制
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
# v1 模型
class UserV1(BaseModel):
id: int
name: str
email: str
# v2 模型(新增字段)
class UserV2(BaseModel):
id: int
name: str
email: str
avatar: Optional[str] = None
created_at: Optional[str] = None
# v1 路由
v1_router = APIRouter(prefix="/v1")
@v1_router.get("/users", response_model=List[UserV1])
async def get_users_v1():
return [
{"id": 1, "name": "张三", "email": "zhangsan@example.com"}
]
# v2 路由
v2_router = APIRouter(prefix="/v2")
@v2_router.get("/users", response_model=List[UserV2])
async def get_users_v2():
return [
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"avatar": "https://example.com/avatar.jpg",
"created_at": "2024-01-01T00:00:00Z"
}
]
# 注册路由
app.include_router(v1_router)
app.include_router(v2_router)
@app.get("/")
async def root():
return {
"message": "API Versioning Demo",
"versions": ["v1", "v2"]
}
7.4 版本控制最佳实践
| 建议 | 说明 |
|---|---|
| 从 v1 开始 | 不要从无版本开始,预留扩展空间 |
| 向后兼容 | 尽量保持旧版本稳定运行 |
| 弃用通知 | 弃用旧版本前提前通知客户端 |
| 版本生命周期 | 制定明确的版本支持周期 |
| 文档同步 | 每个版本都要有对应的文档 |
8. 跨域问题(CORS):前后端分离的拦路虎
当前端和后端不在同一个域名下,浏览器会拦截请求,这就是跨域。
8.1 为什么会有跨域?
浏览器的同源策略(Same-Origin Policy)限制了从一个源加载的文档或脚本如何与另一个源的资源交互。
同源的定义:协议、域名、端口完全相同。
| URL A | URL B | 是否同源 | 原因 |
|---|---|---|---|
http://a.com/page |
http://a.com/api |
是 | 完全相同 |
http://a.com/page |
https://a.com/api |
否 | 协议不同 |
http://a.com/page |
http://api.a.com/data |
否 | 域名不同 |
http://a.com/page |
http://a.com:8080/api |
否 | 端口不同 |
8.2 CORS 解决方案
CORS(跨源资源共享) 是 W3C 标准,通过设置响应头让浏览器放行。
关键响应头:
| 响应头 | 说明 | 示例 |
|---|---|---|
Access-Control-Allow-Origin |
允许的源 | * 或 https://example.com |
Access-Control-Allow-Methods |
允许的方法 | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers |
允许的请求头 | Content-Type, Authorization |
Access-Control-Allow-Credentials |
是否允许携带 Cookie | true |
FastAPI 中启用 CORS:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
9. JSON 格式规范详解
JSON 是前后端数据交换的通用格式,掌握它的规范很重要。
9.1 JSON 数据类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 字符串 | 必须用双引号 | "hello" |
| 数字 | 整数或浮点数 | 42, 3.14 |
| 布尔值 | 只有 true/false | true, false |
| 空值 | 只有 null | null |
| 对象 | 键值对集合 | {"name": "张三"} |
| 数组 | 有序值列表 | [1, 2, 3] |
9.2 JSON 常见陷阱
- 键必须用双引号:
{'name': '张三'}是错的,必须是{"name": "张三"} - 最后一个元素后面不能有逗号:
[1, 2, 3,]是错的 - 不支持注释:JSON 里不能写注释
- 不支持单引号:字符串必须用双引号
9.3 Python 处理 JSON
import json
data = {"name": "张三", "age": 25}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
parsed = json.loads(json_str)
10. 全栈项目总结:我们学到了什么?
回顾这 10 讲,你其实已经掌握了一套完整的全栈开发闭环:
10.1 知识图谱总览
| 阶段 | 讲次 | 核心技能 | 实战产出 |
|---|---|---|---|
| 入门 | 第1-2讲 | 环境配置、变量、流程控制 | 能写简单脚本 |
| 进阶 | 第3讲 | 函数、模块、装饰器 | 代码可复用 |
| 架构 | 第4讲 | 面向对象、设计模式 | 代码有结构 |
| 稳健 | 第5讲 | 异常处理、文件 IO | 程序不崩溃 |
| 高效 | 第6讲 | 并发、正则、高级特性 | 性能优化 |
| 持久 | 第7讲 | MySQL、Redis | 数据能存 |
| 服务 | 第8讲 | FastAPI | 能提供接口 |
| 工程 | 第9讲 | Git、测试、Docker | 团队协作 |
| 网络 | 第10讲 | HTTP、API 设计 | 系统互联 |
10.2 从零到一的能力矩阵
学完这个系列,你应该能够:
- 独立开发:从零搭建一个完整的 Web 项目
- 团队协作:用 Git 管理代码,用测试保证质量
- 生产部署:用 Docker 容器化,用日志排查问题
- 架构设计:理解分层、解耦、设计模式
- 持续学习:具备阅读文档、排查问题的能力
11. 避坑小贴士(终章寄语)
- 别停止练习:看十遍教程,不如自己亲手写一个项目。去写一个属于你自己的博客系统、爬虫工具或者小游戏。
- 读别人的代码:去 GitHub 看看那些优秀的开源项目(比如 FastAPI 源码、Requests 源码),那是最好的进阶教材。
- 拥抱 AI,但不依赖 AI:你可以用 ChatGPT 帮你写个小函数,但你必须理解它背后的逻辑,否则你永远无法解决那些深层次的 Bug。
- 记录和分享:写技术博客,记录你踩过的坑。教是最好的学。
- 关注社区:Python 发展很快,保持对新技术的敏感度。
12. 实战演练:巩固你的内功
题目 1:Requests 爬虫与伪装
需求:
使用 requests 模拟发送一个 GET 请求到 https://httpbin.org/get。
设置请求头 User-Agent 为 “MyPythonBot/1.0”。
带上查询参数 page=1, limit=10。
打印响应的状态码和 JSON 内容。
import requests
url = "https://httpbin.org/get"
headers = {"User-Agent": "MyPythonBot/1.0"}
params = {"page": 1, "limit": 10}
try:
resp = requests.get(url, headers=headers, params=params, timeout=5)
if resp.status_code == 200:
print("请求成功!")
data = resp.json()
print(f"User-Agent: {data['headers']['User-Agent']}")
print(f"Args: {data['args']}")
else:
print(f"请求失败:{resp.status_code}")
except Exception as e:
print(f"发生错误:{e}")
题目 2:JSON 序列化 (datetime 处理)
需求:
有一个字典包含 datetime 对象,直接 json.dumps 会报错。
编写一个自定义的 default 函数,将日期对象转为 “YYYY-MM-DD HH:MM:SS” 格式的字符串。
序列化该字典。
import json
from datetime import datetime
data = {
"id": 101,
"title": "测试订单",
"created_at": datetime.now()
}
def json_encoder(obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
raise TypeError(f"Type {type(obj)} not serializable")
json_str = json.dumps(data, default=json_encoder, indent=4, ensure_ascii=False)
print(json_str)
题目 3:统一 API 响应结构
需求:
编写一个辅助类 ApiResponse。
包含两个静态方法 success(data) 和 error(code, msg)。
返回标准的字典结构:{"code": ..., "msg": ..., "data": ...}。
class ApiResponse:
@staticmethod
def success(data=None):
return {
"code": 200,
"msg": "Success",
"data": data
}
@staticmethod
def error(code=400, msg="Error"):
return {
"code": code,
"msg": msg,
"data": None
}
print(ApiResponse.success({"user_id": 1}))
print(ApiResponse.error(404, "User not found"))
题目 4:JWT Token 生成与验证
需求:
使用 PyJWT 库实现 Token 的生成和验证。
Token 应包含用户 ID 和过期时间。
验证过期 Token 时应返回 None。
import jwt
import time
from datetime import datetime, timedelta, timezone
def create_token(user_id: str, secret: str, expires_seconds: int = 3600):
"""生成 JWT Token"""
now = datetime.now(timezone.utc)
payload = {
"user_id": user_id,
"exp": now + timedelta(seconds=expires_seconds),
"iat": now
}
return jwt.encode(payload, secret, algorithm="HS256")
def verify_token(token: str, secret: str):
"""验证 JWT Token"""
try:
return jwt.decode(token, secret, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
print("Token 已过期")
return None
except jwt.InvalidTokenError:
print("Token 无效")
return None
# 使用示例
SECRET = "my-secret-key-must-be-at-least-32-bytes-long"
# 生成 Token
token = create_token("user_123", SECRET, expires_seconds=2)
print(f"生成的 Token: {token}")
# 立即验证
payload = verify_token(token, SECRET)
print(f"验证结果: {payload}")
# 等待过期后验证
time.sleep(3)
payload = verify_token(token, SECRET)
print(f"过期后验证: {payload}")
13. 系列索引
- 上一篇:第9讲 | 工程化实战:测试、Git 与生产级部署
- 系列完结:感谢大家的陪伴!
写在最后:
兄弟们,编程不是一蹴而就的,它是一场长跑。今天你可能还在为配不好环境而苦恼,一年后你可能就能主导一个千万级用户的项目。
保持好奇心,保持空杯心态。
这个系列结束了,但你的全栈之旅才刚刚开始。
如果这个系列对你有帮助,请务必点赞、收藏、关注! 你的支持是我持续产出高质量内容的动力。
咱们江湖再见,代码里见!
更多推荐



所有评论(0)