'use client';
import { type MouseEvent, type ReactNode } from 'react';
import { ArrowRight, BookOpen, Route, Workflow } from 'lucide-react';
import { courseHref } from './course-navigation';
import { modules } from './module-catalog';
import './course-home.css';
type Navigate = (module: string, lesson?: string) => void;
const moduleDetails = [
{
slug: 'api',
position: '请求入口:浏览器与业务逻辑之间',
responsibility:
'把一个 Python 函数变成别人能调用的 HTTP 接口,约定输入、返回值、错误和通信方式。',
importance:
'接口是前后端的共同约定。边界不清,调用方就不知道请求是否成功、如何处理失败。',
problem:
'参数乱传、错误信息不一致、慢调用卡住服务,以及结果需要逐步展示时怎样通信。',
example:
'商家创建商品、修改名称和价格、切换上架状态;用这些操作练 CRUD,再用商品咨询演示 SSE 与 WebSocket。',
question: '用户的一个操作,怎样成为服务端的一次可靠调用?',
},
{
slug: 'database',
position: '持久数据层:任务与业务记录的真源',
responsibility:
'通过表、约束、索引和事务保存数据;“真源”就是冲突时系统最终以这里的记录为准。',
importance:
'进程会重启、请求会并发。数据库既要让数据留下来,也要守住“哪些状态允许存在”的规则。',
problem:
'数据丢失、重复记录、并发覆盖、慢查询、锁等待,以及备份能否真正恢复。',
example:
'在 shop_lab 从商品与分类需求推导表结构,解释价格为什么按分存、分类为什么拆表,再练事务、索引与恢复。',
question: '服务重启后,哪些事实必须还在?',
},
{
slug: 'api-database',
position: 'API 与 MySQL 的连接处:会话和事务边界',
responsibility:
'把接口接入数据库,安排连接的借用与归还,明确何时提交、何时回滚,以及如何升级表结构。',
importance:
'接口返回“成功”应对应已经提交的事实;重复请求和并发修改不能悄悄破坏业务规则。',
problem:
'连接泄漏、事务跨请求、重复提交、并发更新冲突,以及“记录写了、任务没发出去”的双写窗口。',
example:
'长任务受理时,把任务记录和 outbox 待投递事件写入同一事务;课程用持久化商品服务理解这些边界。',
question: '一次接口成功,到底承诺了哪些数据已经写入?',
},
{
slug: 'tasks',
position: '执行层:任务投递器、队列与独立 worker',
responsibility:
'先区分协程、线程和进程,再把长任务交给独立执行进程;管理状态、并发、重试与取消。',
importance:
'“接口先返回”不会自动带来可靠性。任务能否跨进程退出恢复,要依靠持久记录、投递和确认机制。',
problem:
'请求超时、事件循环阻塞、任务丢失、重复执行、积压,以及 worker 做到一半退出。',
example:
'商家导出商品清单,API 先返回任务 ID,worker 读取商品快照并生成文件;用这条链路练并发、重试和恢复。',
question: '用户不用一直等的工作,之后由谁保证继续执行?',
},
{
slug: 'redis',
position: '加速层:API 旁的可重建缓存',
responsibility:
'把高频读取的结果临时放到 Redis;定义缓存键、有效期、更新失效、容量和故障降级。',
importance:
'缓存让重复读取更快,但可能短暂落后于数据库。先说清可接受的旧数据窗口,才知道怎样使用它。',
problem:
'数据库重复承压、热点同时过期、缓存穿透、内存耗尽,以及缓存不可用时请求雪崩。',
example:
'商品详情被反复读取时使用 Redis 缓存;商家修改商品后处理失效,并以 MySQL 为真源。',
question: '哪些数据可以临时读副本,丢了也能重新构建?',
},
{
slug: 'docker',
position: '运行包装层:API、worker 和依赖服务的容器',
responsibility:
'把代码与依赖构建成镜像,用 Compose 组织服务网络、配置、健康检查、持久卷与资源限制。',
importance:
'同一份代码需要在不同机器上按可重复的方式启动;容器删掉时,数据库数据仍应有独立的保存位置。',
problem:
'本地能跑而部署失败、服务互相找不到、容器重建丢数据、资源争抢,以及升级无法回滚。',
example:
'把 API、worker、MySQL、Redis 作为职责不同的服务运行;课程搭建 shop-service 容器环境。',
question: '怎样把几个进程及其依赖装成可重复运行的一套环境?',
},
{
slug: 'linux',
position: '主机与网络入口:承载进程,对外提供服务',
responsibility:
'管理运行账户、目录权限、端口、systemd、Nginx、TLS、发布回滚和备份恢复流程。',
importance:
'程序能启动只是起点。它还要能被安全访问、在故障后恢复,并且在升级时有明确的退路。',
problem:
'端口不通、权限错误、进程退出、流式响应被代理缓冲、证书配置错误和发布中断。',
example:
'为商品服务的 HTTP 与流式入口配置反向代理,把 shop-service 部署到独立 Linux 练习机。',
question: '从自己的电脑到别人能访问的服务,中间还有哪些工作?',
},
{
slug: 'troubleshooting',
position: '横跨各层:日志、指标、追踪与恢复手册',
responsibility:
'用请求 ID 和任务 ID 串起现场;指标看整体变化,日志看具体事件,追踪看跨组件耗时。',
importance:
'“很慢”只是症状。先找证据和影响范围,才能决定该限流、回滚,还是修复某个依赖。',
problem:
'不知道请求卡在哪、CPU 与连接耗尽、慢 SQL、缓存失效、任务积压,以及修好后无法证明恢复。',
example:
'同一个任务 ID 贯穿 API、投递和 worker,区分“排队久”和“执行慢”;课程进行隔离故障演练。',
question: '用户说任务一直转圈,怎样沿着证据找到卡住的那一段?',
},
{
slug: 'distributed',
position: '设计视角:所有跨进程、跨机器的连接处',
responsibility:
'用架构图与故障推演理解扩容、一致性、复制、分片、消息可靠性和服务治理;练习说明设计取舍。',
importance:
'组件各自正常,不等于协作一定正确。网络超时只能说明没及时收到答复,不能证明对方没执行。',
problem:
'部分失败、重复消息、数据不一致、流量突增、热点、级联故障,以及系统设计题只会堆组件。',
example:
'讨论“任务已完成但响应丢失”“数据库成功但投递失败”等场景;12 节课以理论和面试推演为主。',
question: '多个组件没有同时成功时,系统该如何继续工作?',
},
{
slug: 'review',
position: '课程收尾:逐一检查九个技术模块',
responsibility:
'用理解问题、情境推演和成果证据检验掌握程度,把薄弱点对应到原课时。',
importance:
'能跟着做出结果之后,还需要独立解释原因,并在条件变化时作出有依据的判断。',
problem:
'看答案觉得都懂,合上课件却讲不清、做不出,或不知道还需要补哪一课。',
example:
'继续用商品查询、编辑和清单导出,闭卷回答问题,核对要点,并记录需要回学的部分。',
question: '学完之后,拿什么证明自己已经掌握?',
},
];
function HomeLink({
module,
navigate,
children,
className,
}: {
module: string;
navigate: Navigate;
children: ReactNode;
className?: string;
}) {
return (
) => {
if (
event.button !== 0 ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey
)
return;
event.preventDefault();
navigate(module);
}}
>
{children}
);
}
function ArchitectureNode({
title,
lines,
module,
kind = 'service',
}: {
title: string;
lines: string[];
module?: string;
kind?: 'service' | 'context' | 'queue' | 'storage' | 'runtime';
}) {
const className = `home-architecture-node home-node-${kind}`;
const content = (
<>
{title}
{lines.map((line) => (
{line}
))}
>
);
return module ? (
{content}
) : (
{content}
);
}
function ArchitectureLayer({
name,
responsibility,
kind,
children,
note,
}: {
name: string;
responsibility: string;
kind: string;
children: ReactNode;
note?: string;
}) {
return (
{children}
{note &&
{note}
}
);
}
function ArchitectureDiagram() {
return (
);
}
const timeline = [
[
'提交请求',
'商家点击“导出商品清单”。API 校验请求并确定商品快照;这时尚未承诺文件已经生成。',
'当前请求',
],
[
'记录已提交',
'在一个数据库事务里写入任务和待投递事件。事务提交后,API 返回 202 与任务 ID。',
'受理 ≠ 完成',
],
[
'任务交接',
'投递器读取事件并发送到队列。此时浏览器的原请求已经结束,投递失败可以稍后重试。',
'异步开始',
],
[
'独立执行',
'worker 领取任务,把固定的商品快照写入导出文件;完成后保存结果引用,再确认消息。重复消息需要幂等处理。',
'可能重试',
],
[
'读取结果',
'客户端拿任务 ID 查询 API,或订阅进度。API 从任务记录判断等待、执行、成功或失败。',
'看持久状态',
],
];
export function CourseHome({ navigate }: { navigate: Navigate }) {
const totalLessons = modules.reduce((sum, module) => sum + module.count, 0);
return (
AGENT BACKEND · COURSE GUIDE
先看见整个系统,
再学会每一个模块.
从一个容易理解的电商商品服务出发,逐步学会接住请求、保存商品、导出清单,并在失败时恢复。这些后端能力也构成服务端
Agent 的基础。
- {String(modules.length).padStart(2, '0')}
- 学习模块
- {totalLessons}
- 课时与自测单元
- 8 + 1 + 1
- 后端实践 + 架构理论 + 复盘自测
01 / SYSTEM MAP
把课程放进一张分层架构图
从客户端到运行底座,按职责逐层理解系统;观测与设计能力贯穿各层。节点编号对应下方课程模块,点击可查看定位。
课程覆盖模块
异步任务交接
* Agent 扩展上下文
分层表示职责与支撑关系,请求按需访问相关组件,并非逐层依次执行。橙色箭头表示异步任务交接;API
负责业务鉴权,Nginx 负责入口代理。带 * 的 Agent
编排、模型与工具用于说明扩展位置,不属于本课已实现的完整系统。
A
短请求:当场取回结果
客户端 → 网关 → API → 数据库、缓存或业务调用 →
原路返回。用户等待的是这一次请求完成;慢依赖仍会占用请求的时间预算。
B
长任务:先受理,再执行
API 写入任务和 outbox → 返回任务 ID。投递器 → 队列 → worker →
写回结果。用户之后再查询进度,接收成功并不等于执行成功。
C
运行与可靠性:贯穿整张图
Docker 管运行单元,Linux
管主机与入口,观测帮助找故障。分布式设计关注它们之间的网络、重复、超时与不一致。
两个容易混淆的词:队列像“待办传送带”,worker
是实际做事的进程;Redis 可以用来缓存,也可以承担某些队列的 broker
角色,但两种用途的丢失容忍度、持久化和内存策略不同,不能因产品名相同就视为同一种数据。
02 / MODULE GUIDE
每个模块,补上哪一种能力
前九个模块对应上图中的技术位置,最后一个模块用于复盘自测,检查这些能力能否独立运用。
{moduleDetails.map((detail, index) => {
const catalogEntry = modules.find(
(candidate) => candidate.slug === detail.slug,
);
if (!catalogEntry) return null;
return (
{String(index + 1).padStart(2, '0')}
{catalogEntry.count}{' '}
{detail.slug === 'review' ? '自测单元' : '课时'} ·{' '}
{detail.slug === 'distributed'
? '理论与面试'
: catalogEntry.kicker}
{catalogEntry.name}
{detail.question}
- 所在位置
- {detail.position}
- 负责什么
- {detail.responsibility}
- 为何重要
- {detail.importance}
- 解决问题
- {detail.problem}
- 放进实例
- {detail.example}
进入{catalogEntry.name}
);
})}
03 / ONE LONG TASK
把“导出一份商品清单”走一遍
这是把多个模块连起来的设计示例:关键不在“后台”两个字,而在每个阶段承诺了什么、失败后由谁接手。
{timeline.map(([title, description, state], index) => (
-
{index + 1}
{title}
{state}
{description}
))}
如果 worker 刚写完结果就退出了?
消息可能尚未确认,随后会被再次投递。所以“至少一次投递”仍需业务幂等:同一个任务
ID
再次到达时,要识别已经提交的结果。若任务会向外部系统产生副作用,还需要对方支持幂等键或配套的核对与补偿流程。
去分布式模块推演更多失败场景
04 / LEARNING ROUTE
没有后端经验,就按这条路走
会基础
Python、能执行终端命令即可从第一模块开始。数据库、Docker、Linux
环境在对应模块准备,每一步都带着上一阶段的问题进入下一阶段。
-
先让请求有入口,数据能保存
01 API → 02 数据库 → 03 API +
数据库。先完成一个能正确提交、查询与修改数据的服务。
开始
-
再处理等待、并发与重复读取
04 异步任务 + 05 Redis。两者都建立在持久化 API
之上;任务模块进入队列部分前,先准备 Redis 基础环境。
进入
-
让服务运行起来,也能查清故障
06 Docker → 07 Linux → 08
排障。用部署、停止、升级和恢复检验前面实现的边界。
进入
-
把组件连起来,解释架构为什么这样设计
09
分布式系统设计。用已有组件经验讨论容量、数据与失败;围绕理论和面试场景展开,不安排实战代码。
进入
-
最后,检验每个模块是否真的掌握
10
复盘与自测。每个技术模块对应一个自测单元:独立回答问题、推演变化、核对掌握证据,再按缺口回学。
开始自测
);
}