📚前言
📒FDE系列内容总纲:
🚄前置课程列表:
见文档结尾附录。
🚀阶段2·Day 47:对接企业系统 --- 飞书 / 钉钉 API
FDE 学习系列教程 · 第二阶段 · 第 6 周 · Day 2 预计时长:3 小时 | 难度:★★★★☆ | 前置知识:Day 46 认证、FastAPI、环境变量
📌 一句话目标:掌握用 httpx 调用外部 HTTP 接口,理解企业开放平台"app_id/secret 换 token"的通用模式,实现带 token 缓存、自动刷新、失败重试的飞书/钉钉客户端。
🧑🤝🧑 开场:FDE 的核心战场------让系统之间对话
昨天你是"守门人",给自己的 API 加锁。今天换到"访客"视角------带着凭证去调用别人的 API。
FDE 在客户现场最高频的一类需求就是对接企业 IM(即时通讯)。随便举几个真实场景:
-
设备温度超标,自动往飞书运维群里发告警消息
-
工单创建成功,通知到钉钉对应负责人
-
每天早上 9 点,读取值班表,把今日待办推送给值班同事
-
新员工入职,从企业通讯录拉取部门信息同步到你的系统
这些事的本质都一样:你的程序,按照对方平台规定的方式,调用它的 HTTP 接口。
今天的内容确实硬核,一次消化不了正常,先跟着把流程跑通,理解"换 token → 带 token 调接口"这条主线,细节以后对着平台文档查就行。
今天的能力地图:
通用 HTTP 客户端 httpx ------ 任何第三方 API 都用它调
│
▼
企业平台通用认证模式 ------ app_id + app_secret 换 access_token
│
▼
飞书实战 / 钉钉对比 ------ 发消息、读群列表
│
▼
工程化:token 缓存、自动刷新、失败重试
💡 关于练习环境:真实调用飞书/钉钉需要你有企业管理员权限去"创建应用"。考虑到不是每个人都有现成企业环境,今天我会同时给你两套方案:
方案 A(推荐先做) :本地搭一个模拟服务器(Mock),零门槛练通整个调用流程
方案 B(有条件再做):注册飞书自建应用,真实调用
两套方案的客户端代码几乎一样------这正是抽象的价值:先在 Mock 上把逻辑写对,接真实平台只改地址和凭证。
📖 一、认识 httpx:现代 Python HTTP 客户端
调外部 API,本质就是用 Python 发 HTTP 请求。你可能听过 requests(老牌经典),今天用 httpx ------它接口和 requests 几乎一样,但原生支持异步(async),和你的 FastAPI 是绝配(FastAPI 的异步接口里绝不能用阻塞的 requests)。
安装
pip install httpx
三种最常用请求
import httpx
# ① GET 请求(查数据)
resp = httpx.get("https://httpbin.org/get")
print(resp.status_code) # 200
print(resp.json()) # 响应体转成字典
# ② POST 发 JSON(提交数据)------ 调企业 API 90% 是 POST
resp = httpx.post(
"https://httpbin.org/post",
json={"name": "注塑机A1", "temperature": 91}, # 自动序列化为 JSON、设好 Content-Type
)
print(resp.json()["json"]) # 对方收到的数据
# ③ 带请求头(认证 token 就放这)
resp = httpx.get(
"https://httpbin.org/get",
headers={"Authorization": "Bearer xxx-token"},
)
响应对象的常用属性
resp = httpx.get("https://httpbin.org/get")
resp.status_code # HTTP 状态码:200/401/404...
resp.text # 响应体(字符串)
resp.json() # 响应体解析成 dict/list(对方返回 JSON 时)
resp.headers # 响应头
resp.raise_for_status() # 如果状态码是 4xx/5xx,直接抛异常
为什么企业 API 推荐用 Client(连接复用)
# 零散调用:每次请求都新建一次连接(慢)
httpx.get(url1)
httpx.post(url2, ...)
# 用 Client:复用底层连接池(快),还能统一配置 base_url、请求头
with httpx.Client(base_url="https://open.feishu.cn", timeout=10) as client:
client.post("/open-apis/auth/...") # URL 只写路径部分
client.get("/open-apis/im/v1/chats")
# with 结束自动关闭连接
# 异步版(FastAPI 里用):AsyncClient + await
async with httpx.AsyncClient() as client:
r = await client.get(url)
📌 FDE 记住:写脚本一次性调用 用
httpx.get/post简单;封装成客户端类、要多次调用 (比如今天的飞书客户端)用Client/AsyncClient,配base_url和统一超时。
📖 二、企业开放平台的通用认证模式
飞书、钉钉、企业微信,以及绝大多数开放平台,认证流程惊人地一致。先把这个"万能套路"搞懂,看任何平台文档都不慌:
┌────────────────────────────────────────────────────────────┐
│ 对接企业 API 的四步通用流程 │
└────────────────────────────────────────────────────────────┘
第 1 步:注册应用,拿到一对"身份证"
app_id(公开标识)+ app_secret(私密密钥)
第 2 步:用身份证换"临时通行证"
POST 认证接口 {app_id, app_secret}
→ 返回 access_token(有效期通常 2 小时)
第 3 步:带着 token 调业务接口
GET/POST 业务接口
Header: Authorization: Bearer <access_token>
→ 发消息、读通讯录、建日程......
第 4 步:token 过期前自动重新获取
缓存 token,快过期时再换新的(别每次调用都换)
为什么这么绕?不直接用 app_secret 调接口?
· app_secret 是长期密钥,泄露危害大,要尽量少在网络上传
· access_token 是短期令牌,2 小时过期,即使泄露损失可控
· token 可以携带权限范围(scope),平台能精细化授权
和昨天学的 JWT 思想一脉相承:长期密钥换短期令牌。
各家平台的术语对照(看着不同,本质一样)
| 平台 | 应用凭证 | 换取的令牌 | 认证接口(大致) |
|---|---|---|---|
| 飞书 | app_id + app_secret | tenant_access_token | /open-apis/auth/v3/tenant_access_token/internal |
| 钉钉 | appkey + appsecret | access_token | /gettoken |
| 企业微信 | corpid + corpsecret | access_token | /gettoken |
🖥️ 三、方案 A:搭一个 Mock 服务器(零门槛先练通)
在没有真实企业凭证时,我们用 FastAPI 本地模拟一个"飞书风格"的开放平台。它实现两个接口:换 token、发消息。你写的客户端去调它,流程和调真实飞书完全一致。
新建 mock_platform.py:
"""
模拟企业开放平台(飞书风格)
运行:uvicorn mock_platform:app --port 9000
"""
from fastapi import FastAPI, Header, HTTPException
import time
app = FastAPI(title="模拟企业开放平台")
# 模拟平台后台存储
APP_ID = "cli_mock_123"
APP_SECRET = "mock_secret_456"
VALID_TOKENS: dict[str, float] = {} # token -> 过期时间戳
MESSAGES = [] # 收到的消息记录
@app.post("/auth/v3/tenant_access_token/internal")
def get_token(body: dict):
"""第 2 步:app_id + secret 换 access_token"""
if body.get("app_id") != APP_ID or body.get("app_secret") != APP_SECRET:
# 飞书真实接口错误时 code 非 0
return {"code": 99991663, "msg": "app_id or app_secret invalid"}
token = f"t-mock-{int(time.time() * 1000)}"
# 真实飞书有效期 7200 秒;这里设短一点(30秒)方便测试自动刷新
VALID_TOKENS[token] = time.time() + 30
return {"code": 0, "msg": "ok", "tenant_access_token": token, "expire": 30}
def _check_token(authorization: str):
"""统一校验 token"""
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(401, "missing token")
token = authorization.split("Bearer ", 1)[1]
expire = VALID_TOKENS.get(token)
if not expire:
raise HTTPException(401, "invalid token")
if time.time() > expire:
VALID_TOKENS.pop(token, None)
raise HTTPException(401, "token expired")
return token
@app.post("/im/v1/messages")
def send_message(body: dict, authorization: str = Header(None)):
"""第 3 步:带 token 发消息"""
_check_token(authorization)
MESSAGES.append(body)
return {"code": 0, "msg": "ok", "data": {"message_id": f"om_{len(MESSAGES)}"}}
@app.get("/im/v1/chats")
def list_chats(authorization: str = Header(None)):
"""读群列表"""
_check_token(authorization)
return {"code": 0, "data": {"items": [
{"chat_id": "oc_ops_001", "name": "设备运维群"},
{"chat_id": "oc_safe_002", "name": "安全生产群"},
]}}
@app.get("/debug/messages")
def debug_messages():
"""调试用:查看平台收到了哪些消息"""
return {"count": len(MESSAGES), "messages": MESSAGES}
启动它(占用 9000 端口):
uvicorn mock_platform:app --port 9000
新开终端,先手动验证一下这个模拟平台:
# 换 token
curl -X POST http://localhost:9000/auth/v3/tenant_access_token/internal \
-H "Content-Type: application/json" \
-d '{"app_id":"cli_mock_123","app_secret":"mock_secret_456"}'
# {"code":0,"tenant_access_token":"t-mock-...","expire":30}
# 错误凭证
curl -X POST http://localhost:9000/auth/v3/tenant_access_token/internal \
-H "Content-Type: application/json" \
-d '{"app_id":"wrong","app_secret":"wrong"}'
# {"code":99991663,"msg":"app_id or app_secret invalid"}
Mock 就绪。接下来写真正有价值的东西------一个能复用的企业 IM 客户端类。
🖥️ 四、封装企业 IM 客户端(核心代码)
新建 im_client.py。这个类的设计思路适用于飞书、钉钉乃至任何类似平台:
"""
企业 IM 客户端:token 缓存 + 自动刷新 + 失败重试
"""
import time
import logging
import httpx
logging.basicConfig(level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger(__name__)
class IMWikiClient:
"""对接企业开放平台的通用客户端(默认指向本地 Mock,可改 base_url 接真实飞书)"""
def __init__(self, app_id: str, app_secret: str,
base_url: str = "http://localhost:9000",
timeout: float = 10.0):
self.app_id = app_id
self.app_secret = app_secret
self.base_url = base_url.rstrip("/")
# token 缓存:None 表示还没获取
self._token: str | None = None
self._token_expire_at: float = 0
# 连接池客户端,统一超时
self._http = httpx.Client(base_url=self.base_url, timeout=timeout)
# ---------- token 管理 ----------
def _fetch_token(self) -> str:
"""真正去请求新 token"""
log.info("正在获取新的 access_token ...")
resp = self._http.post(
"/auth/v3/tenant_access_token/internal",
json={"app_id": self.app_id, "app_secret": self.app_secret},
)
data = resp.json()
if data.get("code") != 0:
raise RuntimeError(f"获取 token 失败:{data}")
self._token = data["tenant_access_token"]
# 提前 5 分钟(Mock 环境提前 25 秒)认为过期,留足余量
buffer = 300 if data.get("expire", 0) > 100 else 25
self._token_expire_at = time.time() + data["expire"] - buffer
log.info("获取成功,token 将在 %.0f 秒后刷新", data["expire"] - buffer)
return self._token
def _get_token(self) -> str:
"""获取可用 token:缓存有效就用缓存,否则换新的"""
if self._token and time.time() < self._token_expire_at:
return self._token
return self._fetch_token()
# ---------- 带重试的请求 ----------
def _request(self, method: str, path: str, **kwargs):
"""统一请求:token 失效自动重取并重试一次,网络错误重试 3 次"""
max_network_retry = 3
for attempt in range(1, max_network_retry + 1):
token = self._get_token()
headers = kwargs.pop("headers", {})
headers["Authorization"] = f"Bearer {token}"
try:
resp = self._http.request(method, path, headers=headers, **kwargs)
except httpx.HTTPError as e:
log.warning("网络异常(第 %s 次):%s", attempt, e)
if attempt == max_network_retry:
raise
time.sleep(1 * attempt)
continue
# token 失效(过期/被吊销):清缓存重新获取,重试一次
if resp.status_code == 401:
log.warning("token 已失效,清除缓存后重试")
self._token = None
continue
resp.raise_for_status()
data = resp.json()
# 业务层错误码(飞书风格:code=0 才算成功)
if isinstance(data, dict) and data.get("code", 0) != 0:
raise RuntimeError(f"接口返回错误:{data}")
return data
raise RuntimeError("请求重试次数耗尽")
# ---------- 业务方法 ----------
def send_text(self, chat_id: str, text: str) -> dict:
"""发送文本消息到群聊"""
import json
return self._request(
"POST", "/im/v1/messages",
params={"receive_id_type": "chat_id"},
json={
"receive_id": chat_id,
"msg_type": "text",
"content": json.dumps({"text": text}, ensure_ascii=False),
},
)
def list_chats(self) -> list:
"""获取群列表"""
data = self._request("GET", "/im/v1/chats")
return data.get("data", {}).get("items", [])
def close(self):
self._http.close()
# 支持 with 语法
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
跑通完整调用
新建 run_demo.py:
from im_client import IMWikiClient
# 用 Mock 的凭证;接真实飞书时换成真实 app_id/secret 和官方 base_url
with IMWikiClient(
app_id="cli_mock_123",
app_secret="mock_secret_456",
base_url="http://localhost:9000",
) as client:
# ① 读群列表
chats = client.list_chats()
print("群列表:", chats)
# ② 发告警消息
result = client.send_text(
chat_id="oc_ops_001",
text="🚨【设备告警】注塑机A3 温度 91℃,超过阈值 80℃,请立即处理!",
)
print("发送结果:", result)
# ③ 再发一条(这次应该复用缓存的 token,不会重新获取)
client.send_text("oc_ops_001", "📋 巡检任务已完成,共检查 5 台设备")
python run_demo.py
观察日志,重点看 token 行为:
[INFO] 正在获取新的 access_token ...
[INFO] 获取成功,token 将在 5 秒后刷新
群列表: [{'chat_id': 'oc_ops_001', 'name': '设备运维群'}, ...]
发送结果: {'code': 0, 'data': {'message_id': 'om_1'}}
(第 3 次调用没有再打印"获取新 token"------缓存命中 ✅)
到 Mock 的调试接口确认消息真的发出去了:
curl http://localhost:9000/debug/messages
# {"count":2,"messages":[{"receive_id":"oc_ops_001",...}]}
验证自动刷新
Mock 的 token 有效期设成了 30 秒(提前 25 秒刷新)。在 run_demo.py 里加一段等待,观察过期后自动换新:
import time
print("等待 token 过期 ...")
time.sleep(8)
client.send_text("oc_ops_001", "⏰ 这条消息用的是刷新后的新 token")
# 日志会再次出现"正在获取新的 access_token ..."
📖 五、Token 管理的四个工程要点
这个客户端类是今天的精华,把背后的设计原则讲透:
┌────────────────────────────────────────────────────────────┐
│ ① 缓存(Cache) │
│ 不要每次调业务接口都先换 token。换 token 有网络开销, │
│ 平台也有调用频率限制。拿到后存起来,有效期内反复用。 │
│ │
│ ② 提前刷新(Refresh ahead) │
│ 别等 token 真正过期那一刻才换------万一正好卡在请求中途 │
│ 过期就失败了。提前几分钟(如 5 分钟)就认为该换了。 │
│ │
│ ③ 失效兜底(Invalidate & retry) │
│ 即使提前刷新,token 仍可能被管理员手动吊销。调用业务 │
│ 接口收到 401 时:清空缓存 → 强制换新 → 重试一次。 │
│ │
│ ④ 网络重试(Retry with backoff) │
│ 网络抖动、平台瞬时不可用很常见。失败后等 1s、2s、3s │
│ 递增重试(退避),不要无限重试,超过次数就报错让人处理。 │
└────────────────────────────────────────────────────────────┘
token 生命周期时间轴:
换取 token
│
│◄──────────── 缓存有效,直接用 ───────────►│
│ │
│ 提前刷新点 │ 真正过期
│ │ │
───●──────────────────────────────────●────────●──────────► 时间
0 expire-300 expire
↑
代码在这里就主动换新 token
(保证任何请求用的 token 都不会过期)
💡 多线程/多协程高并发时还有个细节:可能 10 个请求同时发现 token 过期,一起去换新 token("惊群效应")。生产环境会加锁,让第一个请求去换,其余等待复用。学习阶段知道有这回事即可。
📖 六、钉钉 API 的差异(对照学习)
把客户端的认证部分换成钉钉,你会发现几乎就是换了个 URL 和字段名。钉钉获取 token:
GET https://oapi.dingtalk.com/gettoken?appkey=xxx&appsecret=xxx
返回:
{
"errcode": 0,
"access_token": "xxxx",
"expires_in": 7200
}
差异对照:
| 环节 | 飞书 | 钉钉 |
|---|---|---|
| 凭证字段 | app_id / app_secret | appkey / appsecret |
| 换 token | POST + JSON body | GET + query 参数 |
| 成功判断 | code == 0 |
errcode == 0 |
| token 字段 | tenant_access_token | access_token |
| 有效期字段 | expire | expires_in |
| 带 token 调接口 | 请求头 Authorization: Bearer |
URL 参数 ?access_token=xxx |
一个钉钉版 token 获取方法长这样(体会"换汤不换药"):
def _fetch_dingtalk_token(self):
resp = self._http.get(
"https://oapi.dingtalk.com/gettoken",
params={"appkey": self.app_id, "appsecret": self.app_secret},
)
data = resp.json()
if data.get("errcode") != 0:
raise RuntimeError(f"钉钉 token 获取失败:{data}")
self._token = data["access_token"]
self._token_expire_at = time.time() + data["expires_in"] - 300
return self._token
📌 学对接的正确姿势 :不要背每个平台的接口!而是掌握通用模式 + 学会查官方文档。任何平台先找三样东西:①认证 (怎么拿 token)②接口地址和参数 ③错误码表 。飞书开放平台
open.feishu.cn、钉钉开放平台open.dingtalk.com都有在线 API 调试台,可以填参数直接试。
🖥️ 七、方案 B:接真实飞书(有条件再做)
如果你有飞书企业版(或免费注册一个测试企业),可以真实调用:
准备步骤
-
打开飞书开放平台 飞书开放平台 ,用企业账号登录
-
进入"开发者后台" → 创建企业自建应用,填名字描述
-
在"凭证与基础信息"里拿到 App ID 和 App Secret
-
进入"权限管理",开通权限:
-
im:message:send_as_bot(以应用身份发消息) -
im:chat:readonly(读群列表)
-
-
发布版本并由企业管理员审核通过(自建应用通常秒过)
-
把机器人加进一个群(群设置 → 群机器人 → 添加你的应用机器人),拿到 chat_id
改两行代码切到真实平台
import os
with IMWikiClient(
app_id=os.getenv("FEISHU_APP_ID"), # cli_xxxxxxxx
app_secret=os.getenv("FEISHU_APP_SECRET"),
base_url="https://open.feishu.cn", # ← 换成官方地址
) as client:
chats = client.list_chats()
print(chats)
# client.send_text("oc_真实群id", "来自 FDE 的真实告警")
export FEISHU_APP_ID="cli_xxxxxxxx"
export FEISHU_APP_SECRET="xxxxxxxxxx"
python run_demo.py
⚠️ 凭证安全:app_secret 等同于应用密码,绝不能写死代码、提交 Git、发到群里。一律走环境变量或配置中心(呼应第 4 周 .env、第 5 周 12-Factor)。
真实平台常见错误码速查(飞书)
| code | 含义 | 排查方向 |
|---|---|---|
| 0 | 成功 | --- |
| 99991663 | token 无效 | app_id/secret 错或 token 失效 |
| 99991664 | token 过期 | 刷新 token |
| 99991668 | 权限不足 | 去权限管理加 scope 并发版 |
| 230002 | 群/会话不存在 | chat_id 错、机器人没进群 |
| 99991400 | 请求过于频繁 | 触发限流,降低频率/加退避 |
📝 本课小结
| 知识点 | 一句话记住 |
|---|---|
| httpx | Python 发 HTTP;Client 复用连接,AsyncClient 配 FastAPI |
| 常用方法 | resp.status_code / .json() / .raise_for_status() |
| 企业认证通用模式 | app_id+secret → 换 access_token → 带 token 调接口 |
| token 有效期 | 通常 2 小时,短期令牌降低泄露风险 |
| 缓存 | token 有效期内复用,别每次都换 |
| 提前刷新 | 到期前 5 分钟主动换新 |
| 401 兜底 | 清缓存重新换 token 并重试 |
| 网络重试 | 失败退避重试 3 次,超限报错 |
| 飞书 | POST 换 token,code==0,Bearer 头 |
| 钉钉 | GET 换 token,errcode==0,query 带 token |
| 凭证管理 | app_secret 走环境变量,绝不进 Git |
| 学习方法 | 掌握通用模式 + 查文档,不背接口 |
🧠 核心认知 :对接第三方系统,你写的代码里最值钱的不是"怎么发请求"(httpx 几行就够),而是周边的工程可靠性------token 的缓存与刷新、失败的重试、错误码的处理、凭证的保护。这些做好了,你的对接程序才能在客户现场长期稳定运行,而不是三天两头因为 token 过期、网络抖动报警。
📋 课后练习
练习 1:跑通 Mock 全流程(约 30 分钟)
-
启动 mock_platform,用 curl 手动换一次 token、带 token 调一次群列表
-
运行 run_demo.py,确认消息发到了
/debug/messages -
故意把 app_secret 写错,观察客户端抛出的错误信息
-
在两次调用之间 sleep 8 秒,从日志确认 token 自动刷新
练习 2:给客户端加新能力(约 30 分钟)
-
在 Mock 平台加一个
GET /contact/v3/users接口(带 token 校验,返回模拟的员工列表) -
在 IMWikiClient 里加
list_users()方法调用它 -
写代码调用并打印员工名单,体会"新增一个平台能力 = Mock 加接口 + 客户端加方法"的扩展模式
练习 3:理解重试(约 20 分钟)
-
把客户端的
base_url改成一个不存在的地址(如http://localhost:9999),运行观察重试 3 次的日志和等待时间 -
思考:为什么重试间隔要"递增"(1s、2s、3s)而不是固定 1 秒?(提示:平台过载时,大量客户端同时固定间隔重试会造成"重试风暴")
-
查一查"指数退避(exponential backoff)"是什么意思
🔭 下节预告
今天是你的程序主动 调别人(发消息、读数据)------"拉"模式。明天学反过来的场景:别人有事情发生时,主动通知你的程序 ------"推"模式,也就是 Webhook。
比如:飞书群里有人 @机器人 提了一个工单,飞书服务器会立刻 POST 一个请求到你的接口;你要接收、验证它真的是飞书发来的(防伪造)、处理、还要防止同一条事件被重复处理(幂等)。这是事件驱动自动化的核心,也是 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博客