ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【FDE系列】阶段2:Day 48:Webhook 处理与数据映射

【FDE系列】阶段2:Day 48:Webhook 处理与数据映射

📚前言

📒FDE系列内容总纲:

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

🚄前置课程列表:

见文档结尾附录。


🚀阶段2·Day 48:Webhook 处理与数据映射

FDE 学习系列教程 · 第二阶段 · 第 6 周 · Day 3预计时长:3 小时 | 难度:★★★★☆ | 前置知识:Day 47 调用外部 API、FastAPI 请求处理

📌一句话目标:理解 Webhook 事件推送机制,实现接收回调的接口(URL 验证、签名防伪、幂等去重、快速响应),并把外部系统的数据结构映射成内部格式。


🧑‍🤝‍🧑 开场:别再每分钟问一次"有新消息吗"

昨天你的程序是个"主动出击"的角色——想知道有没有新工单,就调一次飞书接口去查。这种模式叫轮询(Polling)。

但轮询有个很蠢的问题:

你的程序:有新消息吗? 飞书:没有 你的程序:有新消息吗? 飞书:没有 你的程序:有新消息吗? 飞书:没有 ……(每分钟问一次,一天问 1440 遍,99% 的回答是"没有")…… 你的程序:有新消息吗? 飞书:有了!

浪费请求、浪费资源、数据还有延迟(最坏要等一个轮询周期才发现)。

Webhook(网络钩子)把方向反过来了——你不再去问,而是告诉飞书:"有事发生时,你主动来调我的接口"。

轮询(Pull,昨天) Webhook(Push,今天) 你 ──问──► 飞书 飞书群里来新消息 你 ◄─没── 飞书 │ 你 ──问──► 飞书 │ 飞书主动 POST 你的接口 你 ◄─没── 飞书 ▼ (大部分请求是浪费) 你的 FastAPI:收到!立刻处理 (事件发生秒级触达,零浪费)

今天学的就是怎么接好这个主动推过来的请求。看似简单(不就是一个 POST 接口吗),但有四个工程关要过:URL 验证、签名防伪、幂等去重、快速响应。最后还要解决一个实际问题——飞书的数据结构和你的工单表不一样,得做数据映射(翻译)。

这块专业细节不少,一次记不住没关系,先把"为什么要有这四关"理解透,代码能跑通,以后照着平台文档填空即可。


📖 一、Webhook 完整工作流程(以飞书为例)

Webhook 是一种“反向 API”机制,用于实现系统间的实时事件通知。当某个事件发生时,源系统会主动向预先配置的 URL 发送 HTTP 请求(通常是 POST),将事件数据推送给接收方,而不是让接收方反复轮询。

配置阶段(告诉平台往哪推)

┌────────────────────────────────────────────────────────────┐ │ ① 你在飞书开放平台"事件订阅"页面填入: │ │ 请求地址(回调 URL):https://你的域名/webhook/feishu │ │ 加密密钥 / 校验令牌(Verification Token) │ │ │ │ ② 飞书立刻发一个"URL 验证请求"(challenge 握手) │ │ 你的接口必须原样返回 challenge,证明地址归你管 │ │ │ │ ③ 验证通过后,你勾选要订阅的事件: │ │ 例如 im.message.receive_v1(收到新消息) │ │ │ │ ④ 以后这些事件一发生,飞书服务器就 POST 到你的回调 URL │ └────────────────────────────────────────────────────────────┘

两种数据流向,别搞混

昨天(主动调用 / 出站): 你的 FastAPI ──HTTP──► 飞书 API 你是客户端,飞书是服务端 认证:你在请求头带 access_token 今天(Webhook / 入站): 飞书服务器 ──HTTP──► 你的 FastAPI(/webhook/feishu) 飞书成了客户端,你成了服务端 安全:你要验证"这个请求真的是飞书发来的吗?"(签名/令牌)

⚠️ 一个硬性要求:回调 URL 必须是公网可访问的 HTTPS 地址(飞书服务器在云上,它访问不到你localhost)。本地开发时用ngrok之类的内网穿透工具把本地端口临时暴露成公网地址。今天用 Mock 在本地模拟飞书推送,先不折腾公网,逻辑完全一样。


📖 二、第一关:URL 验证(challenge 握手)

你在飞书后台填回调地址的那一刻,飞书会立刻发一个验证请求,确认两件事:①这个地址真的有服务在听 ②确实是你本人在配置(而不是恶作剧填了别人的地址)。

验证请求长这样:

{ "type": "url_verify", "challenge": "a1b2c3d4-e5f6-7890", "token": "你的 verification_token" }

各字段含义

  • type:值为"url_verify"(或"url_verification"),表示这是一个验证请求,而非真实的业务事件通知。

  • challenge:一个随机生成的字符串。你的服务器必须原样返回这个值,以证明你收到了请求并能正确响应。

  • token:你的验证令牌(Verification Token)。接收方可以用它来核对请求是否确实来自预期的发送方。

你要做的:把 challenge 字段原样返回:

{"challenge": "a1b2c3d4-e5f6-7890"}

握手成功,飞书才允许你保存这个回调地址。这是 Webhook 接口要处理的第一种特殊请求。


🖥️ 三、搭一个可运行的 Webhook 接收服务

今天用一个 FastAPI 应用接收事件,再配一个事件发送器脚本模拟飞书推送(这样不用公网、不用真实飞书也能完整演练)。

新建webhook_receiver.py:

""" Webhook 接收服务:处理 URL 验证 + 事件回调 运行:uvicorn webhook_receiver:app --port 8000 """ from fastapi import FastAPI, Request, BackgroundTasks, HTTPException import json import time import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") log = logging.getLogger(__name__) app = FastAPI(title="Webhook 接收服务") # 配置(真实项目放环境变量) VERIFY_TOKEN = "fde-verify-token-2026" # 幂等去重表:记录已经处理过的 event_id(生产用 Redis/数据库) processed_events: set[str] = set() # 模拟"工单数据库",处理后的事件落到这里 tickets_db: list[dict] = [] # ========================================== # 数据映射函数(第五部分细讲) # ========================================== def feishu_msg_to_ticket(event: dict) -> dict: """把飞书消息事件翻译成内部工单结构""" message = event.get("message", {}) sender = event.get("sender", {}) sender_id_obj = sender.get("sender_id", {}) # 飞书的 content 是 JSON 字符串,需要二次解析 content_raw = message.get("content", "{}") try: content_text = json.loads(content_raw).get("text", "") except json.JSONDecodeError: content_text = content_raw return { "source": "feishu", "source_msg_id": message.get("message_id"), "chat_id": message.get("chat_id"), "reporter_open_id": sender_id_obj.get("open_id"), "content": content_text, "received_at": time.strftime("%Y-%m-%d %H:%M:%S"), "status": "open", } # ========================================== # 后台耗时处理(第四部分细讲为什么要放后台) # ========================================== def process_event_background(event_type: str, event: dict): """真正干活的逻辑(可能要查库、调接口,耗时几秒)""" log.info("后台开始处理事件:%s", event_type) if event_type == "im.message.receive_v1": ticket = feishu_msg_to_ticket(event) tickets_db.append(ticket) log.info("✅ 已生成工单 #%d:%s", len(tickets_db), ticket["content"]) else: log.info("收到暂不处理的事件类型:%s", event_type) # 这里还可以:调昨天的 IMWikiClient 回一条消息、写 MySQL、触发审批…… # ========================================== # Webhook 统一入口 # ========================================== @app.post("/webhook/feishu") async def feishu_webhook(request: Request, bg: BackgroundTasks): body = await request.json() log.info("收到回调:%s", body.get("type", body.get("header", {}).get("event_type"))) # ===== 第一关:URL 验证握手 ===== if body.get("type") == "url_verify": if body.get("token") != VERIFY_TOKEN: raise HTTPException(status_code=403, detail="验证令牌不匹配") log.info("通过 URL 验证,返回 challenge") return {"challenge": body.get("challenge")} # ===== 第二关:校验事件来源(令牌)===== header = body.get("header", {}) if header.get("token") != VERIFY_TOKEN: log.warning("⚠️ 令牌不匹配,拒绝该请求") raise HTTPException(status_code=403, detail="非法事件来源") # ===== 第三关:幂等去重 ===== event_id = header.get("event_id", "") if event_id in processed_events: log.info("⚠️ 事件 %s 已处理过,直接返回(幂等)", event_id) return {"code": 0, "msg": "duplicate ignored"} processed_events.add(event_id) # ===== 第四关:把耗时处理丢后台,立刻响应 ===== event_type = header.get("event_type", "") event = body.get("event", {}) bg.add_task(process_event_background, event_type, event) # 必须快速返回 200,告诉飞书"我收到了" return {"code": 0, "msg": "ok"} # 调试用:查看已生成的工单 @app.get("/tickets") def show_tickets(): return {"count": len(tickets_db), "tickets": tickets_db}

启动:

uvicorn webhook_receiver:app --port 8000

模拟飞书发送事件

新建event_sender.py,它扮演"飞书服务器",负责发握手请求和消息事件:

""" 模拟飞书事件推送器(本地练习用) """ import httpx import uuid BASE = "http://localhost:8000/webhook/feishu" TOKEN = "fde-verify-token-2026" def send_url_verify(): """模拟飞书配置回调地址时的握手""" challenge = str(uuid.uuid4()) r = httpx.post(BASE, json={"type": "url_verify", "challenge": challenge, "token": TOKEN}) print("握手响应:", r.json()) assert r.json()["challenge"] == challenge print("✅ challenge 原样返回,URL 验证通过\n") def send_message(text: str, event_id: str | None = None, message_id: str | None = None): """模拟飞书推送一条新消息事件""" event_id = event_id or f"ev_{uuid.uuid4().hex[:12]}" message_id = message_id or f"om_{uuid.uuid4().hex[:12]}" payload = { "schema": "2.0", "header": { "event_id": event_id, "event_type": "im.message.receive_v1", "token": TOKEN, }, "event": { "sender": {"sender_id": {"open_id": "ou_zhang_san_001"}}, "message": { "message_id": message_id, "chat_id": "oc_ops_001", "message_type": "text", "content": f'{{"text":"{text}"}}', # 注意:content 是 JSON 字符串 }, }, } r = httpx.post(BASE, json=payload) print(f"发送消息「{text}」→ 响应:", r.json()) if __name__ == "__main__": send_url_verify() send_message("注塑机A3 温度 91 度,请安排检修") send_message("冲压机B2 振动值异常")

运行(确保 receiver 已启动):

python event_sender.py

在receiver 终端观察处理日志:

[INFO] 收到回调:url_verify [INFO] 通过 URL 验证,返回 challenge [INFO] 收到回调:im.message.receive_v1 [INFO] 后台开始处理事件:im.message.receive_v1 [INFO] ✅ 已生成工单 #1:注塑机A3 温度 91 度,请安排检修 [INFO] 收到回调:im.message.receive_v1 [INFO] ✅ 已生成工单 #2:冲压机B2 振动值异常

查看工单:

curl http://localhost:8000/tickets

📖 四、四关逐一讲透

第一关:URL 验证

上面代码和日志已经演示了。记住:同一个回调接口会收到两类请求——配置时的url_verify握手,和运行时的真实事件。代码里先用if body.get("type") == "url_verify"把握手分流出去。

第二关:验证来源——防止有人伪造请求打你的接口

你的回调 URL 一旦公开,任何人都能 POST 伪造的"事件"过来(比如伪造一万条假工单)。怎么确认请求真的来自飞书?

两种主流验证方式: ① 校验令牌(Verification Token) 飞书每个事件 body 里带 token,和你在后台配置的一致才接收 简单直观,今天演示的就是这种 ② 签名验证(Signature,更严格) 飞书用【加密密钥 + 请求体 + 时间戳】算出一个签名放请求头 你用相同算法重算一遍,对得上才接收 还能校验时间戳,防止"录下一个合法请求反复重放"

签名验证的原理(理解思想,代码按平台文档抄):

飞书发送时: 签名 = 加密算法( EncryptKey + timestamp + 请求体 ) 请求头带上:X-Lark-Signature、X-Lark-Request-Timestamp 你接收时: 1. 取出 timestamp、signature、body 2. 用同一个 EncryptKey 重算签名 3. 和请求头里的比对:一致 → 真飞书;不一致 → 拒绝(403) 4. timestamp 离现在超过 5 分钟 → 可能是重放攻击,拒绝

💡 学习阶段用 token 校验足够;对安全要求高的生产对接,按飞书文档启用加密策略 + 签名校验。核心思想就一句:用一个只有你和平台知道的"暗号",证明对方身份。

第三关:幂等——同一条事件处理多次,结果必须相同

"幂等"昨天提过(调一次和调 N 次结果一样)。Webhook 场景为什么必须幂等?因为平台的重传机制:

飞书 POST 事件给你 │ ├─ 你的服务处理慢了(超过 3 秒)/ 那一刻正好重启 / 网络抖动 │ ▼ 飞书没在规定时间内收到 200 响应 │ ▼ 飞书认为投递失败,【重试】,把同一个事件再发一遍(可能发好几遍) │ ▼ 如果不做幂等:一条"设备告警"被你存成 3 张重复工单、通知群 3 次!

我们的对策:每个事件有唯一编号 event_id,处理前先查它处理过没有。

processed_events: set[str] = set() # 已处理记录 event_id = header.get("event_id") if event_id in processed_events: return {"msg": "duplicate ignored"} # 处理过了,直接回成功,不重复干活 processed_events.add(event_id)

亲手验证——用相同的 event_id发两次:

# 在 event_sender.py 里追加,或单独执行 import httpx payload_same = { "header": {"event_id": "ev_FIXED_001", "event_type": "im.message.receive_v1", "token": TOKEN}, "event": { "sender": {"sender_id": {"open_id": "ou_test"}}, "message": {"message_id": "om_fixed", "chat_id": "oc_x", "content": '{"text":"重复事件测试"}'}, }, } for i in range(2): r = httpx.post(BASE, json=payload_same) print(f"第 {i+1} 次:", r.json())
第 1 次:{'code': 0, 'msg': 'ok'} 第 2 次:{'code': 0, 'msg': 'duplicate ignored'} ← 被幂等拦截

查/tickets,"重复事件测试"只出现一次。

⚠️ 教学代码用内存set存 event_id,有两个生产级局限:①服务重启 set 就清空 ②多实例部署时各存各的。生产环境用Redis(设过期时间,比如存 24 小时)或数据库唯一索引来做去重。思路完全一样:用唯一键挡住重复。

第四关:快速响应 + 后台处理

飞书这类平台要求回调接口几秒内返回 200(飞书默认约 3 秒),否则就重发。但你的真实业务可能很慢:写数据库、调别的接口、发通知、跑分析……加起来可能十几秒。

矛盾的解法:接口收到事件后,先把活儿丢到后台,立刻回 200;真正的处理异步进行。

❌ 错误做法(同步处理完才响应): 收到事件 → 写库(2s) → 调接口(3s) → 发通知(2s) → 返回 200 └────────────── 7 秒 ──────────────┘ 超过 3 秒 → 飞书以为失败 → 重发 → 重复处理 ✅ 正确做法(后台任务): 收到事件 → 校验、去重 → 把任务丢后台 → 立刻返回 200(<100ms) │ └─► 后台慢慢处理(多久都行)

FastAPI 用BackgroundTasks实现(代码里的bg.add_task(...))。更复杂的场景会上 Celery/RQ 等专业任务队列 + Redis,但思想一致:接收与处理解耦,先应答后干活。


📖 五、数据映射:当对方的"语言"和你不一样

事件安全地收下了,但还有最后一道坎——飞书的数据结构 ≠ 你的工单表结构。

对比一下:

飞书消息事件(event) 你的工单表(tickets) ───────────────────── ───────────────── message.message_id ──映射──► source_msg_id message.chat_id ──映射──► chat_id sender.sender_id.open_id ──映射──► reporter_open_id message.content ──解析──► content (JSON 字符串!) (纯文本) (没有这个字段) ──补充──► source = "feishu" (没有这个字段) ──补充──► status = "open" header.create_time ──转换──► received_at(统一格式)

这个"翻译"过程就是数据映射(Data Mapping),是系统集成的日常核心工作。回看代码里的映射函数:

def feishu_msg_to_ticket(event: dict) -> dict: message = event.get("message", {}) sender = event.get("sender", {}) # ① 字段路径不同:用 .get() 层层取出,还能防空 # ② 字段名不同:左边飞书,右边你的系统 # ③ 格式不同:content 是 JSON 字符串,要 json.loads 解开取 text # ④ 缺失字段:用代码补默认值(source、status) ...

数据映射要处理的四类典型差异

差异类型

例子

处理方法

字段名不同

message_id → source_msg_id

映射函数字段对应

层级不同

sender.sender_id.open_id 嵌套三层

.get()链式取值

格式不同

content 是 JSON 字符串、时间是毫秒时间戳

json.loads、时间转换

值域不同

飞书 status 是中文/枚举码,你的系统用另一套

枚举值映射表(字典)

对方没有

你的 source/status 字段飞书不提供

填默认值或由代码生成

枚举值映射的典型写法

当对方的状态码和你的不一样时,用一张"翻译字典":

# 飞书优先级码 → 内部优先级 PRIORITY_MAP = { "1": "低", "2": "中", "3": "高", } internal_priority = PRIORITY_MAP.get(feishu_priority_code, "中") # 未知值给默认 # 多系统并存时,映射层让你的核心业务不用关心数据来自哪个平台 # 明天/以后接钉钉,只需再写一个 dingtalk_msg_to_ticket()

📌架构思想:把"对接某个外部平台"的逻辑隔离在适配层(mapper + client),核心业务只认内部统一的数据格式。这样将来要加钉钉、企业微信,核心代码一行不改,只增加对应的适配器。这就是 Day 26 学的分层思想在系统集成场景的应用。

适配层(每个平台一个) 核心层(平台无关) 飞书事件 ─► feishu_msg_to_ticket() ─┐ ├─► 统一工单结构 ─► 工单表/业务逻辑 钉钉事件 ─► dingtalk_msg_to_ticket()┘ (未来) 外部平台千变万化,核心系统岿然不动

📝 本课小结

知识点

一句话记住

Webhook

事件发生时平台主动 POST 你的接口(推模式)

对比轮询

推模式秒级触达、零浪费;轮询有延迟、费请求

URL 验证

配置时原样返回 challenge 证明地址归你

回调分流

同接口先分 url_verify,再处理真实事件

来源验证

token 令牌 / 签名校验,防伪造

重放防护

签名场景校验时间戳

幂等

用 event_id 去重,重复事件只处理一次

快速响应

3 秒内回 200,耗时处理丢 BackgroundTasks

去重存储

教学用 set,生产用 Redis/唯一索引

数据映射

字段名/层级/格式/值域/缺失 五类差异翻译

content 解析

飞书消息 content 是 JSON 字符串要 loads

适配层

外部差异隔离在 mapper,核心业务平台无关

本地开发

ngrok 暴露公网;Mock 发送器本地演练

🧠 核心认知:写 Webhook 接口,接收数据本身只占 10% 的工作量,90% 在处理"分布式世界的不确定性"——请求可能是伪造的(验签)、可能重复送达(幂等)、你的处理可能超时(快速响应+后台)、数据格式和你不一样(映射)。把这四件事做扎实,才是一个能在生产环境稳定接收事件的接口。


📋 课后练习

练习 1:跑通四关验证(约 30 分钟)

  1. 启动 receiver,运行 sender 完成 URL 握手和两条消息事件,确认生成 2 张工单

  2. 用相同 event_id 发两次,确认第二次返回 duplicate 且工单不增加

  3. 故意把 sender 里的 TOKEN 改错,确认 receiver 返回 403 拒绝

  4. 用curl http://localhost:8000/tickets查看最终工单结构

练习 2:后台处理与快速响应(约 20 分钟)

  1. 在process_event_background开头加time.sleep(5)模拟耗时操作

  2. 在 sender 里测量从发送到收到响应的时间(用time.time()包住 httpx.post)

  3. 确认:即使后台要处理 5 秒,接口响应时间仍然很短(说明活儿确实被丢到后台了)

练习 3:扩展数据映射(约 30 分钟)

  1. 飞书文本消息 content 形如{"text":"xxx"}。扩展映射:如果文本以"紧急"开头,把工单 status 设为"urgent",否则"open"

  2. 新增一个dingtalk_msg_to_ticket(event)函数,处理钉钉风格的数据(字段名不同,如text.content、senderStaffId,时间是毫秒时间戳),把它也映射成同样的内部工单结构

  3. 思考:为什么要让飞书和钉钉映射后得到完全相同的工单字段?


🔭 下节预告

两天下来,你的系统既能主动调外部 API,又能接收外部事件推送,已经具备了"双向连接"的能力。但接口越写越多,怎么让别人(和未来的你自己)看得懂、调得动?怎么保证改了代码没改坏?

明天学两件让项目"可交付、可维护"的事:

  1. OpenAPI 文档:FastAPI 自动生成的交互式文档(Swagger UI),学会加描述、示例、标签、响应模型,让接口自己"会说话"

  2. pytest 接口测试:给 API 写自动化测试,正常流程和异常流程都覆盖,以后改代码跑一遍测试就知道有没有改坏

这是第二阶段收官综合项目前的最后一块拼图,明天见!


🌍附录:前置课程列表

阶段一:认知启蒙(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 41(up详细版):Docker 入门 — 把环境装进盒子-CSDN博客

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

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

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

【FDE系列】阶段2:Day 43(UP详细版):Docker Compose — 多容器一键编排-CSDN博客

【FDE系列】阶段2:Day 44:Nginx 反向代理 + Git 版本控制-CSDN博客

【FDE系列】阶段2:Day 44(UP详细版):Nginx 反向代理 + Git 版本控制-CSDN博客

【FDE系列】阶段2:Day 45:综合实战 — Docker + Nginx + Git 完整部署与本周收官-CSDN博客

【FDE系列】阶段2:Day 45(up详细版):综合实战 — Docker + Nginx + Git 完整部署与本周收官-CSDN博客

【FDE系列】阶段2:Day 46:RESTful 设计与认证授权-CSDN博客

【FDE系列】阶段2:Day 46(UP详细版):RESTful 设计与认证授权-CSDN博客

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

【FDE系列】阶段2:Day 47(UP详细版):对接企业系统 — 飞书 / 钉钉 API-CSDN博客

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进